假设我们有一个bash脚本,如下所示:
echo "x" &
echo "y" &
echo "z" &
.....
echo "Z" &
wait
有没有办法收集子外壳程序/子进程的退出代码?寻找这样做的方法,找不到任何东西。我需要并行运行这些子shell,否则,这会更容易。
我正在寻找一个通用的解决方案(我有一个未知/动态数量的子进程可以并行运行)。
假设我们有一个bash脚本,如下所示:
echo "x" &
echo "y" &
echo "z" &
.....
echo "Z" &
wait
有没有办法收集子外壳程序/子进程的退出代码?寻找这样做的方法,找不到任何东西。我需要并行运行这些子shell,否则,这会更容易。
我正在寻找一个通用的解决方案(我有一个未知/动态数量的子进程可以并行运行)。
Answers:
亚历山大·米尔斯(Alexander Mills)使用handleJobs的答案给了我一个很好的起点,但也给了我这个错误
警告:run_pending_traps:trap_list [17]中的值错误:0x461010
相反,我只是存储每个孩子的pid,然后等待,并专门获取每个孩子的退出代码。我从子流程的角度来看,找到了这种更清洁的方法,这些子流程在函数中产生了子流程,并且避免了在我打算等待子进程的情况下等待父进程的风险。由于不使用陷阱,因此会更清楚发生什么情况。
#!/usr/bin/env bash
# it seems it does not work well if using echo for function return value, and calling inside $() (is a subprocess spawned?)
function wait_and_get_exit_codes() {
children=("$@")
EXIT_CODE=0
for job in "${children[@]}"; do
echo "PID => ${job}"
CODE=0;
wait ${job} || CODE=$?
if [[ "${CODE}" != "0" ]]; then
echo "At least one test failed with exit code => ${CODE}" ;
EXIT_CODE=1;
fi
done
}
DIRN=$(dirname "$0");
commands=(
"{ echo 'a'; exit 1; }"
"{ echo 'b'; exit 0; }"
"{ echo 'c'; exit 2; }"
)
clen=`expr "${#commands[@]}" - 1` # get length of commands - 1
children_pids=()
for i in `seq 0 "$clen"`; do
(echo "${commands[$i]}" | bash) & # run the command via bash in subshell
children_pids+=("$!")
echo "$i ith command has been issued as a background job"
done
# wait; # wait for all subshells to finish - its still valid to wait for all jobs to finish, before processing any exit-codes if we wanted to
#EXIT_CODE=0; # exit code of overall script
wait_and_get_exit_codes "${children_pids[@]}"
echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"
# end
for job in "${childen[@]}"; do
应该很酷for job in "${1}"; do
children_pids+=("$!")
实际上正在捕获子外壳所需的pid。
使用wait
具有PID,这将:
等待直到由每个进程ID pid或作业规范jobspec指定的子进程退出,并返回等待的最后一个命令的退出状态。
进行时,您需要保存每个进程的PID:
echo "x" & X=$!
echo "y" & Y=$!
echo "z" & Z=$!
您还可以在脚本中使用set -m
和使用%n
jobspec 来启用作业控制,但是您几乎肯定不希望作业控制还有很多其他副作用。
wait
将返回与完成过程相同的代码。您可以wait $X
在以后的任何(合理)时间使用来访问最终代码,$?
也可以将其用作true / false:
echo "x" & X=$!
echo "y" & Y=$!
...
wait $X
echo "job X returned $?"
wait
将暂停直到命令完成(如果尚未执行)。
如果要避免这样的停顿,可以设置trap
onSIGCHLD
,计算终止的数量,并wait
在所有结束时立即处理所有。您wait
几乎可以一直独自使用,从而摆脱困境。
wait $X
在任何(合理的)稍后时间运行。
使用compound command
-将语句放在括号中:
( echo "x" ; echo X: $? ) &
( true ; echo TRUE: $? ) &
( false ; echo FALSE: $? ) &
将给出输出
x
X: 0
TRUE: 0
FALSE: 1
并行运行多个命令的一种真正不同的方法是使用GNU Parallel。列出要运行的命令列表,并将其放入文件中list
:
cat > list
sleep 2 ; exit 7
sleep 3 ; exit 55
^D
并行运行所有命令,并在文件中收集退出代码job.log
:
cat list | parallel -j0 --joblog job.log
cat job.log
输出为:
Seq Host Starttime JobRuntime Send Receive Exitval Signal Command
1 : 1486892487.325 1.976 0 0 7 0 sleep 2 ; exit 7
2 : 1486892487.326 3.003 0 0 55 0 sleep 3 ; exit 55
PIPESTATUS
您应该检查一下。这seq 10 | gzip -c > seq.gz ; echo ${PIPESTATUS[@]}
将返回0 0
(第一个和最后一个命令的退出代码)。
这是您要查找的通用脚本。唯一的缺点是您的命令用引号引起来,这意味着通过IDE突出显示语法不会真正起作用。否则,我尝试了其他几个答案,这是最好的答案。这个答案包含了wait <pid>
@Michael给出的使用想法,但是通过使用trap
似乎效果最好的命令又走了一步。
#!/usr/bin/env bash
set -m # allow for job control
EXIT_CODE=0; # exit code of overall script
function handleJobs() {
for job in `jobs -p`; do
echo "PID => ${job}"
CODE=0;
wait ${job} || CODE=$?
if [[ "${CODE}" != "0" ]]; then
echo "At least one test failed with exit code => ${CODE}" ;
EXIT_CODE=1;
fi
done
}
trap 'handleJobs' CHLD # trap command is the key part
DIRN=$(dirname "$0");
commands=(
"{ echo 'a'; exit 1; }"
"{ echo 'b'; exit 0; }"
"{ echo 'c'; exit 2; }"
)
clen=`expr "${#commands[@]}" - 1` # get length of commands - 1
for i in `seq 0 "$clen"`; do
(echo "${commands[$i]}" | bash) & # run the command via bash in subshell
echo "$i ith command has been issued as a background job"
done
wait; # wait for all subshells to finish
echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"
# end
感谢@michael homer使我步入正轨,但是使用trap
命令是AFAICT的最佳方法。
@rolf答案的另一种变化:
保存退出状态的另一种方法是
mkdir /tmp/status_dir
然后有每个脚本
script_name="${0##*/}" ## strip path from script name
tmpfile="/tmp/status_dir/${script_name}.$$"
do something
rc=$?
echo "$rc" > "$tmpfile"
这为每个状态文件提供了唯一的名称,包括创建该文件的脚本的名称及其进程ID(以防正在运行同一脚本的多个实例),您可以将其保存以供日后参考,并将其全部放入放在同一位置,这样您就可以在完成后删除整个子目录。
您甚至可以通过以下操作从每个脚本中保存多个状态:
tmpfile="$(/bin/mktemp -q "/tmp/status_dir/${script_name}.$$.XXXXXX")"
它会像以前一样创建文件,但是会向其中添加一个唯一的随机字符串。
或者,您可以仅将更多状态信息附加到同一文件。
script3
仅在script1
和script2
成功时才会执行,script1
并且script2
将并行执行:
./script1 &
process1=$!
./script2 &
process2=$!
wait $process1
rc1=$?
wait $process2
rc2=$?
if [[ $rc1 -eq 0 ]] && [[ $rc2 -eq 0 ]];then
./script3
fi