【发布时间】:2021-11-19 02:18:50
【问题描述】:
只有在所有子流程(任务)完成后,我的脚本才应该退出。我使用xargs 并行运行任务。如果任务以错误结束,它应该等待所有正在运行的任务完成,但不应启动新任务。我在这里运行 3 个任务:sleep 4、sleep 2 和 sleep 1。并行任务不超过 2 个。 sleep 1 任务崩溃,但由于某种原因 xargs 没有等待 sleep 4 完成,提前退出。
#!/usr/bin/env bash
set -eu -o pipefail
function foo() {
local sec="$1"
echo "start foo $sec"
sleep "$sec"
echo "finished foo $sec"
if ((sec == 1)); then
return 1
fi
}
export -f foo
echo "starting..."
printf '%s\0' 4 2 1 | xargs -t -0 -I{} -P 2 bash -c 'foo "{}" || exit 255' || echo "finished early, exit_code=$?"
echo "finished"
❯ ./测试员
开始...
bash -c 'foo "4" ||出口 255'
bash -c 'foo "2" ||出口 255'
开始 foo 4
开始 foo 2
完成 foo 2
bash -c 'foo "1" ||出口 255'
开始富 1
完成 foo 1
xargs: bash: 以状态 255 退出;中止
提前结束,exit_code=124
完成的
.. 最后一个命令花了 3 秒
❯ 完成 foo 4
在我看到 bash 提示符后打印最后一行。有趣的是,如果我尝试运行 4 个任务(4、2、1、5),代码会按预期工作:
printf '%s\0' 4 2 1 5 | xargs -t -0 -I{} -P 2 bash -c 'foo "{}" || exit 255' || echo "finished early, exit_code=$?"
这是 xargs 中的错误,还是我做错了什么?
更新:xargs 版本:(GNU findutils) 4.7.0 在 Linux Mint 20.2 上
【问题讨论】:
-
请注意,
xargs -I{} bash -c '...{}...'会带来严重的安全风险,无论您在{}周围使用何种引用。考虑数据项$(rm -rf ~)'$(rm -rf ~)'——它在所有可能的上下文中执行(不带引号的、单引号的、双引号的)。 -
(作为另一个没有严格解决您的问题的旁白:我也强烈建议不要使用
set -e- 它的行为在 shell 之间以及同一 shell 的各个版本之间差异很大,使得代码难以检查正确性;请参阅exercise section of BashFAQ #105) -
另外,我强烈建议
printf '%s\0' 4 2 1 5。格式字符串会根据需要重复多次以使用所有参数。此外,您希望有一个最终的 NUL - NUL 是终止符,而不是分隔符。就像read如果末尾没有换行符(如果末尾没有 NUL 则read -d '')返回非零退出状态一样,当您没有最终分隔符时 xargs 的行为也没有明确定义。 -
另外,回复:
function foo() {,参见wiki.bash-hackers.org/scripting/obsolete -
(回到我之前开始的切线:作为
xargs -I{} bash -c '...{}...'的更安全替代方案,请考虑xargs bash -c 'for arg; do foo "$arg" || exit 255; done' _;它也更有效,因为您可以将更多项目传递给每个bash 副本 - - 使用xargs -n参数调整数量 - 并减少支付 shell 启动成本)。