Зачекайте, поки процес закінчиться


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.
теїка казура

Відповіді:


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 (Про зомбі): Наступний коментар Тедді вище ще недостатній, оскільки вони можуть бути зомбі. Дивіться мою відповідь нижче щодо рішення Linux.
теїка казура

4
Чи не є це рішення ризиком для перегонів? Під час сну sleep 0.5процес із $pidзагибеллю може загинути, а з цим може бути створений інший процес $pid. І ми зрештою чекаємо на 2 різні процеси (або навіть більше) з однаковими $pid.
ks1322

2
@ ks1322 Так, цей код справді має перегоновий стан.
Тедді

4
Чи не PID-адреси зазвичай генеруються послідовно? Яка ймовірність того, що підрахунок обернеться за одну секунду?
esmiralha

53

Я виявив, що "kill -0" не працює, якщо процес належить root (або інший), тому я використав 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

31

Цей цикл bash скриптів закінчується, якщо процес не існує, або це зомбі.

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

Він не обмежується оболонкою, але самі ОС не мають системних викликів для спостереження за завершенням процесу, що не стосується дитини.


1
Хороший. Хоча мені довелося оточити grep /proc/$PID/statusподвійними цитатами ( bash: test: argument expected)
Гріддо,

Гум ... просто спробував ще раз, і це спрацювало. Напевно, я зробив щось не так минулого разу.
Гріддо

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

1
На жаль, це не працює в BusyBox - в його ps, ні -pчи s=підтримуються
ZimbiX

14

FreeBSD та Solaris мають цю зручну pwait(1)утиліту, яка робить саме те, що ви хочете.

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


2
> BSD and Solaris: Огляд трьох великих BSD, які приходять на думку; ні OpenBSD, ні NetBSD не мають такої функції (на своїх сторінках), як це робить лише FreeBSD, як ви можете легко перевірити на man.openbsd.org .
benaryorg

Здається, ти маєш рацію. Mea culpa ... Вони всі реалізують kqueue, тому компіляція FreeBSD pwait(1)була б тривіальною, однак. Чому б не імпортувати цю функцію інших BSD ...
Михайло Т.

1
plink me@oracle box -pw redacted "pwait 6998";email -b -s "It's done" etcпросто дозволив мені йти додому зараз замість годин.
zzxyz

11

З баш-сторінки

   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
Це правда, але він може чекати лише дитини поточної оболонки. Ви не можете чекати жодного процесу.
гумік

@gumik: "Якщо n не вказано, очікуються всі поточно активні дочірні процеси" . Це прекрасно працює. waitБез аргументів буде блокуватися процес, поки будь-які дочірні процеси не закінчаться. Чесно кажучи, я не бачу сенсу чекати будь-якого процесу, оскільки там завжди тривають системні процеси.
coderofsalvation

1
@coderofsalvation (сон 10 і сон 3 і чекати) займає 10 секунд, щоб повернутися: зачекайте без аргументів заблокується, поки ВСІ дочірні процеси не завершаться. ОП хоче отримати повідомлення, коли закінчиться процес першої дитини (або призначений).
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 відповідає лише імені_процесу, а не самій програші. Він має перевагу в підтримці випадків, коли ім'я_процесу не знаходиться в кінці рядка в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 (використовуючи верхню команду та ідентифікатор процесу):

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

Замініть process_idідентифікатор процесу вашої програми.


4
Downvote: довгий grep -v grepтрубопровід є масовим антипатерном, і це передбачає, що у вас немає однакових пов'язаних процесів. Якщо натомість ви знаєте PID, це може бути адаптоване до належно працюючого рішення.
tripleee

Дякую трійці за коментар. Я додав прапор, щоб певною мірою-w уникнути проблеми . Я також додав ще два рішення на основі вашого коментаря. grep -v grep
Саїд БК

5

Гаразд, так здається, відповідь - ні, немає вбудованого інструменту.

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

strace -qqe '' -p <PID>

1
Хороший! Здається, хоча неможливо приєднати до даного PID з двох різних місць (я отримую Operation not permittedдля другого страце-екземпляра); Ви можете це підтвердити?
eudoxos

@eudoxos Так, на сторінці для ptrace написано: (...)"tracee" always means "(one) thread"(і я підтверджую вказану вами помилку). Щоб більше процесів чекало цього шляху, вам доведеться скласти ланцюжок.
ему

2

Блокування рішення

Використовуйте 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 часі, оскільки потрібно запобігати високому використанню процесора.

Реалістичне використання:

В очікуванні припинення всіх процесів + інформуйте користувача про всі запущені 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'
}

Примітки

Ці функції отримують PID через аргументи $@як масив BASH.


2

У мене була та сама проблема, я вирішив проблему, убивши процес, а потім чекаю, коли кожен процес закінчить використання файлової системи PROC:

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

опитування дуже погано, ви можете відвідати міліцію :)
Олександр Міллз

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

Одна команда очікування може бути використана для очікування декількох процесів:

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 до кінця рядка в вихідний пс , а не сам по собі.


Жахливо: Десь у командному рядку може бути декілька процесів з цим ім'ям , включений власний греп. Замість того, щоб переспрямовувати на /dev/null, -qслід використовувати з grep. Можливо, ще один екземпляр процесу почався, коли ваш цикл спав, і ви ніколи не дізнаєтесь ...
Михайло Т.

Я не впевнений, чому ви виділили цю відповідь як «жахливу» - як подібний підхід запропонований іншими? У той час як-q пропозиція є дійсною, як я вже згадував у своїй відповіді, конкретно, закінчуючий $засіб grep не збігатиметься з назвою "десь у командному рядку", а також не відповідає собі. Ви насправді спробували це на OSX?
П’єрз

0

Рішення Rauno Palosaari для Timeout in Seconds Darwin- це відмінне рішення для UNIX-подібної ОС, яка не має GNU 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 до п'яти секунд:

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

У цьому прикладі, якщо PID 5959 закінчується за власним бажанням до того, як минуть п'ять секунд, ${?}це 0. Якщо не ${?}повертається 1через п’ять секунд.

Можливо, варто виразно зазначити, що в +r 1 , то 1є інтервал опитування (в секундах), так що він може бути змінений відповідно до ситуації.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.