it-swarm.com.de

Ausführen des Shell-Befehls und Erfassen der Ausgabe

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'
770
Silver Light

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.

Moderne Versionen von Python (3.5 oder höher): 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.

Ältere Versionen von Python (2.7-3.4): _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.

Komplexe Anwendungen und ältere Versionen von Python (2.6 und niedriger): 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.

Anmerkungen

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.

962
senderle

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
182
byte_array

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,
105
vartec

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)
63
Max Ekman

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

In Python 3.5:

import subprocess

output = subprocess.run("ls -l", Shell=True, stdout=subprocess.PIPE, 
                        universal_newlines=True)
print(output.stdout)
22
cmlaverdiere

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

20
itz-azhar

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")
15
Muhammad Hassan

Moderne Python Lösung (> = 3.1):

import subprocess     
res = subprocess.check_output(lcmd, stderr=subprocess.STDOUT)
13
zlr

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')
11
Aaron Hall

Ich hatte einen etwas anderen Geschmack des gleichen Problems mit den folgenden Anforderungen:

  1. Erfassen und Zurückgeben von STDOUT-Nachrichten, während sie sich im STDOUT-Puffer ansammeln (d. H. In Echtzeit).
    • @ vartec löste dies pythonisch mit seinem Einsatz von Generatoren und der "Ausbeute"
      Keyword oben
  2. Alle STDOUT-Zeilen drucken (, auch wenn der Prozess beendet wird, bevor der STDOUT-Puffer vollständig gelesen werden kann )
  3. Verschwenden Sie keine CPU-Zyklen, um den Prozess mit hoher Frequenz abzufragen
  4. Überprüfen Sie den Rückkehrcode des Unterprozesses
  5. Geben Sie STDERR (getrennt von STDOUT) aus, wenn der Fehlerrückgabecode nicht Null ist.

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)
9
The Aelfinn

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...'
3

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.

3
Ethan Strider

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

2
Neo li

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
1
milanbalazs

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))
0
M. R.

führen Sie beispielsweise ('ls -ahl') differenzierte drei/vier mögliche Rückgaben und Betriebssystemplattformen aus:

  1. keine Ausgabe, aber erfolgreich ausgeführt
  2. leerzeile ausgeben, erfolgreich ausführen
  3. lauf fehlgeschlagen
  4. etwas ausgeben, erfolgreich ausführen

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 ""
0
Jerry T