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'
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.
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.
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.
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
_.
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à.
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
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,
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)
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')
Dans Python 3.5:
import subprocess
output = subprocess.run("ls -l", Shell=True, stdout=subprocess.PIPE,
universal_newlines=True)
print(output.stdout)
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.
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")
Moderne Python solution (> = 3.1):
import subprocess
res = subprocess.check_output(lcmd, stderr=subprocess.STDOUT)
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')
J'ai eu un goût légèrement différent du même problème avec les exigences suivantes:
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)
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...'
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.
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
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
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))
par exemple, execute ('ls -ahl') différencié trois/quatre retours possibles et plates-formes de système d'exploitation:
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 ""