Jak uruchomić procesy zewnętrzne z Pythonem i modułem podprocesowym

Jak uruchomić procesy zewnętrzne z Pythonem i modułem podprocesowym

W naszych skryptach automatyzacji często musimy uruchomić i monitorować programy zewnętrzne, aby wykonać nasze pożądane zadania. Podczas pracy z Pythonem możemy użyć modułu podprocesowego do wykonywania wspomnianych operacji. Ten moduł jest częścią standardowej biblioteki języka programowania. W tym samouczku szybko na to przyjrzymy się i poznamy podstawy jego użycia.

W tym samouczku się nauczysz:

  • Jak używać funkcji „uruchom” do odrodzenia procesu zewnętrznego
  • Jak uchwycić standardowe wyjście i błąd standardowy
  • Jak sprawdzić status istnienia procesu i podnieść wyjątek, jeśli się nie powiedzie
  • Jak wykonać proces w skorupce pośredniej
  • Jak ustawić limit czasu na proces
  • Jak używać klasy Popen bezpośrednio do rilania dwóch procesów
Jak uruchomić procesy zewnętrzne z Pythonem i modułem podprocesowym

Zastosowane wymagania i konwencje oprogramowania

Wymagania oprogramowania i konwencje linii poleceń Linux
Kategoria Wymagania, konwencje lub wersja oprogramowania
System Niezależny dystrybucja
Oprogramowanie Python3
Inny Znajomość programowania Pythona i obiektowego
Konwencje # - Wymaga, aby podane Linux -commands były wykonywane z uprawnieniami root bezpośrednio jako użytkownik root lub za pomocą sudo Komenda
$-wymaga wykonania Linux-commands jako zwykłego niewidzianego użytkownika

Funkcja „uruchom”

uruchomić funkcja została dodana do podproces moduł tylko w stosunkowo niedawnych wersjach Pythona (3.5). Używanie go jest teraz zalecanym sposobem na odrodzenie procesów i powinien obejmować najczęstsze przypadki użycia. Przed wszystkim innym zobaczmy jego najprostsze użycie. Załóżmy, że chcemy uruchomić LS -al Komenda; W skorupce Pythona ucieklibyśmy:

>>> Importuj podproces >>> proces = podproces.Run ([„ls”, „-l”, „-a”]) 

Wyjście polecenia zewnętrznego jest wyświetlane na ekranie:

Łącznie 132 DRWX------. 22 EGDOC EGDOC 4096 30 listopada 12:18 . drwxr-xr-x. 4 Root Root 4096 22 listopada 13: 11… -Rw-------. 1 EGDOC EGDOC 10438 grudnia 1 12:54 .bash_history -Rw-r-r--. 1 EGDOC EGDOC 18 lipca 27 15:10 .bash_logout […] 

Właśnie tutaj użyliśmy pierwszego, obowiązkowego argumentu zaakceptowanego przez funkcję, która może być sekwencją, która „opisuje” polecenie i jego argumenty (jak w przykładzie) lub ciąg, którego należy używać podczas działania z Shell = True Argument (zobaczymy to później).

Uchwycenie polecenia stdout i stderr

Co jeśli nie chcemy, aby dane wyjściowe procesu były wyświetlane na ekranie, ale zamiast tego przechwycone, aby można było się odwołać po wyjściu z procesu? W takim przypadku możemy ustawić capture_output argument funkcji PRAWDA:

>>> proces = podproces.RUN ([„ls”, '-l', '-a'], capture_output = true) 

Jak możemy później odzyskać wyjście (stdout i stderr) procesu? Jeśli zaobserwujesz powyższe przykłady, możesz zobaczyć, jak użyliśmy proces zmienna, aby odwołać się do tego, co zwraca uruchomić Funkcja: Ukończony proces obiekt. Ten obiekt reprezentuje proces, który został uruchomiony przez funkcję i ma wiele przydatnych właściwości. Między innymi, Stdout I Stderr są używane do „przechowywania” odpowiednich deskryptorów polecenia, jeśli, jak powiedzieliśmy, capture_output argument jest ustawiony na PRAWDA. W takim przypadku, aby uzyskać Stdout z procesu, który byśmy uruchomili:

>>> proces.Stdout 

Stdout i stderr są przechowywane jako Sekwencje bajtów domyślnie. Jeśli chcemy, aby były przechowywane jako struny, musimy ustawić tekst argument uruchomić funkcja PRAWDA.



Zarządzaj awarią procesu

Polecenie, które przeprowadziliśmy w poprzednich przykładach, zostało wykonane bez błędów. Jednak podczas pisania programu należy wziąć pod uwagę wszystkie sprawy, więc co, jeśli proces zrodzony się nie powiedzie? Domyślnie nic nie wydarzyło się nic „specjalnego”. Zobaczmy przykład; Prowadzimy LS polecenie ponownie, próbując wymienić treść /źródło Katalog, który zwykle w Linux nie jest czytelny przez normalnych użytkowników:

>>> proces = podproces.RUN ([„ls”, '-l', '-a', '/root']) 

Jedną rzeczą, którą możemy zrobić, aby sprawdzić, czy uruchomiono proces, jest sprawdzenie jego statusu istnienia, który jest przechowywany w kod powrotu własność Ukończony proces obiekt:

>>> proces.ReturnCode 2 

Widzieć? W tym przypadku kod powrotu był 2, potwierdzenie, że proces napotkał problem zezwolenia i nie został pomyślnie zakończony. Moglibyśmy przetestować wyjście procesu w ten sposób, a bardziej elegancko, abyśmy mogli zrobić, aby wyjątek został podniesiony, gdy nastąpi porażka. Wejdz do sprawdzać argument uruchomić funkcja: gdy jest ustawiona na PRAWDA i odrodzony proces się nie powiedzie, Zwany ProcesSerror Wyjątek jest podniesiony:

>>> proces = podproces.run (['ls', '-l', '-a', '/root'], check = true) ls: nie można otworzyć katalogu '/root': uprawnienie odmówione traceback (najnowsze połączenie ostatnie): plik "" , wiersz 1, w pliku "/usr/lib64/python3.9/podproces.Py ", wiersz 524, w runach o nazwieProcessSerror (kod retcode, proces.Args, podproces.Zwany ProcesSerror: Command '[' ls ',' -l ',' -a ','/root '] „zwrócił status wyjścia niezerowego 2. 

Obsługiwanie wyjątki W Python jest dość łatwe, więc aby zarządzać awarią procesu, moglibyśmy napisać coś w stylu:

>>> spróbuj:… proces = podproces.RUN ([„ls”, '-l', '-a', '/root'], check = true)… z wyjątkiem podprocesów.Nazywany ProcesSerror jako e:… # tylko przykład, coś przydatnego do zarządzania awarią należy zrobić!… Drukuj (f "e.Cmd nie powiodło się!")… LS: Nie można otworzyć katalogu '/root': uprawnienie odmówione [„ ls ',' -l ',' -a ','/root '] nie powiodło się! >>> 

Zwany ProcesSerror Wyjątek, jak powiedzieliśmy, jest podniesiony, gdy proces wychodzi z non 0 status. Obiekt ma właściwości takie jak kod powrotu, CMD, Stdout, Stderr; To, co reprezentują, jest dość oczywiste. Na przykład w powyższym przykładzie właśnie użyliśmy CMD właściwość, w celu zgłoszenia sekwencji używanej do opisania polecenia i jej argumentów w wiadomości, którą napisaliśmy, gdy wystąpił wyjątek.

Wykonaj proces w skorupce

Procesy uruchomione z uruchomić funkcja, są wykonywane „bezpośrednio”, oznacza to, że do ich uruchomienia nie jest używana: żadne zmienne środowiskowe nie są dostępne dla procesu, a rozszerzenia powłoki nie są wykonywane. Zobaczmy przykład, który obejmuje użycie $ Dom zmienny:

>>> proces = podproces.RUN ([„ls”, „-al”, „$ home”]) LS: nie można uzyskać dostępu do „$ home”: brak takiego pliku lub katalogu 

Jak widać $ Dom Zmienna nie została rozszerzona. Zaleca się wykonywanie procesów w ten sposób, aby uniknąć potencjalnych zagrożeń bezpieczeństwa. Jeśli jednak w niektórych przypadkach musimy wywołać powłokę jako proces pośredni, musimy ustawić powłoka parametr uruchomić funkcja PRAWDA. W takich przypadkach lepsze jest określenie, że polecenie ma zostać wykonane i jego argumenty jako strunowy:

>>> proces = podproces.RUN („ls -al $ home”, shell = true) ogółem 136 drwx------. 23 EGDOC EGDOC 4096 grudnia 3 09:35 . drwxr-xr-x. 4 Root Root 4096 22 listopada 13: 11… -Rw-------. 1 EGDOC EGDOC 11885 2 grudnia 09:35 .bash_history -Rw-r-r--. 1 EGDOC EGDOC 18 lipca 27 15:10 .bash_logout […] 

Wszystkie zmienne istniejące w środowisku użytkownika mogą być używane podczas wywoływania powłoki jako procesu pośredniego: chociaż może to wyglądać przydatne, może być źródłem problemów, szczególnie w przypadku potencjalnie niebezpiecznego wkładu, co może prowadzić zastrzyki skorupowe. Prowadzenie procesu z Shell = True jest zatem zniechęcony i powinien być używany tylko w bezpiecznych przypadkach.



Określanie limitu czasu na proces

Zwykle nie chcemy, aby procesy źle zachowywane działały wiecznie w naszym systemie po ich uruchomieniu. Jeśli używamy koniec czasu parametr uruchomić funkcja, możemy określić czas w sekund. Jeśli nie zostanie ukończony w tym czasie, proces zostanie zabity Sigkill sygnał, którego, jak wiemy, nie może być złapany przez proces. Pokazajmy to, odradzając długotrwały proces i zapewniając limit czasu w kilka sekund:

>>> proces = podproces.Uruchom ([„ping”, „Google.com '], limit czasu = 5) ping Google.com (216.58.206.46) 56 (84) bajty danych. 64 bajtów z MIL07S07-in-F14.1E100.netto (216.58.206.46): ICMP_SEQ = 1 TTL = 113 Czas = 29.3 ms 64 bajtów z LHR35S10-in-F14.1E100.netto (216.58.206.46): ICMP_SEQ = 2 TTL = 113 Time = 28.3 ms 64 bajtów z LHR35S10-in-F14.1E100.netto (216.58.206.46): ICMP_SEQ = 3 ttl = 113 czas = 28.5 ms 64 bajtów z LHR35S10-in-F14.1E100.netto (216.58.206.46): ICMP_SEQ = 4 ttl = 113 czas = 28.5 ms 64 bajtów z LHR35S10-in-F14.1E100.netto (216.58.206.46): ICMP_SEQ = 5 ttl = 113 czas = 28.1 MS Traceback (najnowsze połączenie ostatnie): Plik „”, wiersz 1, w pliku ”/usr/lib64/python3.9/podproces.py ", linia 503, w run stdout, stderr = proces.komunikat (wejście, limit czasu = limit czasu) plik "/usr/lib64/python3.9/podproces.py ", wiersz 1130, w komunikacji stdout, stderr = self._Communiate (Input, Endtime, Timeout) plik ”/usr/lib64/pyhon3.9/podproces.Py ", wiersz 2003, w _komunice self.Poczekaj (limit czasu = jaźń._REMINING_TIME (endTime)) plik "/usr/lib64/python3.9/podproces.py ", wiersz 1185, w oczekiwaniu._Wait (TIMEOUT = TIMEOUT) PLIK ”/USR/Lib64/Python3.9/podproces.Py ", wiersz 1907, w _ -Wait hode hode timeoutexpired (self self.Args, limit czasu) podproces.TimeOutexpired: Command '[„ping”, „Google.com '] „wyczerpano po 4.999826977029443 sekundy 

W powyższym przykładzie uruchomiliśmy świst polecenie bez określania stałej ilości Żądanie ECHO Pakiety, dlatego potencjalnie mogą działać wiecznie. Określiliśmy również limit czasu 5 sekundy za pośrednictwem koniec czasu parametr. Jak możemy zaobserwować początkowo program, ale Limit czasu upłynął Wyjątek został podniesiony, gdy osiągnięto określoną ilość sekund, a proces został zabity.

Funkcje wywołania, check_output i check_call

Jak powiedzieliśmy wcześniej, uruchomić Funkcja jest zalecanym sposobem uruchomienia procesu zewnętrznego i powinna pokryć większość przypadków. Zanim został wprowadzony w Python 3.5, trzy główne funkcje API wysokiego poziomu używane do uruchomienia procesu były dzwonić, check_output I Check_Call; Zobaczmy je krótko.

Po pierwsze, dzwonić funkcja: służy do uruchamiania polecenia opisanego przez Args parametr; czeka na zakończenie polecenia i zwraca jego kod powrotu. Z grubsza odpowiada podstawowe użycie uruchomić funkcjonować.

Check_Call Zachowanie funkcji jest praktycznie takie samo w stosunku do zachowania uruchomić funkcja, gdy sprawdzać parametr jest ustawiony na PRAWDA: uruchamia określone polecenie i czeka na to. Jeśli jego istnienie nie jest 0, A Zwany ProcesSerror Wyjątek jest podniesiony.

Wreszcie check_output Funkcja: Działa podobnie jak Check_Call, Ale zwroty Wyjście programu: nie jest wyświetlane po wykonywaniu funkcji.

Praca na niższym poziomie z klasą Popen

Do tej pory badaliśmy funkcje API wysokiego poziomu w module podprocesów, szczególnie uruchomić. Wszystkie to funkcje, pod maską wchodzą w interakcje z Popen klasa. Z tego powodu w zdecydowanej większości przypadków nie musimy z tym pracować bezpośrednio. Kiedy jednak potrzebna jest większa elastyczność, tworzenie Popen obiekty bezpośrednio stają się konieczne.



Załóżmy, że na przykład chcemy podłączyć dwa procesy, odtwarzając zachowanie „rury” skorupy. Jak wiemy, kiedy wbijamy dwa polecenia w skorupce, standardowe wyjście po lewej stronie rury (|) jest używany jako standardowe wejście tego po prawej stronie (sprawdź ten artykuł na temat przekierowań powłoki, jeśli chcesz dowiedzieć się więcej na ten temat). W poniższym przykładzie wynik rurowania dwa polecenia jest przechowywane w zmiennej:

$ output = "$ (dmesg | grep sda)" 

Naśladowanie tego zachowania za pomocą modułu podprocesowego, bez konieczności ustawiania powłoka parametr do PRAWDA Jak widzieliśmy wcześniej, musimy użyć Popen klasa bezpośrednio:

dmesg = podproces.Popen (['dmesg'], stdout = podproces.Rura) grep = podproces.Popen ([„grep”, „sda”], stdin = dmesg.stdout) dmesg.Stdout.close () wyjście = grep.comunecate () [0] 

Aby zrozumieć powyższy przykład, musimy pamiętać, że proces rozpoczął się od użycia Popen klasa bezpośrednio nie blokuje wykonywania skryptu, ponieważ teraz jest on czeka.

Pierwszą rzeczą, którą zrobiliśmy w powyższym fragmencie kodu, było stworzenie Popen obiekt reprezentujący Dmesg proces. Ustawiliśmy Stdout tego procesu podproces.RURA: Ta wartość wskazuje, że należy otworzyć rurę do określonego strumienia.

Nie stworzyliśmy kolejnej instancji Popen klasa dla Grep proces. w Popen Konstruktor określiliśmy oczywiście polecenie i jego argumenty, ale oto ważna część, ustawiamy standardowe wyjście Dmesg proces, który należy zastosować jako standardowe wejście (stdin = dmesg.Stdout), aby odtworzyć skorupę
Zachowanie rur.

Po utworzeniu Popen obiekt dla Grep polecenie, zamknęliśmy Stdout strumień Dmesg proces za pomocą zamknąć() Metoda: To, jak stwierdzono w dokumentacji, jest potrzebne, aby umożliwić pierwszemu procesowi odbieranie sygnału SIGPIPE. Spróbujmy wyjaśnić, dlaczego. Zwykle, gdy dwa procesy są połączone rurą, jeśli jedna po prawej stronie rury (GREP w naszym przykładzie) wychodzi przed jedną po lewej (DMESG), ten ostatni otrzymuje Sigpipe
sygnał (zepsuta rura) i domyślnie się kończy.

Podczas replikacji zachowania rury między dwoma poleceniami w Pythonie istnieje problem: Stdout pierwszego procesu jest otwarty zarówno w skrypcie nadrzędnym, jak i w standardowym wejściu drugiego procesu. W ten sposób, nawet jeśli Grep Proces kończy się, rura nadal pozostanie otwarta w procesie dzwoniącego (nasz skrypt), dlatego pierwszy proces nigdy nie otrzyma Sigpipe sygnał. Dlatego musimy zamknąć Stdout strumienia pierwszego procesu w naszym
główny skrypt po uruchomieniu drugiego.

Ostatnią rzeczą, którą zrobiliśmy, było zadzwonienie komunikować się() Metoda na Grep obiekt. Tę metodę można zastosować do opcjonalnego przekazywania danych wejściowych do procesu; czeka na zakończenie procesu i zwraca krotkę, w którym proces jest procesem pierwszego członka Stdout (do którego odwołuje się wyjście zmienna), a drugi proces Stderr.

Wnioski

W tym samouczku widzieliśmy zalecany sposób odrwienia procesów zewnętrznych za pomocą Pythona za pomocą podproces moduł i uruchomić funkcjonować. Zastosowanie tej funkcji powinno wystarczyć dla większości przypadków; Gdy jednak potrzebny jest wyższy poziom elastyczności, należy użyć Popen klasa bezpośrednio. Jak zawsze sugerujemy spojrzeć na
Dokumentacja podprocesowa dla pełnego przeglądu podpisu funkcji i klas dostępnych w
moduł.

Powiązane samouczki Linux:

  • Wprowadzenie do automatyzacji, narzędzi i technik Linuksa
  • Mastering Bash Script Loops
  • Rzeczy do zainstalowania na Ubuntu 20.04
  • Zagnieżdżone pętle w skryptach Bash
  • Jak używać polecenia TCPDUMP w Linux
  • Mint 20: Lepsze niż Ubuntu i Microsoft Windows?
  • Obsługa danych wejściowych użytkownika w skryptach Bash
  • Samouczek debugowania GDB dla początkujących
  • Rzeczy do zrobienia po zainstalowaniu Ubuntu 20.04 Focal Fossa Linux
  • Jak monitorować aktywność sieciową w systemie Linux