Bash 中是否有任何内置功能可以等待进程完成?
该wait
命令只允许等待子进程完成。我想知道在继续执行任何脚本之前是否有任何方法可以等待任何进程完成。
执行此操作的机械方法如下,但我想知道 Bash 中是否有任何内置功能。
while ps -p `cat $PID_FILE` > /dev/null; do sleep 1; done
Linux(不适用于 ash 不支持的 Alpine tail --pid
):
tail --pid=$pid -f /dev/null
达尔文(要求$pid
有打开的文件):
lsof -p $pid +r 1 &>/dev/null
Linux:
timeout $timeout tail --pid=$pid -f /dev/null
达尔文(要求$pid
有打开的文件):
lsof -p $pid +r 1m%s -t | grep -qm1 $(date -v+${timeout}S +%s 2>/dev/null || echo INF)
没有内置的。在循环中使用kill -0
一个可行的解决方案:
anywait(){
for pid in "$@"; do
while kill -0 "$pid"; do
sleep 0.5
done
done
}
或者作为更简单的 oneliner 以便一次性使用:
while kill -0 PIDS 2> /dev/null; do sleep 1; done;
正如几位评论员所指出的,如果您想等待您无权向其发送信号的进程,您可以找到其他方法来检测该进程是否正在运行以替换kill -0 $pid
调用。在 Linux 上,test -d "/proc/$pid"
可以在其他系统上使用pgrep
(如果可用)或类似ps | grep "^$pid "
.
我发现如果进程由 root (或其他)拥有,“kill -0”不起作用,所以我使用 pgrep 并想出了:
while pgrep -u root process_name > /dev/null; do sleep 1; done
这将具有可能匹配僵尸进程的缺点。
如果进程不存在,或者它是僵尸进程,则此 bash 脚本循环结束。
PID=<pid to watch>
while s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do
sleep 1
done
编辑:上面的脚本 由Rockallite下面给出。谢谢!
我下面的原始答案适用于 Linux,依赖于procfs
ie /proc/
。我不知道它的便携性:
while [[ ( -d /proc/$PID ) && ( -z `grep zombie /proc/$PID/status` ) ]]; do
sleep 1
done
它不仅限于 shell,而且操作系统本身没有系统调用来监视非子进程终止。
FreeBSD 和 Solaris 有这个方便的pwait(1)
实用程序,它完全可以满足您的需求。
我相信,其他现代操作系统也具有必要的系统调用(例如,MacOS 实现了 BSD kqueue
),但并非所有操作系统都可以从命令行使用它。
从 bash 手册页
wait [n ...]
Wait for each specified process and return its termination status
Each n may be a process ID or a job specification; if a
job spec is given, all processes in that job's pipeline are
waited for. If n is not given, all currently active child processes
are waited for, and the return status is zero. If n
specifies a non-existent process or job, the return status is
127. Otherwise, the return status is the exit status of the
last process or job waited for.
所有这些解决方案都在 Ubuntu 14.04 中进行了测试:
解决方案 1(通过使用 ps 命令): 只是为了增加 Pierz 的答案,我建议:
while ps axg | grep -vw grep | grep -w process_name > /dev/null; do sleep 1; done
在这种情况下,grep -vw grep
确保 grep 仅匹配 process_name 而不是 grep 本身。它的优点是支持 process_name 不在行尾的情况ps axg
。
解决方案 2(通过使用 top 命令和进程名称):
while [[ $(awk '$12=="process_name" {print $0}' <(top -n 1 -b)) ]]; do sleep 1; done
替换process_name
为 中出现的进程名称top -n 1 -b
。请保留引号。
要查看等待它们完成的进程列表,可以运行:
while : ; do p=$(awk '$12=="process_name" {print $0}' <(top -n 1 -b)); [[ $b ]] || break; echo $p; sleep 1; done
解决方案 3(通过使用 top 命令和进程 ID):
while [[ $(awk '$1=="process_id" {print $0}' <(top -n 1 -b)) ]]; do sleep 1; done
替换process_id
为程序的进程 ID。
好的,看来答案是——不,没有内置工具。
设置/proc/sys/kernel/yama/ptrace_scope
为后0
,即可使用该strace
程序。可以使用进一步的开关使其静音,以便它真正被动地等待:
strace -qqe '' -p <PID>
没有等待任何进程完成的内置功能。
您可以发送kill -0
到找到的任何 PID,这样您就不会被僵尸和仍然可见的东西弄糊涂ps
(同时仍然使用 检索 PID 列表ps
)。
在循环中使用wait
,等待终止所有进程:
function anywait()
{
for pid in "$@"
do
wait $pid
echo "Process $pid terminated"
done
echo 'All processes terminated'
}
当所有进程终止时,此函数将立即退出。这是最有效的解决方案。
在循环中使用kill -0
,等待终止所有进程+在检查之间做任何事情:
function anywait_w_status()
{
for pid in "$@"
do
while kill -0 "$pid"
do
echo "Process $pid still running..."
sleep 1
done
done
echo 'All processes terminated'
}
反应时间减少到sleep
时间,因为必须防止高 CPU 使用率。
一个实际的用法:
等待终止所有进程 + 通知用户所有正在运行的 PID。
function anywait_w_status2()
{
while true
do
alive_pids=()
for pid in "$@"
do
kill -0 "$pid" 2>/dev/null \
&& alive_pids+="$pid "
done
if [ ${#alive_pids[@]} -eq 0 ]
then
break
fi
echo "Process(es) still running... ${alive_pids[@]}"
sleep 1
done
echo 'All processes terminated'
}
这些函数通过$@
BASH 数组的参数获取 PID。
有同样的问题,我解决了杀死进程然后等待每个进程完成使用 PROC 文件系统的问题:
while [ -e /proc/${pid} ]; do sleep 0.1; done
当您的进程终止时,使用 inotifywait 监视某些关闭的文件。示例(在 Linux 上):
yourproc >logfile.log & disown
inotifywait -q -e close logfile.log
-e 指定要等待的事件,-q 表示仅在终止时输出最小。在这种情况下,它将是:
logfile.log CLOSE_WRITE,CLOSE
单个等待命令可用于等待多个进程:
yourproc1 >logfile1.log & disown
yourproc2 >logfile2.log & disown
yourproc3 >logfile3.log & disown
inotifywait -q -e close logfile1.log logfile2.log logfile3.log
inotifywait 的输出字符串会告诉你,哪个进程终止了。这仅适用于“真实”文件,不适用于 /proc/ 中的某些内容
在像 OSX 这样的系统上,您可能没有 pgrep,因此您可以在按名称查找进程时尝试这种方法:
while ps axg | grep process_name$ > /dev/null; do sleep 1; done
$
进程名称末尾的符号确保 grep 仅将 process_name 匹配到 ps 输出中的行尾,而不是它本身。
Rauno Palosaari 的 , 的解决方案对于Timeout in Seconds
Darwin
没有 GNU 的类 UNIX 操作系统来说是一个很好的解决方法tail
(它不是特定于Darwin
)。但是,根据类 UNIX 操作系统的年龄,提供的命令行比必要的复杂,并且可能会失败:
lsof -p $pid +r 1m%s -t | grep -qm1 $(date -v+${timeout}S +%s 2>/dev/null || echo INF)
在至少一个旧 UNIX 上,lsof
参数+r 1m%s
失败(即使对于超级用户):
lsof: can't read kernel name list.
这m%s
是一个输出格式规范。更简单的后处理器不需要它。例如,以下命令在 PID 5959 上最多等待 5 秒:
lsof -p 5959 +r 1 | awk '/^=/ { if (T++ >= 5) { exit 1 } }'
在此示例中,如果 PID 5959 在五秒内自行退出,${?}
则为0
。如果五秒后没有${?}
返回。1
可能值得注意的是,在 中+r 1
,1
是轮询间隔(以秒为单位),因此可以根据情况进行更改。