Ausführen von Bash-Befehlen in Python

299

Auf meinem lokalen Computer führe ich ein Python-Skript aus, das diese Zeile enthält

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

Das funktioniert gut.

Dann führe ich denselben Code auf einem Server aus und erhalte die folgende Fehlermeldung

'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

Also habe ich dann ein eingefügt, print bashCommanddas mich als den Befehl im Terminal druckt, bevor es ausgeführt wirdos.system() .

Natürlich bekomme ich wieder den Fehler (verursacht durch os.system(bashCommand) ), aber vor diesem Fehler wird der Befehl im Terminal gedruckt. Dann habe ich einfach diese Ausgabe kopiert und eine Kopie in das Terminal eingefügt und die Eingabetaste gedrückt und es funktioniert ...

Hat jemand eine Ahnung, was los ist?

mkn
quelle
2
Es scheint einen Unterschied in der Umgebung zu geben, je nachdem, wie Sie ausgeführt werden cwm. Vielleicht haben Sie eine Konfiguration in Ihrer .bashrc, die die Umgebung für die interaktive Bash-Verwendung einrichtet?
Sven Marnach
Haben Sie versucht, den Befehl über die Befehlszeile auszuführen, als Sie am Server angemeldet waren? Ihr Beitrag sagt nur, dass Sie "es in das Terminal eingefügt haben".
Sven Marnach
@Sven: Ja, ich meinte, dass ich den Befehl direkt im Terminal des Servers ausgeführt habe
mkn
Es scheint einen Unterschied im PYTHONPATH zu geben, je nachdem, wie Sie laufen cwm. Oder vielleicht gibt es einen Unterschied in PATH und verschiedene Versionen von cwmwerden aufgerufen. Oder verschiedene Versionen von Python. Es ist wirklich schwer, dies ohne Zugang zur Maschine herauszufinden ...
Sven Marnach

Antworten:

314

Nicht benutzen os.system. Es wurde zugunsten des Teilprozesses abgelehnt . Aus der docs : „Dieses Modul soll mehrere älteren Module und Funktionen ersetzen: os.system,os.spawn “.

Wie in Ihrem Fall:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
import subprocess
process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
output, error = process.communicate()
user225312
quelle
8
Dies tat nicht das, was ich wollte, als ich cd 'path\to\somewhere'einen Befehl ausführen musste, gefolgt von einem anderen Bash-Befehl, der irgendwo ausgeführt werden musste. @ user225312
AWrightIV
36
@AWrightIV Wenn Sie möchten, dass Ihr Unterprozess in einem bestimmten Arbeitsverzeichnis ausgeführt wird, können Sie das cwdArgument für Popen verwenden:subprocess.Popen(..., cwd='path\to\somewhere')
wasserdicht
7
Für meinen Befehl brauchte ich shell = True wie hier; stackoverflow.com/questions/18962785/…
user984003
4
In diesem Fall ist es besser, shlex.split () anstelle von string.split () zu verwenden
Alexey Sviridov,
4
... ( stdout=fileleitet die Ausgabe in diesem Fall in eine Datei um. Sie wird implementiert > file). Es wäre falsch, ..., '>', 'file']den letzten Befehl zu übergeben, der die Umleitung erwartet (es funktioniert nicht ohne eine Shell, und wenn Sie eine Shell verwenden, sollten Sie den Befehl als Zeichenfolge übergeben)
jfs
186

Um die früheren Antworten hier etwas zu erweitern, gibt es eine Reihe von Details, die häufig übersehen werden.

  • Lieber subprocess.run()über subprocess.check_call()und Freunde subprocess.call()über subprocess.Popen()über os.system()überos.popen()
  • Verstehe und benutze es wahrscheinlich text=Trueauch universal_newlines=True.
  • Verstehe die Bedeutung von shell=Trueodershell=False und wie sich das Angebot ändert, und die Verfügbarkeit von Shell-Annehmlichkeiten.
  • Unterschiede zwischen verstehen sh und Bash
  • Verstehen Sie, wie ein Unterprozess von seinem übergeordneten Prozess getrennt ist und den übergeordneten Prozess im Allgemeinen nicht ändern kann.
  • Vermeiden Sie es, den Python-Interpreter als Unterprozess von Python auszuführen.

Diese Themen werden im Folgenden ausführlicher behandelt.

Bevorzugen subprocess.run() odersubprocess.check_call()

Das subprocess.Popen() Funktion ist ein Arbeitspferd auf niedriger Ebene, aber es ist schwierig, sie richtig zu verwenden, und Sie kopieren / fügen am Ende mehrere Codezeilen ein ... die bequemerweise bereits in der Standardbibliothek als Satz von Wrapper-Funktionen auf höherer Ebene für verschiedene Zwecke vorhanden sind. die im Folgenden näher erläutert werden.

Hier ist ein Absatz aus der Dokumentation :

Der empfohlene Ansatz zum Aufrufen von Unterprozessen besteht darin, die run()Funktion für alle Anwendungsfälle zu verwenden, die sie verarbeiten kann. Für fortgeschrittenere Anwendungsfälle kann die zugrunde liegende PopenSchnittstelle direkt verwendet werden.

Leider unterscheidet sich die Verfügbarkeit dieser Wrapper-Funktionen zwischen den Python-Versionen.

  • subprocess.run()wurde offiziell in Python 3.5 eingeführt. Es soll alle folgenden ersetzen.
  • subprocess.check_output()wurde in Python 2.7 / 3.1 eingeführt. Es ist im Grunde gleichbedeutend mitsubprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
  • subprocess.check_call()wurde in Python 2.5 eingeführt. Es ist im Grunde gleichbedeutend mitsubprocess.run(..., check=True)
  • subprocess.call()wurde in Python 2.4 im Originalmodul subprocess( PEP-324 ) eingeführt. Es ist im Grunde gleichbedeutend mitsubprocess.run(...).returncode

High-Level-API vs. subprocess.Popen()

Das überarbeitete und erweiterte System subprocess.run()ist logischer und vielseitiger als die älteren Legacy-Funktionen, die es ersetzt. Es gibt ein CompletedProcessObjekt mit verschiedenen Methoden zurück, mit denen Sie den Exit-Status, die Standardausgabe und einige andere Ergebnisse und Statusindikatoren aus dem fertigen Unterprozess abrufen können.

subprocess.run()Dies ist der richtige Weg, wenn Sie lediglich ein Programm benötigen, um die Steuerung auszuführen und an Python zurückzugeben. Für komplexere Szenarien (Hintergrundprozesse, möglicherweise mit interaktiver E / A mit dem übergeordneten Python-Programm) müssen Sie weiterhin subprocess.Popen()alle Installationen selbst verwenden und pflegen. Dies erfordert ein ziemlich kompliziertes Verständnis aller beweglichen Teile und sollte nicht leichtfertig durchgeführt werden. Das einfachere PopenObjekt stellt den (möglicherweise noch laufenden) Prozess dar, der für den Rest der Lebensdauer des Unterprozesses aus Ihrem Code verwaltet werden muss.

Es sollte vielleicht betont werden, dass nur subprocess.Popen()ein Prozess entsteht. Wenn Sie es dabei belassen, wird neben Python gleichzeitig ein Unterprozess ausgeführt, also ein "Hintergrund" -Prozess. Wenn es keine Eingabe oder Ausgabe oder anderweitige Koordinierung mit Ihnen vornehmen muss, kann es nützliche Arbeit parallel zu Ihrem Python-Programm leisten.

Vermeiden Sie os.system()undos.popen()

Seit ewiger Zeit (na ja, da Python 2.5) die osModuldokumentation hat die Empfehlung enthalten ist, bevorzugen subprocessüber os.system():

Das subprocessModul bietet leistungsfähigere Funktionen zum Laichen neuer Prozesse und zum Abrufen ihrer Ergebnisse. Die Verwendung dieses Moduls ist der Verwendung dieser Funktion vorzuziehen.

Die Probleme dabei system()sind, dass es offensichtlich systemabhängig ist und keine Möglichkeiten zur Interaktion mit dem Unterprozess bietet. Es wird einfach ausgeführt, wobei die Standardausgabe und der Standardfehler außerhalb der Reichweite von Python liegen. Die einzige Information, die Python zurückerhält, ist der Exit-Status des Befehls (Null bedeutet Erfolg, obwohl die Bedeutung von Nicht-Null-Werten auch etwas systemabhängig ist).

PEP-324 (das bereits oben erwähnt wurde) enthält eine detailliertere Begründung dafür, warum dies os.systemproblematisch ist und wie subprocessversucht wird, diese Probleme zu lösen.

os.popen()war früher noch stärker entmutigt :

Veraltet seit Version 2.6: Diese Funktion ist veraltet. Verwenden Sie das subprocessModul.

Seit einiger Zeit in Python 3 wurde es jedoch neu implementiert, um es einfach zu verwenden subprocess, und leitet zur subprocess.Popen()Dokumentation weiter, um weitere Informationen zu erhalten.

Verstehe und benutze es normalerweise check=True

Sie werden auch feststellen, dass subprocess.call()es viele der gleichen Einschränkungen gibt wie os.system(). Bei regelmäßiger Verwendung sollten Sie im Allgemeinen prüfen, ob der Prozess erfolgreich abgeschlossen wurde, was subprocess.check_call()und subprocess.check_output()was (wobei letzterer auch die Standardausgabe des fertigen Unterprozesses zurückgibt). In ähnlicher Weise sollten Sie normalerweise check=Truemit verwenden, es subprocess.run()sei denn, Sie müssen dem Unterprozess ausdrücklich erlauben, einen Fehlerstatus zurückzugeben.

In der Praxis löst Python mit check=Trueoder subprocess.check_*eine CalledProcessErrorAusnahme aus, wenn der Unterprozess einen Exit-Status ungleich Null zurückgibt.

Ein häufiger Fehler subprocess.run()besteht darin, wegzulassen check=Trueund überrascht zu sein, wenn Downstream-Code fehlschlägt, wenn der Unterprozess fehlschlägt.

Andererseits bestand ein häufiges Problem mit check_call()und darin, check_output()dass Benutzer, die diese Funktionen blind verwendeten, überrascht waren, als die Ausnahme ausgelöst wurde, z. B. wenn grepkeine Übereinstimmung gefunden wurde. (Sie sollten wahrscheinlich greptrotzdem durch nativen Python-Code ersetzen , wie unten beschrieben.)

Alles in allem müssen Sie verstehen, wie Shell-Befehle einen Exit-Code zurückgeben und unter welchen Bedingungen sie einen Exit-Code ungleich Null (Fehler) zurückgeben, und eine bewusste Entscheidung treffen, wie genau damit umgegangen werden soll.

Verstehe und benutze wahrscheinlich text=Trueakauniversal_newlines=True

Seit Python 3 sind in Python interne Zeichenfolgen Unicode-Zeichenfolgen. Es gibt jedoch keine Garantie dafür, dass ein Unterprozess eine Unicode-Ausgabe oder überhaupt Zeichenfolgen generiert.

(Wenn die Unterschiede nicht sofort offensichtlich sind, wird das Lesen von Pragmatic Unicode von Ned Batchelder empfohlen, wenn nicht sogar obligatorisch. Wenn Sie es vorziehen, befindet sich hinter dem Link eine 36-minütige Videopräsentation, obwohl das Lesen der Seite selbst wahrscheinlich erheblich weniger Zeit in Anspruch nimmt. )

Tief im Inneren muss Python einen bytesPuffer holen und ihn irgendwie interpretieren. Wenn es einen Blob von Binärdaten enthält, sollte es nicht in eine Unicode-Zeichenfolge dekodiert werden, da dies ein fehleranfälliges und fehlerauslösendes Verhalten ist - genau die Art von lästigem Verhalten, das viele Python 2-Skripte durcheinander brachte, bevor es einen Weg dazu gab richtig zwischen codiertem Text und Binärdaten unterscheiden.

Mit text=Trueteilen Sie Python mit, dass Sie tatsächlich Textdaten in der Standardcodierung des Systems zurückerwarten und dass diese nach besten Python-Fähigkeiten in eine Python-Zeichenfolge (Unicode) dekodiert werden sollten (normalerweise UTF-8 bei mäßig bis zu Datumssystem, außer vielleicht Windows?)

Wenn Sie dies nicht zurückfordern, gibt Python Ihnen nur bytesZeichenfolgen in den Zeichenfolgen stdoutund stderr. An einigen Vielleicht zeigen Sie später noch wissen , dass sie Text - Strings , nachdem alle waren, und Sie wissen , ihre Codierung. Dann können Sie sie dekodieren.

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

Python 3.7 führte den kürzeren, aussagekräftigeren und verständlicheren Alias textfür das Keyword-Argument ein, das zuvor etwas irreführend genannt wurde universal_newlines.

Verstehe shell=Truevs.shell=False

Übergeben shell=TrueSie eine einzelne Zeichenfolge an Ihre Shell, und die Shell übernimmt sie von dort.

Wenn shell=FalseSie eine Liste von Argumenten an das Betriebssystem übergeben, umgehen Sie die Shell.

Wenn Sie keine Shell haben, speichern Sie einen Prozess und beseitigen eine ziemlich große Menge an versteckter Komplexität, die Fehler oder sogar Sicherheitsprobleme enthalten kann oder nicht.

Wenn Sie jedoch keine Shell haben, verfügen Sie nicht über Umleitung, Platzhaltererweiterung, Jobsteuerung und eine große Anzahl anderer Shell-Funktionen.

Ein häufiger Fehler besteht darin shell=True, Python eine Liste von Token zu verwenden und diese dann weiterzuleiten oder umgekehrt. Dies funktioniert in einigen Fällen, ist jedoch sehr schlecht definiert und kann auf interessante Weise brechen.

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

Die übliche Erwiderung "aber es funktioniert für mich" ist keine nützliche Gegenargumentation, es sei denn, Sie verstehen genau, unter welchen Umständen sie nicht mehr funktionieren könnte.

Refactoring-Beispiel

Sehr oft können die Funktionen der Shell durch nativen Python-Code ersetzt werden. Simple Awk oder sedSkripte sollten wahrscheinlich stattdessen einfach in Python übersetzt werden.

Um dies teilweise zu veranschaulichen, ist hier ein typisches, aber etwas albernes Beispiel, das viele Shell-Merkmale beinhaltet.

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

Einige Dinge, die hier zu beachten sind:

  • Mit shell=Falsebrauchen Sie nicht das Zitat, das die Shell um Strings benötigt. Das Setzen von Anführungszeichen ist wahrscheinlich ein Fehler.
  • Oft ist es sinnvoll, in einem Unterprozess so wenig Code wie möglich auszuführen. Dies gibt Ihnen mehr Kontrolle über die Ausführung innerhalb Ihres Python-Codes.
  • Allerdings sind komplexe Shell-Pipelines langwierig und manchmal schwierig in Python neu zu implementieren.

Der überarbeitete Code zeigt auch, wie viel die Shell mit einer sehr knappen Syntax wirklich für Sie tut - zum Guten oder zum Schlechten. Python sagt, explizit ist besser als implizit, aber der Python-Code ist ziemlich ausführlich und sieht wohl komplexer aus, als dies wirklich ist. Auf der anderen Seite bietet es eine Reihe von Punkten, an denen Sie mitten in etwas anderem die Kontrolle übernehmen können. Dies wird trivial durch die Verbesserung veranschaulicht, dass wir den Hostnamen einfach zusammen mit der Ausgabe des Shell-Befehls einfügen können. (Dies ist auch in der Shell keineswegs schwierig, sondern auf Kosten einer weiteren Umleitung und möglicherweise eines weiteren Prozesses.)

Gemeinsame Shell-Konstrukte

Der Vollständigkeit halber finden Sie hier kurze Erläuterungen zu einigen dieser Shell-Funktionen sowie einige Hinweise, wie sie möglicherweise durch native Python-Funktionen ersetzt werden können.

  • Globbing aka Wildcard-Erweiterung kann durch glob.glob()oder sehr oft durch einfache Python-String-Vergleiche wie ersetzt werden for file in os.listdir('.'): if not file.endswith('.png'): continue. Bash verfügt über verschiedene andere Erweiterungsfunktionen wie die .{png,jpg}Erweiterung von Klammern und die Erweiterung {1..100}von Tilde (wird ~in Ihr Home-Verzeichnis und allgemein ~accountin das Home-Verzeichnis eines anderen Benutzers erweitert).
  • Shell-Variablen wie $SHELLoder $my_exported_varkönnen manchmal einfach durch Python-Variablen ersetzt werden. Exportierte Shell-Variablen sind verfügbar als z. B. os.environ['SHELL'](die Bedeutung von exportbesteht darin, die Variable für Unterprozesse verfügbar zu machen - eine Variable, die für Unterprozesse nicht verfügbar ist, steht Python offensichtlich nicht zur Verfügung, die als Unterprozess der Shell ausgeführt wird, oder umgekehrt. Das env=Schlüsselwort Mit dem Argument für subprocessMethoden können Sie die Umgebung des Unterprozesses als Wörterbuch definieren. Auf diese Weise können Sie eine Python-Variable für einen Unterprozess sichtbar machen. Mit müssen shell=FalseSie verstehen, wie Sie Anführungszeichen entfernen; ist zum Beispiel cd "$HOME"gleichbedeutend mitos.chdir(os.environ['HOME']) ohne Anführungszeichen um die Verzeichnisnamen. (Sehr oftcdist sowieso nicht nützlich oder notwendig, und viele Anfänger lassen die doppelten Anführungszeichen um die Variable weg und kommen damit bis eines Tages davon ... )
  • Mit der Umleitung können Sie als Standardeingabe aus einer Datei lesen und Ihre Standardeingabe in eine Datei schreiben. grep 'foo' <inputfile >outputfileöffnet sich outputfilezum Schreiben und inputfileLesen und übergibt seinen Inhalt als Standardeingabe an grep, dessen Standardausgabe dann landet outputfile. Dies ist im Allgemeinen nicht schwer durch nativen Python-Code zu ersetzen.
  • Pipelines sind eine Form der Umleitung. echo foo | nlführt zwei Unterprozesse aus, wobei die Standardausgabe von echodie Standardeingabe von ist nl(auf Betriebssystemebene ist dies in Unix-ähnlichen Systemen ein einzelnes Dateihandle). Wenn Sie ein oder beide Enden der Pipeline nicht durch nativen Python-Code ersetzen können, sollten Sie vielleicht doch eine Shell verwenden, insbesondere wenn die Pipeline mehr als zwei oder drei Prozesse enthält (sehen Sie sich jedoch das pipesModul in der Python-Standardbibliothek oder eine Nummer an von moderneren und vielseitigeren Wettbewerbern Dritter).
  • Mit der Jobsteuerung können Sie Jobs unterbrechen, im Hintergrund ausführen, in den Vordergrund zurücksetzen usw. Die grundlegenden Unix-Signale zum Stoppen und Fortsetzen eines Prozesses sind natürlich auch in Python verfügbar. Jobs sind jedoch eine übergeordnete Abstraktion in der Shell, die Prozessgruppen usw. umfasst, die Sie verstehen müssen, wenn Sie so etwas in Python ausführen möchten.
  • Das Zitieren in der Shell ist möglicherweise verwirrend, bis Sie verstehen, dass alles im Grunde eine Zeichenfolge ist. Ist ls -l /also gleichbedeutend mit, 'ls' '-l' '/'aber das Zitieren um Literale ist völlig optional. Nicht zitierte Zeichenfolgen, die Shell-Metazeichen enthalten, werden einer Parametererweiterung, einer Leerzeichen-Tokenisierung und einer Platzhaltererweiterung unterzogen. Doppelte Anführungszeichen verhindern die Leerzeichen-Tokenisierung und die Platzhaltererweiterung, ermöglichen jedoch Parametererweiterungen (Variablensubstitution, Befehlssubstitution und Backslash-Verarbeitung). Dies ist theoretisch einfach, kann jedoch verwirrend werden, insbesondere wenn mehrere Interpretationsebenen vorhanden sind (z. B. ein Remote-Shell-Befehl).

Verstehe die Unterschiede zwischensh und Bash

subprocessführt Ihre Shell-Befehle mit aus, /bin/shsofern Sie nicht ausdrücklich etwas anderes anfordern (außer natürlich unter Windows, wo der Wert der COMSPECVariablen verwendet wird). Dies bedeutet, dass verschiedene Nur-Bash-Funktionen wie Arrays [[usw. nicht verfügbar sind.

Wenn Sie die Nur-Bash-Syntax verwenden müssen, können Sie den Pfad an die Shell als übergeben. executable='/bin/bash'Wenn Ihr Bash an einer anderen Stelle installiert ist, müssen Sie den Pfad anpassen.

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 subprocessist von seinem übergeordneten Element getrennt und kann es nicht ändern

Ein etwas häufiger Fehler ist, so etwas zu tun

subprocess.run('foo=bar', shell=True)
subprocess.run('echo "$foo"', shell=True)  # Doesn't work

was neben dem Mangel an Eleganz auch einen grundlegenden Mangel an Verständnis für den "Sub" -Teil des Namens "Subprozess" verrät.

Ein untergeordneter Prozess wird vollständig getrennt von Python ausgeführt, und wenn er abgeschlossen ist, hat Python keine Ahnung, was er getan hat (abgesehen von den vagen Indikatoren, die er aus dem Beendigungsstatus ableiten und aus dem untergeordneten Prozess ausgeben kann). Ein Kind kann die Umgebung des Elternteils im Allgemeinen nicht ändern. Es kann keine Variable festlegen, das Arbeitsverzeichnis nicht ändern oder in so vielen Worten mit dem übergeordneten Element kommunizieren, ohne dass das übergeordnete Element mitarbeitet.

Die sofortige Lösung in diesem speziellen Fall besteht darin, beide Befehle in einem einzigen Unterprozess auszuführen.

subprocess.run('foo=bar; echo "$foo"', shell=True)

obwohl für diesen speziellen Anwendungsfall die Shell offensichtlich überhaupt nicht erforderlich ist. Denken Sie daran, dass Sie die Umgebung des aktuellen Prozesses (und damit auch seiner untergeordneten Elemente) über manipulieren können

os.environ['foo'] = 'bar'

oder übergeben Sie eine Umgebungseinstellung an einen untergeordneten Prozess mit

subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})

(ganz zu schweigen von der offensichtlichen Umgestaltung subprocess.run(['echo', 'bar']); aber es echoist natürlich ein schlechtes Beispiel für etwas, das in einem Unterprozess ausgeführt werden kann).

Führen Sie Python nicht über Python aus

Dies ist ein etwas zweifelhafter Rat; Es gibt sicherlich Situationen, in denen es sinnvoll oder sogar unbedingt erforderlich ist, den Python-Interpreter als Unterprozess aus einem Python-Skript auszuführen. Sehr häufig ist es jedoch richtig, einfach importdas andere Python-Modul in Ihr aufrufendes Skript zu integrieren und dessen Funktionen direkt aufzurufen.

Wenn das andere Python-Skript unter Ihrer Kontrolle steht und kein Modul ist, sollten Sie es in eines umwandeln . (Diese Antwort ist bereits zu lang, daher werde ich hier nicht auf Details eingehen.)

Wenn Sie Parallelität benötigen, können Sie Python-Funktionen in Unterprozessen mit dem multiprocessingModul ausführen . Es gibt auch Funktionen, threadingdie mehrere Aufgaben in einem einzigen Prozess ausführen (was leichter ist und Ihnen mehr Kontrolle bietet, aber auch dadurch eingeschränkt ist, dass Threads innerhalb eines Prozesses eng miteinander verbunden und an eine einzelne GIL gebunden sind ).

Tripleee
quelle
2
Eine ausführlichere Darstellung, wie Sie es vermeiden können, Python als Unterprozess aufzurufen, finden Sie in dieser Antwort auf eine tangential ähnliche Frage.
Tripleee
4
Es verwirrt mich, dass ich eine neue Antwort auf eine so grundlegende Frage posten musste, um zu zeigen, wie der Befehl aus der Frage idiomatisch ausgeführt wird. Ihre Antwort ist lang, aber ich sehe kein solches Beispiel. Unabhängig: Vermeiden Sie den Ladungskult. Wenn check_call () in Ihrem Fall funktioniert, verwenden Sie es. Ich musste einen Code reparieren, der run()blind verwendet wurde. Das check=TrueFehlen verursachte einen Fehler, der vermieden werden würde, wenn check_call verwendet würde - "check" ist im Namen, Sie können ihn nicht verlieren - es ist die richtige Standardeinstellung: Ignorieren Sie Fehler nicht stillschweigend. Ich habe nicht weiter gelesen.
JFS
1
@jfs Danke für das Feedback, ich hatte tatsächlich vor, einen Abschnitt über Bash vs hinzuzufügen, shaber du hast mich geschlagen. Ich versuche, die Einzelheiten so detailliert darzulegen, dass Anfänger, für die diese Fallstricke nicht offensichtlich sind, etwas langwierig werden. Ihre sollte sonst völlig ausreichen; +1
Tripleee
Hat stderr/stdout = subprocess.PIPEein höherer Leistungsaufwand als die Standardeinstellungen?
Stringers
1
@ Stringer Ich habe nicht getestet, aber ich verstehe nicht, warum es sollte. Wenn Sie diese Rohre an etwas anschließen, das eine Verarbeitung ausführt, muss diese Verarbeitung natürlich berücksichtigt werden. aber es passiert nicht in der Pipe selbst. Die Standardeinstellung ist, dass stdout oder stderr überhaupt nicht erfasst werden, dh was auch immer dort gedruckt wird, liegt außerhalb der Sichtbarkeit und Kontrolle von Python, genau wie bei os.system().
Tripleee
41

Nennen Sie es mit Unterprozess

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

Der Fehler, den Sie erhalten, scheint darauf zurückzuführen zu sein, dass auf dem Server kein Swap-Modul vorhanden ist. Sie sollten Swap auf dem Server installieren und das Skript erneut ausführen

Jakob Bowyer
quelle
3
Das swapModul ist offensichtlich vorhanden, da das Ausführen des Befehls über die Shell funktioniert.
Sven Marnach
2
Nicht auf dem Server, wenn er es auf dem Server ausführt, liegt ein Importfehler vor.
Jakob Bowyer
@mkn: "Dann habe ich einfach diese Ausgabe kopiert und eine Kopie in das Terminal eingefügt und die Eingabetaste gedrückt und es funktioniert ..." - Haben Sie dies auf dem Server oder auf Ihrem Computer versucht?
Sven Marnach
Führen Sie dies auf einem eigenständigen Computer aus, aber es funktioniert nicht, wenn Sie es auf Ihrem Server ausführen? Oder können Sie es auf einem Serverterminal ausführen, aber nicht auf dem Server selbst
Jakob Bowyer
1
es ist falsch Wenn Sie nicht verwenden shell=True, sollten Sie eine Liste verwenden, um mehrere Argumente zu übergeben, dh ['a', 'b', 'c']anstelle von verwenden 'a b c'. Obwohl eine naive Aufteilung aufgrund > file(Shell-Umleitung) im Befehl nicht funktioniert . Weitere Details
JFS
18

Es ist möglich, dass Sie das Bash-Programm mit dem Parameter -c verwenden, um die Befehle auszuführen:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
output = subprocess.check_output(['bash','-c', bashCommand])
Rasierer
quelle
2
subprocess.check_output(bashCommand, shell=True)macht das gleiche. Wenn Ihr Befehl eine statische Zeichenfolge ist, versuchen Sie, ihn selbst in eine Liste zu analysieren, und vermeiden Sie das shell=True; In diesem Fall benötigen Sie die Shell ohnehin für die Umleitung, oder Sie müssen sie in reines Python with open('test.nt', 'w') as dest: output = subprocess.check_output(['cwm' ,'--rdf', 'test.rdf', '--ntriples'], stdout=dest, shell=False)
umgestalten
@tripleee Hinweis: /bin/sh(wird von Unterprozessen verwendet) ist nicht unbedingt bash(Sie können keine Bashismen verwenden). Obwohl man es auf executable='/bin/bashWunsch verwenden könnte. Hier ist ein Codebeispiel
jfs
2
Es ist die erste Antwort, bei der der Befehl erfolgreich gestartet werden sollte (die akzeptierten und die zweiten populären Antworten sind einfach falsch. Ein kleiner Streitpunkt: check_output()ist hier nutzlos (die Ausgabe ist aufgrund der > fileUmleitung immer leer ; verwenden Sie check_call()stattdessen.
jfs
16

Sie können verwenden subprocess , aber ich hatte immer das Gefühl, dass es keine "pythonische" Art war, dies zu tun. Also habe ich Sultan (schamloser Plug) erstellt, der das Ausführen von Befehlszeilenfunktionen vereinfacht.

https://github.com/aeroxis/sultan

David Daniel
quelle
3
Gut gemacht! Viel sauberer und intuitiver als Teilprozesse.
mjd2
Ich danke dir sehr! Das freut mich zu hören!
David Daniel
2
Dies sollte ehrlich gesagt in die Standardbibliothek übernommen werden.
Joshua Detwiler
1
Gibt es eine Möglichkeit, die Ausgabe vom Terminal mit Sultan zu erfassen?
Alvas
Ja, Sie können @alvas ... Hier ist die Dokumentation, wie es geht: sultan.readthedocs.io/en/latest/…
David Daniel
7

Entsprechend dem Fehler fehlt Ihnen ein Paket mit dem Namen swap auf dem Server. Dies /usr/bin/cwmerfordert es. Wenn Sie mit Ubuntu / Debian arbeiten, installieren Sie es python-swapmit aptitude.

kichik
quelle
aber es funktioniert, wenn ich es direkt im Terminal laufen lasse ... also muss der Tausch da sein, nicht?
mkn
Es gibt zwei Möglichkeiten. Entweder kann es nicht gefunden werden swapoder es hätte es gar nicht erst importieren sollen. können Sie import swapmanuell? funktioniert es?
Kichik
hm ich kann nicht Wenn ich Python mit der Eingabe von Python im Terminal starte und dann import swap eingebe, erhalte ich den Fehler "ImportError: Kein Modul namens swap". Das Seltsame ist immer noch, dass es funktioniert, wenn ich den Befehl cwm direkt im Terminal des Servers
ausführe
Versuchen Sie zu drucken, sys.pathwo es funktioniert und wo nicht. Versuchen Sie dann, in den gedruckten Ordnern nach dem Swap-Ordner oder der Datei swap.py zu suchen. Wie Sven sagte, kann es ein Problem mit diesen Pfaden geben, und dies wird Ihnen helfen, es herauszufinden.
Kichik
4

Sie können auch 'os.popen' verwenden. Beispiel:

import os

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

Ausgabe:

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
quelle
1
Die Dokumentation enthält ein großes rotes Kästchen: " Veraltet seit Version 2.6: Diese Funktion ist veraltet. Verwenden Sie das subprocessModul."
Tripleee
1
Fairerweise os.popenhat diese Warnung nicht mehr und ist subprocess.Popen()jetzt einfach eine dünne Hülle .
Tripleee
4

Um den Befehl ohne Shell auszuführen, übergeben Sie den Befehl als Liste und implementieren Sie die Umleitung in Python mit [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)

Hinweis: Nein > test.ntam Ende. stdout=fileimplementiert die Umleitung.


Um den Befehl mit der Shell in Python auszuführen, übergeben Sie den Befehl als Zeichenfolge und aktivieren Sie shell=True:

#!/usr/bin/env python
import subprocess

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

Hier ist die Shell für die Ausgabeumleitung verantwortlich (> test.nt befindet sich im Befehl).


Um einen bash-Befehl auszuführen, der bashisms verwendet, geben Sie die ausführbare bash-Datei explizit an, z. B. um die Ersetzung des bash-Prozesses zu emulieren :

#!/usr/bin/env python
import subprocess

subprocess.check_call('program <(command) <(another-command)',
                      shell=True, executable='/bin/bash')
jfs
quelle
Erwähnen Sie vielleicht, dass dies .split()nicht ausreicht, wenn Zeichenfolgen in Anführungszeichen usw. stehen. Es gibt eine separate Routine, shlex.split()die mit beliebig komplexer Shell-Syntax fertig wird.
Tripleee
@tripleee das .split()funktioniert in diesem Fall. shlex.split()kann manchmal nützlich sein, kann aber in einigen Fällen auch fehlschlagen. Es gibt sehr viele Dinge, die erwähnt werden könnten. Sie können mit dem Link zur oben angegebenen Beschreibung des Unterprozess-Tags beginnen.
JFS
0

Die pythonische Art, dies zu tun, ist die Verwendung subprocess.Popen

subprocess.Popen Nimmt eine Liste, in der das erste Element der auszuführende Befehl ist, gefolgt von allen Befehlszeilenargumenten.

Als Beispiel:

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
Umformulierte Antworten
quelle
Nein, das letzte Beispiel ist dasselbe wie das Ausführen echo -v '"Hello Again!"'von einfachen Anführungszeichen um die doppelten Anführungszeichen.
Tripleee
Um es korrekt zu verwenden subprocesss.Popen, müssen Sie das resultierende Prozessobjekt verwalten (führen Sie mindestens a aus, um wait()zu verhindern, dass es sich in einen Zombie-Prozess verwandelt).
Tripleee