Ich möchte eine Funktion schreiben, die einen Shell-Befehl ausführt und dessen Ausgabe zurückgibt als Zeichenfolge, unabhängig davon, ob es sich um eine Fehler- oder Erfolgsmeldung handelt. Ich möchte nur das gleiche Ergebnis erhalten, das ich mit der Befehlszeile erhalten hätte.
Was wäre ein Codebeispiel, das so etwas tun würde?
Zum Beispiel:
def run_command(cmd):
# ??????
print run_command('mysqladmin create test -uroot -pmysqladmin12')
# Should output something like:
# mysqladmin: CREATE DATABASE failed; error: 'Can't create database 'test'; database exists'
Die Antwort auf diese Frage hängt von der Version von Python ab, die Sie verwenden. Der einfachste Ansatz ist die Verwendung der Funktion subprocess.check_output
:
_>>> subprocess.check_output(['ls', '-l'])
b'total 0\n-rw-r--r-- 1 memyself staff 0 Mar 14 11:04 files\n'
_
_check_output
_ führt ein einzelnes Programm aus, das nur Argumente als Eingabe verwendet.1 Es gibt das Ergebnis genau so zurück, wie es in stdout
ausgegeben wurde. Wenn Sie Eingaben in stdin
schreiben müssen, fahren Sie mit den Abschnitten run
oder Popen
fort. Wenn Sie komplexe Shell-Befehle ausführen möchten, lesen Sie den Hinweis zu _Shell=True
_ am Ende dieser Antwort.
Die Funktion _check_output
_ funktioniert auf fast allen Versionen von Python, die noch weit verbreitet sind (2.7+).2 Bei neueren Versionen ist dies jedoch nicht mehr der empfohlene Ansatz.
run
Wenn Sie Python 3.5 oder höher verwenden und keine Abwärtskompatibilität benötigen wird die Funktion new run
empfohlen. Es bietet eine sehr allgemeine API auf hoher Ebene für das Modul subprocess
. Um die Ausgabe eines Programms zu erfassen, übergeben Sie das Flag _subprocess.PIPE
_ an das Schlüsselwortargument stdout
. Greifen Sie dann auf das Attribut stdout
des zurückgegebenen Objekts CompletedProcess
zu:
_>>> import subprocess
>>> result = subprocess.run(['ls', '-l'], stdout=subprocess.PIPE)
>>> result.stdout
b'total 0\n-rw-r--r-- 1 memyself staff 0 Mar 14 11:04 files\n'
_
Der Rückgabewert ist ein bytes
-Objekt. Wenn Sie also eine richtige Zeichenfolge wünschen, müssen Sie sie decode
. Angenommen, der aufgerufene Prozess gibt eine UTF-8-codierte Zeichenfolge zurück:
_>>> result.stdout.decode('utf-8')
'total 0\n-rw-r--r-- 1 memyself staff 0 Mar 14 11:04 files\n'
_
Dies kann alles zu einem Einzeiler komprimiert werden:
_>>> subprocess.run(['ls', '-l'], stdout=subprocess.PIPE).stdout.decode('utf-8')
'total 0\n-rw-r--r-- 1 memyself staff 0 Mar 14 11:04 files\n'
_
Wenn Sie Eingaben an stdin
des Prozesses übergeben möchten, übergeben Sie ein bytes
-Objekt an das Schlüsselwortargument input
:
_>>> cmd = ['awk', 'length($0) > 5']
>>> input = 'foo\nfoofoo\n'.encode('utf-8')
>>> result = subprocess.run(cmd, stdout=subprocess.PIPE, input=input)
>>> result.stdout.decode('utf-8')
'foofoo\n'
_
Sie können Fehler erfassen, indem Sie _stderr=subprocess.PIPE
_ (Erfassung an _result.stderr
_) oder _stderr=subprocess.STDOUT
_ (Erfassung an _result.stdout
_ zusammen mit der regulären Ausgabe) übergeben. Wenn die Sicherheit keine Rolle spielt, können Sie auch komplexere Shell-Befehle ausführen, indem Sie _Shell=True
_ übergeben, wie in den nachstehenden Hinweisen beschrieben.
Dies erhöht die Komplexität im Vergleich zur alten Arbeitsweise. Aber ich denke, es ist die Auszahlung wert: Jetzt können Sie fast alles tun, was Sie brauchen, um allein mit der Funktion run
zu arbeiten.
check_output
_Wenn Sie eine ältere Version von Python verwenden oder eine bescheidene Abwärtskompatibilität benötigen, können Sie wahrscheinlich die Funktion _check_output
_ verwenden, wie oben kurz beschrieben. Es ist seit Python 2.7 verfügbar.
_subprocess.check_output(*popenargs, **kwargs)
_
Es akzeptiert die gleichen Argumente wie Popen
(siehe unten) und gibt eine Zeichenfolge zurück, die die Ausgabe des Programms enthält. Der Anfang dieser Antwort enthält ein detaillierteres Anwendungsbeispiel. In Python 3.5 und höher entspricht _check_output
_ dem Ausführen von run
mit _check=True
_ und _stdout=PIPE
_ und dem Zurückgeben nur des Attributs stdout
.
Sie können _stderr=subprocess.STDOUT
_ übergeben, um sicherzustellen, dass Fehlermeldungen in der zurückgegebenen Ausgabe enthalten sind. In einigen Versionen von Python kann die Übergabe von _stderr=subprocess.PIPE
_ an _check_output
_ jedoch Deadlocks . Wenn die Sicherheit keine Rolle spielt, können Sie auch komplexere Shell-Befehle ausführen, indem Sie _Shell=True
_ übergeben, wie in den nachstehenden Hinweisen beschrieben.
Wenn Sie stderr
weiterleiten oder Eingaben an den Prozess übergeben müssen, ist _check_output
_ der Aufgabe nicht gewachsen. In diesem Fall sehen Sie sich die Beispiele für Popen
an.
Popen
Wenn Sie eine umfassende Abwärtskompatibilität benötigen oder anspruchsvollere Funktionen benötigen als _check_output
_, müssen Sie direkt mit Popen
-Objekten arbeiten, die die API auf niedriger Ebene für Unterprozesse kapseln.
Der Konstruktor Popen
akzeptiert entweder einen einzelnen Befehl ohne Argumente oder eine Liste Enthält einen Befehl als erstes Element, gefolgt von einer beliebigen Anzahl von Argumenten, jeweils als separates Element in der Liste. shlex.split
kann beim Parsen von Zeichenfolgen in entsprechend formatierte Listen helfen. Popen
-Objekte akzeptieren auch ein Host mit verschiedenen Argumenten für die Prozessverwaltung IO und die Konfiguration auf niedriger Ebene.
Um Eingaben zu senden und Ausgaben zu erfassen, ist communicate
fast immer die bevorzugte Methode. Wie in:
_output = subprocess.Popen(["mycmd", "myarg"],
stdout=subprocess.PIPE).communicate()[0]
_
Oder
_>>> import subprocess
>>> p = subprocess.Popen(['ls', '-a'], stdout=subprocess.PIPE,
... stderr=subprocess.PIPE)
>>> out, err = p.communicate()
>>> print out
.
..
foo
_
Wenn Sie _stdin=PIPE
_ festlegen, können Sie mit communicate
auch Daten über stdin
an den Prozess übergeben:
_>>> cmd = ['awk', 'length($0) > 5']
>>> p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
... stderr=subprocess.PIPE,
... stdin=subprocess.PIPE)
>>> out, err = p.communicate('foo\nfoofoo\n')
>>> print out
foofoo
_
Hinweis Aaron Halls Antwort . Dies bedeutet, dass Sie auf einigen Systemen möglicherweise stdout
, stderr
und stdin
auf PIPE
(oder DEVNULL
) setzen müssen, damit communicate
überhaupt funktioniert.
In einigen seltenen Fällen müssen Sie möglicherweise komplexe Ausgaben in Echtzeit erfassen. Vartec 's Antwort schlägt einen Weg vor, aber andere Methoden als communicate
können zu Deadlocks führen, wenn sie nicht sorgfältig verwendet werden.
Wie bei allen oben genannten Funktionen können Sie, wenn die Sicherheit keine Rolle spielt, komplexere Shell-Befehle ausführen, indem Sie _Shell=True
_ übergeben.
1. Ausführen von Shell-Befehlen: das Argument _Shell=True
_
Normalerweise führt jeder Aufruf von run
, _check_output
_ oder dem Konstruktor Popen
ein einzelnes Programm aus . Das bedeutet, dass es keine ausgefallenen Pfeifen im Bash-Stil gibt. Wenn Sie komplexe Shell-Befehle ausführen möchten, können Sie _Shell=True
_ übergeben, das von allen drei Funktionen unterstützt wird.
Dies wirft jedoch Sicherheitsbedenken auf. Wenn Sie mehr als nur Light-Scripting ausführen, ist es möglicherweise besser, jeden Prozess einzeln aufzurufen und die Ausgabe von jedem als Eingabe an den nächsten über zu übergeben
_run(cmd, [stdout=etc...], input=other_output)
_
Oder
_Popen(cmd, [stdout=etc...]).communicate(other_output)
_
Die Versuchung, Rohre direkt zu verbinden, ist groß. widerstehen. Andernfalls werden Sie wahrscheinlich Deadlocks sehen oder hackige Dinge wie this tun müssen.
2. Unicode-Überlegungen
_check_output
_ gibt einen String in Python 2 zurück, aber ein bytes
-Objekt in Python 3. Es lohnt sich, sich einen Moment Zeit zu nehmen, um etwas über Unicode zu lernen wenn du es noch nicht getan hast.
Dies ist viel einfacher, funktioniert aber nur unter Unix (einschließlich Cygwin) und Python2.7.
import commands
print commands.getstatusoutput('wc -l file')
Es wird ein Tupel mit (return_value, output) zurückgegeben.
Verwenden Sie für eine Lösung, die sowohl in Python2 als auch in Python3 funktioniert, stattdessen das Modul subprocess
:
from subprocess import Popen, PIPE
output = Popen(["date"],stdout=PIPE)
response = output.communicate()
print response
Sowas in der Art:
def runProcess(exe):
p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while(True):
# returns None while subprocess is running
retcode = p.poll()
line = p.stdout.readline()
yield line
if retcode is not None:
break
Beachten Sie, dass ich stderr auf stdout umleitung, es ist möglicherweise nicht genau das, was Sie wollen, aber ich möchte auch Fehlermeldungen.
Diese Funktion ergibt Zeile für Zeile, wenn sie kommt (normalerweise müsste man warten, bis der Unterprozess beendet ist, um die Ausgabe als Ganzes zu erhalten).
Für Ihren Fall wäre die Verwendung:
for line in runProcess('mysqladmin create test -uroot -pmysqladmin12'.split()):
print line,
Vartecs Die Antwort liest nicht alle Zeilen, also habe ich eine Version gemacht, die dies tat:
def run_command(command):
p = subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
return iter(p.stdout.readline, b'')
Die Verwendung entspricht der akzeptierten Antwort:
command = 'mysqladmin create test -uroot -pmysqladmin12'.split()
for line in run_command(command):
print(line)
Dies ist eine knifflige aber supereinfache Lösung, die in vielen Situationen funktioniert:
import os
os.system('sample_cmd > tmp')
print open('tmp', 'r').read()
Mit der Ausgabe des Befehls wird eine temporäre Datei (hier tmp) erstellt, aus der Sie die gewünschte Ausgabe ablesen können.
Zusätzlicher Hinweis aus den Kommentaren: Sie können die tmp-Datei bei einem einmaligen Auftrag entfernen. Wenn Sie dies mehrmals tun müssen, müssen Sie den tmp nicht löschen.
os.remove('tmp')
In Python 3.5:
import subprocess
output = subprocess.run("ls -l", Shell=True, stdout=subprocess.PIPE,
universal_newlines=True)
print(output.stdout)
Ich hatte das gleiche Problem, fand aber einen sehr einfachen Weg, dies zu tun:
import subprocess
output = subprocess.getoutput("ls -l")
print(output)
Hoffe es hilft aus
Hinweis: Diese Lösung ist Python3-spezifisch, da subprocess.getoutput()
in Python2 nicht funktioniert
Sie können die folgenden Befehle verwenden, um einen beliebigen Shell-Befehl auszuführen. Ich habe sie auf Ubuntu verwendet.
import os
os.popen('your command here').read()
Anmerkung: Dies ist veraltet seit python 2.6. Jetzt müssen Sie subprocess.Popen
verwenden. Unten ist das Beispiel
import subprocess
p = subprocess.Popen("Your command", Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
print p.split("\n")
Moderne Python Lösung (> = 3.1):
import subprocess
res = subprocess.check_output(lcmd, stderr=subprocess.STDOUT)
Ihre Laufleistung kann variieren. Ich habe versucht, Vartecs Lösung unter Windows auf Python 2.6.5 zu testen. Es wurden jedoch Fehler gemeldet, und es funktionierten keine anderen Lösungen. Mein Fehler war: WindowsError: [Error 6] The handle is invalid
.
Ich stellte fest, dass ich jedem Handle PIPE zuweisen musste, damit es die erwartete Ausgabe zurückgab - das Folgende funktionierte für mich.
import subprocess
def run_command(cmd):
"""given Shell command, returns communication Tuple of stdout and stderr"""
return subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE).communicate()
und wie folgt aufrufen ([0]
erhält das erste Element des Tupels, stdout
):
run_command('tracert 11.1.0.1')[0]
Nachdem ich mehr darüber gelernt habe, glaube ich, dass ich diese Pipe-Argumente brauche, da ich an einem benutzerdefinierten System arbeite, das verschiedene Handles verwendet, sodass ich alle Standardeinstellungen direkt steuern musste.
So stoppen Sie Konsolen-Popups (unter Windows):
def run_command(cmd):
"""given Shell command, returns communication Tuple of stdout and stderr"""
# instantiate a startupinfo obj:
startupinfo = subprocess.STARTUPINFO()
# set the use show window flag, might make conditional on being in Windows:
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
# pass as the startupinfo keyword argument:
return subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
startupinfo=startupinfo).communicate()
run_command('tracert 11.1.0.1')
Ich hatte einen etwas anderen Geschmack des gleichen Problems mit den folgenden Anforderungen:
Ich habe die vorherigen Antworten kombiniert und optimiert, um Folgendes zu erhalten:
import subprocess
from time import sleep
def run_command(command):
p = subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
Shell=True)
# Read stdout from subprocess until the buffer is empty !
for line in iter(p.stdout.readline, b''):
if line: # Don't print blank lines
yield line
# This ensures the process has completed, AND sets the 'returncode' attr
while p.poll() is None:
sleep(.1) #Don't waste CPU-cycles
# Empty STDERR buffer
err = p.stderr.read()
if p.returncode != 0:
# The run_command() function is responsible for logging STDERR
print("Error: " + str(err))
Dieser Code würde genauso wie die vorherigen Antworten ausgeführt:
for line in run_command(cmd):
print(line)
Das Teilen des anfänglichen Befehls für subprocess
kann schwierig und umständlich sein.
Verwenden Sie shlex.split()
, um sich selbst zu helfen.
Beispielbefehl
git log -n 5 --since "5 years ago" --until "2 year ago"
Der Code
from subprocess import check_output
from shlex import split
res = check_output(split('git log -n 5 --since "5 years ago" --until "2 year ago"'))
print(res)
>>> b'commit 7696ab087a163e084d6870bb4e5e4d4198bdc61a\nAuthor: Artur Barseghyan...'
Ohne shlex.split()
würde der Code wie folgt aussehen
res = check_output([
'git',
'log',
'-n',
'5',
'--since',
'5 years ago',
'--until',
'2 year ago'
])
print(res)
>>> b'commit 7696ab087a163e084d6870bb4e5e4d4198bdc61a\nAuthor: Artur Barseghyan...'
Wenn Sie einen Shell-Befehl für mehrere Dateien ausführen müssen, hat dies den Trick für mich getan.
import os
import subprocess
# Define a function for running commands and capturing stdout line by line
# (Modified from Vartec's solution because it wasn't printing all lines)
def runProcess(exe):
p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
return iter(p.stdout.readline, b'')
# Get all filenames in working directory
for filename in os.listdir('./'):
# This command will be run on each file
cmd = 'nm ' + filename
# Run the command and capture the output line by line.
for line in runProcess(cmd.split()):
# Eliminate leading and trailing whitespace
line.strip()
# Split the output
output = line.split()
# Filter the output and print relevant lines
if len(output) > 2:
if ((output[2] == 'set_program_name')):
print filename
print line
Edit: Ich habe gerade die Lösung von Max Persson mit dem Vorschlag von J. F. Sebastian gesehen. Ging voran und nahm das auf.
Laut @senderle, wenn Sie Python3.6 wie ich verwenden:
def sh(cmd, input=""):
rst = subprocess.run(cmd, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, input=input.encode("utf-8"))
assert rst.returncode == 0, rst.stderr.decode("utf-8")
return rst.stdout.decode("utf-8")
sh("ls -a")
Handelt genau so, als würden Sie den Befehl in bash ausführen
Wenn Sie das Modul subprocess
python verwenden, können Sie STDOUT, STDERR und den Rückkehrcode des Befehls separat behandeln. Sie können ein Beispiel für die vollständige Implementierung des Befehlsaufrufers sehen. Natürlich können Sie es mit try..except
erweitern, wenn Sie möchten.
Die folgende Funktion gibt den STDOUT-, STDERR- und Return-Code zurück, damit Sie sie im anderen Skript verarbeiten können.
import subprocess
def command_caller(command=None)
sp = subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE, Shell=False)
out, err = sp.communicate()
if sp.returncode:
print(
"Return code: %(ret_code)s Error message: %(err_msg)s"
% {"ret_code": sp.returncode, "err_msg": err}
)
return sp.returncode, out, err
Die Ausgabe kann in eine Textdatei umgeleitet und dann zurückgelesen werden.
import subprocess
import os
import tempfile
def execute_to_file(command):
"""
This function execute the command
and pass its output to a tempfile then read it back
It is usefull for process that deploy child process
"""
temp_file = tempfile.NamedTemporaryFile(delete=False)
temp_file.close()
path = temp_file.name
command = command + " > " + path
proc = subprocess.run(command, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
if proc.stderr:
# if command failed return
os.unlink(path)
return
with open(path, 'r') as f:
data = f.read()
os.unlink(path)
return data
if __== "__main__":
path = "Somepath"
command = 'ecls.exe /files ' + path
print(execute(command))
führen Sie beispielsweise ('ls -ahl') differenzierte drei/vier mögliche Rückgaben und Betriebssystemplattformen aus:
funktion unten
def execute(cmd, output=True, DEBUG_MODE=False):
"""Executes a bash command.
(cmd, output=True)
output: whether print Shell output to screen, only affects screen display, does not affect returned values
return: ...regardless of output=True/False...
returns Shell output as a list with each elment is a line of string (whitespace stripped both sides) from output
could be
[], ie, len()=0 --> no output;
[''] --> output empty line;
None --> error occured, see below
if error ocurs, returns None (ie, is None), print out the error message to screen
"""
if not DEBUG_MODE:
print "Command: " + cmd
# https://stackoverflow.com/a/40139101/2292993
def _execute_cmd(cmd):
if os.name == 'nt' or platform.system() == 'Windows':
# set stdin, out, err all to PIPE to get results (other than None) after run the Popen() instance
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, Shell=True)
else:
# Use bash; the default is sh
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, Shell=True, executable="/bin/bash")
# the Popen() instance starts running once instantiated (??)
# additionally, communicate(), or poll() and wait process to terminate
# communicate() accepts optional input as stdin to the pipe (requires setting stdin=subprocess.PIPE above), return out, err as Tuple
# if communicate(), the results are buffered in memory
# Read stdout from subprocess until the buffer is empty !
# if error occurs, the stdout is '', which means the below loop is essentially skipped
# A prefix of 'b' or 'B' is ignored in Python 2;
# it indicates that the literal should become a bytes literal in Python 3
# (e.g. when code is automatically converted with 2to3).
# return iter(p.stdout.readline, b'')
for line in iter(p.stdout.readline, b''):
# # Windows has \r\n, Unix has \n, Old mac has \r
# if line not in ['','\n','\r','\r\n']: # Don't print blank lines
yield line
while p.poll() is None:
sleep(.1) #Don't waste CPU-cycles
# Empty STDERR buffer
err = p.stderr.read()
if p.returncode != 0:
# responsible for logging STDERR
print("Error: " + str(err))
yield None
out = []
for line in _execute_cmd(cmd):
# error did not occur earlier
if line is not None:
# trailing comma to avoid a newline (by print itself) being printed
if output: print line,
out.append(line.strip())
else:
# error occured earlier
out = None
return out
else:
print "Simulation! The command is " + cmd
print ""