Running an external command with parentheses as arguments

Viewed 23

I've got a bash command that works:

find content/posts \( -iname '*.jpg' \) -print0 | xargs -0 -P8 -n2 mogrify -auto-orient

I'm trying to convert it into a Rust command:

let mut cmd = Command::new("/usr/bin/find")
        .args([
                post.dest_dir(&dest_dir).as_os_str().to_str().unwrap(),
                "(-iname '*.jpg')",
                "-print0",
        ])
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();

let output = Command::new("/usr/bin/xargs")
        .args(["-0", "-P8", "-n2", "mogrify", "-auto-orient"])
        .stdin(cmd.stdout.take().unwrap())
        .output()
        .unwrap_or_else(|e| panic!("failed to execute process: {}", e));

However I'm getting the following output:

find: (-iname '*.jpg'): No such file or directory
exit code: exit status: 1

mogrify: no decode delegate for this image format `' @ error/constitute.c/ReadImage/741.
mogrify: no decode delegate for this image format `MD' @ error/constitute.c/ReadImage/741.

mogrify: insufficient image data in file `content/posts/2021-06-26-united-states/DSCF1507.JPG' @ error/jpeg.c/ReadJPEGImage_/1120.

It appears to be reading the right directory but it's not respecting the pattern passed to find \( -iname '*.jpg' \) and including markdown files.

Is there a proper way to pass those arguments into Command?

1 Answers

Two rules will make this work:

  1. Everything that's space-separated in the original bash command needs to be passed as a separate argument.

  2. Quoting with ' and escaping with \ are shell syntax that don't need to be carried over to the Rust version.

You can also use post.dest_dir(&dest_dir) directly by passing it in a separate .arg() call. This gets rid of the string conversions and the possibility of failure if the path is not valid UTF-8.

let mut cmd = Command::new("/usr/bin/find")
    .arg(post.dest_dir(&dest_dir))
    .args(["(", "-iname", "*.jpg", ")", "-print0"]);
Related