等待过程完成


147

Bash中是否有任何内置功能来等待进程完成?

wait命令仅允许一个人等待子进程完成。我想知道在执行任何脚本之前是否有任何方法可以等待任何进程完成。

一种机械的方法如下,但是我想知道Bash中是否有任何内置功能。

while ps -p `cat $PID_FILE` > /dev/null; do sleep 1; done

4
让我举注意两点:1. 指出以下mp3foley,“杀-0”并不总是工作POSIX。2.也许您还想确保该进程不是僵尸进程,实际上它是一个终止的进程。有关 详细信息,请参见mp3foley的评论的。
teika kazura

2
另一个警告最初ks1322 指出):使用除子进程以外的PID并不可靠。如果您想要一种安全的方法,请使用例如IPC。
teika kazura'5

Answers:


138

等待任何过程完成

Linux:

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)

42
谁知道tail会做到这一点。
ctrl-alt-delor

8
tail通过轮询kill(pid, SIG_0)一个进程(使用进行发现strace)在后台进行工作。
Att Righ

2
请注意,lsof使用轮询(即+r 1超时),我个人是在寻找不使用轮询的MacOS解决方案。
亚历山大·米尔斯

1
对于僵尸来说,这个技巧失败了。对于您无法终止的进程,这没关系;tail有线kill (pid, 0) != 0 && errno != EPERM
teika kazura

2
@AlexanderMills,如果您可以容忍macOS系统在命令执行时不进入睡眠状态,那么caffeinate -w $pid就可以解决问题。
zneak

83

没有内置的。使用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 "


2
注意:这并不总是有效,如下面指出mp3foley。有关详细信息,请参见该评论和我的评论。
teika kazura

2
警告2(关于僵尸):上面Teddy的后续评论还不够,因为它们可能是僵尸。请参阅下面的Linux解决方案答案
teika kazura'5

4
这种解决方案不会冒着竞争状况的危险吗?在睡眠期间sleep 0.5,与的$pid进程可能会死,并且可能使用该进程创建另一个进程$pid。最后,我们将等待两个具有相同的不同过程(甚至更多)$pid
ks1322 2014年

2
@ ks1322是的,此代码中确实包含竞争条件。
Teddy

4
通常不是按顺序生成PID的吗?一秒钟内计数的可能性是多少?
esmiralha '16

53

我发现如果进程由root(或其他)拥有,则“ kill -0”不起作用,因此我使用了pgrep并提出了:

while pgrep -u root process_name > /dev/null; do sleep 1; done

这将具有可能匹配僵尸进程的缺点。


2
好观察。在POSIX中,kill(pid, sig=0)如果调用者进程没有杀死特权,则系统调用将失败。因此,/ bin / kill -0和“ kill -0”(内置bash)在相同条件下也会失败。
teika kazura 2013年

31

如果该进程不存在或为僵尸,则此bash脚本循环结束。

PID=<pid to watch>
while s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do
    sleep 1
done

编辑:以上脚本 由Rockallite 在下面给出。谢谢!

我的一部开拓创新的回答下面的Linux工程,依托procfs/proc/。我不知道它的可移植性:

while [[ ( -d /proc/$PID ) && ( -z `grep zombie /proc/$PID/status` ) ]]; do
    sleep 1
done

它不限于外壳,但OS本身没有系统调用来监视非子进程的终止。


1
好一个。尽管我不得不grep /proc/$PID/status用双引号(bash: test: argument expected)包围
-Griddo

嗡嗡声...只是再次尝试,它奏效了。我想我上次做错了。
Griddo 2014年

7
while s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do sleep 1; done
Rockallite

1
不幸的是,这在BusyBox中不起作用-在它的ps,中都没有-ps=不受支持
ZimbiX

14

FreeBSD和Solaris有这个方便的pwait(1)实用程序,它可以根据您的需要进行操作。

我相信,其他现代OS也具有必要的系统调用(例如,MacOS实现了BSD的调用kqueue),但并非所有人都能从命令行使用它。


2
> BSD and Solaris:检查想到的三大BSD;OpenBSD和NetBSD都不具有此功能(在其手册页中),只有FreeBSD具备此功能,因为您可以轻松地检查man.openbsd.org
benaryorg '17

好像你是对的。Mea culpa ...它们都实现了kqueue,因此编译FreeBSD pwait(1)并不容易。为什么其他BSD的导入功能无法让我逃脱...
Mikhail T.

1
plink me@oracle box -pw redacted "pwait 6998";email -b -s "It's done" etc只是让我现在回家,而不是现在的几个小时。
zzxyz

11

从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.

56
没错,但是只能等待当前shell的子进程。您不能等待任何过程。
gumik

@gumik:“如果未指定n,则等待所有当前活动的子进程”。这很完美.. wait没有args会阻塞进程,直到任何子进程完成。老实说,我一直等待任何进程,因为总是有系统进程在进行。
coderofsalvation

1
@coderofsalvation(sleep 10&sleep 3&wait)需要10秒才能返回:没有args的等待将阻塞,直到所有子进程完成。OP希望在第一个子(或指定)过程完成时得到通知。
android.weasel

如果该过程没有后台运行或在后台运行(在Solaris,Linux或Cygwin上),也将不起作用。例如 sleep 1000 ctrl-z wait [sleep pid]立即返回
zzxyz

6

所有这些解决方案均已在Ubuntu 14.04中进行了测试:

解决方案1(通过使用ps命令): 总结一下皮尔斯的答案,我建议:

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(通过使用顶部命令和进程名称):

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(通过使用顶部命令和进程ID):

while [[ $(awk '$1=="process_id" {print $0}' <(top -n 1 -b)) ]]; do sleep 1; done

替换process_id为您的程序的进程ID。


4
Downvote:长的grep -v grep管道是一个庞大的反模式,这以您没有相同名称的不相关进程为前提。如果您知道PID,则可以将其调整为正常工作的解决方案。
三胞胎

感谢Tripleee的评论。我添加了该标志-w以在grep -v grep 某种程度上避免该问题。我还根据您的评论添加了两个解决方案。
Saeid BK

5

好的,看来答案是-不,没有内置工具。

设置/proc/sys/kernel/yama/ptrace_scope为后0,可以使用该strace程序。可以使用其他开关使它静音,从而使其真正被动地等待:

strace -qqe '' -p <PID>

1
好一个!似乎不可能从两个不同的位置附加到给定的PID(我得到Operation not permitted第二个strace实例);你能确认吗?
eudoxos

@eudoxos是的,ptrace的联机帮助页上说:((...)"tracee" always means "(one) thread"并且我确认您提到的错误)。为了让更多的进程以这种方式等待,您必须建立一条链。
动车组

2

阻塞解决方案

wait循环使用in,以等待终止所有进程:

function anywait()
{

    for pid in "$@"
    do
        wait $pid
        echo "Process $pid terminated"
    done
    echo 'All processes terminated'
}

当所有进程终止时,此功能将立即退出。这是最有效的解决方案。

无阻塞解决方案

使用kill -0in循环,等待终止所有进程+在两次检查之间执行任何操作:

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 。


2

遇到相同的问题,我解决了终止进程,然后等待每个进程使用PROC文件系统完成的问题:

while [ -e /proc/${pid} ]; do sleep 0.1; done

投票非常糟糕,您可以从警察那里进行探访:)
Alexander Mills

2

没有内置功能可以等待任何过程完成。

您可以发送kill -0给找到的任何PID,这样您就不会为僵尸和仍然可见的东西感到困惑ps(尽管仍然使用来检索PID列表ps)。


1

当进程终止时,使用inotifywait监视一些已关闭的文件。示例(在Linux上):

yourproc >logfile.log & disown
inotifywait -q -e close logfile.log

-e指定要等待的事件,-q表示仅在终止时输出最少。在这种情况下,它将是:

logfile.log CLOSE_WRITE,CLOSE

单个wait命令可用于等待多个进程:

yourproc1 >logfile1.log & disown
yourproc2 >logfile2.log & disown
yourproc3 >logfile3.log & disown
inotifywait -q -e close logfile1.log logfile2.log logfile3.log

inotifywait的输出字符串将告诉您哪个进程终止。这仅适用于“真实”文件,不适用于/ proc /


0

在OSX之类的系统上,您可能没有pgrep,因此在按名称查找进程时可以尝试使用以下方法:

while ps axg | grep process_name$ > /dev/null; do sleep 1; done

$进程名称末尾的符号可确保grep仅将process_name与ps输出中的行末匹配,而不与本身匹配。


糟糕:命令行中可能有多个具有该名称的进程,其中包括您自己的grep。与其重定向到/dev/null-q不应该与一起使用grep。该过程的另一个实例可能是在您的循环正在休眠时开始的,您将永远不知道...
Mikhail T.

我不确定您为什么将这个答案选为“可怕”,因为其他人已经提出了类似的方法?尽管该-q建议是有效的,但正如我在回答中提到的那样,终止$手段grep不会与名称“命令行中的某处”匹配,也不会与自身匹配。您实际上在OSX上尝试过吗?
Pierz

0

Rauno Palosaari针对的解决方案Timeout in Seconds Darwin是针对不具有GNU tail(并非特定于Darwin)的类UNIX操作系统的出色解决方案。但是,根据类似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上等待最多五秒钟:

lsof -p 5959 +r 1 | awk '/^=/ { if (T++ >= 5) { exit 1 } }'

在此示例中,如果PID 5959在五秒钟过去之前自行退出,${?}则为0。如果五秒后未${?}返回1

值得注意的是,中+r 11是轮询间隔(以秒为单位),因此可以根据情况进行更改。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.