我有许多bash脚本执行很多类似的任务,他们使用一些外部二进制程序。问题是二进制程序通常不会像它们应该那样终止。由于我的脚本运行了数千次,因此很快发生这些进程的许多空闲/接近死亡的实例正在累积。我无法修复这些程序,因此我需要确保我的bash脚本正在终止它们。
SE中已经有一些主题涉及终止bash脚本过程的任务。我已经应用并测试了那里写的内容,并且在某种程度上它起作用了。但它对我的情况来说效果不好,我不明白为什么,因此我开了一个新问题。
我的脚本有一个层次结构,这里以简化的方式显示: 脚本A调用脚本B,脚本B并行调用脚本C的多个实例以使用所有CPU。例如。脚本B并行运行脚本C的5个实例,当脚本C的一个实例完成时,它启动一个新的脚本C,总共运行数千个脚本C.脚本C调用几个不会终止的外部二进制文件/命令很好。它们在后台并行并相互通信。
但是,我的脚本C能够检测外部命令何时完成其工作,即使它们尚未终止,然后我的bash脚本退出。
为了在完成bash脚本期间终止所有外部程序,我添加了一个退出陷阱:
# Exit cleanup
cleanup_exit() {
# Running the termination in an own process group to prevent it from preliminary termination. Since it will run in the background it will not cause any delays
setsid nohup bash -c "
touch /tmp/trace_1 # To see if this code was really executed to this point
# Trapping signals to prevent that this function is terminated preliminary
trap '' SIGINT SIGQUIT SIGTERM SIGHUP ERR
touch /tmp/trace_2 # To see if this code was really executed to this point
# Terminating the main processes
kill ${pids[@]} 1>/dev/null 2>&1 || true
touch /tmp/trace_3
sleep 5
touch /tmp/trace_4
kill -9 ${pids[@]} 1>/dev/null 2>&1 || true
touch /tmp/trace_5
# Terminating the child processes of the main processes
echo "Terminating the child processes"
pkill -P ${pids[@]} 1>/dev/null 2>&1 || true
touch /tmp/trace_6
sleep 1
pkill -9 -P ${pids[@]} 1>/dev/null 2>&1 || true
touch /tmp/trace_7
# Terminating everything else which is still running and which was started by this script
pkill -P $$ || true
touch /tmp/trace_8
sleep 1
pkill -9 -P $$ || true
touch /tmp/trace_9
"
}
trap "cleanup_exit" SIGINT SIGQUIT SIGTERM EXIT
现在,如果我并行运行非常少的脚本C实例,这似乎有效。如果我将数字增加到更多,例如10(工作站功能强大,应该能够并行处理脚本C和外部程序的几十个并行实例),然后它不再起作用,并且数百个外部程序实例正在快速累积。
但我不明白为什么。例如,累积的其中一个进程的PID是32048.在日志中我可以看到退出陷阱的执行:
+ echo ' * Snapshot 190 completed after 3 seconds.'
* Snapshot 190 completed after 3 seconds.
+ break
+ cleanup_exit
+ echo
+ echo ' * Cleaning up...'
* Cleaning up...
+ setsid nohup bash -c '
touch /tmp/trace_1 # To see if this code was really executed to this point
# Trapping signals to prevent that this function is terminated preliminary
trap '\'''\'' SIGINT SIGQUIT SIGTERM SIGHUP ERR
touch /tmp/trace_2 # To see if this code was really executed to this point
# Terminating the main processes
kill 31678' '32048 1>/dev/null 2>&1 || true
touch /tmp/trace_3
sleep 5
touch /tmp/trace_4
kill -9 31678' '32048 1>/dev/null 2>&1 || true
touch /tmp/trace_5
# Terminating the child processes of the main processes
pkill -P 31678' '32048 1>/dev/null 2>&1 || true
touch /tmp/trace_6
sleep 1
pkill -9 -P 31678' '32048 1>/dev/null 2>&1 || true
touch /tmp/trace_7
# Terminating everything else which is still running and which was started by this script
pkill -P 31623 || true
touch /tmp/trace_8
sleep 1
pkill -9 -P 31623 || true
touch /tmp/trace_9
'
显然,此过程的PID用于退出陷阱,但该过程未退出。为了测试我在这个过程中再次手动运行kill命令,然后它确实退出了。
最有趣的是,只显示最高为5的跟踪文件。没有超过5,但为什么?
更新:我刚刚发现,即使我并行运行脚本C的一个实例,即顺序,它只能运行一段时间。突然在某个时间点,进程不再被终止,但是它们开始永远地停留并积累。机器不应该并行过载一个过程。在我的日志文件中,退出陷阱仍然像以前一样正确调用,没有区别。内存也是免费的,CPU也是部分免费的。
答案 0 :(得分:5)
对任何shell脚本进行良好的健全性检查是在其上运行ShellCheck:
.xib
事实上,你的xtrace在这一行上做了一些奇怪的事情:
Line 9:
kill ${pids[@]} 1>/dev/null 2>&1 || true
^-- SC2145: Argument mixes string and array. Use * or separate argument.
此处的问题是,您的kill 31678' '32048 1>/dev/null 2>&1 || true
^^^--- What is this?
扩展为多个单词,${pids[@]}
仅解释第一个单词。这是一个简化的例子:
bash -c
这最终写出pids=(2 3 4)
bash -c "echo killing ${pids[@]}"
而没有提到3或4.这相当于运行
killing 2
其他pid只是成为位置参数bash -c "echo killing 2" "3" "4"
和$0
而不是执行命令的一部分。
相反,与ShellCheck suggested一样,您希望$1
将所有pid与空格连接起来并将它们作为单个参数插入:
*
打印pids=(2 3 4)
bash -c "echo killing ${pids[*]}"
。