简体   繁体   中英

std::process:Command not returning Err() result when using 'bash -c'

I'm trying to understand why I'm not getting an Err() result when using bash -c expression to run a command.

Here is an example and the output below. I expect out2 and out3 to be an Err() but out3 is an Ok() with the failed status.

I'm using bash -c to execute a command from a given string, which is easy.

It's possible to get an Err() result using bash -c syntax?

#![allow(unused)]

use std::process::{Command, Output};

fn main() {
    let out1 = Command::new("bash").arg("-c").arg("ls").output();
    println!("out1: {:?}", out1);

    let out2 = Command::new("wrongcommand").arg("-c").arg("ls").output();
    println!("out2: {:?}", out2);

    let out3 = Command::new("bash").arg("-c").arg("wrongcommand").output();
    println!("out3: {:?}", out3);
}

Output:

out1: Ok(Output { status: ExitStatus(ExitStatus(0)), stdout: "Cargo.lock\nCargo.toml\ncrate-information.json\nsrc\ntarget\n", stderr: "" })
out2: Err(Os { code: 2, kind: NotFound, message: "No such file or directory" })
out3: Ok(Output { status: ExitStatus(ExitStatus(32512)), stdout: "", stderr: "bash: wrongcommand: command not found\n" })

I tried from command line and

$ bash -c wrongcommand

and

$ wrongcommand

and both return the same exit code (127). That's why I expected that Command failed in the same way.

That can be explained pretty easily. Follow along on the list of error codes

out1 is Ok for obvious reasons

out2 is an Err type because Command directly looked for the process, could not find it, and returned ENOENT (code 2). This error happened within rust, nothing was actually executed.

out3 is Ok because the process that Command ran is bash , and it returned its status. In this case, it wouldn't have found the command, so by bash statuses, it'll have returned 127. However, it's not that easy, because there is an additional layer of information contained in ExitStatus . On unix, when a process fails, it actually returns a 16bit/32bit integer (based on platform/libc/etc), separated in two:

  • The highest bits are what signal triggered the return
  • The lowest 8 bits are the return status of the process

And, to no surprise, if we shift 32512 8 bits to the right (it was an u16 ), we get... 127!

The takeaway is simple:

  • Err definitely means the process didn't run
  • Ok means the main process ran, but you'll need to check ExitStatus::success() to confirm that it actually did (ie 0 as exit status)

You could recover it like so:

Command::new("bash").arg("-c").arg("wrongcommand").output().and_then(|r| match r.status.success() {
  true => Ok(r),
  false => Err(io::Error::new(io::ErrorKind::InvalidData, "Process error"))
});

You can play with this on the playground . success() is a decently reliable indicator of child process success; all it does is check if the least significant 8 bits of the exit status is non-zero.

Obviously, this does not help if a process returns non-zero on success, but that's a different problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM