From 46778db842423832e8c9364e03af34102ff50c71 Mon Sep 17 00:00:00 2001 From: Pablo Garcia Date: Fri, 7 Aug 2026 14:52:49 +0200 Subject: [PATCH] xargs: treat whitespace-only input as no argument (fixes #771) With input consisting solely of delimiters (e.g. a single ASCII space), the whitespace-delimited argument reader would emit a zero-length token. The child process then received an empty-string argument, producing errors like `ls: cannot access ''`. GNU xargs treats a run of delimiters with no content between them as producing no argument at all (the command runs once with zero extra args, like empty input). The reader's EOF handling returned `None` only when no bytes had been read (`i == 0`). Input that was consumed but was pure whitespace left `result` empty while `i > 0`, so the loop fell through and returned an empty argument. Return `None` whenever `result` is empty at EOF, which covers both the no-bytes and whitespace-only cases. --- src/xargs/mod.rs | 8 +++++++- tests/test_xargs.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/xargs/mod.rs b/src/xargs/mod.rs index ff833335..712bbcf2 100644 --- a/src/xargs/mod.rs +++ b/src/xargs/mod.rs @@ -605,7 +605,13 @@ where format!("Unterminated quote: {q}"), )); } - if i == 0 { + // Input that consists only of delimiters (whitespace) + // produces no argument — a run of delimiters with no + // content between them yields nothing, matching GNU xargs. + // `i == 0` handles the case where we read no bytes at all; + // `result.is_empty()` additionally covers input we consumed + // but that was pure whitespace. + if result.is_empty() { return Ok(None); } pending.clear(); diff --git a/tests/test_xargs.rs b/tests/test_xargs.rs index 997c7e85..eca5a0b6 100644 --- a/tests/test_xargs.rs +++ b/tests/test_xargs.rs @@ -73,6 +73,50 @@ fn xargs_if_empty() { ucmd().args(&["--no-run-if-empty"]).succeeds().no_output(); } +#[test] +fn xargs_whitespace_only_input() { + // Input that consists only of delimiters (whitespace) produces no + // argument, matching GNU xargs: a run of delimiters with no content + // between them yields no argument. The command still runs once with no + // extra args (the default, as with empty input). + ucmd().pipe_in(" ").succeeds().no_stderr().stdout_only("\n"); + + // Other ASCII whitespace should behave the same as a space. + ucmd() + .pipe_in("\t") + .succeeds() + .no_stderr() + .stdout_only("\n"); + ucmd() + .pipe_in(" \t\n \n") + .succeeds() + .no_stderr() + .stdout_only("\n"); + + // With --no-run-if-empty, whitespace-only input means nothing to do. + ucmd() + .args(&["--no-run-if-empty"]) + .pipe_in(" ") + .succeeds() + .no_output(); + + // The same holds when reading from a file with -a (issue #771 repro): + // the child must receive zero arguments, not an empty-string argument. + let temp_file = tempfile::NamedTempFile::new().unwrap(); + std::fs::write(temp_file.path(), b" ").unwrap(); + let result = ucmd() + .args(&[ + "-a", + &temp_file.path().to_string_lossy(), + &path_to_testing_commandline(), + "-", + "--no_print_cwd", + ]) + .succeeds(); + result.no_stderr(); + assert_eq!(result.stdout_str(), "args=\n--no_print_cwd\n"); +} + #[test] fn xargs_replace_empty_input() { ucmd()