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

375

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

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

Владимир Безуглый
источник
8
$! верно. Вы уверены, что запускаете сценарий в BG? образец пожалуйста.
pixelbeat
7
Да, $! верно. Я был неправ.
Владимир Безуглый
11
$$ содержит текущий PID скрипта.
HUB

Ответы:

583

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

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

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

CAMH
источник
22
С $! возвращает pid последнего фонового процесса. Возможно ли, что что-то начинается между fooи $!, и мы получаем что-то pid вместо foos?
WiSaGaN
53
@ WiSaGaN: Нет. Между этими строками нет ничего. Любая другая активность в системе не повлияет на это. $! будет расширен до PID последнего фонового процесса в этой оболочке .
camh
8
... который использует вас, если fooслучается несколько команд по конвейеру (например, tail -f somefile.txt | grep sometext). В таких случаях вы получите PID команды grep, $!а не команду tail, если это то, что вы искали. Вам нужно будет использовать jobsили psили лайки в этом случае.
Джон Рикс,
1
@JohnRix: не обязательно. Вы получите pid grep, но если вы убьете это, tail получит SIGPIPE, когда попытается записать в канал. Но как только вы пытаетесь войти в любой сложный процесс управления / контроля, bash / shell становится довольно болезненным.
Съемка
5
Еще одно достойное решение предлагается в (комментарий к ответу) Как получить pid только что начавшегося процесса : о, и «oneliner»: /bin/sh -c 'echo $$>/tmp/my.pid && exec program args' &- sysfault 24 ноября '10 в 14:28
imz - Иван Захарящев
148

Вы можете использовать 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.

jldupont
источник
Чтобы использовать это в сценарии оболочки, вам нужно обработать вывод.
Фил
6
@Phil Чтобы перечислять только pids: jobs -p. Чтобы перечислить pid определенной работы: jobs -p% 3. Нет необходимости обрабатывать вывод.
Эрик Аронесты
1
@ Эрик с разными командами / аргументами вы меняете контекст моего комментария. Без предложенного дополнительного аргумента вывод требует обработки. Предложить улучшение к ответу!
Фил
Сохранение PID $!сразу после запуска делает его более переносимым и простым в большинстве ситуаций. Это то, что делает в настоящее время принятый ответ.
tripleee
jobs -pвернулся так же, как jobs -lна Lubuntu 16.4
Тимо
46
  • $$ pid текущего скрипта
  • $! pid последнего фонового процесса

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

$ echo $$
3748

$ sleep 100 &
[1] 192

$ echo $!
192

$ kill %1

[1]+  Terminated              sleep 100
подиум
источник
echo %1не возвращает фоновый процесс на моем Ubuntu, тогда echo $!как
Timo
25

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

pkill -P $$

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

Алексей Полонский
источник
Очень удобно! Это лучший способ убедиться, что вы не оставляете открытые процессы в фоновом режиме.
Лепе
@lepe: Не совсем. Если вы дедушка и бабушка, это не сработает: после bash -c 'bash -c "sleep 300 &"' & запуска pgrep -P $$ничего не отобразится, потому что сон не будет прямым потомком вашей оболочки.
Петре
1
@AlexeyPolonsky: так и должно быть: убить все дочерние процессы оболочки, а не скрипт. Потому что $$относится к текущей оболочке.
Тимо
выступая 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`
Timo
Даже процессы могут отсоединить их от родительского процесса. Простой трюк состоит в том, чтобы вызвать fork (создать внука), а затем просто позволить дочернему процессу завершиться, пока внуки продолжают выполнять свою работу. (ключевое слово - демонизация) Но даже если child продолжает работать слишком быстро, pkill -P недостаточно для передачи сигнала внукам. Инструмент, подобный pstree, должен следовать всему зависимому дереву процессов. Но это не поймет демонов, запущенных из процесса, поскольку их родитель - процесс 1. Например:bash -c 'bash -c "sleep 10 & wait $!"' & sleep 0.1; pstree -p $$
Pauli Nieminen
4

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

#!/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
Луис Рамирес
источник
3

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

pstree -p user

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

villaa
источник
1
Чтобы использовать это в сценарии оболочки, вам придется интенсивно обрабатывать вывод.
Фил
1

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

trap 'kill $( pgrep -P $$ | tr "\n" " " )' SIGINT SIGTERM EXIT
errant.info
источник
Разве это не убило бы много?
Фил
Да, в этом вопросе никогда не упоминалось о том, чтобы после выхода некоторые фоновые дети оставались в живых.
errant.info
trap 'pkill -P $$' SIGING SIGTERM EXITвыглядит проще, но я этого не проверял.
Петр
Для совместимости не используйте SIGпрефикс. Это разрешено POSIX, но только как расширение, которое могут поддерживать реализации : pubs.opengroup.org/onlinepubs/007904975/utilities/trap.html Например, оболочка dash не поддерживает.
Йош