告知左侧故障管道的右侧?

时间:2011-07-03 21:33:13

标签: bash pipe pipeline

我喜欢在shell脚本中的函数之间使用类似生成器的模式。像这样:

parse_commands /da/cmd/file | process_commands

但是,这种模式的基本问题是,如果parse_command遇到错误,我发现通知process_command失败的唯一方法是明确告诉它(例如echo“FILE_NOT_FOUND”)。这意味着必须对parse_command中的每个潜在错误操作进行隔离。

process_command是否有办法检测到左侧是否以非零退出代码退出?

9 个答案:

答案 0 :(得分:29)

在bash脚本的顶部使用set -o pipefail,这样当管道左侧出现故障(退出状态!= 0)时,右侧不会执行。

答案 1 :(得分:14)

即使第一个流程已经结束,管道流程是否仍在继续,或者是您无法知道第一个流程失败的问题?

如果是后者,您可以查看PIPESTATUS变量(实际上是BASH数组)。这将为您提供第一个命令的退出代码:

parse_commands /da/cmd/file | process_commands
temp=("${PIPESTATUS[@]}")
if [ ${temp[0]} -ne 0 ]
then
    echo 'parse_commands failed'
elif [ ${temp[1]} -ne 0 ]
then
    echo 'parse_commands worked, but process_commands failed'
fi

否则,您将不得不使用协同进程。

答案 2 :(得分:5)

与运算符(&&)不同,管道运算符(|)通过同时生成两个进程来工作,因此第一个进程可以将其输出传递给第二个进程,而无需缓冲中间数据。这允许在几乎没有内存或磁盘使用的情况下处理大量数据。

因此,第一个进程的退出状态在第二个进程完成之前不可用。

答案 3 :(得分:4)

您可以尝试使用fifo:

mkfifo /tmp/a
cat /tmp/a | process_commands &

parse_cmd /da/cmd/file > /tmp/a || (echo "error"; # kill process_commands)

答案 4 :(得分:2)

我没有足够的声誉来评论,但accepted answer在第5行错过了结束}

修复此问题后,代码将抛出-ne: unary operator expected错误,指出问题:PIPESTATUSif命令后的条件覆盖,因此process_commands的返回值永远不会检查{1}}!

这是因为[ ${PIPESTATUS[0]} -ne 0 ]equivalent to test ${PIPESTATUS[0]} -ne 0,它会像其他任何命令一样更改$PIPESTATUS。例如:

return0 () { return 0;}
return3 () { return 3;}

return0 | return3
echo "PIPESTATUS: ${PIPESTATUS[@]}"

按预期返回PIPESTATUS: 0 3。但是,如果我们介绍条件呢?

return0 | return3
if [ ${PIPESTATUS[0]} -ne 0 ]; then
    echo "1st command error: ${PIPESTATUS[0]}"
elif [ ${PIPESTATUS[1]} -ne 0 ]; then
    echo "2nd command error: ${PIPESTATUS[1]}"
else
    echo "PIPESTATUS: ${PIPESTATUS[@]}"
    echo "Both return codes = 0."
fi

我们收到[: -ne: unary operator expected错误,并且:

PIPESTATUS: 2
Both return codes = 0.

要解决此问题,$PIPESTATUS应存储在不同的数组变量中,如下所示:

return0 | return3
TEMP=("${PIPESTATUS[@]}")
echo "TEMP: ${TEMP[@]}"
if [ ${TEMP[0]} -ne 0 ]; then
    echo "1st command error: ${TEMP[0]}"
elif [ ${TEMP[1]} -ne 0 ]; then
    echo "2nd command error: ${TEMP[1]}"
else
    echo "TEMP: ${TEMP[@]}"
    echo "All return codes = 0."
fi

打印哪些:

TEMP: 0 3
2nd command error: 3

按预期。

编辑:我修复了接受的答案,但我将这个解释留给后人。

答案 5 :(得分:0)

如果你有command1 && command2那么command2只会在第一个命令成功时执行 - 否则布尔短路就会启动。使用它的一种方法是建立第一个命令(你的parse_commands...转储到临时文件然后从该文件中获取第二个命令。

编辑:通过明智地使用;,您可以整理临时文件,例如

(command1 && command2) ; rm temporaryfile

答案 6 :(得分:0)

有一种方法可以在bash 4.0中执行此操作,它会从ash中添加coproc内置函数。这个协同处理工具是从ksh借用的,它使用不同的语法。我在我的系统上访问的唯一支持coprocesses的shell是ksh。这是用ksh编写的解决方案:

parse_commands  /da/cmd/file |&
parser=$!

process_commands <&p &
processor=$!

if wait $parser
then
    wait $processor
    exit $?
else
    kill $processor
    exit 1
fi

我们的想法是在后台启动parse_commands,并将管道连接到主shell。 pid保存在parser中。然后以process_commands的输出作为输入启动parse_commands。 (这就是<&p所做的。)这也是在后台放置,其pid保存在processor

通过管道连接后台的两个,我们的主shell可以自由地等待解析器终止。如果它没有错误地终止,我们等待处理器完成并退出其返回码。如果它以 错误终止,我们将终止处理器并以非零状态退出。

将其翻译为使用bash 4.0 / ash coproc内置应该是相当简单的,但我没有好的文档,也没有测试方法。

答案 7 :(得分:0)

您可以在显式子shell中运行parse_commands /da/cmd/file并通过管道echo将此子shell的退出状态运行到process_commands,该管道也在显式子shell中运行,以处理包含的管道数据在/dev/stdin

远非优雅,但似乎完成了工作:)

一个简单的例子:

(
( ls -l ~/.bashrcxyz; echo $? ) | 
( 
piped="$(</dev/stdin)"; 
[[ "$(tail -n 1 <<<"$piped")" -eq 0 ]] && printf '%s\n' "$piped" | sed '$d' || exit 77 
); 
echo $?
)

答案 8 :(得分:0)

怎么样:

parse_commands /da/cmd/file > >(process_commands)