//! Build sequential child commands from quoted or NUL-delimited input. use sausage_spawn::{Command, Stdio}; use std::io::{self, Read}; fn run() -> Result> { let args: Vec<_> = std::env::args().skip(1).collect(); let mut i = 0; let mut count = usize::MAX; let mut null = false; let mut no_empty = false; let mut replace = None; while i < args.len() { match args[i].as_str() { "-I" => { i += 1; replace = Some(args.get(i).ok_or("missing -I argument")?.clone()); count = 1; } arg if arg.starts_with("-I") => { replace = Some(arg[2..].to_owned()); count = 1; } "-0" => null = true, "-r" => no_empty = true, "-n" => { i += 1; count = args.get(i).ok_or("missing -n argument")?.parse()?; } "--" => { i += 1; break; } arg if arg.starts_with("-n") => count = arg[2..].parse()?, arg if arg.starts_with('-') => return Err(format!("unsupported option: {arg}").into()), _ => break, } i += 1; } if count == 0 { return Err("-n must be positive".into()); } let argv = if i == args.len() { vec!["echo".into()] } else { args[i..].to_vec() }; let mut input = String::new(); io::stdin().read_to_string(&mut input)?; let values = if null { input.split_terminator('\0').map(str::to_owned).collect() } else if replace.is_some() { input .split_terminator('\n') .map(|line| line.trim_start_matches([' ', '\t'])) .filter(|line| !line.is_empty()) .map(replacement) .collect::, _>>()? } else { shlex::split(&input).ok_or("unmatched quote in input")? }; if values.is_empty() && (no_empty || replace.is_some()) { return Ok(0); } let mut result = 0; let batches = if values.is_empty() { vec![&[][..]] } else { values.chunks(count).collect() }; for batch in batches { let command = if let Some(ref token) = replace { argv.iter() .map(|arg| arg.replace(token, &batch.join(" "))) .collect::>() } else { argv.iter().cloned().chain(batch.iter().cloned()).collect() }; match Command::new(&command[0]) .args(&command[1..]) .stdin(Stdio::Null) .status() { Ok(0) => (), Ok(255) => return Ok(124), Ok(_) => result = 123, Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(127), Err(_) => return Ok(126), } } Ok(result) } fn replacement(line: &str) -> Result> { let mut result = String::new(); let mut quote = None; let mut escaped = false; for ch in line.chars() { if escaped { result.push(ch); escaped = false; } else if ch == '\\' && quote != Some('\'') { escaped = true; } else if quote == Some(ch) { quote = None; } else if quote.is_none() && (ch == '\'' || ch == '"') { quote = Some(ch); } else { result.push(ch); } } if quote.is_some() || escaped { return Err("unmatched quote or escape in input".into()); } Ok(result) } fn main() { std::process::exit(match run() { Ok(code) => code, Err(e) => { eprintln!("xargs: {e}"); 1 } }); }