Jak uzyskać identyfikator procesu w tle?

375

Zaczynam proces w tle od skryptu powłoki i chciałbym go zabić po zakończeniu skryptu.

Jak uzyskać PID tego procesu ze skryptu powłoki? O ile widzę zmienna $!zawiera PID bieżącego skryptu, a nie proces w tle.

Włodzimierz Bezugły
źródło
8
$! jest poprawne. Czy na pewno zaczynasz skrypt w BG? próbka proszę.
pixelbeat
7
Tak $! jest poprawne. Myliłem się.
Volodymyr Bezuglyy
11
$$ zawiera bieżący skrypt PID.
HUB

Odpowiedzi:

582

Musisz zapisać PID procesu w tle podczas jego uruchamiania:

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

Nie można używać kontroli zadań, ponieważ jest to funkcja interaktywna i powiązana z terminalem sterującym. Do skryptu niekoniecznie musi być podłączony terminal, więc kontrola zadań niekoniecznie będzie dostępna.

camh
źródło
22
Od $! zwraca pid ostatniego procesu w tle. Czy to możliwe, że coś zaczyna się między fooi $!, i otrzymujemy, że coś jest pid zamiast zamiast foo?
WiSaGaN
53
@WiSaGaN: Nie. Nie ma nic między tymi wierszami. Żadna inna aktywność w systemie nie wpłynie na to. $! rozwinie się do PID ostatniego procesu w tle w tej powłoce .
camh
8
... który cię węży, jeśli foozdarzy się wiele poleceń potokowych (np. tail -f somefile.txt | grep sometext). W takich przypadkach otrzymasz PID polecenia grep $!zamiast polecenia tail, jeśli tego właśnie szukałeś. W tym przypadku będziesz musiał użyć jobslub pslubić.
John Rix,
1
@JohnRix: Niekoniecznie. Dostaniesz pid grep, ale jeśli go zabijesz, ogon dostanie SIGPIPE, gdy spróbuje napisać na rurze. Ale gdy tylko spróbujesz dostać się do jakiegokolwiek trudnego zarządzania / kontroli procesu, bash / shell staje się dość bolesny.
camh
5
Inne godne rozwiązania sugeruje (komentarz do odpowiedzi na) Jak uzyskać pid dopiero rozpoczętego procesu : och, i „oneliner”: /bin/sh -c 'echo $$>/tmp/my.pid && exec program args' &- dysfunkcja 24 listopada 10 o 14:28
imz - Ivan Zakharyaschev
148

Możesz użyć jobs -lpolecenia, aby dostać się do określonego zadaniaL

^Z
[1]+  Stopped                 guard

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

W tym przypadku 46841 jest PID.

Od help jobs:

-l Zgłoś identyfikator grupy procesów i katalog roboczy zadań.

jobs -p to kolejna opcja, która pokazuje tylko PID.

Jldupont
źródło
Aby użyć tego w skrypcie powłoki, musisz przetworzyć dane wyjściowe.
Phil
6
@Phil Aby wyświetlić tylko pids: jobs -p. Aby wyświetlić pid określonego zadania: jobs -p% 3. Nie ma potrzeby przetwarzania danych wyjściowych.
Erik Aronesty
1
@Erik za pomocą różnych poleceń / argumentów zmieniasz kontekst mojego komentarza. Bez sugerowania dodatkowego argumentu dane wyjściowe wymagają przetworzenia. Zaproponuj poprawę odpowiedzi!
Phil
Zapisywanie PID od $!razu po uruchomieniu jest bardziej przenośne i prostsze w większości sytuacji. Tak właśnie działa obecnie akceptowana odpowiedź.
tripleee
jobs -ppowrócił tak samo jak jobs -lna Lubuntu 16.4
Timo
46
  • $$ to pid bieżącego skryptu
  • $! jest pid ostatniego procesu w tle

Oto przykładowy zapis z sesji bash ( %1odnosi się do liczby porządkowej procesu w tle, jak widać z jobs):

$ echo $$
3748

$ sleep 100 &
[1] 192

$ echo $!
192

$ kill %1

[1]+  Terminated              sleep 100
wybieg dla modelek
źródło
echo %1nie zwraca procesu w tle na moim Ubuntu, podczas echo $!gdy
Timo
25

Jeszcze prostszy sposób na zabicie całego procesu potomnego skryptu bash:

pkill -P $$

-PFlag działa w ten sam sposób z pkilli pgrep- robi się procesów potomnych, tylko pkillprocesy potomne zginąć iz pgrepPID dziecko są wypisywane na standardowe wyjście.

Aleksiej Polonsky
źródło
Bardzo wygodne! Jest to najlepszy sposób, aby upewnić się, że nie pozostawiasz otwartych procesów w tle.
lepe
@lepe: Niezupełnie. Jeśli jesteś dziadkiem, to nie zadziała: po bash -c 'bash -c "sleep 300 &"' & uruchomieniu pgrep -P $$nic nie pokazuje, ponieważ sen nie będzie bezpośrednim dzieckiem twojej skorupy.
petre
1
@AlexeyPolonsky: powinno być: zabij wszystkie procesy potomne powłoki, a nie skrypt. Ponieważ $$odnosi się do bieżącej powłoki.
Timo,
występując bash -c 'bash -c "sleep 300 &"' & ; pgrep -P $$, [1] <pid>. So at least it shows something, but this is probably not the output of wchodzę na standardowe wyjście pgrep`
Timo
Nawet procesy mogą odłączyć je od procesu nadrzędnego. Prostą sztuczką jest wywołanie rozwidlenia (utworzenie wnuka), a następnie po prostu pozwolić procesowi potomnemu wyjść, podczas gdy wnuk kontynuuje pracę. (demonizacja jest słowem kluczowym) Ale nawet jeśli dziecko nadal działa zbyt pkill -P nie wystarcza, aby przekazać sygnał wnukom. Narzędzie takie jak pstree jest wymagane do śledzenia całego drzewa procesów zależnych. Ale to nie złapie demonów rozpoczętych od procesu, ponieważ ich rodzic jest procesem 1, np .:bash -c 'bash -c "sleep 10 & wait $!"' & sleep 0.1; pstree -p $$
Pauli Nieminen
4

to właśnie zrobiłem. Sprawdź to, mam nadzieję, że może pomóc.

#!/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

Następnie po prostu uruchom go jako: ./bgkill.shoczywiście z odpowiednimi uprawnieniami

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
Luis Ramirez
źródło
3

Możesz także użyć pstree:

pstree -p user

Zwykle daje to tekstową reprezentację wszystkich procesów dla „użytkownika”, a opcja -p podaje identyfikator procesu. O ile rozumiem, nie zależy to od posiadania procesów przez bieżącą powłokę. Pokazuje także widelce.

Villaa
źródło
1
Aby użyć tego w skrypcie powłoki, musisz mocno przetworzyć dane wyjściowe.
Phil
1

pgrepmoże uzyskać wszystkie podrzędne identyfikatory PID procesu nadrzędnego. Jak wspomniano wcześniej, $$obecne skrypty PID. Tak więc, jeśli chcesz, aby skrypt, który czyści się po sobie, powinien załatwić sprawę:

trap 'kill $( pgrep -P $$ | tr "\n" " " )' SIGINT SIGTERM EXIT
errant.info
źródło
Czy to nie zabiłoby dużo?
Phil
Tak, pytanie nigdy nie wspominało o utrzymaniu przy życiu niektórych dzieci z przeszłości.
errant.info
trap 'pkill -P $$' SIGING SIGTERM EXITwygląda na prostsze, ale go nie przetestowałem.
petre
Dla kompatybilności nie używaj SIGprefiksu. Jest to dozwolone przez POSIX ale tylko jako przedłużenie że implementacje mogą wspierać: pubs.opengroup.org/onlinepubs/007904975/utilities/trap.html Skorupa kreska na przykład nie.
josch