web-dev-qa-db-fra.com

Exécution de commandes Bash dans Python

Sur ma machine locale, je lance un script python contenant cette ligne.

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
os.system(bashCommand)

Cela fonctionne bien.

Ensuite, je lance le même code sur un serveur et je reçois le message d'erreur suivant

'import site' failed; use -v for traceback
Traceback (most recent call last):
File "/usr/bin/cwm", line 48, in <module>
from swap import  diag
ImportError: No module named swap

J'ai donc inséré un print bashCommand qui m'imprime plus que la commande du terminal avant de l'exécuter avec os.system().

Bien sûr, je reçois à nouveau l'erreur (causée par os.system(bashCommand)), mais avant cette erreur, la commande est imprimée dans le terminal. Ensuite, je viens de copier cette sortie et faire un copier/coller dans le terminal et appuyer sur Entrée et ça marche ...

Quelqu'un at-il une idée de ce qui se passe?

234
mkn

N'utilisez pas os.system. Il a été déconseillé en faveur de sous-processus . À partir de docs : "Ce module vise à remplacer plusieurs modules et fonctions plus anciens: os.system, os.spawn".

Comme dans votre cas:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
import subprocess
process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
output, error = process.communicate()
265
user225312

Pour développer un peu les réponses précédentes, un certain nombre de détails sont souvent négligés.

  • Préférer subprocess.run() sur subprocess.check_call() et les amis sur subprocess.call() sur subprocess.Popen() sur os.system() sur os.popen()
  • Comprenez et utilisez probablement text=True, alias universal_newlines=True.
  • Comprenez la signification de Shell=True ou Shell=False et comment cela change de guillemet et de la disponibilité des commodités de Shell.
  • Comprendre les différences entre sh et Bash
  • Comprenez comment un sous-processus est séparé de son parent et ne peut généralement pas changer de 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() ou subprocess.check_call()

La fonction subprocess.Popen() est un bourreau de travail de bas niveau, mais il est difficile à utiliser correctement et vous finissez par copier/coller plusieurs lignes de code ... qui existent déjà dans la bibliothèque standard sous la forme d'un 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 appeler des sous-processus consiste à utiliser la fonction run() pour tous les cas d'utilisation qu'elle peut gérer. Pour les cas d'utilisation plus avancés, l'interface Popen sous-jacente peut être utilisée directement.

Malheureusement, la disponibilité de ces fonctions d'encapsulation varie selon les versions Python.

  • subprocess.run() a été officiellement introduit dans Python 3.5. Il est destiné à remplacer tout ce qui suit.
  • subprocess.check_output() a été introduit dans Python 2.7/3.1. C'est fondamentalement équivalent à subprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
  • subprocess.check_call() a été introduit dans Python 2.5. C'est fondamentalement équivalent à subprocess.run(..., check=True)
  • subprocess.call() a été introduit dans Python 2.4 du module subprocess d'origine ( PEP-324 ). C'est fondamentalement équivalent à subprocess.run(...).returncode

API de haut niveau vs subprocess.Popen()

Le subprocess.run() refactoré et étendu est plus logique et plus polyvalent que les anciennes fonctions héritées qu’il remplace. Il renvoie un objet CompletedProcess qui comporte diverses méthodes permettant d'extraire le statut de sortie, la sortie standard et quelques autres résultats et indicateurs de statut du sous-processus terminé.

subprocess.run() est la voie à suivre si vous avez simplement besoin d'un programme à exécuter et à rendre le contrôle à Python. Pour des scénarios plus complexes (processus en arrière-plan, peut-être avec des E/S interactives avec le programme parent Python), vous devez toujours utiliser subprocess.Popen() et prendre en charge toute la tuyauterie. Cela nécessite une compréhension assez complexe de toutes les pièces mobiles et ne doit pas être entrepris à la légère. Le plus simple objet Popen représente le processus (éventuellement toujours 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 subprocess.Popen() crée simplement un processus. Si vous vous en tenez à cela, vous avez un sous-processus s'exécutant simultanément avec Python, donc un processus "d'arrière-plan". S'il n'a pas besoin de faire une entrée ou une sortie ou de coordonner autrement avec vous, il peut faire un travail utile en parallèle avec votre programme Python.

Évitez os.system() et os.popen()

Depuis toujours, (depuis Python 2.5), la os documentation du module contenait la recommandation de préférer subprocess à os.system():

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.

Le problème avec system() est que cela dépend évidemment du système et ne permet pas d'interagir avec le sous-processus. Il fonctionne simplement, avec une sortie standard et une erreur standard hors de portée de Python. La seule information reçue par Python est l'état de sortie de la commande (zéro signifie succès, bien que la signification des valeurs non nulles dépende aussi un peu du système).

PEP-324 (qui a déjà été mentionné ci-dessus) contient une justification plus détaillée de la raison pour laquelle os.system est problématique et comment subprocess tente de résoudre ces problèmes.

os.popen() était encore plus fortement déconseillé :

Déconseillé depuis la version 2.6: Cette fonction est obsolète. Utilisez le module subprocess.

Cependant, depuis Python 3, il a été réimplémenté pour utiliser simplement subprocess et redirige vers la documentation subprocess.Popen() pour plus de détails.

Comprendre et utiliser habituellement check=True

Vous remarquerez également que subprocess.call() a beaucoup des mêmes limitations que os.system(). En utilisation normale, vous devez généralement vérifier si le processus s'est terminé avec succès, avec les noms subprocess.check_call() et subprocess.check_output() do (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 statut d'erreur.

En pratique, avec check=True ou subprocess.check_*, Python lève une exception CalledProcessError si le sous-processus renvoie un état de sortie différent de zéro.

Une erreur courante avec subprocess.run() consiste à omettre check=True et à être surpris lorsque le code en aval échoue si le sous-processus échoue.

D'autre part, l'un des problèmes courants avec check_call() et check_output() était que les utilisateurs qui utilisaient aveuglément ces fonctions étaient surpris lorsque l'exception était déclenchée, par exemple. lorsque grep n'a pas trouvé de correspondance. (Vous devriez probablement remplacer grep par du code natif Python, 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 quant à la manière dont il doit être géré.

Comprendre et probablement utiliser text=True aka universal_newlines=True

Depuis Python 3, les chaînes internes à Python sont des chaînes Unicode. Cependant, rien ne garantit 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, il est recommandé d'utiliser Pragmatic Unicode de Ned Batchelder, sinon obligatoire, à lire. Il existe une présentation vidéo de 36 minutes derrière le lien si vous préférez, bien que vous lisiez la page vous-même. prendra probablement beaucoup moins de temps.)

Au fond, Python doit extraire un tampon bytes et l’interpréter d’une manière ou d’une autre. S'il contient un blob de données binaires, il est ne devrait pas == être décodé en chaîne Unicode, car il s'agit d'un comportement source d'erreurs et de bogues, précisément le genre de comportement embêtant qui énormément de nombreux Python 2 scripts, avant qu'il y ait un moyen de faire la distinction entre le texte codé et les données binaires.

Avec text=True, vous indiquez à Python que vous attendez en fait des données textuelles dans le codage par défaut du système et qu'elles doivent être décodées dans un Python (Unicode) chaîne au mieux de la capacité de Python (habituellement UTF-8 sur tout système moyennement à jour, sauf peut-être Windows?)

S'il s'agit de not ce que vous demandez en retour, Python vous donnera simplement les chaînes bytes dans les chaînes stdout et stderr. Peut-être que, à un moment donné, vous do == savez qu'il s'agit de chaînes de texte après tout et que vous connaissez leur codage. 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 de mot clé qui était auparavant quelque peu trompeur appelé universal_newlines.

Comprendre Shell=True vs Shell=False

Avec Shell=True, vous transmettez une seule chaîne à votre shell, qui le reprend à partir de là.

Avec Shell=False, vous transmettez une liste d’arguments au système d’exploitation, sans passer par le shell.

Lorsque vous ne possédez pas de shell, vous enregistrez un processus et vous débarrassez de ne quantité assez importante de complexité cachée, qui peut ou non contenir des bogues ou même des problèmes de sécurité.

D'autre part, lorsque vous ne possédez pas de shell, vous ne disposez pas de redirection, d'expansion des caractères génériques, de contrôle des travaux et d'un grand nombre d'autres fonctionnalités de shell.

Une erreur courante consiste à utiliser Shell=True et à transmettre ensuite Python une liste de jetons, ou inversement. Cela fonctionne parfois dans certains cas, mais il est vraiment mal défini et pourrait casser 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 de comprendre exactement dans quelles circonstances cela pourrait cesser de fonctionner.

Exemple de refactoring

Très souvent, les fonctionnalités du shell peuvent être remplacées par du code natif Python. Les scripts simples Awk ou sed devraient probablement simplement être traduits en Python à la place.

Pour illustrer cela partiellement, voici un exemple typique mais légèrement ridicule 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 de la citation requise par le shell autour des chaînes. De toute façon, mettre des citations est probablement une erreur.
  • Il est souvent logique d’exécuter le moins de code possible dans un sous-processus. Cela vous donne plus de contrôle sur l'exécution depuis votre code Python.
  • Cela dit, les pipelines Shell complexes sont fastidieux et il est parfois difficile de les réimplémenter en Python.

Le code refactorisé illustre également à quel point le shell fait vraiment pour vous avec une syntaxe très succincte - pour le meilleur ou pour le pire. Python dit explicite vaut mieux que implicite mais le code Python est plutôt détaillé. et semble sans doute plus complexe que ce n'est vraiment. D'autre part, il offre un certain nombre de points où vous pouvez prendre le contrôle au milieu de quelque chose d'autre, comme en témoigne trivialement l'amélioration qui permet d'inclure facilement le nom d'hôte avec le résultat de la commande Shell. (Ceci n’est nullement difficile à faire dans Shell, non plus, mais aux dépens d’un autre détournement et peut-être d’un autre processus.)

Constructions Shell communes

Par souci d'exhaustivité, voici une brève explication de certaines de ces fonctionnalités de Shell et quelques remarques sur la manière dont elles peuvent éventuellement être remplacées par des fonctionnalités natives Python.

  • Globbing ou extension générique peut être remplacé par glob.glob() ou très souvent par de simples comparaisons de chaînes Python telles que for file in os.listdir('.'): if not file.endswith('.png'): continue. Bash dispose de diverses autres installations d’expansion, telles que .{png,jpg} extension d’accolade et {1..100} ainsi que l’expansion de tilde (~ se développe dans votre répertoire personnel et plus généralement ~account dans le répertoire personnel de un autre utilisateur)
  • La redirection vous permet de lire un fichier en tant qu'entrée standard et d'écrire votre sortie standard dans un fichier. grep 'foo' <inputfile >outputfile ouvre outputfile en écriture et inputfile en lecture et transmet son contenu en tant qu'entrée standard à grep, dont la sortie standard atterrit ensuite dans outputfile. Ce n’est généralement pas difficile à remplacer par du code natif Python.
  • 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 l'une ou les deux extrémités du pipeline par du code natif Python, envisagez peut-être d'utiliser un shell, surtout si le pipeline comporte plus de deux ou trois processus (regardez bien le Module pipes dans la Python bibliothèque standard 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 ramener au premier plan, etc. Les signaux Unix de base pour arrêter et poursuivre un processus sont bien sûr également disponibles à partir de Python. Mais les tâches 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 cela depuis Python.

Comprendre les différences entre sh et Bash

subprocess exécute vos commandes Shell avec /bin/sh sauf indication contraire de votre part (sauf bien sûr sous Windows, où il utilise la valeur de la variable COMSPEC). Cela signifie que les diverses fonctionnalités de Bash uniquement telles que les tableaux, [[ etc ne sont pas disponibles.

Si vous devez utiliser la syntaxe Bash uniquement, vous pouvez indiquer le chemin d'accès au shell sous la forme executable='/bin/bash' (où, bien sûr, si votre base Bash est installée 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')

Un subprocess est séparé de son parent et ne peut pas le changer

Une erreur un peu commune 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 "sous-partie" du nom "sous-processus".

Un processus enfant est complètement séparé de Python et, une fois terminé, Python n’a aucune idée de ce qu’il a fait (à part les indicateurs vagues qu’il peut déduire du statut de sortie et de la sortie du processus enfant). Un enfant ne peut généralement pas changer l'environnement du parent; il ne peut pas définir de variable, modifier le répertoire de travail ou, en d'autres termes, 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 ne nécessite pas du tout le shell. N'oubliez pas que vous pouvez manipuler l'environnement du processus actuel (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 oublier l'évidente refactoring subprocess.run(['echo', 'bar']); mais echo est un exemple médiocre de quelque chose à exécuter dans un sous-processus, bien sûr).

Ne pas exécuter Python à partir de Python

C'est un conseil légèrement douteux; il existe certainement des situations où il est logique, voire indispensable, d'exécuter l'interpréteur Python en tant que sous-processus d'un script Python. Mais très souvent, la bonne approche consiste simplement à import l'autre module Python dans votre script d'appel 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, considérez en le transformant en un . (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 module multiprocessing. Il existe également threading qui exécute plusieurs tâches dans un même processus ( ce qui est plus léger et vous donne plus de contrôle, mais aussi plus contraint dans le sens où les threads d'un processus sont étroitement couplés et liés à un seul GIL .)

87
tripleee

Appelez-le avec un sous-processus

import subprocess
subprocess.Popen("cwm --rdf test.rdf --ntriples > test.nt")

L'erreur que vous obtenez semble être parce qu'il n'y a pas de module d'échange sur le serveur, vous devez installer swap sur le serveur, puis réexécuter le script.

39
Jakob Bowyer

Il est possible que vous utilisiez le programme bash, avec le paramètre -c pour exécuter les commandes:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
output = subprocess.check_output(['bash','-c', bashCommand])
18
Razor

Vous pouvez utiliser 'subprocess', mais j'ai toujours pensé que ce n'était pas une façon de le faire 'Pythonic'. J'ai donc créé Sultan (fiche éhontée) qui facilite l'exécution de fonctions de ligne de commande.

https://github.com/aeroxis/sultan

9
David Daniel

Selon l'erreur, il vous manque un paquet nommé swap sur le serveur. Ce /usr/bin/cwm le nécessite. Si vous êtes sur Ubuntu/Debian, installez python-swap en utilisant aptitude.

8
kichik

Aussi, vous pouvez utiliser 'os.popen'. Exemple:

import os

command = os.popen('ls -al')
print(command.read())
print(command.close())

Sortie:

total 16
drwxr-xr-x 2 root root 4096 ago 13 21:53 .
drwxr-xr-x 4 root root 4096 ago 13 01:50 ..
-rw-r--r-- 1 root root 1278 ago 13 21:12 bot.py
-rw-r--r-- 1 root root   77 ago 13 21:53 test.py

None
2
ricardo130

Pour exécuter la commande sans shell, transmettez-la en tant que liste et implémentez la redirection dans Python à l'aide de [subprocess] :

_#!/usr/bin/env python
import subprocess

with open('test.nt', 'wb', 0) as file:
    subprocess.check_call("cwm --rdf test.rdf --ntriples".split(),
                          stdout=file)
_

Remarque: pas de _> test.nt_ à la fin. _stdout=file_ implémente la redirection.


Pour exécuter la commande à l'aide du shell en Python, transmettez-la en tant que chaîne et activez _Shell=True_:

_#!/usr/bin/env python
import subprocess

subprocess.check_call("cwm --rdf test.rdf --ntriples > test.nt",
                      Shell=True)
_

Voici le shell est responsable de la redirection de sortie (_> test.nt_ est dans la commande).


Pour exécuter une commande bash utilisant des bashismes, spécifiez explicitement l'exécutable bash, par exemple pour émuler la substitution de processus bash :

_#!/usr/bin/env python
import subprocess

subprocess.check_call('program <(command) <(another-command)',
                      Shell=True, executable='/bin/bash')
_
2
jfs

Pour ce faire, Pythonic utilise subprocess.Popen

_subprocess.Popen_ prend une liste où le premier élément est la commande à exécuter, suivie de tous les arguments de la ligne de commande.

Par exemple:

_import subprocess

args = ['echo', 'Hello!']
subprocess.Popen(args) // same as running `echo Hello!` on cmd line

args2 = ['echo', '-v', '"Hello Again"']
subprocess.Popen(args2) // same as running 'echo -v "Hello Again!"` on cmd line
_
0
RewordedAnswers