使用后台进程捕获并退出大 shell 脚本
Trap and exit big shell script with background processes
我有 2 个 shell 包含 ffmpeg 命令的脚本(command1.sh 和 command2.sh)。 command2.sh 有大约 500 个 ffmpeg 命令,它们通过“;”一个接一个地触发,而 command1.sh 处理音频 ffmpeg 命令。
主要问题: 杀死整个脚本需要太多时间,它会占用我执行另一个脚本所需的 CPU 1-2 分钟的能量所以我白白失去了 CPU 能量,因为我无法立即杀死它。
代码:
我有 init.sh 包含:
trap 'print TERM received;exit' 15
chmod +x command1.sh;
chmod +x command2.sh;
./command1.sh & ./command2.sh
所以它在后台触发。
然后我执行 pkill init.sh 我抓住了 shell trap 和 exit shell 脚本,但我得到 exitCode: 1, failed: true 并且后台命令仍然执行 1 分钟,直到它们被我在之后执行的另一个 kill ${pid} 杀死pkill.
shell 终止并行子任务
一些备注:
chmod +x
在这里没用。您完全可以 运行 sh command1.sh &
代替。
- 你必须独立终止所有子任务
- 由于这个问题被标记为shell,我的回答没有使用bashisms。这里的所有脚本都在 bash、dash 和 busybox.
下测试
类似于:
#!/bin/sh
for cmd in ./command1.sh ./command2.sh;do
exec $cmd &
PIDS="$PIDS $!"
done
trap "kill $PIDS;exit" 15
wait
当然,在 for cmd in
和 ;do
之间,您可以放置任意数量的 commandXX.sh
(只要您将行长度保持在安装的 OS 支持的最大值])
测试脚本:
这是一个快速 bash 测试脚本,它在 2.0 到 12.99 秒之间随机休眠,然后在退出前打印 done.
:
#!/bin/bash
declare -i toSleep
case in '' | *[!0-9]* ) toSleep='RANDOM%10+2' ;; * ) toSleep= ;; esac
exec {dummy}<> <(:)
read -t $toSleep.$RANDOM -u $dummy _
echo done.
我已将其保存到 command1.sh
、chmod +x ..
并链接到 command2.sh
...
更便携的包装器:
#!/bin/sh
for cmd in "$@";do
exec $cmd &
PIDS="$PIDS $!"
done
printf "You have to: kill -TERM %d\nto end %d tasks: %s\n" \
$$ $(echo $PIDS|wc -w) "$PIDS"
trap "kill $PIDS;echo 'Process $$ killed.';exit" 15
wait
echo "Process $$ running $@ ended normally"
您可以将此 shell 脚本保存到名为 simpleParallel.sh
的文件中,作为示例,然后:
chmod +x simpleParallel.sh
./simpleParallel.sh ./command1.sh ./command2.sh
You have to: kill -TERM 741297
to end 2 tasks: 741298 741299
那么如果你 kill -TERM 741297
来自其他地方,
Process 741297 killed.
但如果你不这样做,你可能会读到类似这样的内容:
./simpleParallel.sh ./command1.sh ./command2.sh
You have to: kill -TERM 741968
to end 2 tasks: 741969 741970
done.
done.
Process 741968 running ./command1.sh ./command2.sh ended normally
注意:如果您在一个进程已完成时发送 kill 命令,您可能会看到如下错误消息:
./simpleParallel.sh: 1: kill: No such process
参见 bash 版本可以避免这个错误。
从另一个 终端控制台
在 运行ning ./simpleParallel.sh
之前,您可以 运行 tty
不带参数:
tty
/dev/pts/2
然后在新的免费 window 中,您可以 运行:
watch ps --tty pts/2
同时使用另一个 window 再次执行 运行 kill
命令。
bash 有一些 bash 主义 ,现在:
最近bash下,有很多特色
- 您可以使用数组来存储后台任务的 PID。
- 从 5.0 版本开始,有一个
$EPOCHREALTIME
变量,它扩展为自 Unix 纪元以来的秒数,粒度为微秒。
- 从 5.0 版开始,关联数组允许下标包含空格。
- 从5.1版本开始,
wait
有一个新的[-p VARNAME]
选项,它存储wait -n
返回的PID
或wait
没有参数。
我的脚本可以变成:
#!/bin/bash
declare -A PIDS ENDED
started=$EPOCHREALTIME
for cmd; do
exec "$cmd" &
PIDS["$cmd"]=$!
CMDS[$!]="$cmd"
done
printf "You have to: kill -TERM %d\nto end %d tasks: %s\n" \
$$ ${#PIDS[@]} "${PIDS[*]}"
trapExit(){
kill "${PIDS[@]}"
printf 'Process %s + %d task killed: %s\n' $$ ${#PIDS[@]} "${!PIDS[*]}"
showDone
exit 1
}
trap trapExit 15
showDone() {
for cmd in "${!ENDED[@]}";do
read -r elap < <(bc -l <<<"${ENDED["$cmd"]}-$started")
printf "Elapsed: %.4f sec for %s\n" "$elap" "$cmd"
done
}
while ((${#PIDS[@]}));do if wait -n -p pid ;then
printf "Process %d done (%s).\n" "$pid" "${CMDS[pid]}"
ENDED["${CMDS[pid]}"]=$EPOCHREALTIME
unset PIDS["${CMDS[pid]}"] CMDS[pid]
fi; done
echo "Process $$ running $* ended normally"
showDone
输出可能如下所示:
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1309816
to end 2 tasks: 1309817 1309818
done.
Process 1309817 done (./command1.sh).
done.
Process 1309818 done (./command2.sh).
Process 1309816 running ./command1.sh ./command2.sh ended normally
Elapsed: 3.1644 sec for ./command1.sh
Elapsed: 4.2488 sec for ./command2.sh
或
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1310031
to end 2 tasks: 1310032 1310033
done.
Process 1310033 done (./command2.sh).
done.
Process 1310032 done (./command1.sh).
Process 1310031 running ./command1.sh ./command2.sh ended normally
Elapsed: 9.1868 sec for ./command1.sh
Elapsed: 3.2310 sec for ./command2.sh
如果你 kill
他们早:
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294577
to end 2 tasks: 1294578 1294579
Process 1294577 + 2 task killed: ./command1.sh ./command2.sh
如果你 kill
他们以后:
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294958
to end 2 tasks: 1294959 1294960
done.
Process 1294959 done (./command1.sh).
Process 1294958 + 1 task killed: ./command2.sh
Elapsed: 3.1779 sec for ./command1.sh
或
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294971
to end 2 tasks: 1294972 1294973
done.
Process 1294973 done (./command2.sh).
Process 1294971 + 1 task killed: ./command1.sh
Elapsed: 6.9344 sec for ./command2.sh
并且没有关于试图杀死不存在的 pid 的错误消息。
我有 2 个 shell 包含 ffmpeg 命令的脚本(command1.sh 和 command2.sh)。 command2.sh 有大约 500 个 ffmpeg 命令,它们通过“;”一个接一个地触发,而 command1.sh 处理音频 ffmpeg 命令。
主要问题: 杀死整个脚本需要太多时间,它会占用我执行另一个脚本所需的 CPU 1-2 分钟的能量所以我白白失去了 CPU 能量,因为我无法立即杀死它。
代码: 我有 init.sh 包含:
trap 'print TERM received;exit' 15
chmod +x command1.sh;
chmod +x command2.sh;
./command1.sh & ./command2.sh
所以它在后台触发。
然后我执行 pkill init.sh 我抓住了 shell trap 和 exit shell 脚本,但我得到 exitCode: 1, failed: true 并且后台命令仍然执行 1 分钟,直到它们被我在之后执行的另一个 kill ${pid} 杀死pkill.
shell 终止并行子任务
一些备注:
chmod +x
在这里没用。您完全可以 运行sh command1.sh &
代替。- 你必须独立终止所有子任务
- 由于这个问题被标记为shell,我的回答没有使用bashisms。这里的所有脚本都在 bash、dash 和 busybox. 下测试
类似于:
#!/bin/sh
for cmd in ./command1.sh ./command2.sh;do
exec $cmd &
PIDS="$PIDS $!"
done
trap "kill $PIDS;exit" 15
wait
当然,在 for cmd in
和 ;do
之间,您可以放置任意数量的 commandXX.sh
(只要您将行长度保持在安装的 OS 支持的最大值])
测试脚本:
这是一个快速 bash 测试脚本,它在 2.0 到 12.99 秒之间随机休眠,然后在退出前打印 done.
:
#!/bin/bash
declare -i toSleep
case in '' | *[!0-9]* ) toSleep='RANDOM%10+2' ;; * ) toSleep= ;; esac
exec {dummy}<> <(:)
read -t $toSleep.$RANDOM -u $dummy _
echo done.
我已将其保存到 command1.sh
、chmod +x ..
并链接到 command2.sh
...
更便携的包装器:
#!/bin/sh
for cmd in "$@";do
exec $cmd &
PIDS="$PIDS $!"
done
printf "You have to: kill -TERM %d\nto end %d tasks: %s\n" \
$$ $(echo $PIDS|wc -w) "$PIDS"
trap "kill $PIDS;echo 'Process $$ killed.';exit" 15
wait
echo "Process $$ running $@ ended normally"
您可以将此 shell 脚本保存到名为 simpleParallel.sh
的文件中,作为示例,然后:
chmod +x simpleParallel.sh
./simpleParallel.sh ./command1.sh ./command2.sh
You have to: kill -TERM 741297
to end 2 tasks: 741298 741299
那么如果你 kill -TERM 741297
来自其他地方,
Process 741297 killed.
但如果你不这样做,你可能会读到类似这样的内容:
./simpleParallel.sh ./command1.sh ./command2.sh
You have to: kill -TERM 741968
to end 2 tasks: 741969 741970
done.
done.
Process 741968 running ./command1.sh ./command2.sh ended normally
注意:如果您在一个进程已完成时发送 kill 命令,您可能会看到如下错误消息:
./simpleParallel.sh: 1: kill: No such process
参见 bash 版本可以避免这个错误。
从另一个 终端控制台
在 运行ning ./simpleParallel.sh
之前,您可以 运行 tty
不带参数:
tty
/dev/pts/2
然后在新的免费 window 中,您可以 运行:
watch ps --tty pts/2
同时使用另一个 window 再次执行 运行 kill
命令。
bash 有一些 bash 主义 ,现在:
最近bash下,有很多特色
- 您可以使用数组来存储后台任务的 PID。
- 从 5.0 版本开始,有一个
$EPOCHREALTIME
变量,它扩展为自 Unix 纪元以来的秒数,粒度为微秒。 - 从 5.0 版开始,关联数组允许下标包含空格。
- 从5.1版本开始,
wait
有一个新的[-p VARNAME]
选项,它存储wait -n
返回的PID
或wait
没有参数。
我的脚本可以变成:
#!/bin/bash
declare -A PIDS ENDED
started=$EPOCHREALTIME
for cmd; do
exec "$cmd" &
PIDS["$cmd"]=$!
CMDS[$!]="$cmd"
done
printf "You have to: kill -TERM %d\nto end %d tasks: %s\n" \
$$ ${#PIDS[@]} "${PIDS[*]}"
trapExit(){
kill "${PIDS[@]}"
printf 'Process %s + %d task killed: %s\n' $$ ${#PIDS[@]} "${!PIDS[*]}"
showDone
exit 1
}
trap trapExit 15
showDone() {
for cmd in "${!ENDED[@]}";do
read -r elap < <(bc -l <<<"${ENDED["$cmd"]}-$started")
printf "Elapsed: %.4f sec for %s\n" "$elap" "$cmd"
done
}
while ((${#PIDS[@]}));do if wait -n -p pid ;then
printf "Process %d done (%s).\n" "$pid" "${CMDS[pid]}"
ENDED["${CMDS[pid]}"]=$EPOCHREALTIME
unset PIDS["${CMDS[pid]}"] CMDS[pid]
fi; done
echo "Process $$ running $* ended normally"
showDone
输出可能如下所示:
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1309816
to end 2 tasks: 1309817 1309818
done.
Process 1309817 done (./command1.sh).
done.
Process 1309818 done (./command2.sh).
Process 1309816 running ./command1.sh ./command2.sh ended normally
Elapsed: 3.1644 sec for ./command1.sh
Elapsed: 4.2488 sec for ./command2.sh
或
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1310031
to end 2 tasks: 1310032 1310033
done.
Process 1310033 done (./command2.sh).
done.
Process 1310032 done (./command1.sh).
Process 1310031 running ./command1.sh ./command2.sh ended normally
Elapsed: 9.1868 sec for ./command1.sh
Elapsed: 3.2310 sec for ./command2.sh
如果你 kill
他们早:
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294577
to end 2 tasks: 1294578 1294579
Process 1294577 + 2 task killed: ./command1.sh ./command2.sh
如果你 kill
他们以后:
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294958
to end 2 tasks: 1294959 1294960
done.
Process 1294959 done (./command1.sh).
Process 1294958 + 1 task killed: ./command2.sh
Elapsed: 3.1779 sec for ./command1.sh
或
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294971
to end 2 tasks: 1294972 1294973
done.
Process 1294973 done (./command2.sh).
Process 1294971 + 1 task killed: ./command1.sh
Elapsed: 6.9344 sec for ./command2.sh
并且没有关于试图杀死不存在的 pid 的错误消息。