Exécuter la commande shell à partir de Python et capturer la sortie

je veux écrire une fonction qui exécutera une commande shell et retournera sa sortie comme une chaîne de caractères , peu importe, Est-ce un message d'erreur ou de succès. Je veux juste obtenir le même résultat que j'aurais obtenu avec la ligne de commande.

quel serait un exemple de code qui ferait une telle chose?

par exemple:

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'
618
demandé sur Peter Mortensen 2011-01-21 17:55:44

13 réponses

La réponse à cette question dépend de la version de Python que vous utilisez. L'approche la plus simple consiste à utiliser la fonction 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 exécute un seul programme qui ne prend que les arguments en entrée. 1 il renvoie le résultat exactement tel qu'imprimé à stdout . Si vous devez écrire stdin , allez aux sections run ou Popen . Si vous pour exécuter des commandes shell complexes, voir la note sur shell=True à la fin de cette réponse.

la fonction check_output fonctionne sur presque toutes les versions de Python encore largement utilisées (2.7+). 2 mais pour les versions plus récentes, ce n'est plus l'approche recommandée.

versions modernes de Python (3.5 ou plus): run

si vous utilisez Python 3.5 ou plus haut, et n'ont pas besoin de rétrocompatibilité , la nouvelle run fonction est recommandé. Il fournit une API de haut niveau très générale pour le module subprocess . Pour capturer la sortie d'un programme, passez le drapeau subprocess.PIPE à l'argument de mot-clé stdout . Puis accédez à l'attribut stdout de l'objet retourné CompletedProcess :

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

Le retour la valeur est un objet bytes , donc si vous voulez une chaîne appropriée, vous aurez besoin de decode . En supposant que le processus appelé renvoie une chaîne de caractères encodée UTF-8:

>>> result.stdout.decode('utf-8')
'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

tout cela peut être comprimé en une seule doublure:

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

si vous voulez passer l'entrée au processus stdin , passer un bytes objet au input argument de mot clé:

>>> cmd = ['awk', 'length("151940920") > 5']
>>> input = 'foo\nfoofoo\n'.encode('utf-8')
>>> result = subprocess.run(cmd, stdout=subprocess.PIPE, input=input)
>>> result.stdout.decode('utf-8')
'foofoo\n'

vous pouvez capturer des erreurs en passant stderr=subprocess.PIPE (capture à result.stderr ) ou stderr=subprocess.STDOUT (capture à result.stdout avec sortie régulière). Lorsque la sécurité n'est pas un problème, vous pouvez également exécuter des commandes shell plus complexes en passant shell=True comme décrit dans les notes ci-dessous.

Cela ajoute un peu de complexité, par rapport à l'ancienne façon de faire les choses. Mais je pense que ça vaut le coup: maintenant vous pouvez faire presque tout ce que vous devez faire avec la fonction run seule.

anciennes versions de Python (2.7-3.4): check_output

si vous utilisez une ancienne version de Python, ou si vous avez besoin d'une modeste compatibilité ascendante, vous pouvez probablement utiliser la fonction check_output comme décrit brièvement ci-dessus. Il est disponible depuis Python 2.7.

subprocess.check_output(*popenargs, **kwargs)  

il prend les mêmes arguments que Popen (voir ci-dessous), et renvoie une chaîne contenant la sortie du programme. Le début de cette réponse est plus exemple d'utilisation détaillé.

vous pouvez passer stderr=subprocess.STDOUT pour vous assurer que les messages d'erreur sont inclus dans la sortie retournée -- mais ne passez pas stderr=subprocess.PIPE à check_output . Il peut causer blocages . Lorsque la sécurité n'est pas un problème, vous pouvez également exécuter des commandes shell plus complexes en passant shell=True comme décrit dans les notes ci-dessous.

si vous avez besoin de pipe de stderr ou passer entrée au processus, check_output ne sera pas à la hauteur. Voir les exemples Popen ci-dessous dans ce cas.

applications Complexes et anciennes versions de Python (2.6 et ci-dessous): Popen

si vous avez besoin d'une compatibilité en arrière profonde, ou si vous avez besoin de fonctionnalités plus sophistiquées que celles de check_output , vous devrez travailler directement avec les objets Popen , qui encapsulent l'API de bas niveau pour les sous-processus.

le Popen constructeur accepte une seule commande sans arguments, ou une liste contenant une commande par son premier élément, suivi d'un nombre quelconque d'arguments, chacun d'eux comme un élément distinct dans la liste. shlex.split peut aider les chaînes de caractères dans des listes correctement formatées. Les objets Popen acceptent aussi un host of different arguments pour la gestion de processus et de bas niveau configuration.

pour envoyer des entrées et des sorties de capture, communicate est presque toujours la méthode préférée. Comme dans:

output = subprocess.Popen(["mycmd", "myarg"], 
                          stdout=subprocess.PIPE).communicate()[0]

ou

>>> import subprocess
>>> p = subprocess.Popen(['ls', '-a'], stdout=subprocess.PIPE, 
...                                    stderr=subprocess.PIPE)
>>> out, err = p.communicate()
>>> print out
.
..
foo

si vous définissez stdin=PIPE , communicate vous permet également de passer des données au processus via stdin :

>>> cmd = ['awk', 'length("151980920") > 5']
>>> p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
...                           stderr=subprocess.PIPE,
...                           stdin=subprocess.PIPE)
>>> out, err = p.communicate('foo\nfoofoo\n')
>>> print out
foofoo

Note réponse D'Aaron Hall , qui indique que sur certains systèmes, vous devrez peut-être définir stdout , stderr , et stdin tout à PIPE (ou DEVNULL ) pour obtenir communicate de travailler à tous.

dans certains cas rares, vous pouvez avoir besoin complexe, la capture de sortie en temps réel. Vartec ' S réponse suggère une voie à suivre, mais les méthodes autres que communicate sont sujettes à des blocages si elles ne sont pas utilisées avec soin.

Comme avec toutes les fonctions ci-dessus, lorsque la sécurité n'est pas un problème, vous pouvez exécuter plus commandes shell complexes en passant shell=True .

Notes

1. Exécution des commandes shell: l'argument shell=True

Normalement, chaque appel à run , check_output , ou le Popen constructeur exécute un programme unique . Ça veut dire pas de pipes de style bash. Si vous voulez exécuter des commandes shell complexes, vous pouvez passer shell=True , que les trois les fonctions de soutien.

Cependant, cela soulève problèmes de sécurité . Si vous faites autre chose que des scripts légers, vous feriez mieux d'appeler chaque processus séparément, et de passer la sortie de chacun comme une entrée à la suivante, via

run(cmd, [stdout=etc...], input=other_output)

ou

Popen(cmd, [stdout=etc...]).communicate(other_output)

la tentation de raccorder directement les tuyaux est forte; résistez-y. Autrement, vous verrez probablement des blocages ou à faire des choses comme ce .

2. Unicode considérations

check_output renvoie une chaîne en Python 2, mais un objet bytes en Python 3. Cela vaut la peine de prendre un moment pour en savoir plus sur unicode si vous ne l'avez pas déjà.

739
répondu senderle 2017-05-23 12:03:09

c'est beaucoup plus facile, mais ne fonctionne que sur Unix (y compris Cygwin).

import commands
print commands.getstatusoutput('wc -l file')

il retourne un tuple avec le (return_value, output)

173
répondu byte_array 2016-07-11 23:16:15

quelque chose comme ça:

def runProcess(exe):    
    p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    while(True):
      retcode = p.poll() #returns None while subprocess is running
      line = p.stdout.readline()
      yield line
      if(retcode is not None):
        break

notez que je redirige stderr vers stdout, ce n'est peut-être pas exactement ce que vous voulez, mais je veux aussi des messages d'erreur.

cette fonction produit ligne par ligne comme ils viennent (normalement, vous auriez à attendre la fin de subprocess pour obtenir la sortie dans son ensemble).

pour votre cas l'usage serait:

for line in runProcess('mysqladmin create test -uroot -pmysqladmin12'.split()):
    print line,
97
répondu vartec 2015-09-17 17:02:36

la réponse de Vartec ne lit pas toutes les lignes, donc j'ai fait une version qui a fait:

def run_command(command):
    p = subprocess.Popen(command,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.STDOUT)
    return iter(p.stdout.readline, b'')

L'Usage est le même que la réponse acceptée:

command = 'mysqladmin create test -uroot -pmysqladmin12'.split()
for line in run_command(command):
    print(line)
56
répondu Max Ekman 2017-05-23 11:33:24

il s'agit d'une délicate mais super simple solution qui fonctionne dans de nombreuses situations:

import os
os.system('sample_cmd > tmp')
print open('tmp', 'r').read()

un fichier temporaire(ici tmp) est créé avec la sortie de la commande et vous pouvez en lire la sortie désirée.

note supplémentaire des commentaires: Vous pouvez supprimer le fichier tmp en cas de travail ponctuel. Si vous devez le faire plusieurs fois, il n'est pas nécessaire de supprimer le tmp.

os.remove('tmp')
37
répondu Mehdi1902 2017-10-27 07:19:52

Modern Python solution (>=3.1):

 res = subprocess.check_output(lcmd, stderr=subprocess.STDOUT)
13
répondu zlr 2014-04-20 20:30:59

En Python 3.5:

import subprocess

output = subprocess.run("ls -l", shell=True, stdout=subprocess.PIPE, 
                        universal_newlines=True)
print(output.stdout)
13
répondu cmlaverdiere 2016-07-29 23:00:42

vous pouvez utiliser les commandes suivantes pour exécuter n'importe quelle commande shell. Je les ai utilisés sur ubuntu.

import os
os.popen('your command here').read()
12
répondu Muhammad Hassan 2017-07-03 19:04:53

votre kilométrage peut varier, j'ai essayé le spin de @senderle sur la solution de Vartec dans Windows sur Python 2.6.5, mais j'ai eu des erreurs, et aucune autre solution n'a fonctionné. Mon erreur était: WindowsError: [Error 6] The handle is invalid .

j'ai trouvé que je devais assigner des tuyaux à chaque poignée pour obtenir le retour de la production que je m'attendais - les suivants ont fonctionné pour moi.

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

et appelez comme ceci, ( [0] obtient le premier élément du tuple, stdout ):

run_command('tracert 11.1.0.1')[0]

après avoir appris plus, je crois que j'ai besoin de ces arguments pipe parce que je travaille sur un système personnalisé qui utilise des poignées différentes, donc j'ai dû contrôler directement toutes les MST.

pour arrêter les popups de console( avec Windows), faites ceci:

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')
9
répondu Aaron Hall 2015-08-07 15:21:14

j'ai eu le même problème Mais trouvé un moyen très simple de le faire suivre cette

import subprocess
Input = subprocess.getoutput("ls -l")
print(Input)

j'Espère que ça aide

Note: Cette solution est spécifique à python3 en tant que subprocess.getoutput() ne fonctionne pas en python2

9
répondu itz-azhar 2017-05-18 12:29:19

j'avais un goût légèrement différent du même problème avec les exigences suivantes:

  1. Capture et renvoie les messages STDOUT au fur et à mesure qu'ils s'accumulent dans le tampon STDOUT (c'est-à-dire en temps réel).
    • @vartec a résolu cela Pythoniquement avec son utilisation de générateurs et le "rendement "

      mot-clé au-dessus de
  2. Print toutes les lignes STDOUT ( même si le processus se termine avant le tampon STDOUT peuvent être lues )
  3. Ne perdez pas de cycles CPU de l'interrogation du processus à haute fréquence", 1519140920"
  4. Vérifier le code de retour de la sous-procédure
  5. Imprimer STDERR (séparé de STDOUT) si nous obtenons un code de retour d'erreur non-zéro.

:

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

ce code serait exécuté comme les réponses précédentes:

for line in run_command(cmd):
    print(line)
7
répondu The Aelfinn 2017-09-18 03:31:23

Si vous avez besoin d'exécuter une commande shell sur plusieurs fichiers, cela a fait l'affaire pour moi.

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: Viens de voir Max Persson solution avec J. F. Sebastian de la suggestion. Allé de l'avant et intégrées.

2
répondu Ethan Strider 2015-04-01 15:54:55

par exemple, execute ("ls -lah') différencié trois/quatre retours et les plateformes OS:

  1. pas de sortie, mais de l'exécuter avec succès
  2. sortie ligne vide, exécution réussie
  3. exécuter en "échec de l'151950920"
  4. la sortie de quelque chose, d'exécuter avec succès

fonction ci-dessous

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
répondu Jerry T 2017-08-02 21:55:38