Files
sausage/userland/findutils/src/xargs.rs
T

123 lines
3.7 KiB
Rust

//! Build sequential child commands from quoted or NUL-delimited input.
use sausage_spawn::{Command, Stdio};
use std::io::{self, Read};
fn run() -> Result<i32, Box<dyn std::error::Error>> {
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::<Result<Vec<_>, _>>()?
} 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::<Vec<_>>()
} 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<String, Box<dyn std::error::Error>> {
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
}
});
}