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=Trueczy shell=Falsei jak zmienia cytowanie i dostępności udogodnień powłoki.
- Poznaj różnice między
shi 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 Popeninterfejs 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 subprocessmodule ( 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 CompletedProcessobiekt, 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 Popenobiekt 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 osdokumentacji moduł zawierał zalecenie preferowany subprocessprzez os.system():
subprocessModuł 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.systemjest problematyczne i jak subprocesspró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 subprocessmodułu.
Jednak od pewnego czasu w Pythonie 3 został ponownie zaimplementowany, aby po prostu używać subprocessi 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=Truez, subprocess.run()chyba że specjalnie musisz zezwolić podprocesowi na zwrócenie statusu błędu.
W praktyce za pomocą check=Truelub subprocess.check_*Python zgłasza CalledProcessErrorwyjątek, jeśli podproces zwraca niezerowy status wyjścia.
Częstym błędem subprocess.run()jest pomijanie check=Truei 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 grepnie znaleźli dopasowania. (Prawdopodobnie i tak powinieneś zastąpić grepnatywnym 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=Trueakauniversal_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ć bytesbufor 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=Truemó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 stdouti 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 textargumentu słowa kluczowego, który wcześniej był nieco myląco nazywany universal_newlines.
Zrozum shell=Truevsshell=False
Gdy shell=Trueprzekażesz pojedynczy ciąg do powłoki, a powłoka zabierze go stamtąd.
Gdy shell=Falseprzekaż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=Truea 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 sedskrypty 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=Falsenie 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 ~accountdo katalogu domowego innego użytkownika)
- Zmienne powłoki takie jak
$SHELLlub $my_exported_varczasami mogą być po prostu zastąpione zmiennymi Python. Wyeksportowane zmienne powłoki są dostępne jako np. os.environ['SHELL'](Oznacza exportto, ż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 subprocessmetod 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=Falsemusisz 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ęstocdi 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 >outputfileotwiera się outputfiledo pisania i inputfiledo czytania oraz przekazuje jego zawartość jako standardowe wejście, do grepktó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 | nluruchamia dwa podprocesy, w których standardowe wyjście echojest 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 pipesmoduł 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 shi Bash
subprocessuruchamia polecenia powłoki, /bin/shchyba że wyraźnie zażądasz inaczej (z wyjątkiem oczywiście systemu Windows, w którym używa wartości COMSPECzmiennej). 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 subprocessjest 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 echojest 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 importinny 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 multiprocessingmodułem. Istnieje również threadingfunkcja, 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ę,.bashrcktóra konfiguruje środowisko do interaktywnego korzystania z bash?