Aby nieco rozwinąć wcześniejsze odpowiedzi tutaj, istnieje wiele szczegółów, które są często pomijane.
- Wolę
subprocess.run()
nad subprocess.check_call()
i przyjaciele ponad subprocess.call()
ponad subprocess.Popen()
ponad os.system()
ponados.popen()
- Zrozum i prawdopodobnie używaj
text=True
, alias universal_newlines=True
.
- Zrozumieć sens
shell=True
czy shell=False
i jak zmienia cytowanie i dostępności udogodnień powłoki.
- Poznaj różnice między
sh
i Bash
- Zrozum, w jaki sposób podproces jest oddzielny od swojego elementu nadrzędnego, i na ogół nie może zmienić elementu nadrzędnego.
- Unikaj uruchamiania interpretera języka Python jako podprocesu języka Python.
Tematy te zostały omówione bardziej szczegółowo poniżej.
Wolę subprocess.run()
lubsubprocess.check_call()
Ta subprocess.Popen()
funkcja to koń roboczy niskiego poziomu, ale jej prawidłowe użycie jest trudne i kończy się kopiowanie / wklejanie wielu wierszy kodu ... które dogodnie istnieją już w standardowej bibliotece jako zestaw funkcji opakowania wyższego poziomu do różnych celów, które zostały przedstawione bardziej szczegółowo poniżej.
Oto akapit z dokumentacji :
Zalecanym podejściem do wywoływania podprocesów jest użycie run()
funkcji dla wszystkich przypadków użycia, które może obsłużyć. W przypadku bardziej zaawansowanych przypadków użycia Popen
interfejs podstawowy może być używany bezpośrednio.
Niestety dostępność tych funkcji otoki różni się w zależności od wersji Pythona.
subprocess.run()
został oficjalnie wprowadzony w Pythonie 3.5. Ma to zastąpić wszystkie poniższe.
subprocess.check_output()
został wprowadzony w Pythonie 2.7 / 3.1. Jest to w zasadzie odpowiedniksubprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
subprocess.check_call()
został wprowadzony w Pythonie 2.5. Jest to w zasadzie odpowiedniksubprocess.run(..., check=True)
subprocess.call()
został wprowadzony w Pythonie 2.4 w oryginalnym subprocess
module ( PEP-324 ). Jest to w zasadzie odpowiedniksubprocess.run(...).returncode
Interfejs API wysokiego poziomu vs. subprocess.Popen()
Przebudowane i rozszerzone subprocess.run()
są bardziej logiczne i wszechstronne niż starsze funkcje, które zastępuje. Zwraca CompletedProcess
obiekt, który ma różne metody, które pozwalają ci odzyskać status wyjścia, standardowe wyjście oraz kilka innych wyników i wskaźników statusu z ukończonego podprocesu.
subprocess.run()
jest właściwą drogą, jeśli potrzebujesz tylko programu do uruchomienia i przywrócenia kontroli w Pythonie. W przypadku bardziej zaangażowanych scenariuszy (procesy w tle, być może z interaktywnymi wejściami / wyjściami z programem nadrzędnym Python) nadal musisz samodzielnie korzystać subprocess.Popen()
z całej instalacji hydraulicznej. Wymaga to dość skomplikowanego zrozumienia wszystkich ruchomych części i nie powinno być podejmowane pochopnie. Prostszy Popen
obiekt reprezentuje (prawdopodobnie nadal działający) proces, który musi być zarządzany z twojego kodu przez pozostałą część cyklu życia podprocesu.
Należy chyba podkreślić, że po subprocess.Popen()
prostu tworzy proces. Jeśli to zostawisz, masz podproces działający równolegle z Pythonem, więc proces „w tle”. Jeśli nie musi on wprowadzać danych wyjściowych ani danych wyjściowych ani w inny sposób koordynować z tobą, może wykonywać przydatne prace równolegle z programem Python.
Unikaj os.system()
ios.popen()
Od czasu wiecznego (dobrze, ponieważ Python 2.5) Do os
dokumentacji moduł zawierał zalecenie preferowany subprocess
przez os.system()
:
subprocess
Moduł zapewnia bardziej wydajne urządzenia do tarła nowych procesów i pobierania ich wyników; korzystanie z tego modułu jest lepsze niż korzystanie z tej funkcji.
Problem system()
polega na tym, że jest to oczywiście zależne od systemu i nie oferuje sposobów interakcji z podprocesem. Po prostu działa, ze standardowym wyjściem i standardowym błędem poza zasięgiem Pythona. Jedyną informacją, którą Python otrzymuje z powrotem, jest status wyjścia polecenia (zero oznacza sukces, chociaż znaczenie niezerowych wartości jest również w pewnym stopniu zależne od systemu).
PEP-324 (o którym już wspomniano powyżej) zawiera bardziej szczegółowe uzasadnienie, dlaczego os.system
jest problematyczne i jak subprocess
próbuje rozwiązać te problemy.
os.popen()
był jeszcze bardziej zniechęcany :
Przestarzałe od wersji 2.6: Ta funkcja jest przestarzała. Użyj subprocess
modułu.
Jednak od pewnego czasu w Pythonie 3 został ponownie zaimplementowany, aby po prostu używać subprocess
i przekierowuje do subprocess.Popen()
dokumentacji w celu uzyskania szczegółowych informacji.
Zrozum i zwykle używaj check=True
Zauważysz również, że subprocess.call()
ma wiele takich samych ograniczeń jak os.system()
. W regularnym użyciu powinieneś ogólnie sprawdzić, czy proces zakończył się pomyślnie, co subprocess.check_call()
i subprocess.check_output()
zrobić (w przypadku gdy ten ostatni zwraca również standardowe wyjście gotowego podprocesu). Podobnie zwykle powinieneś używać check=True
z, subprocess.run()
chyba że specjalnie musisz zezwolić podprocesowi na zwrócenie statusu błędu.
W praktyce za pomocą check=True
lub subprocess.check_*
Python zgłasza CalledProcessError
wyjątek, jeśli podproces zwraca niezerowy status wyjścia.
Częstym błędem subprocess.run()
jest pomijanie check=True
i zaskoczenie, gdy kod podrzędny zawiedzie, jeśli podproces nie powiedzie się.
Z drugiej strony częstym problemem check_call()
i check_output()
było to, że użytkownicy, którzy ślepo korzystali z tych funkcji, byli zaskoczeni, gdy zgłoszono wyjątek, np. Gdy grep
nie znaleźli dopasowania. (Prawdopodobnie i tak powinieneś zastąpić grep
natywnym kodem Python, jak opisano poniżej).
Wszystko to się liczyło, musisz zrozumieć, w jaki sposób polecenia powłoki zwracają kod wyjścia i pod jakimi warunkami zwracają niezerowy (błąd) kod wyjścia, i podejmują świadomą decyzję, w jaki sposób należy go obsługiwać.
Zrozum i prawdopodobnie użyj text=True
akauniversal_newlines=True
Od Python 3 ciągi znaków wewnętrznych w Pythonie są ciągami znaków Unicode. Ale nie ma gwarancji, że podproces generuje dane wyjściowe Unicode lub łańcuchy.
(Jeśli różnice nie są od razu oczywiste, zalecamy Pragmatic Unicode Neda Batcheldera , jeśli nie jest to absolutnie obowiązkowe, czytanie. Jeśli wolisz, za linkiem znajduje się 36-minutowa prezentacja wideo, chociaż samodzielne przeczytanie strony prawdopodobnie zajmie znacznie mniej czasu. )
W głębi duszy Python musi pobrać bytes
bufor i jakoś go zinterpretować. Jeśli zawiera on kroplę danych binarnych, nie powinien być dekodowany na ciąg Unicode, ponieważ jest to podatne na błędy i powodujące błędy zachowanie - dokładnie takie nieznośne zachowanie, które zagadało wiele skryptów Python 2, zanim istniał sposób na właściwie rozróżnia zakodowany tekst i dane binarne.
Za pomocą text=True
mówisz Pythonowi, że w rzeczywistości oczekujesz danych tekstowych w domyślnym kodowaniu systemu i że należy je zdekodować do ciągu znaków w języku Python (Unicode), najlepiej jak to możliwe (zwykle UTF-8 na dowolnym, średnio do system dat, może z wyjątkiem Windowsa?)
Jeśli nie o to prosisz, Python po prostu poda ci bytes
łańcuchy w łańcuchach stdout
i stderr
. Być może w niektórych późniejszym ty nie wiesz, że były one ciągi tekstowe po wszystkim, i znasz ich kodowanie. Następnie możesz je odkodować.
normal = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True,
text=True)
print(normal.stdout)
convoluted = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True)
# You have to know (or guess) the encoding
print(convoluted.stdout.decode('utf-8'))
W Pythonie 3.7 wprowadzono krótszy, bardziej opisowy i zrozumiały alias text
argumentu słowa kluczowego, który wcześniej był nieco myląco nazywany universal_newlines
.
Zrozum shell=True
vsshell=False
Gdy shell=True
przekażesz pojedynczy ciąg do powłoki, a powłoka zabierze go stamtąd.
Gdy shell=False
przekażesz listę argumentów do systemu operacyjnego, omijając powłokę.
Gdy nie masz powłoki, zapisujesz proces i pozbywasz się dość dużej ilości ukrytej złożoności, która może , ale nie musi zawierać błędy, a nawet problemy z bezpieczeństwem.
Z drugiej strony, gdy nie masz powłoki, nie masz przekierowania, rozszerzenia symboli wieloznacznych, kontroli zadań i wielu innych funkcji powłoki.
Częstym błędem jest używanie, shell=True
a następnie przekazywanie Pythonowi listy tokenów lub odwrotnie. Zdarza się to w niektórych przypadkach, ale jest naprawdę źle zdefiniowane i może się zepsuć na ciekawe sposoby.
# XXX AVOID THIS BUG
buggy = subprocess.run('dig +short stackoverflow.com')
# XXX AVOID THIS BUG TOO
broken = subprocess.run(['dig', '+short', 'stackoverflow.com'],
shell=True)
# XXX DEFINITELY AVOID THIS
pathological = subprocess.run(['dig +short stackoverflow.com'],
shell=True)
correct = subprocess.run(['dig', '+short', 'stackoverflow.com'],
# Probably don't forget these, too
check=True, text=True)
# XXX Probably better avoid shell=True
# but this is nominally correct
fixed_but_fugly = subprocess.run('dig +short stackoverflow.com',
shell=True,
# Probably don't forget these, too
check=True, text=True)
Wspólna retorta „ale to działa dla mnie” nie jest użyteczną obaleniem, chyba że dokładnie zrozumiesz, w jakich okolicznościach może przestać działać.
Przykład refaktoryzacji
Bardzo często funkcje powłoki można zastąpić rodzimym kodem Python. Prosty Awk lub sed
skrypty powinny raczej zostać po prostu przetłumaczone na Python.
Aby częściowo to zilustrować, oto typowy, ale nieco głupi przykład, który obejmuje wiele funkcji powłoki.
cmd = '''while read -r x;
do ping -c 3 "$x" | grep 'round-trip min/avg/max'
done <hosts.txt'''
# Trivial but horrible
results = subprocess.run(
cmd, shell=True, universal_newlines=True, check=True)
print(results.stdout)
# Reimplement with shell=False
with open('hosts.txt') as hosts:
for host in hosts:
host = host.rstrip('\n') # drop newline
ping = subprocess.run(
['ping', '-c', '3', host],
text=True,
stdout=subprocess.PIPE,
check=True)
for line in ping.stdout.split('\n'):
if 'round-trip min/avg/max' in line:
print('{}: {}'.format(host, line))
Kilka rzeczy do zapamiętania tutaj:
- Dzięki
shell=False
nie potrzebujesz cytowania, które powłoka wymaga wokół łańcuchów. W każdym razie wpisywanie cytatów jest prawdopodobnie błędem.
- Często ma sens uruchamianie możliwie najmniejszej ilości kodu w podprocesie. Zapewnia to większą kontrolę nad wykonywaniem z poziomu kodu Pythona.
- To powiedziawszy, skomplikowane potoki powłoki są żmudne i czasem trudne do ponownego wdrożenia w Pythonie.
Refaktoryzowany kod pokazuje również, jak bardzo powłoka naprawdę robi dla ciebie za pomocą bardzo zwięzłej składni - na lepsze lub gorsze. Python twierdzi, że jawne jest lepsze niż niejawne, ale kod w Pythonie jest dość szczegółowy i prawdopodobnie wygląda na bardziej złożony niż w rzeczywistości. Z drugiej strony oferuje szereg punktów, w których można przejąć kontrolę nad czymś innym, co w trywialny sposób ilustruje ulepszenie, które możemy łatwo dołączyć nazwę hosta wraz z wyjściem polecenia powłoki. (Nie jest to wcale trudne do wykonania w powłoce, ale kosztem kolejnej zmiany i być może innego procesu).
Typowe konstrukcje powłokowe
Dla kompletności, oto krótkie objaśnienia niektórych z tych funkcji powłoki oraz kilka uwag, w jaki sposób można je zastąpić rodzimymi funkcjami języka Python.
- Globbing, czyli rozszerzenie z symbolami wieloznacznymi, można zastąpić
glob.glob()
prostymi ciągami Python, takimi jak bardzo często for file in os.listdir('.'): if not file.endswith('.png'): continue
. Bash ma różne inne funkcje rozszerzania, takie jak .{png,jpg}
rozwijanie nawiasów klamrowych, a {1..100}
także rozszerzanie tyldy ( ~
rozwija się do katalogu domowego, a bardziej ogólnie ~account
do katalogu domowego innego użytkownika)
- Zmienne powłoki takie jak
$SHELL
lub $my_exported_var
czasami mogą być po prostu zastąpione zmiennymi Python. Wyeksportowane zmienne powłoki są dostępne jako np. os.environ['SHELL']
(Oznacza export
to, że zmienna jest dostępna dla podprocesów - zmienna, która nie jest dostępna dla podprocesów, oczywiście nie będzie dostępna dla Pythona działającego jako podproces powłoki lub odwrotnie. Słowo env=
kluczowe argument do subprocess
metod pozwala zdefiniować środowisko podprocesu jako słownik, więc jest to jeden ze sposobów, aby zmienna Python była widoczna dla podprocesu). Z shell=False
musisz zrozumieć, jak usunąć wszelkie cytaty; na przykład cd "$HOME"
odpowiada os.chdir(os.environ['HOME'])
bez cudzysłowów wokół nazwy katalogu. (Bardzo częstocd
i tak nie jest użyteczne ani konieczne, a wielu początkujących pomija podwójne cudzysłowy wokół zmiennej i unika jej do pewnego dnia ... )
- Przekierowanie pozwala na odczyt z pliku jako standardowego wejścia i zapisanie standardowego wyjścia do pliku.
grep 'foo' <inputfile >outputfile
otwiera się outputfile
do pisania i inputfile
do czytania oraz przekazuje jego zawartość jako standardowe wejście, do grep
którego następnie trafia standardowe wyjście outputfile
. Zasadniczo nie jest to trudne do zastąpienia rodzimym kodem Python.
- Rurociągi są formą przekierowania.
echo foo | nl
uruchamia dwa podprocesy, w których standardowe wyjście echo
jest standardowym wejściem nl
(na poziomie systemu operacyjnego, w systemach uniksopodobnych jest to uchwyt pojedynczego pliku). Jeśli nie możesz zastąpić jednego lub obu końców potoku rodzimym kodem Python, być może pomyśl o użyciu powłoki, zwłaszcza jeśli potok ma więcej niż dwa lub trzy procesy (spójrz na pipes
moduł w standardowej bibliotece Python lub liczbę bardziej nowoczesnych i wszechstronnych konkurentów zewnętrznych).
- Kontrola zadań pozwala przerywać zadania, uruchamiać je w tle, przywracać na pierwszy plan itp. Podstawowe sygnały uniksowe do zatrzymania i kontynuowania procesu są oczywiście również dostępne w Pythonie. Ale zadania są abstrakcją wyższego poziomu w powłoce, która obejmuje grupy procesów itp., Którą musisz zrozumieć, jeśli chcesz zrobić coś takiego z Pythona.
- Cytowanie w powłoce może być mylące, dopóki nie zrozumiesz, że wszystko jest w zasadzie łańcuchem. Więc
ls -l /
jest równoważna 'ls' '-l' '/'
ale cytowania wokół literały jest całkowicie opcjonalne. Niecytowane łańcuchy zawierające metaznaki powłoki podlegają interpretacji parametrów, tokenizacji białych znaków i interpretacji symboli wieloznacznych; podwójne cudzysłowy zapobiegają tokenizacji białych znaków i interpretacji symboli wieloznacznych, ale umożliwiają rozszerzenie parametrów (podstawianie zmiennych, podstawianie poleceń i przetwarzanie odwrotnego ukośnika). Teoretycznie jest to proste, ale może być oszałamiające, szczególnie gdy istnieje kilka warstw interpretacji (na przykład polecenie zdalnej powłoki).
Poznaj różnice między sh
i Bash
subprocess
uruchamia polecenia powłoki, /bin/sh
chyba że wyraźnie zażądasz inaczej (z wyjątkiem oczywiście systemu Windows, w którym używa wartości COMSPEC
zmiennej). Oznacza to, że różne funkcje dostępne tylko w Bash, takie jak tablice [[
itp., Nie są dostępne.
Jeśli potrzebujesz użyć tylko składni Bash, możesz przekazać ścieżkę do powłoki jako executable='/bin/bash'
(oczywiście jeśli Bash jest zainstalowany gdzie indziej, musisz dostosować ścieżkę).
subprocess.run('''
# This for loop syntax is Bash only
for((i=1;i<=$#;i++)); do
# Arrays are Bash-only
array[i]+=123
done''',
shell=True, check=True,
executable='/bin/bash')
A subprocess
jest oddzielne od swojego rodzica i nie może go zmienić
Nieco powszechnym błędem jest robienie czegoś takiego
subprocess.run('foo=bar', shell=True)
subprocess.run('echo "$foo"', shell=True) # Doesn't work
który oprócz braku elegancji zdradza także fundamentalny brak zrozumienia „pod” części nazwy „podproces”.
Proces potomny działa całkowicie niezależnie od Pythona, a kiedy się kończy, Python nie ma pojęcia, co zrobił (oprócz niejasnych wskaźników, które może wywnioskować ze statusu wyjścia i wyniku procesu potomnego). Dziecko zasadniczo nie może zmienić środowiska rodzica; nie może ustawić zmiennej, zmienić katalogu roboczego lub, innymi słowy, komunikować się z rodzicem bez współpracy z rodzicem.
Natychmiastową poprawką w tym konkretnym przypadku jest uruchomienie obu poleceń w jednym podprocesie;
subprocess.run('foo=bar; echo "$foo"', shell=True)
choć oczywiście ten szczególny przypadek użycia wcale nie wymaga powłoki. Pamiętaj, że możesz manipulować środowiskiem bieżącego procesu (a więc także jego elementów podrzędnych) za pośrednictwem
os.environ['foo'] = 'bar'
lub przekaż ustawienie środowiska procesowi potomnemu za pomocą
subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})
(nie wspominając o oczywistym refaktoryzacji subprocess.run(['echo', 'bar'])
; ale echo
jest to kiepski przykład czegoś, co można uruchomić w podprocesie, oczywiście).
Nie uruchamiaj Pythona z Pythona
To jest nieco wątpliwa rada; z pewnością istnieją sytuacje, w których ma to sens lub jest absolutnym wymogiem do uruchomienia interpretera Pythona jako podprocesu ze skryptu Python. Ale bardzo często poprawnym podejściem jest po prostu import
inny moduł Python w skrypcie wywołującym i wywoływanie jego funkcji bezpośrednio.
Jeśli drugi skrypt Pythona jest pod twoją kontrolą i nie jest to moduł, rozważ przekształcenie go w jeden . (Ta odpowiedź jest już za długa, więc nie będę tutaj zagłębiał się w szczegóły).
Jeśli potrzebujesz równoległości, możesz uruchamiać funkcje Pythona w podprocesach z multiprocessing
modułem. Istnieje również threading
funkcja, która uruchamia wiele zadań w jednym procesie (co jest lżejsze i daje większą kontrolę, ale także bardziej ograniczone, ponieważ wątki w procesie są ściśle powiązane i powiązane z jednym GIL .)
cwm
. Może masz jakąś konfigurację,.bashrc
która konfiguruje środowisko do interaktywnego korzystania z bash?