Jak uzyskać pid właśnie rozpoczętego procesu

70

Chcę rozpocząć proces (np. MyCommand) i uzyskać jego pid (aby pozwolić go później zabić).

Próbowałem ps i filtruj według nazwy, ale nie mogę rozróżnić procesu według nazw

myCommand
ps ux | awk '/<myCommand>/ {print $2}' 

Ponieważ nazwy procesów nie są unikalne.

Mogę uruchomić proces przez:

myCommand &

Odkryłem, że mogę uzyskać ten PID poprzez:

echo $!

Czy jest jakieś prostsze rozwiązanie?

Z przyjemnością wykonam moją komendę i otrzymam jej PID w wyniku polecenia z jednej linii.

rafalmag
źródło

Odpowiedzi:

77

Co może być prostszego niż echo $!? Jako jedna linia:

myCommand & echo $!
Jacek Konieczny
źródło
Dziękuję, że połączenie tych poleceń z „&” bardzo mi pomogło.
rafalmag
8
w skrypcie bash, w pętli uruchamiającej programy, $! nie jest dokładne. Czasami zwraca pid samego skryptu, czasem grep lub awk uruchamiane ze skryptu jakieś rozwiązanie, aby konkretnie uzyskać pid procesu właśnie uruchomionego w tym scenariuszu? coś takiego jak pid = myprogrambyłoby niesamowite
2
Zauważ, że wymaga to uruchomienia polecenia przy użyciu a &jako poprzedniej linii, w przeciwnym razie echo zasadniczo zwróci puste.
rogerdpack
2
przypisanie do zmiennej typu command & echo $!zawiesza wykonanie na tym etapie :(
Shashank Vivek
26

Zawiń polecenie małym skryptem

#!/bin/bash
yourcommand &
echo $! >/path/to/pid.file
użytkownik9517
źródło
20

Możesz użyć sh -ci, execaby uzyskać PID polecenia nawet przed jego uruchomieniem.

Aby rozpocząć myCommand, aby wydrukować jego PID, zanim zacznie działać, możesz użyć:

sh -c 'echo $$; exec myCommand'

Jak to działa:

To uruchamia nową powłokę, drukuje PID tej powłoki, a następnie używa execwbudowanego do zastąpienia powłoki poleceniem, upewniając się, że ma ten sam PID. Kiedy twoja powłoka uruchamia polecenie z execwbudowanym narzędziem, to faktycznie staje się tym poleceniem , a nie bardziej powszechnym zachowaniem polegającym na tworzeniu nowej kopii, która ma swój własny PID, a następnie staje się poleceniem.

Uważam, że jest to o wiele prostsze niż alternatywy obejmujące asynchroniczne wykonywanie (z &), kontrolę zadań lub wyszukiwanie za pomocą ps. Te podejścia są w porządku, ale chyba że masz konkretny powód, aby z nich korzystać - na przykład być może polecenie już działa, w takim przypadku szukanie PID lub sterowanie zadaniami miałoby sens - sugeruję rozważenie tego w pierwszej kolejności. (I na pewno nie rozważyłbym napisania złożonego skryptu lub innego programu, aby to osiągnąć).

Ta odpowiedź zawiera przykład tej techniki.


Części tego polecenia można czasami pominąć, ale nie zwykle.

Nawet jeśli używana powłoka jest w stylu Bourne'a, a zatem obsługuje execwbudowaną semantykę, ogólnie nie powinieneś próbować unikać sh -c(lub równoważnego) tworzenia nowego, osobnego procesu powłoki w tym celu, ponieważ:

  • Gdy powłoka się stanie myCommand, nie będzie ona czekała na uruchomienie kolejnych poleceń. sh -c 'echo $$; exec myCommand; foonie będzie mógł uruchomić się foopo zamianie na myCommand. O ile nie piszesz skryptu, który uruchamia to jako ostatnie polecenie, nie możesz po prostu użyć echo $$; exec myCommandw powłoce, w której uruchamiasz inne polecenia.
  • Nie można do tego użyć podpowłoki . (echo $$; exec myCommand)może być ładniejszy niż składniowo sh -c 'echo $$; exec myCommand', ale po uruchomieniu $$wewnątrz ( ), daje PID powłoki macierzystej, a nie samej podpowłoce. Ale to PID podpowłoki będzie PID nowego polecenia. Niektóre powłoki zapewniają własne nieprzenośne mechanizmy wyszukiwania PID podpowłoki, których można użyć do tego celu. W szczególności, w Bash 4 , (echo $BASHPID; exec myCommand)działa.

Na koniec zauważ, że niektóre powłoki wykonają optymalizację , uruchamiając polecenie tak, jakby do exec(tj. Najpierw rezygnują z rozwidlenia), gdy wiadomo, że powłoka nie będzie musiała nic robić później. Niektóre powłoki próbują to zrobić za każdym razem, gdy jest to ostatnie polecenie do uruchomienia, podczas gdy inne zrobią to tylko wtedy, gdy nie będzie żadnych innych poleceń przed lub po poleceniu, a inne w ogóle tego nie zrobią. Efekt jest taki, że jeśli zapomnisz napisać execi po prostu użyć, sh -c 'echo $$; myCommand'to czasami da ci odpowiedni PID w niektórych systemach z niektórymi powłokami. Odradzam poleganie na takim zachowaniu , a zamiast tego zawsze uwzględnianie, execkiedy tego potrzebujesz.

Eliah Kagan
źródło
Przed uruchomieniem myCommandmuszę ustawić wiele zmiennych środowiskowych w moim skrypcie bash. Czy zostaną one przeniesione do środowiska, w którym uruchomiono execpolecenie?
user5359531
Wygląda na to, że moje środowisko przenosi się na execpolecenie. Jednak to podejście nie działa, gdy myCommanduruchamiane są inne procesy, z którymi trzeba pracować; kiedy wydajemy kill -INT <pid>gdzie piduzyskano w ten sposób, sygnał nie dociera do podprocesów rozpoczętych przez myCommand, natomiast jeśli uruchomię myCommand w bieżącej sesji i Ctrl + C, sygnały propagują się poprawnie.
user5359531
1
Próbowałem tego, ale pid procesu myCommand wydaje się być wynikiem pid przez echo $$ +1. czy robię coś źle?
crobar
Moje polecenie wygląda następująco:sh -c 'echo $$; exec /usr/local/bin/mbdyn -f "input.file" -o "/path/to/outputdir" > "command_output.txt" 2>&1 &'
crobar
7

Nie znam prostszego rozwiązania, ale nie używam $! wystarczająco dobry? Zawsze możesz przypisać wartość do innej zmiennej, jeśli będziesz jej potrzebować później, jak powiedzieli inni.

Na marginesie, zamiast pipowania z ps możesz użyć pgreplub pidof.

Carlpett
źródło
5

użyj exec ze skryptu bash po zarejestrowaniu pid do pliku:

przykład:

załóżmy, że masz skrypt o nazwie „forever.sh”, który chcesz uruchomić z argumentami p1, p2, p3

kod źródłowy forever.sh:

#!/bin/sh

while [ 1 -lt 2 ] ; do
    logger "$0 running with parameters \"$@\""
    sleep 5
done

utwórz plik reaper.sh:

#!/bin/sh

echo $$ > /var/run/$1.pid
exec "$@"

uruchom forever.sh przez reaper.sh:

./reaper.sh ./forever.sh p1 p2 p3 p4 &

forever.sh robi tylko rejestrowanie linii do syslog co 5 sekund

masz teraz pid w /var/run/forever.sh.pid

cat /var/run/forever.sh.pid 
5780

i forever.sh uruchamia aok. syslog grep:

Nov 24 16:07:17 pinkpony cia: ./forever.sh running with parameters "p1 p2 p3 p4"

możesz to zobaczyć w tabeli procesów:

ps axuwww|grep 'forever.sh p1' |grep -v grep
root      5780  0.0  0.0   4148   624 pts/7    S    16:07   0:00 /bin/sh ./forever.sh p1 p2 p3 p4
użytkownik237419
źródło
3
och, a „oneliner”: / bin / sh -c 'echo $$> / tmp / my.pid && exec program args' &
user237419
1
Aby właściwie zachować wewnętrzne spacje w argumentach, powinieneś użyć exec "$@"zamiast exec $*. Technicznie rzecz biorąc, musisz zachować nie białe znaki, ale występowanie znaków w parametrze powłoki IFS (domyślnie jest to spacja, tabulator i nowa linia).
Chris Johnsen,
punkt wzięty. :)
user237419,
Dziękuję, nie znałem parametru $$. To może być bardzo przydatne.
rafalmag
3

W powłoce bash alternatywą $!może być jobs -pwbudowana. W niektórych przypadkach !in $!interpretowane jest przez powłokę przed (lub zamiast) rozszerzeniem zmiennej, co prowadzi do nieoczekiwanych wyników.

To na przykład nie zadziała:

((yourcommand) & echo $! >/var/run/pidfile)

podczas gdy to:

((yourcommand) & jobs -p >/var/run/pidfile)
mustaccio
źródło
Myślę, że miałeś na myśli ((twoje polecenie) i zadania -p> / var / run / pidfile).
Dom,
1

Możesz użyć czegoś takiego jak:

$ myCommand ; pid=$!

Lub

$ myCommand && pid=$!

Te dwa polecenia mogą być połączeniami za pomocą ;lub &&. W drugim przypadku pid zostanie ustawiony tylko wtedy, gdy pierwsze polecenie się powiedzie. Możesz pobrać identyfikator procesu z $pid.

Khaled
źródło
3
OP chce zdobyć PID, aby mógł go później zabić. ; i && wymagają oryginalnego procesu wyjścia przed echo $! jest wykonywany.
user9517,
Tak masz rację. To da ci pid po zakończeniu myCommand.
Khaled
6
Odwołanie $!po &&lub ;nigdy nie da identyfikatora PID procesu rozpoczętego dla lewej strony separatora poleceń. $!jest ustawiany tylko dla procesów uruchamianych asynchronicznie (np. zwykle z, &ale niektóre powłoki mają także inne metody)
Chris Johnsen
jest to pomocne i dlaczego nikt nie głosuje poza mną? dobra robota :)
świątynia
1

To jest trochę hack-odpowiedź i prawdopodobnie nie zadziała dla większości ludzi. Jest to również ogromne ryzyko związane z bezpieczeństwem, więc nie rób tego, chyba że jesteś pewien, że będziesz bezpieczny, a dane wejściowe zostaną zdezynfekowane i ... cóż, masz pomysł.

Skompiluj tutaj mały program C do pliku binarnego o nazwie start(lub cokolwiek chcesz), a następnie uruchom program jako./start your-program-here arg0 arg1 arg2 ...

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    if (argc >= 2)
    {
        printf("%lu\n", (long unsigned) getpid());
        if (execvp(argv[1], &argv[1]) < 0)
        {
            perror(NULL);
            return 127;
        }
    }
    return 0;
}

Krótko mówiąc, to wydrukuje PID stdout, a następnie załaduje program do procesu. Powinien nadal mieć ten sam PID.

tonysdg
źródło
Jak mogę zapisać numer PID zwracany przez ten kod w zmiennej bash? Z jakiegoś powodu dla mnie niejasnego stdoutnie wydaje się, aby został odnotowany w tym przykładzie:RESULT="$(./start_and_get_pid.out echo yo)"; echo "$RESULT"
Tfb9
@ Tfb9: Szczerze polecam jedno z innych podejść; Napisałem to ponad 2 lata temu i jest to zdecydowanie jedna z przedstawionych metod trudniejszych / podatnych na błędy (a przynajmniej tak mi się wydaje).
tonysdg
Dziękuję za notatkę. Myślę, że rozwiązałem z tym problemsleep 10 & PID_IS=$!; echo $PID_IS
Tfb9