Pour développer un peu les réponses précédentes, il y a un certain nombre de détails qui sont généralement négligés.
- Préférez
subprocess.run()
plus subprocess.check_call()
et des amis subprocess.call()
sur subprocess.Popen()
plus de os.system()
plusos.popen()
- Comprendre et probablement utiliser
text=True
, alias universal_newlines=True
.
- Comprendre le sens de
shell=True
ou shell=False
et comment elle change de devis et la disponibilité des commodités shell.
- Comprendre les différences entre
sh
et Bash
- Comprendre comment un sous-processus est séparé de son parent et ne peut généralement pas changer le parent.
- Évitez d'exécuter l'interpréteur Python en tant que sous-processus de Python.
Ces sujets sont traités plus en détail ci-dessous.
Préférez subprocess.run()
ousubprocess.check_call()
La subprocess.Popen()
fonction est un bourreau de travail de bas niveau, mais elle est difficile à utiliser correctement et vous finissez par copier / coller plusieurs lignes de code ... qui existent déjà dans la bibliothèque standard en tant qu'ensemble de fonctions d'encapsulation de niveau supérieur à diverses fins, qui sont présentés plus en détail dans ce qui suit.
Voici un paragraphe de la documentation :
L'approche recommandée pour invoquer des sous-processus consiste à utiliser la run()
fonction pour tous les cas d'utilisation qu'elle peut gérer. Pour des cas d'utilisation plus avancés, l' Popen
interface sous-jacente peut être utilisée directement.
Malheureusement, la disponibilité de ces fonctions d'encapsuleur diffère selon les versions de Python.
subprocess.run()
a été officiellement introduit dans Python 3.5. Il est destiné à remplacer tous les éléments suivants.
subprocess.check_output()
a été introduit dans Python 2.7 / 3.1. Il est fondamentalement équivalent àsubprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
subprocess.check_call()
a été introduit dans Python 2.5. Il est fondamentalement équivalent àsubprocess.run(..., check=True)
subprocess.call()
a été introduit en Python 2.4 dans le subprocess
module d' origine ( PEP-324 ). Il est fondamentalement équivalent àsubprocess.run(...).returncode
API de haut niveau vs subprocess.Popen()
Le refactorisé et étendu subprocess.run()
est plus logique et plus polyvalent que les anciennes fonctions héritées qu'il remplace. Il renvoie un CompletedProcess
objet qui a diverses méthodes qui vous permettent de récupérer l'état de sortie, la sortie standard et quelques autres résultats et indicateurs d'état du sous-processus terminé.
subprocess.run()
est le chemin à parcourir si vous avez simplement besoin d'un programme pour exécuter et retourner le contrôle à Python. Pour des scénarios plus complexes (processus d'arrière-plan, peut-être avec des E / S interactives avec le programme parent Python), vous devez toujours utiliser subprocess.Popen()
et prendre soin de toute la plomberie vous-même. Cela nécessite une compréhension assez complexe de toutes les pièces mobiles et ne doit pas être entrepris à la légère. L' Popen
objet le plus simple représente le processus (peut-être encore en cours d'exécution) qui doit être géré à partir de votre code pour le reste de la durée de vie du sous-processus.
Il convient peut-être de souligner que ne fait subprocess.Popen()
que créer un processus. Si vous vous en tenez à cela, vous avez un sous-processus qui s'exécute simultanément avec Python, donc un processus "d'arrière-plan". S'il n'a pas besoin de faire d'entrée ou de sortie ou de se coordonner avec vous, il peut faire un travail utile en parallèle avec votre programme Python.
Évitez os.system()
etos.popen()
Depuis des temps éternels (enfin, depuis Python 2.5), la os
documentation du module contient la recommandation de préférer subprocess
à os.system()
:
Le subprocess
module offre 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.
Le problème system()
est qu'il est évidemment dépendant du système et n'offre pas de moyens d'interagir avec le sous-processus. Il s'exécute simplement, avec une sortie standard et une erreur standard hors de portée de Python. La seule information que Python reçoit est l'état de sortie de la commande (zéro signifie succès, bien que la signification des valeurs non nulles soit également quelque peu dépendante du système).
PEP-324 (qui a déjà été mentionné ci-dessus) contient une justification plus détaillée des raisons pour lesquelles cela os.system
pose problème et comment les subprocess
tentatives de résoudre ces problèmes.
os.popen()
autrefois encore plus fortement découragé :
Déconseillé depuis la version 2.6: cette fonction est obsolète. Utilisez le subprocess
module.
Cependant, depuis un certain temps en Python 3, il a été réimplémenté pour simplement utiliser subprocess
et redirige vers la subprocess.Popen()
documentation pour plus de détails.
Comprendre et utiliser généralement check=True
Vous remarquerez également que subprocess.call()
plusieurs des mêmes limitations que os.system()
. En utilisation régulière, vous devez généralement vérifier si le processus s'est terminé avec succès, lequel subprocess.check_call()
et subprocess.check_output()
faire (où ce dernier renvoie également la sortie standard du sous-processus terminé). De même, vous devez généralement utiliser check=True
avec subprocess.run()
sauf si vous devez spécifiquement autoriser le sous-processus à renvoyer un état d'erreur.
En pratique, avec check=True
ou subprocess.check_*
, Python lèvera une CalledProcessError
exception si le sous-processus renvoie un état de sortie différent de zéro.
Une erreur courante avec subprocess.run()
est d'omettre check=True
et d'être surpris lorsque le code en aval échoue si le sous-processus a échoué.
D'un autre côté, un problème commun avec check_call()
et check_output()
était que les utilisateurs qui utilisaient aveuglément ces fonctions étaient surpris lorsque l'exception était levée, par exemple lorsqu'ils grep
ne trouvaient pas de correspondance. (Vous devriez probablement remplacer le grep
code Python natif de toute façon, comme indiqué ci-dessous.)
Tout compte, vous devez comprendre comment les commandes shell renvoient un code de sortie, et dans quelles conditions elles renverront un code de sortie non nul (erreur), et prendre une décision consciente sur la façon exacte dont elle doit être gérée.
Comprendre et probablement utiliser text=True
akauniversal_newlines=True
Depuis Python 3, les chaînes internes à Python sont des chaînes Unicode. Mais il n'y a aucune garantie qu'un sous-processus génère une sortie Unicode ou des chaînes du tout.
(Si les différences ne sont pas immédiatement évidentes, la lecture Unicode Pragmatique de Ned Batchelder est recommandée, sinon carrément obligatoire. Il y a une présentation vidéo de 36 minutes derrière le lien si vous préférez, bien que la lecture de la page vous-même prenne probablement beaucoup moins de temps. )
Au fond, Python doit récupérer un bytes
tampon et l'interpréter d'une manière ou d'une autre. S'il contient un blob de données binaires, il ne doit pas être décodé en une chaîne Unicode, car c'est un comportement sujet aux erreurs et induisant des bogues - précisément le genre de comportement embêtant qui a criblé de nombreux scripts Python 2, avant qu'il n'y ait un moyen de distinguer correctement entre le texte codé et les données binaires.
Avec text=True
, vous dites à Python que vous attendez en fait des données textuelles dans l'encodage par défaut du système et qu'elles doivent être décodées en une chaîne Python (Unicode) au mieux des capacités de Python (généralement UTF-8 sur toute modérément jusqu'à système de date, sauf peut-être Windows?)
Si ce n'est pas ce que vous demandez en retour, Python vous donnera simplement des bytes
chaînes dans les chaînes stdout
et stderr
. Peut - être à un moment plus tard , vous ne savez qu'ils étaient des chaînes de texte après tout, et vous connaissez leur encodage. Ensuite, vous pouvez les décoder.
normal = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True,
text=True)
print(normal.stdout)
convoluted = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True)
# You have to know (or guess) the encoding
print(convoluted.stdout.decode('utf-8'))
Python 3.7 a introduit l'alias plus court et plus descriptif et compréhensible text
pour l'argument mot-clé qui était auparavant appelé de manière quelque peu trompeuse universal_newlines
.
Comprendre shell=True
vsshell=False
Avec shell=True
vous passez une seule chaîne à votre shell, et le shell le prend à partir de là.
Avec shell=False
vous passez une liste d'arguments au système d'exploitation, en contournant le shell.
Lorsque vous n'avez pas de shell, vous enregistrez un processus et vous débarrassez d'une quantité assez importante de complexité cachée, qui peut ou non héberger des bogues ou même des problèmes de sécurité.
D'un autre côté, lorsque vous n'avez pas de shell, vous n'avez pas de redirection, d'extension générique, de contrôle des travaux et d'un grand nombre d'autres fonctionnalités du shell.
Une erreur courante consiste à utiliser shell=True
puis à transmettre à Python une liste de jetons, ou vice versa. Cela se produit dans certains cas, mais est vraiment mal défini et pourrait se briser de manière intéressante.
# XXX AVOID THIS BUG
buggy = subprocess.run('dig +short stackoverflow.com')
# XXX AVOID THIS BUG TOO
broken = subprocess.run(['dig', '+short', 'stackoverflow.com'],
shell=True)
# XXX DEFINITELY AVOID THIS
pathological = subprocess.run(['dig +short stackoverflow.com'],
shell=True)
correct = subprocess.run(['dig', '+short', 'stackoverflow.com'],
# Probably don't forget these, too
check=True, text=True)
# XXX Probably better avoid shell=True
# but this is nominally correct
fixed_but_fugly = subprocess.run('dig +short stackoverflow.com',
shell=True,
# Probably don't forget these, too
check=True, text=True)
La réplique commune "mais ça marche pour moi" n'est pas une réfutation utile à moins que vous ne compreniez exactement dans quelles circonstances elle pourrait cesser de fonctionner.
Exemple de refactoring
Très souvent, les fonctionnalités du shell peuvent être remplacées par du code natif Python. De simples Awk ou sed
scripts devraient probablement être simplement traduits en Python à la place.
Pour illustrer partiellement cela, voici un exemple typique mais légèrement idiot qui implique de nombreuses fonctionnalités de shell.
cmd = '''while read -r x;
do ping -c 3 "$x" | grep 'round-trip min/avg/max'
done <hosts.txt'''
# Trivial but horrible
results = subprocess.run(
cmd, shell=True, universal_newlines=True, check=True)
print(results.stdout)
# Reimplement with shell=False
with open('hosts.txt') as hosts:
for host in hosts:
host = host.rstrip('\n') # drop newline
ping = subprocess.run(
['ping', '-c', '3', host],
text=True,
stdout=subprocess.PIPE,
check=True)
for line in ping.stdout.split('\n'):
if 'round-trip min/avg/max' in line:
print('{}: {}'.format(host, line))
Quelques points à noter ici:
- Avec
shell=False
vous n'avez pas besoin des guillemets que le shell requiert autour des chaînes. Mettre des guillemets de toute façon est probablement une erreur.
- Il est souvent judicieux d'exécuter le moins de code possible dans un sous-processus. Cela vous donne plus de contrôle sur l'exécution à partir de votre code Python.
- Cela dit, les pipelines shell complexes sont fastidieux et parfois difficiles à réimplémenter en Python.
Le code refactorisé illustre également à quel point le shell fait vraiment pour vous avec une syntaxe très laconique - pour le meilleur ou pour le pire. Python dit qu'explicite est meilleur qu'implicite, mais le code Python est plutôt verbeux et semble sans doute plus complexe qu'il ne l'est réellement. D'autre part, il offre un certain nombre de points où vous pouvez prendre le contrôle au milieu d'autre chose, comme en témoigne trivialement l'amélioration que nous pouvons facilement inclure le nom d'hôte avec la sortie de la commande shell. (Ce n'est en aucun cas difficile à faire dans la coquille non plus, mais au détriment d'un autre détournement et peut-être d'un autre processus.)
Constructions Shell communes
Pour être complet, voici de brèves explications sur certaines de ces fonctionnalités du shell, et quelques notes sur la façon dont elles peuvent peut-être être remplacées par des fonctionnalités natives Python.
- L'extension Globbing aka wildcard peut être remplacée par
glob.glob()
ou très souvent par de simples comparaisons de chaînes Python comme for file in os.listdir('.'): if not file.endswith('.png'): continue
. Bash a diverses autres possibilités d'expansion comme l' .{png,jpg}
expansion des accolades et l'expansion {1..100}
de tilde ( ~
s'étend à votre répertoire personnel et plus généralement ~account
au répertoire personnel d'un autre utilisateur)
- Les variables shell comme
$SHELL
ou $my_exported_var
peuvent parfois simplement être remplacées par des variables Python. Exportés variables shell sont disponibles comme par exemple os.environ['SHELL']
(le sens de export
est de rendre la variable disponible pour les sous - processus -. Une variable qui n'est pas disponible pour les sous - processus ne sera évidemment pas disponible pour Python en cours d' exécution en tant que sous - processus de la coquille, ou vice - versa Le env=
mot - clé L'argument des subprocess
méthodes vous permet de définir l'environnement du sous-processus comme un dictionnaire, c'est donc une façon de rendre une variable Python visible pour un sous-processus). Avec shell=False
vous devrez comprendre comment supprimer les devis; par exemple, cd "$HOME"
équivaut à os.chdir(os.environ['HOME'])
sans guillemets autour du nom du répertoire. (Très souventcd
n'est pas utile ou nécessaire de toute façon, et de nombreux débutants omettent les guillemets doubles autour de la variable et s'en tirent jusqu'au jour ... )
- La redirection vous permet de lire à partir d'un fichier comme entrée standard et d'écrire votre sortie standard dans un fichier.
grep 'foo' <inputfile >outputfile
s'ouvre outputfile
pour l'écriture et la inputfile
lecture, et passe son contenu comme entrée standard à grep
, dont la sortie standard atterrit ensuite outputfile
. Ce n'est généralement pas difficile à remplacer par du code Python natif.
- Les pipelines sont une forme de redirection.
echo foo | nl
exécute deux sous-processus, où la sortie standard de echo
est l'entrée standard de nl
(au niveau du système d'exploitation, dans les systèmes de type Unix, il s'agit d'un descripteur de fichier unique). Si vous ne pouvez pas remplacer une ou les deux extrémités du pipeline par du code Python natif, pensez peut-être à utiliser un shell après tout, surtout si le pipeline a plus de deux ou trois processus (bien que regardez le pipes
module dans la bibliothèque standard Python ou un certain nombre de concurrents tiers plus modernes et plus polyvalents).
- Le contrôle des tâches vous permet d'interrompre des tâches, de les exécuter en arrière-plan, de les remettre au premier plan, etc. Les signaux Unix de base pour arrêter et poursuivre un processus sont bien sûr également disponibles depuis Python. Mais les travaux sont une abstraction de niveau supérieur dans le shell qui implique des groupes de processus, etc. que vous devez comprendre si vous voulez faire quelque chose comme ça à partir de Python.
- Citer dans le shell est potentiellement déroutant jusqu'à ce que vous compreniez que tout est fondamentalement une chaîne. So
ls -l /
est équivalent à 'ls' '-l' '/'
mais la citation autour des littéraux est complètement facultative. Les chaînes non cotées qui contiennent des métacaractères shell subissent une expansion des paramètres, une tokenisation des espaces blancs et une expansion des caractères génériques; les guillemets doubles empêchent la tokenisation des espaces blancs et l'expansion des caractères génériques mais permettent des extensions de paramètres (substitution de variables, substitution de commandes et traitement de barre oblique inverse). C'est simple en théorie mais peut devenir déroutant, surtout quand il y a plusieurs couches d'interprétation (une commande shell distante, par exemple).
Comprendre les différences entre sh
et Bash
subprocess
exécute vos commandes shell à /bin/sh
moins que vous ne demandiez spécifiquement le contraire (sauf bien sûr sous Windows, où il utilise la valeur de la COMSPEC
variable). Cela signifie que diverses fonctionnalités uniquement Bash comme les tableaux, [[
etc. ne sont pas disponibles.
Si vous devez utiliser la syntaxe Bash uniquement, vous pouvez passer le chemin d'accès au shell en tant que executable='/bin/bash'
(où bien sûr si votre Bash est installé ailleurs, vous devez ajuster le chemin d'accès).
subprocess.run('''
# This for loop syntax is Bash only
for((i=1;i<=$#;i++)); do
# Arrays are Bash-only
array[i]+=123
done''',
shell=True, check=True,
executable='/bin/bash')
A subprocess
est distinct de son parent et ne peut pas le modifier
Une erreur quelque peu courante consiste à faire quelque chose comme
subprocess.run('foo=bar', shell=True)
subprocess.run('echo "$foo"', shell=True) # Doesn't work
qui, outre le manque d'élégance, trahit également un manque fondamental de compréhension de la partie "sous" du nom "sous-processus".
Un processus enfant s'exécute complètement séparément de Python, et lorsqu'il se termine, Python n'a aucune idée de ce qu'il a fait (à part les vagues indicateurs qu'il peut déduire de l'état de sortie et de la sortie du processus enfant). Un enfant ne peut généralement pas changer l'environnement des parents; il ne peut pas définir une variable, changer le répertoire de travail ou, en autant de mots, communiquer avec son parent sans la coopération du parent.
La solution immédiate dans ce cas particulier consiste à exécuter les deux commandes dans un seul sous-processus;
subprocess.run('foo=bar; echo "$foo"', shell=True)
bien évidemment, ce cas d'utilisation particulier ne nécessite pas du tout du shell. N'oubliez pas que vous pouvez manipuler l'environnement du processus en cours (et donc aussi ses enfants) via
os.environ['foo'] = 'bar'
ou passer un paramètre d'environnement à un processus enfant avec
subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})
(sans parler de la refactorisation évidente subprocess.run(['echo', 'bar'])
; mais echo
c'est un mauvais exemple de quelque chose à exécuter dans un sous-processus en premier lieu, bien sûr).
Ne pas exécuter Python à partir de Python
Il s'agit d'un conseil légèrement douteux; il y a certainement des situations où cela a du sens ou est même une exigence absolue pour exécuter l'interpréteur Python en tant que sous-processus à partir d'un script Python. Mais très souvent, l'approche correcte consiste simplement à import
l'autre module Python dans votre script appelant et à appeler ses fonctions directement.
Si l'autre script Python est sous votre contrôle et qu'il ne s'agit pas d'un module, envisagez de le transformer en un seul . (Cette réponse est déjà trop longue, je ne vais donc pas entrer dans les détails ici.)
Si vous avez besoin de parallélisme, vous pouvez exécuter des fonctions Python dans des sous-processus avec le multiprocessing
module. Il y a aussi threading
qui exécute plusieurs tâches dans un seul processus (ce qui est plus léger et vous donne plus de contrôle, mais aussi plus contraint dans la mesure où les threads d'un processus sont étroitement couplés et liés à un seul GIL .)
cwm
. Peut-être avez-vous une configuration.bashrc
qui configure l'environnement pour une utilisation bash interactive?