web-dev-qa-db-fra.com

Exécution de la commande Shell et capture du résultat

Je veux écrire une fonction qui exécutera une commande Shell et renverra sa sortie sous forme de chaîne, peu importe, s'agit-il d'une erreur ou d'un message de réussite. Je veux juste obtenir le même résultat que celui 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'
770
Silver Light

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 programme unique qui prend uniquement des arguments en entrée.1 Il renvoie le résultat exactement tel que imprimé à stdout. Si vous devez écrire votre entrée dans stdin, passez directement aux sections run ou Popen. Si vous souhaitez exécuter des commandes Shell complexes, reportez-vous à la note sur _Shell=True_ à la fin de cette réponse.

La fonction _check_output_ fonctionne avec 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 (version 3.5 ou supérieure): run

Si vous utilisez Python 3.5 ou supérieur, et n’avez pas besoin de compatibilité ascendante , la nouvelle fonction run est recommandée. Il fournit une API de haut niveau très générale pour le module subprocess. Pour capturer la sortie d'un programme, passez l'indicateur _subprocess.PIPE_ à l'argument de mot clé stdout. Accédez ensuite à l'attribut stdout de l'objet CompletedProcess renvoyé:

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

La valeur de retour est un objet bytes, donc si vous voulez une chaîne appropriée, vous aurez besoin de decode. En supposant que le processus appelé retourne une chaîne codée en 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 compressé en une ligne:

_>>> 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 une entrée au stdin du processus, transmettez un objet bytes à l'argument de mot clé 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'
_

Vous pouvez capturer des erreurs en passant _stderr=subprocess.PIPE_ (capture à _result.stderr_) ou _stderr=subprocess.STDOUT_ (capture à _result.stdout_ avec la sortie normale). Lorsque la sécurité ne vous concerne pas, vous pouvez également exécuter des commandes Shell plus complexes en transmettant _Shell=True_ comme décrit dans les remarques ci-dessous.

Cela ajoute un peu de complexité par rapport à l'ancienne façon de faire les choses. Mais je pense que cela en vaut la peine: 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 version plus ancienne de Python ou si vous avez besoin d’une compatibilité ascendante modeste, 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 contient un exemple d'utilisation plus détaillé. Dans Python 3.5 et versions supérieures, _check_output_ équivaut à exécuter run avec _check=True_ et _stdout=PIPE_ et à ne renvoyer que l'attribut stdout.

Vous pouvez passer _stderr=subprocess.STDOUT_ pour vous assurer que les messages d'erreur sont inclus dans la sortie renvoyée - mais dans certaines versions de Python, passer _stderr=subprocess.PIPE_ à _check_output_ peut provoquer impasses . Lorsque la sécurité ne vous concerne pas, vous pouvez également exécuter des commandes Shell plus complexes en transmettant _Shell=True_ comme décrit dans les remarques ci-dessous.

Si vous devez canaliser à partir de stderr ou transmettre une entrée au processus, _check_output_ ne sera pas à la hauteur de la tâche. Voir les exemples Popen ci-dessous dans ce cas.

Applications complexes et versions héritées de Python (2.6 et versions antérieures): Popen

Si vous avez besoin d'une compatibilité ascendante profonde ou si vous avez besoin de fonctionnalités plus sophistiquées que celles fournies par _check_output_, vous devez travailler directement avec les objets Popen, qui encapsulent l'API de bas niveau pour les sous-processus.

Le constructeur Popen accepte soit une seule commande sans argument, soit une liste contenant une commande en tant que premier élément, suivi d'un nombre quelconque d'arguments, chacun en tant qu'élément distinct de la liste. shlex.split peut aider à analyser les chaînes dans des listes correctement formatées. Les objets Popen acceptent également un hôte de différents arguments pour le processus IO de gestion et de configuration de bas niveau.

communicate est presque toujours la méthode préférée pour envoyer des entrées et des sorties de capture. Un péché:

_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 transmettre des données au processus via stdin:

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

Remarque Réponse d'Aaron Hall , ce qui indique que sur certains systèmes, vous devrez peut-être définir stdout, stderr et stdin sur PIPE (ou DEVNULL) pour que communicate fonctionne.

Dans de rares cas, vous aurez peut-être besoin d'une capture de sortie complexe en temps réel. La réponse de Vartec suggère une voie à suivre, mais des méthodes autres que communicate sont sujettes aux blocages si elles ne sont pas utilisées avec précaution.

Comme pour toutes les fonctions ci-dessus, lorsque la sécurité ne vous préoccupe pas, vous pouvez exécuter des commandes Shell plus complexes en transmettant _Shell=True_.

Remarques

1. Commandes en cours d'exécution du shell: l'argument _Shell=True_

Normalement, chaque appel à run, _check_output_ ou au constructeur Popen exécute un seul programme . Cela signifie pas de tuyaux de style bash fantaisie. Si vous souhaitez exécuter des commandes Shell complexes, vous pouvez transmettre _Shell=True_, pris en charge par les trois fonctions.

Cependant, cela soulève problèmes de sécurité . Si vous faites autre chose que des scripts clairs, il vaudrait peut-être mieux appeler chaque processus séparément et transmettre le résultat de chacun d'eux comme entrée au suivant,

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

Ou

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

La tentation de connecter directement les tuyaux est forte; Résiste. Sinon, vous rencontrerez probablement des blocages ou devrez faire des choses hacky comme this .

2. Considérations Unicode

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

962
senderle

C'est beaucoup plus facile, mais ne fonctionne que sous Unix (y compris Cygwin) et Python2.7.

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

Il retourne un tuple avec (return_value, output).

Pour une solution fonctionnant à la fois en Python2 et en Python3, utilisez plutôt le module subprocess:

from subprocess import Popen, PIPE
output = Popen(["date"],stdout=PIPE)
response = output.communicate()
print response
182
byte_array

Quelque chose comme ca:

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

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 donne ligne par ligne à l’arrivée (normalement, vous devez attendre la fin du sous-processus pour obtenir la sortie dans son ensemble).

Pour votre cas, l'utilisation serait:

for line in runProcess('mysqladmin create test -uroot -pmysqladmin12'.split()):
    print line,
105
vartec

La réponse de Vartec ne lit pas toutes les lignes. J'ai donc créé une version qui:

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

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

command = 'mysqladmin create test -uroot -pmysqladmin12'.split()
for line in run_command(command):
    print(line)
63
Max Ekman

Ceci est une solution délicate mais super simple 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 le résultat de la commande et vous pouvez y lire le résultat souhaité.

Remarque supplémentaire à partir des commentaires: vous pouvez supprimer le fichier tmp dans le cas d'un travail ponctuel. Si vous devez le faire plusieurs fois, il n'est pas nécessaire de supprimer le tmp.

os.remove('tmp')
47
Mehdi1902

Dans Python 3.5:

import subprocess

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

J'ai eu le même problème, mais j'ai trouvé un moyen très simple de faire ceci:

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

J'espère que ça aide

Remarque: Cette solution est spécifique à Python3 car subprocess.getoutput() ne fonctionne pas en Python2.

20
itz-azhar

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

Note: Ceci est obsolète depuis python 2.6. Vous devez maintenant utiliser subprocess.Popen. Ci-dessous l'exemple

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 solution (> = 3.1):

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

Votre kilométrage peut varier, j'ai essayé de faire évoluer la solution Vartec sous Windows de @ senderle sur Python 2.6.5, mais des erreurs se sont produites et aucune autre solution n'a fonctionné. Mon erreur était: WindowsError: [Error 6] The handle is invalid.

J'ai constaté que je devais affecter PIPE à chaque poignée pour qu'elle renvoie le résultat attendu: les éléments 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 en avoir appris davantage, je pense avoir besoin de ces arguments de canal, car je travaille sur un système personnalisé qui utilise des poignées différentes. J'ai donc dû contrôler directement toutes les normes.

Pour arrêter les popups de la console (avec Windows), procédez comme suit:

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

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

  1. Capturez et renvoyez les messages STDOUT au fur et à mesure qu'ils s'accumulent dans la mémoire tampon STDOUT (c'est-à-dire en temps réel).
    • @ vartec a résolu ce problème pythoniquement avec son utilisation des générateurs et le 'rendement'
      mot-clé ci-dessus
  2. Imprimer toutes les lignes STDOUT ( même si le processus est terminé avant que le tampon STDOUT ne puisse être entièrement lu )
  3. Ne perdez pas de cycle de processeur en interrogeant le processus à haute fréquence
  4. Vérifiez le code de retour du sous-processus
  5. Imprimez STDERR (séparé de STDOUT) si nous obtenons un code de retour d'erreur non nul.

J'ai combiné et peaufiné les réponses précédentes pour arriver à ce qui suit:

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é de la même manière que les réponses précédentes:

for line in run_command(cmd):
    print(line)
9
The Aelfinn

Fractionner la commande initiale pour le subprocess pourrait être difficile et fastidieux.

Utilisez shlex.split() pour vous aider.

exemple de commande

git log -n 5 --since "5 years ago" --until "2 year ago"

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

Sans shlex.split() le code ressemblerait à ceci

res = check_output([
    'git', 
    'log', 
    '-n', 
    '5', 
    '--since', 
    '5 years ago', 
    '--until', 
    '2 year ago'
])
print(res)
>>> b'commit 7696ab087a163e084d6870bb4e5e4d4198bdc61a\nAuthor: Artur Barseghyan...'
3
Artur Barseghyan

Si vous avez besoin d'exécuter une commande Shell sur plusieurs fichiers, c'est ce qui a été fait 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: Je viens de voir la solution de Max Persson avec la suggestion de J.F. Sebastian. Je suis allé de l'avant et incorporé cela.

3
Ethan Strider

Selon @senderle, si vous utilisez python3.6 comme moi:

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

Agira exactement comme vous exécutez la commande dans bash

2
Neo li

Si vous utilisez le module subprocess python, vous pouvez gérer séparément les commandes STDOUT, STDERR et le code de retour de la commande. Vous pouvez voir un exemple d'implémentation complète de l'appelant de commande. Vous pouvez bien sûr l'étendre avec try..except si vous le souhaitez.

La fonction ci-dessous renvoie les codes STDOUT, STDERR et Return afin que vous puissiez les gérer dans l'autre script.

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

La sortie peut être redirigée vers un fichier texte, puis relue.

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.

par exemple, execute ('ls -ahl') différencié trois/quatre retours possibles et plates-formes de système d'exploitation:

  1. pas de sortie, mais exécuté avec succès
  2. sortie ligne vide, exécution réussie
  3. course a échoué
  4. afficher quelque chose, 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
Jerry T