Как получить идентификатор фонового процесса?

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

Как получить PID этого процесса из моего сценария оболочки? Насколько я понимаю, переменная $! содержит PID текущего скрипта, а не фонового процесса.


person Volodymyr Bezuglyy    schedule 15.12.2009    source источник
comment
$! верно. Вы уверены, что запускаете сценарий в BG? образец пожалуйста.   -  person pixelbeat    schedule 15.12.2009
comment
Да $! верно. Я был неправ.   -  person Volodymyr Bezuglyy    schedule 16.12.2009
comment
$$ содержит текущий PID скрипта.   -  person HUB    schedule 20.03.2013
comment
Обратите внимание, что $$ может быть родительским PID в bash: testfun() { echo "\$\$=$$ \$BASHPID=$BASHPID"; }; echo "my pid is $$"; testfun & wait   -  person Mikko Rantalainen    schedule 22.09.2020


Ответы (8)


Вам необходимо сохранить PID фонового процесса во время его запуска:

foo &
FOO_PID=$!
# do other stuff
kill $FOO_PID

Вы не можете использовать управление заданиями, поскольку это интерактивная функция, привязанная к управляющему терминалу. К сценарию вовсе не обязательно должен быть подключен терминал, поэтому управление заданиями не обязательно будет доступно.

person camh    schedule 16.12.2009
comment
С $! возвращает идентификатор последнего фонового процесса. Возможно ли, что что-то начинается между foo и $!, и мы получаем pid этого чего-то вместо foo? - person WiSaGaN; 16.10.2013
comment
@WiSaGaN: Нет. Между этими строчками ничего нет. Любая другая активность в системе не повлияет на это. $! расширится до PID последнего фонового процесса в этой оболочке. - person camh; 17.10.2013
comment
... что вас засыпает, если foo - это несколько команд, переданных по конвейеру (например, tail -f somefile.txt | grep sometext). В таких случаях вы получите PID команды grep от $!, а не от команды tail, если это то, что вы искали. В этом случае вам нужно будет использовать jobs или ps или подобные. - person John Rix; 14.10.2014
comment
@JohnRix: Не обязательно. Вы получите pid grep, но если вы его убьете, tail получит SIGPIPE при попытке записи в канал. Но как только вы пытаетесь попасть в какое-либо сложное управление / контроль процессов, bash / shell становится довольно болезненным. - person camh; 15.10.2014
comment
@camh: В моем конкретном сценарии требовалось убить только первый процесс в цепочке, чтобы второй получил EOF и выполнил обычную обработку выхода (это был сценарий awk с блоком END, который мне нужно было выполнить, чтобы вывести некоторую статистику). Удаление хвостовой команды (после получения ее PID с помощью jobs -p) дает желаемый результат. - person John Rix; 17.10.2014
comment
Другое достойное решение предлагается в (комментарий к ответу) Как получить pid только что запущенного процесса: да, и единственный: /bin/sh -c 'echo $$>/tmp/my.pid && exec program args' & - sysfault 24 нояб. - person imz -- Ivan Zakharyaschev; 02.06.2015
comment
Что касается конвейерных команд, они получают идентификатор группы процессов (PGID), который совпадает с PID процесса, который их создал. Так что tail -f somefile.txt | grep sometext; FOO_PID=$!; kill $FOO_PID; должен убить всю трубу. И если вам нужен PID отдельной части канала, вы можете обойти его при создании канала. Если вы не создали его, в этом случае вам нужно будет сделать pgrep или pkill, чтобы получить / убить все процессы по имени и / или аргументам, а не по PID. - person Beejor; 18.02.2019
comment
Что, если ваш foo & процесс никогда не заканчивается, и вы хотите подавить то, что он отправляет на стандартный вывод? - person James T.; 27.04.2021
comment
@JamesT. Вы должны размещать свой вопрос как вопрос, а не как комментарий. Это не связано с этим ответом. - person camh; 28.04.2021

Вы можете использовать команду jobs -l, чтобы перейти к конкретному заданию.

^Z
[1]+  Stopped                 guard

my_mac:workspace r$ jobs -l
[1]+ 46841 Suspended: 18           guard

В данном случае 46841 - это PID.

От help jobs:

-l Сообщает идентификатор группы процессов и рабочий каталог заданий.

jobs -p - еще один вариант, который показывает только PID.

person jldupont    schedule 15.12.2009
comment
Чтобы использовать это в сценарии оболочки, вам нужно обработать вывод. - person Phil; 15.10.2013
comment
@Phil Чтобы вывести только pid: jobs -p. Чтобы перечислить pid определенного задания: jobs -p% 3. Нет необходимости обрабатывать вывод. - person Erik Aronesty; 27.08.2014
comment
@Erik разными командами / аргументами вы меняете контекст моего комментария. Без предлагаемого дополнительного аргумента вывод требует обработки. Предложите улучшение ответа! - person Phil; 28.08.2014
comment
Сохранение PID из $! сразу после того, как вы начали, более переносимо и проще в большинстве ситуаций. Это то, что делает принятый в настоящее время ответ. - person tripleee; 18.09.2017
comment
jobs -p вернул то же, что и jobs -l в Lubuntu 16.4 - person Timo; 02.12.2017
comment
@tripleee, у вас также меньше шансов сохранить $!, когда вы немного позже поймете, что вам нужно убить это задание в интерактивной оболочке. - person Calimo; 10.02.2019
comment
@Calimo Я согласен с тем, что jobs - это способ перейти в интерактивный сеанс, но это опубликовано как ответ на вопрос о том, как это сделать программно из сценария. - person tripleee; 10.02.2019

  • $$ - идентификатор текущего скрипта
  • $! - идентификатор последнего фонового процесса

Вот пример стенограммы сеанса bash (%1 относится к порядковому номеру фонового процесса, как видно из jobs):

$ echo $$
3748

$ sleep 100 &
[1] 192

$ echo $!
192

$ kill %1

[1]+  Terminated              sleep 100
person catwalk    schedule 15.12.2009
comment
echo %1 не возвращает фоновый процесс на моем Ubuntu, тогда как echo $! делает - person Timo; 02.12.2017
comment
Обратите внимание, что $$ не всегда является текущим PID. Например, если вы определяете новую функцию в bash и запускаете функцию в фоновом режиме, $$ внутри этой функции содержит PID процесса, который запустил функцию в фоновом режиме. Если вам нужен PID фактического процесса, выполняющего какой-либо данный код, вместо этого вы должны использовать $BASHPID. - person Mikko Rantalainen; 22.09.2020

Еще более простой способ убить все дочерние процессы сценария bash:

pkill -P $$

Флаг -P работает таким же образом с pkill и pgrep - он получает дочерние процессы, только с pkill дочерние процессы уничтожаются, а с pgrep дочерние PID выводятся на стандартный вывод.

person Alexey Polonsky    schedule 28.04.2014
comment
Очень удобно! Это лучший способ гарантировать, что открытые процессы не останутся в фоновом режиме. - person lepe; 08.10.2015
comment
@lepe: Не совсем так. Если вы дедушка или бабушка, это не сработает: после bash -c 'bash -c "sleep 300 &"' & запуска pgrep -P $$ ничего не покажет, потому что сон не будет прямым потомком вашей оболочки. - person petre; 15.02.2016
comment
@AlexeyPolonsky: должно быть: убивать все дочерние процессы оболочки, а не скрипта. Потому что $$ относится к текущей оболочке. - person Timo; 02.12.2017
comment
выполняя bash -c 'bash -c "sleep 300 &"' & ; pgrep -P $$, попадаю на stdout [1] <pid>. So at least it shows something, but this is probably not the output of pgrep` - person Timo; 02.12.2017
comment
Даже процессы могут отсоединять их от родительского процесса. Простой трюк - вызвать fork (создать внука), а затем просто позволить дочернему процессу завершиться, пока внук продолжает выполнять работу. (ключевое слово - демонизация) Но даже если ребенок продолжает работать слишком долго, pkill -P недостаточно для передачи сигнала внукам. Такой инструмент, как pstree, необходим для отслеживания всего зависимого дерева процессов. Но это не поймает демонов, запущенных из процесса, поскольку их родительский процесс - 1. например: bash -c 'bash -c "sleep 10 & wait $!"' & sleep 0.1; pstree -p $$ - person Pauli Nieminen; 07.11.2019

это то, что я сделал. Проверьте это, надеюсь, это поможет.

#!/bin/bash
#
# So something to show.
echo "UNO" >  UNO.txt
echo "DOS" >  DOS.txt
#
# Initialize Pid List
dPidLst=""
#
# Generate background processes
tail -f UNO.txt&
dPidLst="$dPidLst $!"
tail -f DOS.txt&
dPidLst="$dPidLst $!"
#
# Report process IDs
echo PID=$$
echo dPidLst=$dPidLst
#
# Show process on current shell
ps -f
#
# Start killing background processes from list
for dPid in $dPidLst
do
        echo killing $dPid. Process is still there.
        ps | grep $dPid
        kill $dPid
        ps | grep $dPid
        echo Just ran "'"ps"'" command, $dPid must not show again.
done

Затем просто запустите его как: ./bgkill.sh, конечно, с соответствующими разрешениями

root@umsstd22 [P]:~# ./bgkill.sh
PID=23757
dPidLst= 23758 23759
UNO
DOS
UID        PID  PPID  C STIME TTY          TIME CMD
root      3937  3935  0 11:07 pts/5    00:00:00 -bash
root     23757  3937  0 11:55 pts/5    00:00:00 /bin/bash ./bgkill.sh
root     23758 23757  0 11:55 pts/5    00:00:00 tail -f UNO.txt
root     23759 23757  0 11:55 pts/5    00:00:00 tail -f DOS.txt
root     23760 23757  0 11:55 pts/5    00:00:00 ps -f
killing 23758. Process is still there.
23758 pts/5    00:00:00 tail
./bgkill.sh: line 24: 23758 Terminated              tail -f UNO.txt
Just ran 'ps' command, 23758 must not show again.
killing 23759. Process is still there.
23759 pts/5    00:00:00 tail
./bgkill.sh: line 24: 23759 Terminated              tail -f DOS.txt
Just ran 'ps' command, 23759 must not show again.
root@umsstd22 [P]:~# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root      3937  3935  0 11:07 pts/5    00:00:00 -bash
root     24200  3937  0 11:56 pts/5    00:00:00 ps -f
person Luis Ramirez    schedule 20.02.2013

Вы также можете использовать pstree:

pstree -p user

Обычно это дает текстовое представление всех процессов для «пользователя», а опция -p дает идентификатор процесса. Насколько я понимаю, это не зависит от того, что процессы принадлежат текущей оболочке. Также показаны вилки.

person villaa    schedule 18.05.2012
comment
Чтобы использовать это в сценарии оболочки, вам придется сильно обработать вывод. - person Phil; 15.10.2013

pgrep может получить все дочерние PID родительского процесса. Как упоминалось ранее, $$ - это текущий PID скрипта. Итак, если вам нужен сценарий, который очищается после себя, это должно помочь:

trap 'kill $( pgrep -P $$ | tr "\n" " " )' SIGINT SIGTERM EXIT
person errant.info    schedule 02.05.2013
comment
Разве это не убьет всех? - person Phil; 15.10.2013
comment
Да, будет, в вопросе никогда не упоминалось о сохранении некоторых второстепенных детей после выхода. - person errant.info; 24.10.2013
comment
trap 'pkill -P $$' SIGING SIGTERM EXIT выглядит проще, но я не тестировал. - person petre; 15.02.2016
comment
Для совместимости не используйте префикс SIG. Это разрешено POSIX, но только как расширение, которое может поддерживать реализации: pubs.opengroup.org/onlinepubs/007904975/utilities/trap.html Оболочка тире, например, не работает. - person josch; 04.12.2018
comment
Предполагая, что если у вас есть pgrep, у вас также может быть pkill, более короткая версия более лаконична. - person dragon788; 01.11.2020

Я сталкивался с этой проблемой много раз, предоставляя различные объекты инфраструктуры. Часто вам понадобится временный прокси с использованием kubectl или временного порта. Я обнаружил, что команда timeout является хорошим решением для это, поскольку это позволяет моему сценарию быть самодостаточным, и я могу быть уверен, что процесс закончится. Я пытаюсь установить небольшие таймауты и перезапустить скрипт, если он мне все еще нужен.

person matttrach    schedule 23.03.2021