Skip to content

Commit 5be1c48

Browse files
authored
fix: filter empty elements in xargs (#144)
1 parent d7efbb3 commit 5be1c48

File tree

2 files changed

+58
-14
lines changed

2 files changed

+58
-14
lines changed

src/shell/commands/xargs.rs

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,16 @@ fn xargs_collect_args(
6161
args.push("echo".into());
6262
}
6363

64-
if let Some(delim) = &flags.delimiter {
65-
// strip a single trailing newline (xargs seems to do this)
66-
let text = if *delim == '\n' {
67-
if let Some(text) = text.strip_suffix(&delim.to_string()) {
68-
text
69-
} else {
70-
&text
71-
}
72-
} else {
73-
&text
74-
};
64+
if flags.delimiter.is_some() || flags.is_null_delimited {
65+
let delimiter = flags.delimiter.unwrap_or('\0');
66+
args.extend(text.split(delimiter).map(|t| t.into()));
7567

76-
args.extend(text.split(*delim).map(|t| t.into()));
77-
} else if flags.is_null_delimited {
78-
args.extend(text.split('\0').map(|t| t.into()));
68+
// remove last arg if it is empty
69+
if let Some(last) = args.last() {
70+
if last.is_empty() {
71+
args.pop();
72+
}
73+
}
7974
} else {
8075
args.extend(delimit_blanks(&text)?);
8176
}
@@ -315,4 +310,44 @@ mod test {
315310
"unmatched quote; by default quotes are special to xargs unless you use the -0 option",
316311
);
317312
}
313+
314+
#[test]
315+
fn test_xargs_collect_args() {
316+
fn collect(cli_args: &[&str], input: &str) -> Vec<String> {
317+
let stdin = ShellPipeReader::from_str(input);
318+
let cli_args = cli_args.iter().map(|s| s.into()).collect::<Vec<_>>();
319+
let result = xargs_collect_args(&cli_args, stdin).unwrap();
320+
result
321+
.into_iter()
322+
.map(|s| s.to_str().unwrap().to_string())
323+
.collect()
324+
}
325+
326+
// Test default behavior
327+
let result = collect(&[], "arg1 arg2\narg3");
328+
assert_eq!(result, ["echo", "arg1", "arg2", "arg3"]);
329+
330+
let result = collect(&[], "arg1 arg2\narg3\n");
331+
assert_eq!(result, ["echo", "arg1", "arg2", "arg3"]);
332+
333+
// printf "arg1 arg2\narg3\n\n" | xargs
334+
// > arg1 arg2 arg3
335+
let result = collect(&[], "arg1 arg2\n\narg3\n\n\n");
336+
assert_eq!(result, ["echo", "arg1", "arg2", "arg3"]);
337+
338+
// Test null-delimited with trailing null
339+
let result = collect(&["-0"], "arg1\0arg2\0arg3\0");
340+
assert_eq!(result, ["echo", "arg1", "arg2", "arg3"]);
341+
342+
// Test null-delimited with multiple nulls (all ignored)
343+
let result = collect(&["-0"], "arg1\0\0arg2\0arg3\0\0");
344+
assert_eq!(result, ["echo", "arg1", "", "arg2", "arg3", ""]);
345+
346+
// Test custom delimiter with trailing delimiter
347+
let result = collect(&["-d", ":"], "arg1:arg2:arg3:");
348+
assert_eq!(result, ["echo", "arg1", "arg2", "arg3"]);
349+
350+
let result = collect(&["-d", ":"], "arg1::arg2:arg3::");
351+
assert_eq!(result, ["echo", "arg1", "", "arg2", "arg3", ""]);
352+
}
318353
}

src/shell/types.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,15 @@ impl ShellPipeReader {
271271
Self::StdFile(std_file)
272272
}
273273

274+
#[cfg(test)]
275+
#[allow(clippy::should_implement_trait)]
276+
pub fn from_str(data: &str) -> Self {
277+
use std::io::Write;
278+
let (read, mut write) = os_pipe::pipe().unwrap();
279+
write.write_all(data.as_bytes()).unwrap();
280+
Self::OsPipe(read)
281+
}
282+
274283
pub fn into_stdio(self) -> std::process::Stdio {
275284
match self {
276285
Self::OsPipe(pipe) => pipe.into(),

0 commit comments

Comments
 (0)