我喜欢在shell脚本中的函数之间使用类似生成器的模式。像这样:
parse_commands /da/cmd/file | process_commands
但是,这种模式的基本问题是,如果parse_command遇到错误,我发现通知process_command失败的唯一方法是明确告诉它(例如echo“FILE_NOT_FOUND”)。这意味着必须对parse_command中的每个潜在错误操作进行隔离。
process_command是否有办法检测到左侧是否以非零退出代码退出?
答案 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
错误,指出问题:PIPESTATUS
被if
命令后的条件覆盖,因此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)