Il s'agit d'exécuter des processus depuis python.
Les cas d'usage sont les suivants : faire du multiprocessing, exécuter des scripts/applications déjà optimisées dans d'autres langages (grep, …), ou des applications systèmes pour lesquelles un wrapper python n'aurait que peu d'intérêt(btrfs-send, etc.)
On va utiliser le module subprocess, intégré à la bibliothèque standard de python.
La fonction de haut niveau subprocess.run() nous aidera.
import subprocess result = subprocess.run(["ls", "-l"], capture_output=True, text=True) print("STDOUT:", result.stdout)
On notera :
result qui va nous permettre de décortiquer ce qui s'est passésubprocess.run() # capture_output : si True, capture stdout et stderr # text = True : la sortie est un string plutôt qu'un byte # cwd : définit le current work directory # timeout : en secondes
On obtient un objet CompletedProcess qui contient les attributs suivants :
Admettons que l'on souhaite exécuter une commande via ssh sur une machine distante, par exemple ssh user@xxx.xxx.xxx.xxx “ls -t /tmp | head -n 1”.
Malgré la présence de guillemets “ ” sur la commande, l'argument n'en prend pas via subprocess.run([]).
En revanche, toute la commande distante est dans le même argument de subprocess.run(). ça donne :
subprocess.run(["ssh", "user@xxx.xxx.xxx.xxx", "ls -t /tmp | head -n 1"]) # on peut rajouter les options capture_output=True, text=True, ...
subprocess.Popen() est la fonction à la base de presque toutes les autres. Elle offre plus d'options, mais l'utiliser est plus complexe. Elle permet d'interagir avec les entrées et sorties de façon non bloquante.
source
Popen.communicate(input=None, timeout=None) permet d'interagir avec le processus : envoyer des données à stdin, lire des données depuis stdout et stderr (jusqu'à ce que end-of-file est atteint), attend la fin du processus et détermine le code de fin du processus (returncode attribute).
communicate() returns a tuple (stdout_data, stderr_data). The data will be strings if streams were opened in text mode; otherwise, bytes.
Note that if you want to send data to the process’s stdin, you need to create the Popen object with stdin=PIPE. Similarly, to get anything other than None in the result tuple, you need to give stdout=PIPE and/or stderr=PIPE too.
Timeout est donné en seconde. Si le processus n'est pas fini avant la fin du timeout, lève une exception TimeoutExpired.
Le returncode est disponible sur l'objet Popen directement via Popen.returncode.
# -- Interagir avec les entrées / sorties -- # # EXEMPLE from subprocess import Popen, PIPE process = Popen(["sort"], stdin=PIPE, stdout=PIPE, stderr=PIPE, text=True) stdout, stderr = process.communicate(input="banana\napple\ncherry") print(stdout)
# redirection des entrées et sorties with open("input.txt", "w") as f: f.write("banana\napple\ncherry") with open("input.txt", "r") as infile, open("output.txt", "w") as outfile: process = Popen(["sort"], stdin=infile, stdout=outfile)
# Interagir avec les entrées / sorties import subprocess process1 = subprocess.Popen(['cat'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) process2 = subprocess.Popen(['grep', 'Hello'], stdin=process1.stdout, stdout=subprocess.PIPE) # Write data to the input of the first process process1.stdin.write(b'Hello, world!\n') process1.stdin.close() # Get the output of the second process output = process2.communicate()[0] # Print the output (ici en format byte et non string, car pas de text=True) print(output.decode())
# -- Gérer les erreurs et exceptions -- # import subprocess try: process1 = subprocess.Popen(['echo', 'Hello, world!'], stdout=subprocess.PIPE) process2 = subprocess.Popen(['grep', 'Hello'], stdin=process1.stdout, stdout=subprocess.PIPE) # Get the output of the second process output = process2.communicate()[0] # Print the output print(output.decode()) # Check the return code of each process if process1.returncode != 0: raise subprocess.CalledProcessError(process1.returncode, process1.args) if process2.returncode != 0: raise subprocess.CalledProcessError(process2.returncode, process2.args) except subprocess.CalledProcessError as e: print(f"Error: {e}")
Le paramètre shell accepte 2 valeurs :
ls -l *.txt. Risque de shell injection selon la commande, et notamment si des variables la compose (issue d'une précédente commande par ex)[“ls”,“-l”]. Considéré comme plus secure que shell=True
Note : le premier processus ne peut être exécuté avec subprocess.run(), on aurait une erreur. Le 1er processus doit être invoqué par subprocess.Popen(). Le 2e processus peut être invoqué par subprocess.run()
# -- Enchainer les entrées et sorties -- # ## EXEMPLE 1 import subprocess p1 = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE) result = subprocess.run(["grep", "txt"], stdin=p1.stdout) print("STDOUT:", result.stdout) print("STDERR:", result.stderr) ## EXEMPLE 2 import subprocess ls_process = subprocess.Popen(["ls"], stdout=subprocess.PIPE, text=True) grep_process = subprocess.Popen(["grep", "sample"], stdin=ls_process.stdout, stdout=subprocess.PIPE, text=True) output, error = grep_process.communicate() print(output) print(error)
source et bien sur la doc officielle de python (peut-être moins accessible/lisible).
Pour des usages facilités, le module subprocess expose d'autres fonctions de haut niveau:
subprocess.run() : This is the recommended method for invoking subprocesses in Python 3.5 and above. It runs a command, waits for it to finish, and then returns a CompletedProcess instance that contains information about the executed processsubprocess.call() : Runs a command, waits for it to finish, and then returns the return code. It's a simple way to run a command and check its return code but doesn't capture outputsubprocess.check_call() : Similar to subprocess.call, but raises a CalledProcessError exception if the command returns a non-zero exit codesubprocess.check_output() : Runs a command, waits for it to finish, captures its output, and then returns that output as a byte string. It raises a CalledProcessError if the command returns a non-zero exit code