Exécuter des commandes Bash en Python

299

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 bashCommandqui 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?

mkn
la source
2
Il semble y avoir une différence dans l'environnement selon la façon dont vous exécutez cwm. Peut-être avez-vous une configuration .bashrcqui configure l'environnement pour une utilisation bash interactive?
Sven Marnach
Avez-vous essayé d'exécuter la commande à partir de la ligne de commande lorsque vous vous êtes connecté sur le serveur? Votre message indique simplement que vous "l'avez collé" dans le terminal ".
Sven Marnach
@Sven: oui, je voulais dire que j'ai exécuté la commande directement dans le terminal du serveur
mkn
Il semble y avoir une différence dans le PYTHONPATH selon la façon dont vous courez cwm. Ou peut-être qu'il y a une différence dans PATH, et différentes versions de cwmsont appelées. Ou différentes versions de Python. Il est vraiment difficile de comprendre cela sans avoir accès à la machine ...
Sven Marnach

Réponses:

314

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:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
import subprocess
process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
output, error = process.communicate()
user225312
la source
8
Cela n'a pas fait ce que je voulais quand j'avais besoin de faire une cd 'path\to\somewhere'autre commande bash qui devait être exécutée quelque part. @ user225312
AWrightIV
36
@AWrightIV Si vous avez besoin que votre sous-processus soit exécuté dans un répertoire de travail particulier, vous pouvez utiliser l' cwdargument pour Popen:subprocess.Popen(..., cwd='path\to\somewhere')
waterproof
7
Pour ma commande, j'avais besoin de shell = True comme ici; stackoverflow.com/questions/18962785/…
user984003
4
Il vaut mieux utiliser shlex.split () au lieu de string.split () dans ce cas
Alexey Sviridov
4
... ( stdout=fileredirige 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)
jfs
186

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=Trueou shell=Falseet comment elle change de devis et la disponibilité des commodités shell.
  • Comprendre les différences entre shet 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' Popeninterface 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 subprocessmodule 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 CompletedProcessobjet 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' Popenobjet 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 osdocumentation du module contient la recommandation de préférer subprocessà os.system():

Le subprocessmodule 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.systempose problème et comment les subprocesstentatives 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 subprocessmodule.

Cependant, depuis un certain temps en Python 3, il a été réimplémenté pour simplement utiliser subprocesset 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=Trueavec subprocess.run()sauf si vous devez spécifiquement autoriser le sous-processus à renvoyer un état d'erreur.

En pratique, avec check=Trueou subprocess.check_*, Python lèvera une CalledProcessErrorexception si le sous-processus renvoie un état de sortie différent de zéro.

Une erreur courante avec subprocess.run()est d'omettre check=Trueet 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 grepne trouvaient pas de correspondance. (Vous devriez probablement remplacer le grepcode 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=Trueakauniversal_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 bytestampon 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 byteschaînes dans les chaînes stdoutet 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 textpour l'argument mot-clé qui était auparavant appelé de manière quelque peu trompeuse universal_newlines.

Comprendre shell=Truevsshell=False

Avec shell=Truevous passez une seule chaîne à votre shell, et le shell le prend à partir de là.

Avec shell=Falsevous 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=Truepuis à 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 sedscripts 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=Falsevous 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 ~accountau répertoire personnel d'un autre utilisateur)
  • Les variables shell comme $SHELLou $my_exported_varpeuvent parfois simplement être remplacées par des variables Python. Exportés variables shell sont disponibles comme par exemple os.environ['SHELL'](le sens de exportest 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 subprocessmé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=Falsevous 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 souventcdn'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 >outputfiles'ouvre outputfilepour l'écriture et la inputfilelecture, 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 | nlexécute deux sous-processus, où la sortie standard de echoest 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 pipesmodule 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 shet Bash

subprocessexécute vos commandes shell à /bin/shmoins que vous ne demandiez spécifiquement le contraire (sauf bien sûr sous Windows, où il utilise la valeur de la COMSPECvariable). 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 subprocessest 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 echoc'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 à importl'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 multiprocessingmodule. Il y a aussi threadingqui 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 .)

tripleee
la source
2
Pour une présentation plus détaillée de la façon dont vous pourriez éviter d'appeler Python en tant que sous-processus, consultez cette réponse sur une question tangentiellement similaire.
tripleee
4
cela me dérange que je devais poster une nouvelle réponse à une question aussi basique afin de montrer comment exécuter la commande à partir de la question de manière idiomatique. Votre réponse est longue mais je ne vois pas un tel exemple. Indépendant: évitez de cultiver la cargaison. Si check_call () fonctionne dans votre cas, utilisez-le. J'ai dû corriger un code utilisé run()aveuglément. Manquer a check=Truecausé 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.
jfs
1
@jfs Merci pour vos commentaires, j'avais en fait l'intention d'ajouter une section sur Bash vs shmais 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; +1
tripleee
La stderr/stdout = subprocess.PIPEsurcharge des performances est-elle supérieure aux paramètres par défaut?
Stringers
1
@Stringers, je n'ai pas testé, mais je ne vois pas pourquoi cela devrait. Si vous connectez ces tuyaux à quelque chose qui fait un certain traitement, alors bien sûr, ce traitement doit être pris en compte; mais cela ne se produit pas dans le tuyau lui-même. La valeur par défaut est de ne pas capturer stdout ou stderr du tout, c'est-à-dire que tout ce qui est imprimé est hors de la visibilité et du contrôle de Python, tout comme avec os.system().
tripleee
41

Appelez-le avec un sous-processus

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

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

Jakob Bowyer
la source
3
Le swapmodule est évidemment là, car l'exécution de la commande depuis le shell fonctionne.
Sven Marnach
2
Pas sur le serveur, quand il l'exécute sur le serveur, il y a une erreur d'importation.
Jakob Bowyer
@mkn: "Ensuite, je viens de copier cette sortie et j'ai fait un copier-coller dans le terminal et appuyez sur Entrée et cela fonctionne ..." - Avez-vous essayé cela sur le serveur ou sur votre machine?
Sven Marnach
Est-ce que vous exécutez cela sur un ordinateur autonome, mais que cela ne fonctionne pas lorsque vous l'exécutez sur votre serveur? Ou êtes-vous capable de l'exécuter sur un terminal serveur mais pas sur le serveur lui
Jakob Bowyer
1
c'est faux Si vous n'utilisez pas, shell=Truevous 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étails
jfs
18

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])
Le rasoir
la source
2
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 le shell=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)
tripleee
@tripleee note: /bin/sh(utilisé par le sous-processus) ne l'est pas nécessairement bash(vous ne pouvez pas utiliser de bashismes). Bien que l'on puisse utiliser executable='/bin/bashsi désiré. Voici un exemple de code
jfs
2
c'est la première réponse où la commande doit démarrer avec succès (les réponses acceptées et les 2e réponses populaires sont fausses. Un petit problème: check_output()est inutile ici (la sortie est toujours vide en raison de la > fileredirection; utilisez à la check_call()place.
jfs
16

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

David Daniel
la source
3
Bien joué! Beaucoup plus propre et plus intuitif que le sous-processus.
mjd2
Merci beaucoup! Je suis content d'entendre ça!
David Daniel
2
Cela devrait être honnêtement adopté dans la bibliothèque standard.
Joshua Detwiler
1
Existe-t-il un moyen de capturer la sortie du terminal à l'aide de Sultan?
alvas
Oui, vous pouvez @alvas ... Voici les documents sur la façon de le faire: sultan.readthedocs.io/en/latest/…
David Daniel
7

Selon l'erreur, il manque un package nommé swap sur le serveur. Cela l' /usr/bin/cwmexige. Si vous êtes sur Ubuntu / Debian, installez en python-swaputilisant aptitude.

kichik
la source
mais ça marche quand je l'exécute directement dans le terminal ... donc le swap doit être là, non?
mkn
il y a deux options. soit il ne peut pas le trouver, swapsoit il n'aurait pas dû l'importer en premier lieu. pouvez-vous import swapmanuellement? est-ce que ça marche?
kichik
hm je ne peux pas. Si je démarre python en tapant python dans le terminal et que je tape import swap, j'ai le message d'erreur "ImportError: No module named swap". La chose étrange est toujours que cela fonctionne lorsque j'exécute la commande cwm directement dans le terminal du serveur
mkn
Essayez d'imprimer sys.pathoù 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.
kichik
4

Vous pouvez également utiliser 'os.popen'. Exemple:

import os

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

Production:

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
ricardo130
la source
1
La documentation contient un grand encadré rouge: " Déconseillé depuis la version 2.6: Cette fonction est obsolète. Utilisez le subprocessmodule."
tripleee
1
En toute honnêteté, os.popenn'a plus cet avertissement et est simplement un mince emballage subprocess.Popen()maintenant.
tripleee
4

Pour exécuter la commande sans shell, passez-la sous forme de liste et implémentez la redirection en 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: non > test.nt à la fin. stdout=fileimplé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:

#!/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.ntest 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 :

#!/usr/bin/env python
import subprocess

subprocess.check_call('program <(command) <(another-command)',
                      shell=True, executable='/bin/bash')
jfs
la source
Peut-être mentionne-t-il que ce .split()n'est pas suffisant lorsqu'il y a des chaînes entre guillemets, etc. Il existe une routine distincte shlex.split()qui gère une syntaxe de shell arbitrairement complexe.
tripleee
@tripleee les .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.
jfs
0

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:

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
Réponses reformulées
la source
Non, le dernier exemple est identique à l'exécution echo -v '"Hello Again!"'avec des guillemets simples autour des guillemets doubles.
tripleee
De plus, pour l'utiliser correctement subprocesss.Popen, vous devez gérer l'objet de processus résultant (au minimum, effectuez un wait()pour l'empêcher de se transformer en processus zombie).
tripleee