mirror of
https://github.com/uutils/findutils.git
synced 2026-06-10 15:48:30 -07:00
Add an initial implementation of xargs
This includes much of the core xargs functionality, with the following notable exceptions: - Parallel execution (`-P`): This option currently just does nothing, that way anything that passes -P can at least run without a notable behavior shift (other than simply being slower). - Replacement strings (`-I`): This can easily be worked around via an intermediate shell invocation (e.g. `xargs -L1 sh -c 'do-things-with $@' --`). - EOF strings (`-E`): I've honestly never seen this actually used, though it would not be particularly difficult to implement given the current architecture. Closes #37 Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
This commit is contained in:
Generated
+1
@@ -221,6 +221,7 @@ version = "0.2.0"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"chrono",
|
||||
"clap",
|
||||
"filetime",
|
||||
"glob",
|
||||
"once_cell",
|
||||
|
||||
@@ -11,6 +11,7 @@ authors = ["uutils developers"]
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
clap = "2.34"
|
||||
glob = "0.3"
|
||||
walkdir = "2.3"
|
||||
tempfile = "3"
|
||||
@@ -29,6 +30,10 @@ serial_test = "0.5"
|
||||
name = "find"
|
||||
path = "src/find/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "xargs"
|
||||
path = "src/xargs/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "testing-commandline"
|
||||
path = "src/testing/commandline/main.rs"
|
||||
|
||||
@@ -12,3 +12,4 @@ extern crate walkdir;
|
||||
extern crate tempfile;
|
||||
|
||||
pub mod find;
|
||||
pub mod xargs;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use std::env;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::io::{stdin, stdout, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn usage() -> ! {
|
||||
@@ -14,10 +14,19 @@ fn usage() -> ! {
|
||||
std::process::exit(2);
|
||||
}
|
||||
|
||||
enum ExitWith {
|
||||
Failure,
|
||||
UrgentFailure,
|
||||
#[cfg(unix)]
|
||||
Signal,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Config {
|
||||
exit_with_failure: bool,
|
||||
destination_dir: String,
|
||||
exit_with: Option<ExitWith>,
|
||||
print_stdin: bool,
|
||||
no_print_cwd: bool,
|
||||
destination_dir: Option<String>,
|
||||
}
|
||||
|
||||
fn open_file(destination_dir: &str) -> File {
|
||||
@@ -39,20 +48,61 @@ fn open_file(destination_dir: &str) -> File {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_content(mut f: impl Write, config: &Config, args: &[String]) {
|
||||
if !config.no_print_cwd {
|
||||
writeln!(f, "cwd={}", env::current_dir().unwrap().to_string_lossy())
|
||||
.expect("failed to write to file");
|
||||
}
|
||||
|
||||
if config.print_stdin {
|
||||
let mut s = String::new();
|
||||
stdin()
|
||||
.read_to_string(&mut s)
|
||||
.expect("failed to read from stdin");
|
||||
writeln!(f, "stdin={}", s.trim()).expect("failed to write to file");
|
||||
}
|
||||
|
||||
writeln!(f, "args=").expect("failed to write to file");
|
||||
|
||||
// first two args are going to be the path to this executable and
|
||||
// the destination_dir we want to write to. Don't write either of those
|
||||
// as they'll be non-deterministic.
|
||||
for arg in &args[2..] {
|
||||
writeln!(f, "{}", arg).expect("failed to write to file");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = env::args().collect::<Vec<String>>();
|
||||
if args.len() < 2 || args[1] == "-h" || args[1] == "--help" {
|
||||
usage();
|
||||
}
|
||||
let mut config = Config {
|
||||
destination_dir: args[1].clone(),
|
||||
destination_dir: if args[1] != "-" {
|
||||
Some(args[1].clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
for arg in &args[2..] {
|
||||
if arg.starts_with("--") {
|
||||
match arg.as_ref() {
|
||||
"--exit_with_failure" => {
|
||||
config.exit_with_failure = true;
|
||||
config.exit_with = Some(ExitWith::Failure);
|
||||
}
|
||||
"--exit_with_urgent_failure" => {
|
||||
config.exit_with = Some(ExitWith::UrgentFailure);
|
||||
}
|
||||
#[cfg(unix)]
|
||||
"--exit_with_signal" => {
|
||||
config.exit_with = Some(ExitWith::Signal);
|
||||
}
|
||||
"--no_print_cwd" => {
|
||||
config.no_print_cwd = true;
|
||||
}
|
||||
"--print_stdin" => {
|
||||
config.print_stdin = true;
|
||||
}
|
||||
_ => {
|
||||
usage();
|
||||
@@ -61,20 +111,19 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut f = open_file(&config.destination_dir);
|
||||
// first two args are going to be the path to this executable and
|
||||
// the destination_dir we want to write to. Don't write either of those
|
||||
// as they'll be non-deterministic.
|
||||
f.write_fmt(format_args!(
|
||||
"cwd={}\nargs=\n",
|
||||
env::current_dir().unwrap().to_string_lossy()
|
||||
))
|
||||
.expect("failed to write to file");
|
||||
for arg in &args[2..] {
|
||||
f.write_fmt(format_args!("{}\n", arg))
|
||||
.expect("failed to write to file");
|
||||
}
|
||||
if let Some(destination_dir) = &config.destination_dir {
|
||||
write_content(open_file(destination_dir), &config, &args);
|
||||
} else {
|
||||
write_content(stdout(), &config, &args);
|
||||
}
|
||||
|
||||
match config.exit_with {
|
||||
None => std::process::exit(0),
|
||||
Some(ExitWith::Failure) => std::process::exit(2),
|
||||
Some(ExitWith::UrgentFailure) => std::process::exit(255),
|
||||
#[cfg(unix)]
|
||||
Some(ExitWith::Signal) => unsafe {
|
||||
uucore::libc::raise(uucore::libc::SIGINT);
|
||||
},
|
||||
}
|
||||
std::process::exit(if config.exit_with_failure { 2 } else { 0 });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 Collabora, Ltd.
|
||||
//
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
fn main() {
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
std::process::exit(findutils::xargs::xargs_main(
|
||||
&args.iter().map(|s| s.as_ref()).collect::<Vec<&str>>(),
|
||||
))
|
||||
}
|
||||
+1202
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,326 @@
|
||||
// Copyright 2021 Collabora, Ltd.
|
||||
//
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
/// ! This file contains integration tests for xargs, separate from the unit
|
||||
/// ! tests so that testing-commandline can be built first.
|
||||
extern crate findutils;
|
||||
extern crate tempfile;
|
||||
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
|
||||
use assert_cmd::Command;
|
||||
use predicates::prelude::*;
|
||||
|
||||
use common::test_helpers::*;
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn xargs_basics() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.write_stdin("abc\ndef g\\hi 'i j \"k'")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff("abc def ghi i j \"k\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_null() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(&["-0n1"])
|
||||
.write_stdin("ab c\0d\tef\0")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff("ab c\nd\tef\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_delim() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(&["-d1"])
|
||||
.write_stdin("ab1cd1ef")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff("ab cd ef\n"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(&["-d\\t", "-n1"])
|
||||
.write_stdin("a\nb\td e\tfg")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff("a\nb\nd e\nfg\n"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(&["-dabc"])
|
||||
.assert()
|
||||
.failure()
|
||||
.code(1)
|
||||
.stderr(predicate::str::contains("Invalid"))
|
||||
.stdout(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_null_conflict() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(&["-d\t", "-0n1"])
|
||||
.write_stdin("ab c\0d\tef\0")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff("ab c\nd\tef\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_if_empty() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
// Should echo at least once still.
|
||||
.stdout(predicate::eq("\n"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(&["--no-run-if-empty"])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
// Should never echo.
|
||||
.stdout(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_max_args() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-n2"])
|
||||
.write_stdin("ab cd ef\ngh i")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff("ab cd\nef gh\ni\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_max_lines() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-L2"])
|
||||
.write_stdin("ab cd\nef\ngh i\n\njkl\n")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff("ab cd ef\ngh i jkl\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_max_args_lines_conflict() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
// -n2 is last, so it should be given priority.
|
||||
.args(["-L2", "-n2"])
|
||||
.write_stdin("ab cd ef\ngh i")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::contains("WARNING"))
|
||||
.stdout(predicate::str::diff("ab cd\nef gh\ni\n"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
// -L2 is last, so it should be given priority.
|
||||
.args(["-n2", "-L2"])
|
||||
.write_stdin("ab cd\nef\ngh i\n\njkl\n")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::contains("WARNING"))
|
||||
.stdout(predicate::str::diff("ab cd ef\ngh i jkl\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_max_chars() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-s11"])
|
||||
.write_stdin("ab cd efg")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff("ab cd\nefg\n"));
|
||||
|
||||
// Behavior should be the same with -x, which only takes effect with -L or
|
||||
// -n.
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-xs11"])
|
||||
.write_stdin("ab cd efg")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff("ab cd\nefg\n"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-s10"])
|
||||
.write_stdin("abcdefghijkl ab")
|
||||
.assert()
|
||||
.failure()
|
||||
.code(1)
|
||||
.stderr(predicate::str::contains("Error:"))
|
||||
.stdout(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_exit_on_large() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-xs11", "-n2"])
|
||||
.write_stdin("ab cd efg h i")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff("ab cd\nefg h\ni\n"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-xs11", "-n2"])
|
||||
.write_stdin("abcdefg hijklmn")
|
||||
.assert()
|
||||
.failure()
|
||||
.code(1)
|
||||
.stderr(predicate::str::contains("Error:"))
|
||||
.stdout(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_exec() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args([
|
||||
"-n2",
|
||||
&path_to_testing_commandline(),
|
||||
"-",
|
||||
"--print_stdin",
|
||||
"--no_print_cwd",
|
||||
])
|
||||
.write_stdin("a b c\nd")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff(
|
||||
"stdin=\nargs=\n--print_stdin\n--no_print_cwd\na\nb\n\
|
||||
stdin=\nargs=\n--print_stdin\n--no_print_cwd\nc\nd\n",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_exec_stdin_open() {
|
||||
let mut temp_file = tempfile::NamedTempFile::new().unwrap();
|
||||
|
||||
write!(temp_file, "a b c").unwrap();
|
||||
temp_file.seek(SeekFrom::Start(0)).unwrap();
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args([
|
||||
"-a",
|
||||
&temp_file.path().to_string_lossy(),
|
||||
&path_to_testing_commandline(),
|
||||
"-",
|
||||
"--print_stdin",
|
||||
"--no_print_cwd",
|
||||
])
|
||||
.write_stdin("test")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff(
|
||||
"stdin=test\nargs=\n--print_stdin\n--no_print_cwd\na\nb\nc\n",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_exec_failure() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(&[
|
||||
"-n1",
|
||||
&path_to_testing_commandline(),
|
||||
"-",
|
||||
"--no_print_cwd",
|
||||
"--exit_with_failure",
|
||||
])
|
||||
.write_stdin("a b")
|
||||
.assert()
|
||||
.failure()
|
||||
.code(123)
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(
|
||||
"args=\n--no_print_cwd\n--exit_with_failure\na\n\
|
||||
args=\n--no_print_cwd\n--exit_with_failure\nb\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_exec_urgent_failure() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(&[
|
||||
"-n1",
|
||||
&path_to_testing_commandline(),
|
||||
"-",
|
||||
"--no_print_cwd",
|
||||
"--exit_with_urgent_failure",
|
||||
])
|
||||
.write_stdin("a b")
|
||||
.assert()
|
||||
.failure()
|
||||
.code(124)
|
||||
.stderr(predicate::str::contains("Error:"))
|
||||
.stdout("args=\n--no_print_cwd\n--exit_with_urgent_failure\na\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn xargs_exec_with_signal() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(&[
|
||||
"-n1",
|
||||
&path_to_testing_commandline(),
|
||||
"-",
|
||||
"--no_print_cwd",
|
||||
"--exit_with_signal",
|
||||
])
|
||||
.write_stdin("a b")
|
||||
.assert()
|
||||
.failure()
|
||||
.code(125)
|
||||
.stderr(predicate::str::contains("Error:"))
|
||||
.stdout("args=\n--no_print_cwd\n--exit_with_signal\na\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_exec_not_found() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(&["this-file-does-not-exist"])
|
||||
.assert()
|
||||
.failure()
|
||||
.code(127)
|
||||
.stderr(predicate::str::contains("Error:"))
|
||||
.stdout(predicate::str::is_empty());
|
||||
}
|
||||
@@ -10,6 +10,7 @@ fi
|
||||
# build the rust implementation
|
||||
cargo build --release
|
||||
cp target/release/find ../findutils.gnu/find.rust
|
||||
cp target/release/xargs ../findutils.gnu/xargs.rust
|
||||
|
||||
# Clone and build upstream repo
|
||||
cd ../findutils.gnu
|
||||
@@ -21,6 +22,7 @@ fi
|
||||
|
||||
# overwrite the GNU version with the rust impl
|
||||
cp find.rust find/find
|
||||
cp xargs.rust xargs/xargs
|
||||
|
||||
if test -n "$1"; then
|
||||
# if set, run only the test passed
|
||||
|
||||
Reference in New Issue
Block a user