Вопрос по bash – ПОДОЖДИТЕ, чтобы «любой процесс» закончился

107

Есть ли в bash встроенная функция для ожидания завершения какого-либо процесса? wait Команда только позволяет дождаться завершения дочерних процессов. Я хотел бы знать, есть ли способ подождать завершения какого-либо процесса, прежде чем приступить к выполнению какого-либо сценария.

Механический способ сделать это заключается в следующем, но я хотел бы знать, есть ли какая-либо встроенная функция в bash.

<code>while ps -p `cat $PID_FILE` > /dev/null; do sleep 1; done
</code>
Позвольте мне датьtwo cautions: 1. Какpointed out below отmp3foley, & quot; убить -0 & quot; не всегда работает для POSIX. 2. Вероятно, вы также хотите убедиться, что процесс не является зомби, который является практически завершенным процессом.See mp3foley's comment а такжеmine для деталей. teika kazura
отличная идея! Спасибо Marco Marsala
Another caution (originally pointed out отks1322 ниже): Использование PID, отличного от дочернего процесса, не является надежным. Если вы хотите безопасный способ, используйте, например, IPC. teika kazura

Ваш Ответ

13   ответов
10

   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.
@gumik:"If n is not given, all currently active child processes are waited for" , Это прекрасно работает ..wait без аргументов заблокирует процесс доany child процессы заканчиваются. Честно говоря, я не вижу смысла ждатьany процесс, так как всегда происходят постоянные процессы.
Для возврата @coderofsalvation (сон 10 и сон 3 и ожидание) требуется 10 секунд: ожидание без аргументов заблокирует, пока ВСЕ дочерние процессы не завершатся. ОП хочет получить уведомление о завершении первого дочернего (или назначенного) процесса.
Также не работает, если процесс не основан или не основан (на Solaris, Linux или Cygwin). ех.sleep 1000 ctrl-z wait [sleep pid] немедленно возвращается
Это правда, но он может только ждать дочернего элемента текущей оболочки. Вы не можете ждатьany процесс.
+1 заchild of the current shell.
-1
Blocking solution

wait в цикле, для ожидания завершения всех процессов:

function anywait()
{

    for pid in "[email protected]"
    do
        wait $pid
        echo "Process $pid terminated"
    done
    echo 'All processes terminated'
}

Эта функция будет завершена немедленно после завершения всех процессов. Это самое эффективное решение.

Non-blocking solution

kill -0 в цикле, для ожидания завершения всех процессов + сделать что-нибудь между проверками:

function anywait_w_status()
{
    for pid in "[email protected]"
    do
        while kill -0 "$pid"
        do
            echo "Process $pid still running..."
            sleep 1
        done
    done
    echo 'All processes terminated'
}

Время реакции уменьшилось доsleep время, потому что должны предотвратить высокую загрузку процессора.

A realistic usage:

Ожидание завершения всех процессов + информирование пользователя оall работает PID.

function anywait_w_status2()
{
    while true
    do
        alive_pids=()
        for pid in "[email protected]"
        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'
}
Notes

Эти функции получают PID с помощью аргументов[email protected] как массив BASH.

29

если процесс не существует или он является зомби.

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

EDIT: Приведенный выше скриптбыл дан ниже  отRockallite, Спасибо!

Мой оригинальный ответ ниже работает для Linux, полагаясь наprocfs то есть/proc/, Я не знаю его мобильности:

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

Он не ограничен оболочкой, но сами ОС не имеют системных вызовов для наблюдения за завершением не дочерних процессов.

Или жеwhile s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do sleep 1; done
Хм ... просто попробовал еще раз, и это сработало. Я думаю, я сделал что-то не так в прошлый раз.
Хороший. Хотя я должен был окружитьgrep /proc/$PID/status с двойными кавычками (bash: test: argument expected)
3

которая ждет любого PID и возвращает сразу же, когда PID умирает.

https://github.com/izabera/waiter

Это крошечный C-файл, который вы можете скомпилировать с помощью gcc.

#define _GNU_SOURCE
#include <sys/ptrace.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char *argv[]) {
  if (argc == 1) return 1;
  pid_t pid = atoi(argv[1]);
  if (ptrace(PTRACE_SEIZE, pid, NULL, NULL) == -1) return 1;
  siginfo_t sig;
  return waitid(P_PID, pid, &sig, WEXITED|WNOWAIT);
}

Он работает так же, как отладчик при подключении к процессу.

Вот Это Да! Это так круто! Спасибо! И спасибо автору! Просто не забудьтеsetcap cap_sys_ptrace+ep waiter (см. Makefile в git-репо)
зачем использоватьwaitid() вместоpwait()?
Я предполагаю, что это будет работать, только если pid представляет дочерний элемент текущего процесса?
В самом деле,ptrace(2) кажется кроссплатформенным решением, но ваша маленькая программа требует немного больше работы. Прежде всего,PTRACE_SEIZE не определено ни для BSD (конечно, не для FreeBSD-10.x), ни для более старых Linux (не для CentOS-6.8) - этоa recent addition (ядро 3.4).PT_ATTACH следует использовать вместо Что еще более важно, вызов может вернуться, когда субъектный процесс получает сигнал. Чтобы избежать преждевременного выхода, утилита должна быть в состоянии обнаружить это и вернуться вptrace...
2

Вы могли бы отправитьkill -0 на любой найденный PID, так что вы не будете озадачены зомби и другими вещами, которые все еще будут видны вps (пока все еще извлекается список PID с помощьюps).

5

Solution 1 (by using ps command): Just to add up to Pierz answer, I would suggest:

while ps axg | grep -vw grep | grep -w process_name > /dev/null; do sleep 1; done

В этом случае,grep -vw grep гарантирует, что grep соответствует только process_name, а не самому grep. Преимущество заключается в поддержке случаев, когда имя_процесса находится не в конце строки вps axg.

Solution 2 (by using top command and process name):

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

Solution 3 (by using top command and process ID):

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

замещатьprocess_id с идентификатором процесса вашей программы.

Downvote: длинныйgrep -v grep pipe - это массивный антипаттерн, и это предполагает, что у вас нет связанных процессов с тем же именем. Если вы знаете PID вместо этого, это может быть адаптировано к правильно работающему решению.
Спасибо tripleee за комментарий. Я добавил флаг-w чтобы избежать проблемыgrep -v grep to some extent, Я также добавил еще два решения на основе вашего комментария.
65
To wait for any process to finish

Linux:

tail --pid=$pid -f /dev/null

Дарвин (требует, чтобы$pid имеет открытые файлы):

lsof -p $pid +r 1 &>/dev/null
With timeout (seconds)

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)
Похоже, что решение для Linux работает на Cygwin и Solaris, основываясь на кратком тесте сsleep (Хотяpwait решение ниже для соляриса проще)
Этот трюкfails для зомби. Это нормально для процессов, которые вы не можете убить;tail имеет линиюkill (pid, 0) != 0 && errno != EPERM.
tail работает под капотом путем опросаkill(pid, SIG_0) к процессу (обнаружен с помощьюstrace).
Обратите внимание, что lsof использует опрос, что+r 1 Это тайм-аут, я лично ищу решение для MacOS, которое не использует опрос.
Кто бы знал, чтоtail сделал бы это.
11

pwait(1) утилита, которая делает именно то, что вы хотите.

Я полагаю, что другие современные ОС также имеют необходимые системные вызовы (например, MacOS реализует BSD).kqueue), но не все делают его доступным из командной строки.

plink [email protected] box -pw redacted "pwait 6998";email -b -s "It's done" etc просто позволил мне идти домой, а не через несколько часов.
Я хотел бы, чтобы я мог ответить на этот вопрос десятки раз.
Кажется, ты прав. Меа виноват ... Все они реализуютkqueueтак что компилируем FreeBSDpwait(1) было бы тривиально, однако. Почему бы другой функции импорта BSD не ускользнуло от меня ...
> BSD and Solaris: Проверка трех больших BSD, которые приходят на ум; ни OpenBSD, ни NetBSD не имеют этой функции (на их man-страницах), только FreeBSD, как вы можете легко проверитьman.openbsd.org.
48

убить -0 & quot; не работает, если процесс принадлежит пользователю root (или другому), поэтому я использовал pgrep и придумал:

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

Это могло бы иметь недостаток, заключающийся в том, чтобы соответствовать процессам зомби.

Хорошее наблюдение. В POSIX системный вызовkill(pid, sig=0) завершается неудачей, если вызывающий процесс не имеет привилегии для уничтожения. Таким образом, / bin / kill -0 и & quot; kill -0 & quot; (встроенный bash) тоже не работает при тех же условиях.
0

я решил проблему, убив процесс и затем дождавшись завершения каждого процесса с использованием файловой системы PROC:

while [ -e /proc/${pid} ]; do sleep 0.1; done
Голосование очень плохо, вы могли бы получить в гости от полиции :)
74

kill -0 в цикле для работоспособного решения:

anywait(){

    for pid in "[email protected]"; do
        while kill -0 "$pid"; do
            sleep 0.5
        done
    done
}

Или как более простой способ для простого однократного использования:

while kill -0 PIDS 2> /dev/null; do sleep 1; done;

Как отмечали некоторые комментаторы, если вы хотите дождаться процессов, на которые у вас нет прав отправлять сигналы, вы найдете другой способ определить, запущен ли процесс, чтобы заменитьkill -0 $pid вызов. В Linuxtest -d "/proc/$pid" работает, в других системах вам, возможно, придется использоватьpgrep (если есть) или что-то вродеps | grep ^$pid.

Caution 2 (О зомби). Последующего комментария от Тедди пока недостаточно, поскольку они могут быть зомби. Увидетьmy answer below для решения Linux.
Caution: Это не всегда работает, так какpointed out below отmp3foley, Смотрите этот комментарий иmine для деталей.
PID обычно не генерируются последовательно? Какова вероятность того, что отсчет оборачивается за одну секунду?
Не рискует ли это решение состоянием гонки? Во время снаsleep 0.5обработать с$pid может умереть, и другой процесс может быть создан с тем же$pid, И мы в конечном итоге ждем 2 разных процесса (или даже больше) с тем же$pid.
@ ks1322 Да, в этом коде действительно есть условие гонки.
5

После настройки/proc/sys/kernel/yama/ptrace_scope в0, можно использоватьstrace программа. Другие переключатели могут быть использованы, чтобы заставить его замолчать, чтобы он действительно пассивно ожидал:

strace -qqe '' -p <PID>
Хороший! Кажется, однако, что это не возможно, чтобы прикрепить к данному PID из двух разных мест (я получаюOperation not permitted для второго экземпляра); Вы можете это подтвердить?
@eudoxos Да, на странице ptrace написано:(...)"tracee" always means "(one) thread" (и я подтверждаю ошибку, которую вы упоминаете). Чтобы позволить таким процессам ждать больше, вам нужно создать цепочку.
0

как OSX, у вас может не быть pgrep, поэтому вы можете попробовать этот метод при поиске процессов по имени:

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

$ Символ в конце имени процесса гарантирует, что grep сопоставляет только имя_процесса с концом строки в выводе ps, а не с самим собой.

Я не уверен, почему вы выбрали этот ответ как "Ужасный". - как подобный подход был предложен другими? Пока-q Предложение действительно, как я уже упоминал в своем ответе, в частности,$ означает, что grep не будет соответствовать имени "где-нибудь в командной строке" и при этом это не будет соответствовать. Вы действительно пробовали это на OSX?
Ужасно: может быть несколько процессов с таким именемsomewhere in the command line, ваш собственный grep включен. Вместо перенаправления на/dev/null, -q следует использовать сgrep, Другой экземпляр процесса мог начаться, когда ваш цикл спал, и вы никогда не узнаете ...

Похожие вопросы