Sur ma machine locale, j'exécute un script python qui contient cette ligne
bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
os.system(bashCommand)
Cela fonctionne bien.
Ensuite, j'exécute le même code sur un serveur et j'obtiens 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
Donc ce que j'ai fait alors, c'est que j'ai inséré un print bashCommand
qui m'imprime que la commande dans le terminal avant de l'exécuter os.system()
.
Bien sûr, j'obtiens à nouveau l'erreur (causée par os.system(bashCommand)
) mais avant cette erreur, elle imprime la commande dans le terminal. Ensuite, je viens de copier cette sortie et de faire un copier-coller dans le terminal et d'appuyer sur Entrée et cela fonctionne ...
Quelqu'un a-t-il une idée de ce qui se passe?
cwm
. Peut-être avez-vous une configuration.bashrc
qui configure l'environnement pour une utilisation bash interactive?cwm
. Ou peut-être qu'il y a une différence dans PATH, et différentes versions decwm
sont appelées. Ou différentes versions de Python. Il est vraiment difficile de comprendre cela sans avoir accès à la machine ...Réponses:
Ne pas utiliser
os.system
. Il est déconseillé au profit du sous - processus . Des docs : « Ce module a l' intention de remplacer plusieurs modules et fonctions plus:os.system
,os.spawn
».Comme dans votre cas:
la source
cd 'path\to\somewhere'
autre commande bash qui devait être exécutée quelque part. @ user225312cwd
argument pour Popen:subprocess.Popen(..., cwd='path\to\somewhere')
stdout=file
redirige la sortie vers un fichier dans ce cas. Il implémente> file
). Il serait faux de passer..., '>', 'file']
la dernière commande en attendant la redirection (cela ne fonctionnera pas sans shell et si vous utilisez un shell, vous devriez passer la commande sous forme de chaîne)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.
subprocess.run()
plussubprocess.check_call()
et des amissubprocess.call()
sursubprocess.Popen()
plus deos.system()
plusos.popen()
text=True
, aliasuniversal_newlines=True
.shell=True
oushell=False
et comment elle change de devis et la disponibilité des commodités shell.sh
et BashCes 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 :
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 lesubprocess
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 unCompletedProcess
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 utilisersubprocess.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érersubprocess
àos.system()
: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 lessubprocess
tentatives de résoudre ces problèmes.os.popen()
autrefois encore plus fortement découragé :Cependant, depuis un certain temps en Python 3, il a été réimplémenté pour simplement utiliser
subprocess
et redirige vers lasubprocess.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 queos.system()
. En utilisation régulière, vous devez généralement vérifier si le processus s'est terminé avec succès, lequelsubprocess.check_call()
etsubprocess.check_output()
faire (où ce dernier renvoie également la sortie standard du sous-processus terminé). De même, vous devez généralement utilisercheck=True
avecsubprocess.run()
sauf si vous devez spécifiquement autoriser le sous-processus à renvoyer un état d'erreur.En pratique, avec
check=True
ousubprocess.check_*
, Python lèvera uneCalledProcessError
exception si le sous-processus renvoie un état de sortie différent de zéro.Une erreur courante avec
subprocess.run()
est d'omettrecheck=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()
etcheck_output()
était que les utilisateurs qui utilisaient aveuglément ces fonctions étaient surpris lorsque l'exception était levée, par exemple lorsqu'ilsgrep
ne trouvaient pas de correspondance. (Vous devriez probablement remplacer legrep
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înesstdout
etstderr
. 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.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 trompeuseuniversal_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.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.
Quelques points à noter ici:
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.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.
glob.glob()
ou très souvent par de simples comparaisons de chaînes Python commefor 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)$SHELL
ou$my_exported_var
peuvent parfois simplement être remplacées par des variables Python. Exportés variables shell sont disponibles comme par exempleos.environ['SHELL']
(le sens deexport
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 Leenv=
mot - clé L'argument dessubprocess
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). Avecshell=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 ... )grep 'foo' <inputfile >outputfile
s'ouvreoutputfile
pour l'écriture et lainputfile
lecture, et passe son contenu comme entrée standard àgrep
, dont la sortie standard atterrit ensuiteoutputfile
. Ce n'est généralement pas difficile à remplacer par du code Python natif.echo foo | nl
exécute deux sous-processus, où la sortie standard deecho
est l'entrée standard denl
(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 lepipes
module dans la bibliothèque standard Python ou un certain nombre de concurrents tiers plus modernes et plus polyvalents).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 Bashsubprocess
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 laCOMSPEC
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).A
subprocess
est distinct de son parent et ne peut pas le modifierUne erreur quelque peu courante consiste à faire quelque chose comme
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;
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
ou passer un paramètre d'environnement à un processus enfant avec
(sans parler de la refactorisation évidente
subprocess.run(['echo', 'bar'])
; maisecho
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 aussithreading
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 .)la source
run()
aveuglément. Manquer acheck=True
causé un bogue qui serait évité si check_call était utilisé - "check" est dans le nom, vous ne pouvez pas le perdre - c'est la valeur par défaut correcte: n'ignorez pas les erreurs en silence. Je n'ai pas lu plus loin.sh
mais vous m'avez battu. J'essaie d'énoncer les détails avec suffisamment de détails pour aider les débutants pour lesquels ces pièges ne sont pas évidents, ce qui est un peu long. Le vôtre devrait être tout à fait suffisant sinon; +1stderr/stdout = subprocess.PIPE
surcharge des performances est-elle supérieure aux paramètres par défaut?os.system()
.Appelez-le avec un sous-processus
L'erreur que vous obtenez semble être due au fait qu'il n'y a pas de module de swap sur le serveur, vous devez installer swap sur le serveur puis relancer le script
la source
swap
module est évidemment là, car l'exécution de la commande depuis le shell fonctionne.shell=True
vous devez utiliser une liste pour passer plusieurs arguments, c'est-à-dire utiliser à la['a', 'b', 'c']
place de'a b c'
. Bien qu'une séparation naïve ne fonctionnera pas en raison de> file
(redirection du shell) dans la commande. Plus de détailsIl est possible que vous utilisiez le programme bash, avec le paramètre -c pour exécuter les commandes:
la source
subprocess.check_output(bashCommand, shell=True)
fait la même chose. Si votre commande est une chaîne statique, essayez de l'analyser vous-même dans une liste et évitez leshell=True
; mais dans ce cas, vous avez quand même besoin du shell pour la redirection, sinon vous devrez le refactoriser en Python pur -with open('test.nt', 'w') as dest: output = subprocess.check_output(['cwm' ,'--rdf', 'test.rdf', '--ntriples'], stdout=dest, shell=False)
/bin/sh
(utilisé par le sous-processus) ne l'est pas nécessairementbash
(vous ne pouvez pas utiliser de bashismes). Bien que l'on puisse utiliserexecutable='/bin/bash
si désiré. Voici un exemple de codecheck_output()
est inutile ici (la sortie est toujours vide en raison de la> file
redirection; utilisez à lacheck_call()
place.Vous pouvez l'utiliser
subprocess
, mais j'ai toujours senti que ce n'était pas une façon «pythonique» de le faire. J'ai donc créé Sultan (plug sans vergogne) qui facilite l'exécution des fonctions de ligne de commande.https://github.com/aeroxis/sultan
la source
Selon l'erreur, il manque un package nommé swap sur le serveur. Cela l'
/usr/bin/cwm
exige. Si vous êtes sur Ubuntu / Debian, installez enpython-swap
utilisant aptitude.la source
swap
soit il n'aurait pas dû l'importer en premier lieu. pouvez-vousimport swap
manuellement? est-ce que ça marche?sys.path
où cela fonctionne et où il ne fonctionne pas. Essayez ensuite de rechercher le dossier d'échange ou swap.py dans les dossiers imprimés. Comme l'a dit Sven, il peut y avoir un problème avec ces chemins, et cela vous aidera à le découvrir.Vous pouvez également utiliser 'os.popen'. Exemple:
Production:
la source
subprocess
module."os.popen
n'a plus cet avertissement et est simplement un mince emballagesubprocess.Popen()
maintenant.Pour exécuter la commande sans shell, passez-la sous forme de liste et implémentez la redirection en Python à l'aide de
[subprocess]
:Remarque: non
> test.nt
à la fin.stdout=file
implémente la redirection.Pour exécuter la commande à l'aide du shell en Python, passez la commande sous forme de chaîne et activez
shell=True
:Voici le shell est responsable de la redirection de sortie (
> test.nt
est dans la commande).Pour exécuter une commande bash qui utilise des bashismes, spécifiez explicitement l'exécutable bash, par exemple pour émuler la substitution de processus bash :
la source
.split()
n'est pas suffisant lorsqu'il y a des chaînes entre guillemets, etc. Il existe une routine distincteshlex.split()
qui gère une syntaxe de shell arbitrairement complexe..split()
œuvres dans ce cas.shlex.split()
peut être utile parfois mais il peut aussi échouer dans certains cas. Il y a beaucoup de choses qui pourraient être mentionnées. Vous pouvez commencer par le lien vers la description de la balise de sous-processus fournie ci-dessus.La façon pythonique de procéder consiste à utiliser
subprocess.Popen
subprocess.Popen
prend une liste où le premier élément est la commande à exécuter suivie de tout argument de ligne de commande.Par exemple:
la source
echo -v '"Hello Again!"'
avec des guillemets simples autour des guillemets doubles.subprocesss.Popen
, vous devez gérer l'objet de processus résultant (au minimum, effectuez unwait()
pour l'empêcher de se transformer en processus zombie).