Exécuter une commande shell en python et en sortie de lecture

J’ai ce qui suit:

cmd = "ps aux | grep 'java -jar' | grep -v grep | awk '//{print $2}'".split(' ') p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() print out 

Lorsque je lance la commande dans la console (en dehors de python), j’obtiens la sortie souhaitée. L’exécution de ce code ci-dessus en python imprime une ligne vierge. Je suppose qu’il y a quelque chose avec le cmd (spécifiquement l’opérateur | ) mais je ne peux pas en être sûr.

Je dois y parvenir avec l’installation standard de Python 2.6.6 (pas de modules supplémentaires)

Vous devez utiliser un seul appel à Popen() pour chaque élément de la commande d’origine, tel que connecté par le canal, comme dans

 import subprocess p1 = subprocess.Popen(["ps", "aux"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) p2 = subprocess.Popen(["grep", "java -jar"], stdin=p1.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p3 = subprocess.Popen(["grep", "-v", "grep"], stdin=p2.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p4 = subprocess.Popen(["awk", "//{print $2}"], stdin=p3.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p4.communicate() print out 

La documentation du sous-processus a une discussion approfondie.

Popen par défaut n’exécute que des exécutables , pas des lignes de commande shell . Lorsque vous passez la liste des arguments à Popen ils doivent appeler un exécutable avec ses arguments:

 import subprocess proc = subprocess.Popen(['ps', 'aux']) 

Notez également que vous ne devez pas utiliser str.split pour diviser une commande, car:

 >>> "ps aux | grep 'java -jar' | grep -v grep | awk '//{print $2}'".split(' ') ['ps', 'aux', '|', 'grep', "'java", "-jar'", '', '', '', '', '|', 'grep', '-v', 'grep', '|', 'awk', "'//{print", "$2}'"] 

Notez comment:

  • Les arguments qui ont été cités (par exemple 'java -jar') sont divisés.
  • S’il y a plus d’un espace consécutif, vous obtenez des arguments vides.

Python fournit déjà un module qui sait comment diviser une ligne de commande de manière raisonnable:

 >>> shlex.split("ps aux | grep 'java -jar' | grep -v grep | awk '//{print $2}'") ['ps', 'aux', '|', 'grep', 'java -jar', '|', 'grep', '-v', 'grep', '|', 'awk', '//{print $2}'] 

Notez comment les arguments entre guillemets ont été conservés et que plusieurs espaces sont traités avec élégance. Vous ne pouvez toujours pas transmettre le résultat à Popen , car Popen ne va pas interpréter le | comme pipe par défaut.

Si vous voulez exécuter une ligne de commande shell (c.-à-d. Utiliser des fonctionnalités telles que des pipes, des extensions de chemins, des redirection, etc.), vous devez passer shell=True . Dans ce cas, vous ne devez pas transmettre à Popen une liste de chaînes sous forme d’argument, mais uniquement une chaîne de caractères complète:

 proc = subprocess.Popen("ps aux | grep 'java -jar' | grep -v grep | awk '//{print $2}'", shell=True) 

Si vous passez une liste de chaînes avec shell=True sa signification est différente: le premier élément doit être la ligne de commande complète , tandis que les autres éléments sont passés en tant qu’options du shell utilisé. Par exemple, sur ma machine, le shell par défaut ( sh ) a une option -x qui affichera sur stderr tous les processus exécutés:

 >>> from subprocess import Popen >>> proc = Popen(['ps aux | grep python3', '-x'], shell=True) >>> username 7301 0.1 0.1 39440 7408 pts/9 S+ 12:57 0:00 python3 username 7302 0.0 0.0 4444 640 pts/9 S+ 12:58 0:00 /bin/sh -c ps aux | grep python3 -x username 7304 0.0 0.0 15968 904 pts/9 S+ 12:58 0:00 grep python3 

Ici, vous pouvez voir qu’un /bin/sh été lancé pour exécuter la commande ps aux | python3 ps aux | python3 et avec une option de -x .

(Tout cela est documenté dans la documentation de Popen ).


Cela dit, une façon de réaliser ce que vous voulez est d’utiliser subprocess.check_output :

 subprocess.check_output("ps aux | grep 'java -jar' | grep -v grep | awk '//{print $2}'", shell=True) 

Cependant, ce n’est pas disponible dans python <2.7, il faut donc utiliser Popen et communicate() :

 proc = subprocess.Popen("ps aux | grep 'java -jar' | grep -v grep | awk '//{print $2}'", shell=True, stdout=subprocess.PIPE) out, err = proc.communicate() 

L’alternative consiste à éviter d’utiliser shell=True (ce qui est généralement une très bonne chose , car shell=True présente certains risques de sécurité) et d’écrire manuellement le canal en utilisant plusieurs processus:

 from subprocess import Popen, PIPE ps = Popen(['ps', 'aux'], stdout=PIPE) grep_java = Popen(['grep', 'java -jar'], stdin=ps.stdout, stdout=PIPE) grep_grep = Popen(['grep', '-v', 'grep'], stdin=grep_java.stdout, stdout=PIPE) awk = Popen(['awk', '//{print $2}'], stdin=grep_grep.stdout, stdout=PIPE) out, err = awk.communicate() grep_grep.wait() grep_java.wait() ps.wait() 

Notez que si vous ne vous souciez pas de l’erreur standard, vous pouvez éviter de la spécifier. Il héritera alors de celui du processus en cours.

Il n’y a qu’une seule commande dans le pipeline shell qui ne peut pas être facilement remplacée par du code Python. Vous pouvez donc démarrer plusieurs processus externes et connecter leurs entrées et sorties, mais je voudrais juste lancer le ps aux et append du code Python pour filtrer et extraire les données souhaitées:

 from subprocess import PIPE, Popen def main(): process = Popen(['ps', 'aux'], stdout=PIPE) pids = [ line.split(None, 2)[1] for line in process.stdout if 'java -jar' in line ] process.wait() print '\n'.join(pids) if __name__ == '__main__': main()