Uruchamianie poleceń Bash w Pythonie

299

Na moim komputerze lokalnym uruchamiam skrypt Pythona, który zawiera tę linię

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
os.system(bashCommand)

To działa dobrze.

Następnie uruchamiam ten sam kod na serwerze i pojawia się następujący komunikat o błędzie

'import site' failed; use -v for traceback
Traceback (most recent call last):
File "/usr/bin/cwm", line 48, in <module>
from swap import  diag
ImportError: No module named swap

Więc to, co zrobiłem, to wstawienie, print bashCommandktóre wypisuje mnie niż polecenie w terminalu przed uruchomieniem go os.system().

Oczywiście otrzymuję ponownie błąd (spowodowany przez os.system(bashCommand)), ale przed tym błędem wypisuje polecenie w terminalu. Potem właśnie skopiowałem to wyjście i wkleiłem kopię do terminala i wcisnąłem enter i działa ...

Czy ktoś ma pojęcie, co się dzieje?

mkn
źródło
2
Wygląda na to, że środowisko różni się w zależności od sposobu biegania cwm. Może masz jakąś konfigurację, .bashrcktóra konfiguruje środowisko do interaktywnego korzystania z bash?
Sven Marnach
Czy próbowałeś uruchomić polecenie z wiersza polecenia, gdy jesteś zalogowany na serwerze? Twój post mówi tylko, że „wkleiłeś [it] do terminala”.
Sven Marnach,
@ Sven: tak miałem na myśli, że uruchomiłem polecenie bezpośrednio w terminalu serwera
mkn
Wydaje się, że istnieje różnica w PYTHONPATH w zależności od sposobu biegania cwm. A może istnieje PATH, a cwmnazywa się inną wersję . Lub różne wersje Pythona. Naprawdę trudno to rozgryźć bez dostępu do maszyny ...
Sven Marnach,

Odpowiedzi:

314

Nie używać os.system. Zostało wycofane na korzyść podprocesu . Od docs : „Moduł ten ma na celu zastąpić kilka starsze moduły i funkcje: os.system, os.spawn”.

Jak w twoim przypadku:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
import subprocess
process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
output, error = process.communicate()
użytkownik225312
źródło
8
Nie zrobiłem tego, co chciałem, kiedy musiałem wykonać cd 'path\to\somewhere'kolejną komendę bash, która musiała gdzieś tam zostać uruchomiona. @ user225312
AWrightIV
36
@AWrightIV Jeśli chcesz, aby Twój podproces był uruchamiany w określonym katalogu roboczym, możesz użyć cwdargumentu do Popena:subprocess.Popen(..., cwd='path\to\somewhere')
wodoodporny
7
Do mojej komendy potrzebowałem shell = True jak tutaj; stackoverflow.com/questions/18962785/…
user984003
4
Lepiej użyj shlex.split () zamiast string.split () w tym przypadku
Alexey Sviridov
4
... ( stdout=filew tym przypadku przekierowuje dane wyjściowe do pliku. Implementuje > file). Błędem byłoby przekazać ..., '>', 'file']ostatnie polecenie oczekujące przekierowania (nie będzie działać bez powłoki, a jeśli używasz powłoki, należy przekazać polecenie jako ciąg znaków)
jfs
186

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 .)

potrójny
źródło
2
Aby uzyskać bardziej szczegółowe objaśnienie, w jaki sposób można uniknąć wywoływania Pythona jako podprocesu, zobacz tę odpowiedź na stycznie podobne pytanie.
tripleee
4
dręczy mnie myśl, że musiałem opublikować nową odpowiedź na tak podstawowe pytanie, aby pokazać, jak idiomatycznie uruchomić polecenie z pytania. Twoja odpowiedź jest długa, ale nie widzę takiego przykładu. Niepowiązane: unikaj kultywowania ładunków. Jeśli check_call () działa w twoim przypadku, użyj go. Musiałem naprawić kod, który był używany na run()ślepo. Zaginione check=Truespowodował błąd, który można by uniknąć gdyby check_call użyto - „check” jest w nazwie, nie można stracić -jest prawidłowe domyślnie: nie ignoruj błędy cicho. Nie czytałem dalej.
jfs
1
@jfs Dzięki za opinie, w rzeczywistości planowałem dodać sekcję o Bash vs, shale pobiłeś mnie. Staram się opisać szczegóły wystarczająco szczegółowo, aby pomóc początkującym, dla których te pułapki nie są oczywiste, więc staje się to nieco opóźnione. W przeciwnym razie twoje powinno być wystarczające; +1
potrójny
Czy narzuca stderr/stdout = subprocess.PIPEwyższą wydajność niż ustawienia domyślne?
Stringers
1
@ Stringers Nie testowałem, ale nie rozumiem, dlaczego tak powinno być. Jeśli podłączysz te potoki do czegoś, co wykonuje pewne przetwarzanie, wtedy oczywiście należy to uwzględnić; ale nie dzieje się to w samej rurze. Domyślnie nie przechwytuje stdout ani stderr, tzn. Cokolwiek zostanie wydrukowane, nie jest widoczne i kontrolowane przez Pythona, tak jak w przypadku os.system().
tripleee
41

Nazwij to podprocesem

import subprocess
subprocess.Popen("cwm --rdf test.rdf --ntriples > test.nt")

Wydaje się, że pojawia się błąd, ponieważ na serwerze nie ma modułu wymiany, należy zainstalować swap na serwerze, a następnie ponownie uruchomić skrypt

Jakob Bowyer
źródło
3
swapModuł jest oczywiście tam, bo uruchamiając polecenie pracach powłoki.
Sven Marnach
2
Nie na serwerze, gdy uruchamia go na serwerze, występuje błąd importu.
Jakob Bowyer
@mkn: „Więc właśnie skopiowałem to wyjście i skopiowałem kopię do terminala i nacisnąłem enter i działa ...” - Czy próbowałeś tego na serwerze lub na komputerze?
Sven Marnach
Czy to działa na samodzielnym komputerze w porządku, ale nie działa po uruchomieniu na serwerze? Czy jesteś w stanie uruchomić go na terminalu serwera, ale nie na samym serwerze
Jakob Bowyer
1
jest źle Jeśli nie używasz shell=True, powinieneś użyć listy do przekazania wielu argumentów, tzn. użyj ['a', 'b', 'c']zamiast 'a b c'. Chociaż podział naiwny nie zadziała z powodu > file(przekierowania powłoki) w poleceniu. Więcej szczegółów
jfs
18

Możliwe jest użycie programu bash z parametrem -c do wykonania poleceń:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
output = subprocess.check_output(['bash','-c', bashCommand])
Brzytwa
źródło
2
subprocess.check_output(bashCommand, shell=True)robi to samo. Jeśli twoje polecenie jest ciągiem statycznym, spróbuj parsować go na liście i unikaj shell=True; chociaż w tym przypadku i tak potrzebujesz powłoki do przekierowania, albo będziesz musiał with open('test.nt', 'w') as dest: output = subprocess.check_output(['cwm' ,'--rdf', 'test.rdf', '--ntriples'], stdout=dest, shell=False)
przefakturować
@tripleee note: /bin/sh(używane przez podproces) niekoniecznie bash(nie możesz używać bashism). Chociaż można użyć w executable='/bin/bashrazie potrzeby. Oto przykładowy kod
jfs
2
jest to pierwsza odpowiedź, w której polecenie powinno rozpocząć się pomyślnie (przyjęte i 2 popularne odpowiedzi są po prostu złe. Drobna sprzeczka: check_output()tutaj jest bezużyteczna (wynik jest zawsze pusty z powodu > fileprzekierowania; użyj check_call()zamiast tego
jfs
16

Możesz użyć subprocess, ale zawsze czułem, że nie był to „Python” sposób. Stworzyłem więc Sultan (bezwstydna wtyczka), która ułatwia uruchamianie funkcji wiersza poleceń.

https://github.com/aeroxis/sultan

David Daniel
źródło
3
Dobra robota! Znacznie czystszy i bardziej intuicyjny niż podproces.
mjd2
Dziękuję bardzo! Cieszę się, że to słyszę!
David Daniel
2
Powinno to być szczerze przyjęte w standardowej bibliotece.
Joshua Detwiler
1
Czy istnieje sposób na przechwycenie danych wyjściowych z terminala za pomocą Sultana?
alvas
Tak można @alvas ... Oto docs, jak to zrobić: sultan.readthedocs.io/en/latest/...
David Daniel
7

Zgodnie z błędem brakuje pakietu o nazwie swap na serwerze. To /usr/bin/cwmwymaga tego. Jeśli korzystasz z systemu Ubuntu / Debian, zainstaluj python-swapza pomocą aptitude.

kichik
źródło
ale działa, kiedy uruchamiam go bezpośrednio w terminalu ... więc wymiana musi tam być, nie?
mkn
są dwie opcje. albo nie może go znaleźć, swapalbo nie powinien go zaimportować. czy potrafisz import swapręcznie Czy to działa?
kichik
hm nie mogę. Jeśli zacznę python od wpisania pytona w terminalu, a następnie napiszę import swap, wtedy pojawia się błąd „ImportError: Brak modułu o nazwie swap”. Dziwne jest to, że nadal działa po uruchomieniu polecenia CWM bezpośrednio w terminalu serwera
MKN
Spróbuj wydrukować sys.pathtam, gdzie działa, a gdzie nie. Następnie spróbuj poszukać folderu wymiany lub swap.py w wydrukowanych folderach. Jak powiedział Sven, może istnieć problem z tymi ścieżkami, a to pomoże ci to rozgryźć.
kichik
4

Możesz także użyć „os.popen”. Przykład:

import os

command = os.popen('ls -al')
print(command.read())
print(command.close())

Wynik:

total 16
drwxr-xr-x 2 root root 4096 ago 13 21:53 .
drwxr-xr-x 4 root root 4096 ago 13 01:50 ..
-rw-r--r-- 1 root root 1278 ago 13 21:12 bot.py
-rw-r--r-- 1 root root   77 ago 13 21:53 test.py

None
ricardo130
źródło
1
Dokumentacja zawiera duże czerwone pole: Przestarzałe od wersji 2.6: Ta funkcja jest przestarzała. Użyj subprocessmodułu”.
tripleee
1
Szczerze mówiąc, os.popennie ma już tego ostrzeżenia, a teraz jest po prostu cienkim opakowaniem subprocess.Popen().
tripleee
4

Aby uruchomić polecenie bez powłoki, przekaż polecenie jako listę i zaimplementuj przekierowanie w Pythonie, używając [subprocess]:

#!/usr/bin/env python
import subprocess

with open('test.nt', 'wb', 0) as file:
    subprocess.check_call("cwm --rdf test.rdf --ntriples".split(),
                          stdout=file)

Uwaga: nie > test.ntna końcu. stdout=filerealizuje przekierowanie.


Aby uruchomić polecenie za pomocą powłoki w Pythonie, przekaż polecenie jako ciąg znaków i włącz shell=True:

#!/usr/bin/env python
import subprocess

subprocess.check_call("cwm --rdf test.rdf --ntriples > test.nt",
                      shell=True)

Oto powłoka jest odpowiedzialna za przekierowanie wyjścia ( > test.ntznajduje się w poleceniu).


Aby uruchomić polecenie bash korzystające z bashism, jawnie określ plik wykonywalny bash, np. W celu emulacji podstawienia procesu bash :

#!/usr/bin/env python
import subprocess

subprocess.check_call('program <(command) <(another-command)',
                      shell=True, executable='/bin/bash')
jfs
źródło
Być może wspomnieć, że .split()nie jest to odpowiednie, gdy występują cytowane ciągi itp. Istnieje osobna procedura, shlex.split()która radzi sobie z dowolnie złożoną składnią powłoki.
tripleee
@tripleee .split()działa w tym przypadku. shlex.split()może być czasem użyteczny, ale w niektórych przypadkach może się nie powieść. Można wymienić wiele rzeczy. Możesz zacząć od linku do opisu znacznika podprocesu podanego powyżej.
jfs
0

Pythonicznym sposobem na to jest użycie subprocess.Popen

subprocess.Popen pobiera listę, w której pierwszym elementem jest uruchamiane polecenie, a następnie dowolne argumenty wiersza polecenia.

Jako przykład:

import subprocess

args = ['echo', 'Hello!']
subprocess.Popen(args) // same as running `echo Hello!` on cmd line

args2 = ['echo', '-v', '"Hello Again"']
subprocess.Popen(args2) // same as running 'echo -v "Hello Again!"` on cmd line
RewordedAnswers
źródło
Nie, ostatni przykład jest taki sam, jak bieganie echo -v '"Hello Again!"'z pojedynczymi cudzysłowami wokół podwójnych cudzysłowów.
tripleee
Ponadto, aby poprawnie użyć subprocesss.Popen, musisz zarządzać wynikowym obiektem procesu (przynajmniej wykonaj a, wait()aby zapobiec przekształceniu go w proces zombie).
tripleee