Comment appeler une commande externe (comme si je l'avais tapée au shell Unix ou à l'invite de commande Windows) à partir d'un script Python?
27 réponses
Regardez le module sous-processus dans la bibliothèque standard:
import subprocess
subprocess.run(["ls", "-l"])
L'avantage de subprocess
par rapport à system
est qu'il est plus flexible (vous pouvez obtenir le stdout
, stderr
, le "vrai" code d'état, une meilleure gestion des erreurs, etc. ...).
La documentation officielle recommande le module subprocess
par rapport à l'alternative { {X1}}:
Le module
subprocess
fournit des fonctionnalités plus puissantes pour générer de nouveaux processus et récupérer leurs résultats; l'utilisation de ce module est préférable à l'utilisation de cette fonction [os.system()
].
Remplacement de fonctions plus anciennes par le module de sous-processus dans la documentation subprocess
peut contenir des recettes utiles.
Pour les versions de Python antérieures à 3.5, utilisez call
:
import subprocess
subprocess.call(["ls", "-l"])
J'ai tendance à utiliser le sous-processus avec shlex (pour gérer l'échappement des chaînes entre guillemets):
>>> import subprocess, shlex
>>> command = 'ls -l "/your/path/with spaces/"'
>>> call_params = shlex.split(command)
>>> print call_params
["ls", "-l", "/your/path/with spaces/"]
>>> subprocess.call(call_params)
subprocess.check_call
est pratique si vous ne souhaitez pas tester les valeurs de retour. Il lève une exception sur toute erreur.
Quelques conseils pour détacher le processus enfant de l'appelant (démarrage du processus enfant en arrière-plan).
Supposons que vous souhaitiez démarrer une longue tâche à partir d'un script CGI. Autrement dit, le processus enfant doit vivre plus longtemps que le processus d'exécution de script CGI.
L'exemple classique de la documentation du module de sous-processus est:
import subprocess
import sys
# Some code here
pid = subprocess.Popen([sys.executable, "longtask.py"]) # Call subprocess
# Some more code here
L'idée ici est que vous ne voulez pas attendre dans la ligne 'call subprocess' jusqu'à ce que le longtask.py soit terminé. Mais on ne sait pas ce qui se passe après la ligne «un peu plus de code ici» de l'exemple.
Ma plate-forme cible était FreeBSD, mais le développement était sur Windows, donc j'ai d'abord rencontré le problème sur Windows.
Sous Windows (Windows XP), le processus parent ne se terminera pas tant que le fichier longtask.py n'aura pas terminé son travail. Ce n'est pas ce que vous voulez dans un script CGI. Le problème n'est pas spécifique à Python; dans la communauté PHP, les problèmes sont les mêmes.
La solution consiste à passer DETACHED_PROCESS Création de processus Marquer à la fonction CreateProcess sous-jacente dans l'API Windows. Si vous avez installé pywin32, vous pouvez importer l'indicateur du module win32process, sinon vous devez le définir vous-même:
DETACHED_PROCESS = 0x00000008
pid = subprocess.Popen([sys.executable, "longtask.py"],
creationflags=DETACHED_PROCESS).pid
/ * UPD 2015.10.27 @eryksun dans un commentaire ci-dessous note que l'indicateur sémantiquement correct est CREATE_NEW_CONSOLE (0x00000010) * /
Sur FreeBSD, nous avons un autre problème: lorsque le processus parent est terminé, il termine également les processus enfants. Et ce n'est pas non plus ce que vous voulez dans un script CGI. Certaines expériences ont montré que le problème semblait être lié au partage de sys.stdout. Et la solution de travail était la suivante:
pid = subprocess.Popen([sys.executable, "longtask.py"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
Je n'ai pas vérifié le code sur d'autres plateformes et je ne connais pas les raisons du comportement sur FreeBSD. Si quelqu'un le sait, partagez vos idées. Googler sur le démarrage de processus d'arrière-plan en Python ne fait pas encore la lumière.
Avec la bibliothèque standard
Utilisez le module de sous-processus (Python 3):
import subprocess
subprocess.run(['ls', '-l'])
C'est la voie standard recommandée. Cependant, des tâches plus compliquées (canaux, sortie, entrée, etc.) peuvent être fastidieuses à construire et à écrire.
Remarque sur la version Python: si vous utilisez toujours Python 2, sous-processus .call fonctionne de la même manière.
ProTip: shlex.split peut vous aider à analyser la commande pour run
, call
et d'autres fonctions subprocess
au cas où vous ne voudriez pas (ou vous ne pouvez pas!) les fournir sous forme de listes:
import shlex
import subprocess
subprocess.run(shlex.split('ls -l'))
Avec des dépendances externes
Si cela ne vous dérange pas, utilisez les plumbum:
from plumbum.cmd import ifconfig
print(ifconfig['wlan0']())
C'est le meilleur wrapper subprocess
. Il est multiplateforme, c'est-à-dire qu'il fonctionne à la fois sur les systèmes Windows et Unix. Installez d'ici pip install plumbum
.
Une autre bibliothèque populaire est sh:
from sh import ifconfig
print(ifconfig('wlan0'))
Cependant, sh
a abandonné la prise en charge de Windows, donc ce n'est plus aussi génial qu'avant. Installez d'ici pip install sh
.
Si vous avez besoin de la sortie de la commande que vous appelez, vous pouvez ensuite utiliser subprocess.check_output (Python 2.7+).
>>> subprocess.check_output(["ls", "-l", "/dev/null"])
'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n'
Notez également le paramètre shell.
Si le shell est
True
, la commande spécifiée sera exécutée via le shell. Cela peut être utile si vous utilisez Python principalement pour le flux de contrôle amélioré qu'il offre sur la plupart des shells du système et que vous souhaitez toujours accéder facilement à d'autres fonctionnalités de shell telles que les tuyaux de shell, les caractères génériques de nom de fichier, l'expansion des variables d'environnement et l'extension de ~ au domicile d'un utilisateur annuaire. Cependant, notez que Python lui-même propose des implémentations de nombreuses fonctionnalités de type shell (en particulier,glob
,fnmatch
,os.walk()
,os.path.expandvars()
,os.path.expanduser()
,os.path.expanduser()
, et { {X6}}).
Cela peut être aussi simple:
import os
cmd = "your command"
os.system(cmd)
Appel d'une commande externe en Python
Simple, utilisez subprocess.run
, qui retourne un objet CompletedProcess
:
>>> import subprocess
>>> completed_process = subprocess.run('python --version')
Python 3.6.1 :: Anaconda 4.4.0 (64-bit)
>>> completed_process
CompletedProcess(args='python --version', returncode=0)
Pourquoi?
Depuis Python 3.5, la documentation recommande subprocess.run:
L'approche recommandée pour invoquer des sous-processus consiste à utiliser la fonction run () pour tous les cas d'utilisation qu'elle peut gérer. Pour des cas d'utilisation plus avancés, l'interface Popen sous-jacente peut être utilisée directement.
Voici un exemple d'utilisation la plus simple possible - et il fait exactement comme demandé:
>>> import subprocess
>>> completed_process = subprocess.run('python --version')
Python 3.6.1 :: Anaconda 4.4.0 (64-bit)
>>> completed_process
CompletedProcess(args='python --version', returncode=0)
run
attend que la commande se termine avec succès, puis renvoie un objet CompletedProcess
. Il peut à la place lever TimeoutExpired
(si vous lui donnez un argument timeout=
) ou CalledProcessError
(s'il échoue et que vous passez check=True
).
Comme vous pouvez le déduire de l'exemple ci-dessus, stdout et stderr sont tous deux dirigés par défaut vers vos propres stdout et stderr par défaut.
Nous pouvons inspecter l'objet retourné et voir la commande qui a été donnée et le code retour:
>>> completed_process.args
'python --version'
>>> completed_process.returncode
0
Capture de sortie
Si vous souhaitez capturer la sortie, vous pouvez passer subprocess.PIPE
aux stderr
ou stdout
appropriés:
>>> cp = subprocess.run('python --version',
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
>>> cp.stderr
b'Python 3.6.1 :: Anaconda 4.4.0 (64-bit)\r\n'
>>> cp.stdout
b''
(Je trouve intéressant et légèrement contre-intuitif que les informations de version soient mises sur stderr au lieu de stdout.)
Passer une liste de commandes
On peut facilement passer de la fourniture manuelle d'une chaîne de commande (comme le suggère la question) à la fourniture d'une chaîne construite par programme. Ne créez pas de chaînes par programmation. Il s'agit d'un problème de sécurité potentiel. Il vaut mieux supposer que vous ne faites pas confiance à l'entrée.
>>> import textwrap
>>> args = ['python', textwrap.__file__]
>>> cp = subprocess.run(args, stdout=subprocess.PIPE)
>>> cp.stdout
b'Hello there.\r\n This is indented.\r\n'
Remarque, seul args
doit être transmis en position.
Signature complète
Voici la signature réelle dans la source et comme indiqué par help(run)
:
def run(*popenargs, input=None, timeout=None, check=False, **kwargs):
Les popenargs
et kwargs
sont donnés au constructeur Popen
. input
peut être une chaîne d'octets (ou unicode, si spécifiez le codage ou universal_newlines=True
) qui sera redirigée vers le stdin du sous-processus.
La documentation décrit timeout=
et check=True
mieux que moi:
L'argument timeout est passé à Popen.communicate (). Si le délai expire, le processus enfant sera tué et attendu. L'exception TimeoutExpired sera relancée une fois le processus enfant terminé.
Si la vérification est vraie et que le processus se termine avec un code de sortie différent de zéro, une exception CalledProcessError sera déclenchée. Les attributs de cette exception contiennent les arguments, le code de sortie et stdout et stderr s'ils ont été capturés.
Et cet exemple pour check=True
est meilleur que celui que j'ai pu trouver:
>>> subprocess.run("exit 1", shell=True, check=True) Traceback (most recent call last): ... subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
Signature étendue
Voici une signature étendue, comme indiqué dans la documentation:
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None)
Notez que cela indique que seule la liste d'arguments doit être passée positionnellement. Passez donc les arguments restants comme arguments de mots clés.
Popen
Quand utiliser Popen
à la place? J'aurais du mal à trouver un cas d'utilisation basé uniquement sur les arguments. L'utilisation directe de Popen
vous donnerait cependant accès à ses méthodes, notamment poll
, 'send_signal', 'terminate' et 'wait'.
Voici la signature Popen
donnée dans la source. Je pense que c'est l'encapsulation la plus précise de l'information (par opposition à help(Popen)
):
def __init__(self, args, bufsize=-1, executable=None,
stdin=None, stdout=None, stderr=None,
preexec_fn=None, close_fds=_PLATFORM_DEFAULT_CLOSE_FDS,
shell=False, cwd=None, env=None, universal_newlines=False,
startupinfo=None, creationflags=0,
restore_signals=True, start_new_session=False,
pass_fds=(), *, encoding=None, errors=None):
Mais plus informative est la documentation Popen
:
subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, encoding=None, errors=None)
Exécutez un programme enfant dans un nouveau processus. Sur POSIX, la classe utilise un comportement semblable à os.execvp () pour exécuter le programme enfant. Sous Windows, la classe utilise la fonction Windows CreateProcess (). Les arguments de Popen sont les suivants.
Comprendre la documentation restante sur Popen
restera un exercice pour le lecteur.
Je recommande d'utiliser le module sous-processus au lieu de os.system car il échappe le shell pour vous et est donc beaucoup plus sûr.
subprocess.call(['ping', 'localhost'])
Sous Windows, vous pouvez simplement importer le module subprocess
et exécuter des commandes externes en appelant subprocess.Popen()
, subprocess.Popen().communicate()
et subprocess.Popen().wait()
comme ci-dessous:
# Python script to run a command line
import subprocess
def execute(cmd):
"""
Purpose : To execute a command and return exit status
Argument : cmd - command to execute
Return : exit_code
"""
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(result, error) = process.communicate()
rc = process.wait()
if rc != 0:
print "Error: failed to execute command:", cmd
print error
return result
# def
command = "tasklist | grep python"
print "This process detail: \n", execute(command)
Production:
This process detail:
python.exe 604 RDP-Tcp#0 4 5,660 K
Utilisation:
import os
cmd = 'ls -al'
os.system(cmd)
os - Ce module fournit une manière portable d'utiliser les fonctionnalités dépendantes du système d'exploitation.
Pour les autres os
fonctions, ici est la documentation.
Voici comment j'exécute mes commandes. Ce code a à peu près tout ce dont vous avez besoin
from subprocess import Popen, PIPE
cmd = "ls -l ~/"
p = Popen(cmd , shell=True, stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
print "Return code: ", p.returncode
print out.rstrip(), err.rstrip()
Il y a aussi Plumbum
>>> from plumbum import local
>>> ls = local["ls"]
>>> ls
LocalCommand(<LocalPath /bin/ls>)
>>> ls()
u'build.py\ndist\ndocs\nLICENSE\nplumbum\nREADME.rst\nsetup.py\ntests\ntodo.txt\n'
>>> notepad = local["c:\\windows\\notepad.exe"]
>>> notepad() # Notepad window pops up
u'' # Notepad window is closed by user, command returns
Vérifiez également la bibliothèque Python "pexpect".
Il permet un contrôle interactif des programmes / commandes externes, même ssh, ftp, telnet, etc. Vous pouvez simplement taper quelque chose comme:
child = pexpect.spawn('ftp 192.168.0.24')
child.expect('(?i)name .*: ')
child.sendline('anonymous')
child.expect('(?i)password')
J'utilise toujours fabric
pour ces choses comme:
from fabric.operations import local
result = local('ls', capture=True)
print "Content:/n%s" % (result, )
Mais cela semble être un bon outil: sh
(interface de sous-processus Python).
Regardez un exemple:
from sh import vgdisplay
print vgdisplay()
print vgdisplay('-v')
print vgdisplay(v=True)
import os
cmd = 'ls -al'
os.system(cmd)
Si vous souhaitez renvoyer les résultats de la commande, vous pouvez utiliser {{X0 }}. Cependant, cela est obsolète depuis la version 2.6 en faveur du module de sous-processus , que d'autres réponses ont bien couvert.
Mise à jour:
subprocess.run
est l'approche recommandée à partir de Python 3.5 si votre code n'a pas besoin de maintenir la compatibilité avec les versions antérieures de Python. Il est plus cohérent et offre une facilité d'utilisation similaire à celle d'Envoy. (La tuyauterie n'est cependant pas aussi simple. Voir cette question pour savoir comment.)
Voici quelques exemples de la documentation.
Exécutez un processus:
>>> subprocess.run(["ls", "-l"]) # Doesn't capture output
CompletedProcess(args=['ls', '-l'], returncode=0)
Augmenter en cas d'échec:
>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
Sortie de capture:
>>> subprocess.run(["ls", "-l", "/dev/null"], stdout=subprocess.PIPE)
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n')
Réponse originale:
Je recommande d'essayer Envoy. C'est un wrapper pour le sous-processus, qui à son tour vise à remplacer les anciens modules et les fonctions. Envoy est un sous-processus pour les humains.
Exemple d'utilisation de le README:
>>> r = envoy.run('git config', data='data to pipe in', timeout=2)
>>> r.status_code
129
>>> r.std_out
'usage: git config [options]'
>>> r.std_err
''
Tuyau aussi:
>>> r = envoy.run('uptime | pbcopy')
>>> r.command
'pbcopy'
>>> r.status_code
0
>>> r.history
[<Response 'uptime'>]
J'aime bien shell_command pour sa simplicité. Il est construit au-dessus du module de sous-processus.
Voici un exemple tiré de la documentation:
>>> from shell_command import shell_call
>>> shell_call("ls *.py")
setup.py shell_command.py test_shell_command.py
0
>>> shell_call("ls -l *.py")
-rw-r--r-- 1 ncoghlan ncoghlan 391 2011-12-11 12:07 setup.py
-rw-r--r-- 1 ncoghlan ncoghlan 7855 2011-12-11 16:16 shell_command.py
-rwxr-xr-x 1 ncoghlan ncoghlan 8463 2011-12-11 16:17 test_shell_command.py
0
Plug sans vergogne, j'ai écrit une bibliothèque pour ça: P https://github.com/houqp/shell.py
C'est essentiellement un emballage pour popen et shlex pour l'instant. Il prend également en charge les commandes de canalisation afin que vous puissiez enchaîner les commandes plus facilement en Python. Vous pouvez donc faire des choses comme:
ex('echo hello shell.py') | "awk '{print $2}'"
import os
os.system("your command")
Notez que cela est dangereux, car la commande n'est pas nettoyée. Je vous laisse le soin de google pour la documentation pertinente sur les modules 'os' et 'sys'. Il existe un tas de fonctions (exec * et spawn *) qui feront des choses similaires.
Utilisez le module os:
import os
os.system("your command")
Par exemple,
import os
os.system("ifconfig")
Vous pouvez utiliser Popen, puis vous pouvez vérifier l'état de la procédure:
from subprocess import Popen
proc = Popen(['ls', '-l'])
if proc.poll() is None:
proc.kill()
Consultez subprocess.Popen.
os.system
est OK, mais un peu daté. Ce n'est pas non plus très sûr. Essayez plutôt subprocess
. subprocess
n'appelle pas sh directement et est donc plus sécurisé que os.system
.
Obtenez plus d'informations ici.
Implémentation typique:
import subprocess
p = subprocess.Popen('ls', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in p.stdout.readlines():
print line,
retval = p.wait()
Vous êtes libre de faire ce que vous voulez avec les données stdout
dans le canal. En fait, vous pouvez simplement omettre ces paramètres (stdout=
et stderr=
) et cela se comportera comme os.system()
.
Utilisez le sous-processus .
... ou pour une commande très simple:
import os
os.system('cat testfile')
os.system
ne vous permet pas de stocker les résultats, donc si vous souhaitez stocker les résultats dans une liste ou quelque chose, un subprocess.call
fonctionne.
Sans la sortie du résultat:
import os
os.system("your command here")
Avec sortie du résultat:
import commands
commands.getoutput("your command here")
or
commands.getstatusoutput("your command here")