Question Exécution de la commande shell à partir de Python et capture de la sortie


Je veux écrire une fonction qui exécutera une commande shell et retournera sa sortie comme une chaîne, 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'

598
2018-01-21 14:55


origine


Réponses:


La réponse à cette question dépend de la version de Python que vous utilisez. L'approche la plus simple consiste à utiliser subprocess.check_output fonction:

>>> 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 prend uniquement les arguments en entrée.1 Il renvoie le résultat exactement comme imprimé à stdout. Si vous devez écrire une entrée pour stdin, passez à la run ou Popen sections. Si vous voulez exécuter des commandes shell complexes, voir la note sur shell=True à la fin de cette réponse.

le check_output La fonction 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 supérieur): run

Si vous utilisez Python 3.5 ou plus haut, et ne nécessite pas de compatibilité ascendante, la Nouveau run fonction est recommandé. Il fournit une API très générale de haut niveau pour subprocess module. Pour capturer la sortie d'un programme, passez le subprocess.PIPE drapeau à la stdout argument mot-clé. Ensuite, accédez à stdout attribut du retour CompletedProcess objet:

>>> 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 bytes objet, donc si vous voulez une chaîne correcte, vous aurez besoin de decode il. En supposant que le processus appelé renvoie 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 seule 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 l'entrée au processus stdin, passez un bytes objet à la input argument mot-clé:

>>> 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 les erreurs en passant stderr=subprocess.PIPE (capture à result.stderr) ou stderr=subprocess.STDOUT (capture à result.stdout avec la production 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 juste un peu de complexité, comparé à 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 le run fonctionner seul.

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 rétrocompatibilité modeste, vous pouvez probablement utiliser check_output fonctionnent comme décrit brièvement ci-dessus. Il est disponible depuis Python 2.7.

subprocess.check_output(*popenargs, **kwargs)  

Cela 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 a un exemple d'utilisation plus détaillé.

Tu peux passer stderr=subprocess.STDOUT pour s'assurer que les messages d'erreur sont inclus dans la sortie retournée - mais ne passent pas stderr=subprocess.PIPE à check_output. Il peut causer deadlocks. 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 tuyaux de stderr ou passer une entrée au processus, check_output ne sera pas à la hauteur de la tâche. Voir le Popen exemples ci-dessous dans ce cas.

Applications complexes et versions héritées de Python (2.6 et ci-dessous): Popen

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

le Popen constructeur accepte soit une seule commande sans arguments, ou 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 dans la liste. shlex.split peut aider à analyser les chaînes dans des listes correctement formatées. Popen objets acceptent également un hôte de différents arguments pour la gestion des opérations d'E / S et la configuration de bas niveau.

Pour envoyer l'entrée et capturer la sortie, communicate est presque toujours la méthode préférée. 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 La réponse d'Aaron Hall, ce qui indique que sur certains systèmes, vous devrez peut-être définir stdout, stderr, et stdin tout à PIPE (ou DEVNULL) obtenir communicate travailler du tout.

Dans certains cas rares, vous pouvez avoir besoin d'une capture de sortie complexe et en temps réel. Vartecla réponse suggère une voie à suivre, mais des méthodes autres que communicate sont sujettes à des blocages s'ils ne sont pas utilisés avec soin.

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

Remarques

1. Exécuter des commandes shell: le shell=True argument

Normalement, chaque appel à run, check_output, ou la Popen constructeur exécute un programme unique. Cela signifie pas de pipes de style bash. Si vous voulez exécuter des commandes shell complexes, vous pouvez passer shell=True, que les trois fonctions prennent en charge.

Cependant, cela augmente préoccupations de sécurité. Si vous faites autre chose que des scripts légers, vous feriez peut-être 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 connecter directement les tuyaux est forte; Résiste. Sinon, vous risquez de voir des blocages ou d'avoir à faire des choses hacky comme ce.

2. Considérations Unicode

check_output retourne une chaîne dans Python 2, mais bytes objet dans Python 3. Cela vaut la peine de prendre un moment pour En savoir plus sur Unicode si tu ne l'as pas déjà fait.


702
2018-01-21 15:27



C'est beaucoup plus simple, 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)


171
2018-02-13 19:41



Quelque chose comme ca:

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 rendements ligne par ligne comme ils viennent (Normalement, vous devrez attendre que le sous-processus se termine 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,

97
2018-01-21 15:02



Vartec réponse ne lit pas toutes les lignes, alors 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'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)

55
2017-10-30 09:24



C'est un rusé 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 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')

33
2017-08-21 21:28



Solution Python moderne (> = 3.1):

 res = subprocess.check_output(lcmd, stderr=subprocess.STDOUT)

12
2018-04-20 20:30



En Python 3.5:

import subprocess

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

11
2017-07-29 20:03



Vous pouvez utiliser les commandes suivantes pour exécuter une commande shell. Je les ai utilisés sur Ubuntu.

import os
os.popen('your command here').read()

11
2018-04-04 19:08



Votre kilométrage peut varier, j'ai essayé @ spin de senderle sur la solution de Vartec dans Windows sur Python 2.6.5, mais je recevais 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 PIPE à chaque poignée pour l'obtenir pour retourner la sortie que je m'attendais - ce qui suit a 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 ça, ([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 de tuyau parce que je travaille sur un système personnalisé qui utilise des poignées différentes, donc j'ai dû contrôler directement tous les std.

Pour arrêter les popups de la 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
2018-02-18 23:32