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

问题描述 投票:23回答:9

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

parse_commands /da/cmd/file | process_commands

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

难道没有办法使用非零退出代码检测左侧是否退出?

bash pipe pipeline
9个回答
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

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


29
投票

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


5
投票

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

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


4
投票

您可以尝试使用fifo进行一些工作:

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

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

2
投票

我没有足够的声誉评论,但accepted answer错过了第5行的关闭}

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

这是因为[ ${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

如预期。

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


0
投票

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

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

(command1 && command2) ; rm temporaryfile

0
投票

在bash 4.0中有一种方法可以做到这一点,它添加了来自灰的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,并将管道连接到主机壳。 pid保存在parser中。然后process_commandsparse_commands的输出作为输入。 (这就是<&p所做的。)这也被放在背景中,其pid保存在processor中。

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

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


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 $?
)

0
投票

关于什么:

parse_commands /da/cmd/file > >(process_commands)
© www.soinside.com 2019 - 2024. All rights reserved.