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