如何根据退出代码的结果通过管道传输命令的标准输出

如何根据退出代码的结果通过管道传输命令的标准输出

扩展自这个问题,我们有一个用例,我们希望根据该命令是成功还是失败来通过管道传输命令的标准输出。

我们从一个基本的管道开始

command | grep -P "foo"

但是我们注意到有时command不会向标准输出输出任何内容,但确实有退出代码0.我们想忽略这种情况,只grep在退出代码为1

对于一个工作示例,我们可以实现如下命令:

OUTPUT=$(command) # exit code is 0 or 1
RESULT=$?
if [ $RESULT -eq 0 ]; then
  return $RESULT; # if the exit code is 0 then we simply pass it forward
else
  grep -P "foo" <<< $OUTPUT; # otherwise check if the stdout contains "foo"
fi

但这有很多缺点,即您必须编写脚本,这意味着您不能只在控制台中执行它。这也显得有些业余。

为了更简洁的语法,我想象一个虚构的三元运算符,如果退出代码是1,则它通过管道传输,否则它将向前传递退出代码。

command |?1 grep -P "foo" : $?

是否有一系列的操作符和实用程序可以实现这个结果?

答案1

管道中的命令同时运行,这就是管道和进程间通信机制的全部意义。

在:

cmd1 | cmd2

cmd1cmd2同时启动,cmd2处理写入的数据cmd1

如果您只想在失败cmd2时启动cmd1,则必须在完成并报告其退出状态cmd2后启动cmd1,因此您不能使用管道,您必须使用一个临时文件来保存所有数据cmd1已产生:

 cmd1 > file || cmd2 < file; rm -f file

或者像您的示例一样存储在内存中,但这有许多其他问题(例如$(...)删除所有尾随换行符,并且大多数 shell 无法处理其中的 NUL 字节,更不用说大输出的缩放问题)。

zsh在 Linux 上,使用像或这样的shell 将bash此处文档和此处字符串存储在临时文件中,您可以执行以下操作:

{ cmd1 > /dev/fd/3 || cmd2 <&3 3<&-; } 3<<< ignored

让 shell 处理临时文件的创建和清理。

bash 版本 5 现在在创建临时文件后删除了对临时文件的写入权限,因此上述方法不起作用,您需要首先恢复写入权限来解决它:

{ chmod u+w /dev/fd/3
  cmd1 > /dev/fd/3 || cmd2 <&3 3<&-; } 3<<< ignored

手动,POSIXly:

tmpfile=$(
  echo 'mkstemp(template)' |
    m4 -D template="${TMPDIR:-/tmp}/XXXXXX"
) && [ -n "$tmpfile" ] && (
  rm -f -- "$tmpfile" || exit
  cmd1 >&3 3>&- 4<&- ||
    cmd2 <&4 4<&- 3>&-) 3> "$tmpfile" 4< "$tmpfile"

某些系统有一个非标准mktemp命令(尽管系统之间的接口有所不同),这使得临时文件的创建更容易一些(tmpfile=$(mktemp)对于大多数实现来说应该足够了,尽管有些系统不会创建文件,因此您可能需要调整umask)。对于兼容的实现来说,这[ -n "$tmpfile" ]不是必需的m4,但 GNU至少不兼容,因为当调用失败m4时,它不会返回非零退出状态。mkstemp()

另请注意,没有什么可以阻止您运行中的任何代码安慰。你的“脚本”可以在交互式 shell 的提示符下以相同的方式输入(除了return假设代码位于函数中的部分),但您可以将其简化为:

output=$(cmd) || grep foo <<< "$output"

答案2

注意:由于问题已标记,我假设您可以使用 bash 功能。

鉴于您的示例代码,您似乎想要做的是:

  • 如果命令的退出状态为 0,则使用该命令的退出状态,
  • 否则使用grep的退出状态。

在空管道上运行grep不会花费您任何费用,因此一种选择是无论如何都通过管道传输它,并检查命令的退出状态,您可以PIPESTATUS在 bash 中使用数组来获取该状态:

$ (echo foo; exit 1) | grep foo
foo
$ echo "${PIPESTATUS[@]}"
1 0  

这里,subshel​​l 以 1 退出,grep 以 0 退出。

所以你可以这样做:

(command | grep -P "foo"; exit "$((PIPESTATUS[0] ? $? : 0))")

表达式可以简化,但你明白了。


还可以选择简单地伪造输出:

(command && echo foo) | grep -P foo

echo foo仅当命令成功时才会运行where ,从而使 grep 也成功。

相关内容