如何在 Bash 脚本中同时获取命令的结果及其返回码?

pef*_*cio 2 bash bash-scripting

我正在玩 Bash 脚本。我想将命令的结果分配给一个变量,并将其返回代码分配给另一个变量。

前任。:

line_count=$(cat "$filename" | wc -l) #If the file does not exist, an error code is returned
cmd_return_code=$? #I want to assign the previous error code to a variable
Run Code Online (Sandbox Code Playgroud)

我尝试如上所示使用$?,但它捕获了前一个赋值本身的返回码,即 0。

我怎样才能做到这一点?

谢谢

Gor*_*son 5

这里的问题是,在管道中cat "$filename" | wc -l,当文件不存在时,cat会出现错误退出,但wc -l会成功计算从 接收到的 0 行文本cat。管道中最后一个命令的退出状态被视为整个管道的最终状态,因此整个管道被认为是成功的。像这样:

$ cat nosuchfile | wc -l
cat: nosuchfile: No such file or directory
       0
$ echo "$?"
0
Run Code Online (Sandbox Code Playgroud)

通常在 bash 中,您可以使用数组获取管道中单个命令的状态PIPESTATUS,如下所示:

$ cat nosuchfile | wc -l
cat: nosuchfile: No such file or directory
       0
$ echo "${PIPESTATUS[@]}"
1 0
Run Code Online (Sandbox Code Playgroud)

...但这不适用于命令扩展中的管道$( ),因为该管道将在子 shell 中执行,并且PIPESTATUS无法从子 shell 传播;只有最终状态会传递回父 shell:

$ line_count=$(cat nosuchfile | wc -l)
cat: nosuchfile: No such file or directory
$ echo "${PIPESTATUS[@]}"
0
Run Code Online (Sandbox Code Playgroud)

那么,你能对此做些什么呢?好吧,正如 l0b0 所说,一种可能性是设置选项pipefail。您不必对整个脚本执行此操作,您可以通过在命令替换中执行此操作来仅针对特定子 shell 进行设置:

$ line_count=$(set -o pipefail; cat nosuchfile | wc -l)
cat: nosuchfile: No such file or directory
$ echo "$?"
1
Run Code Online (Sandbox Code Playgroud)

对于这个特定的命令,您还可以消除管道(这就是所谓的cat 的无用使用,或 UUOC),并wc通过将文件名作为参数传递来直接从文件中读取:

$ line_count=$(wc -l nosuchfile)
wc: nosuchfile: open: No such file or directory
$ echo $?
1
Run Code Online (Sandbox Code Playgroud)

...或者通过使用输入重定向:

$ line_count=$(wc -l <nosuchfile)
-bash: nosuchfile: No such file or directory
$ echo $?
1
Run Code Online (Sandbox Code Playgroud)

这两个选项之间有一些区别:如果您将文件名传递给wc(并且它存在),它将输出文件名以及行数:

$ line_count=$(wc -l realfile.txt)
$ echo "$line_count"
       6 realfile.txt
Run Code Online (Sandbox Code Playgroud)

...而使用重定向选项则不会。另外,使用第二个选项时,shell 负责打开文件(并将打开的文件句柄交给命令wc),因此如果失败,shell 会给出错误(请注意,错误消息来自“-bash”,不wc)并且wc永远不会运行。