Interagir avec des Processus Externes via le Module Subprocess de Python

Le module subprocess de la bibliothèque standard de Python permet d'exécuter de nouvelles applications et de gérer leurs entrées/sorties et leurs codes de retour. Il est conçu pour remplacer les fonctions et modules plus anciens comme os.system, os.spawn*, os.popen*, popen2.* et commands.*, offrant une interface plus puissante et flexible pour la création et la gestion de processus enfants.

Principales fonctions du module subprocess

Le module subprocess propose plusieurs fonctions pour différentes stratégies d'exécution de commandes :

  • subprocess.run() : La fonction recommandée pour la plupart des cas d'utilisation simples, car elle exécute la commande et attend sa fin. Elle renvoie un objet CompletedProcess.
  • subprocess.Popen() : Fournit un contrôle plus fin sur le processus enfant. C'est le bloc de construction sous-jacent à partir duquel les autres fonctions sont implémentées. Il est utilisé lorsque vous avez besoin d'interagir avec le processus de manière asynchrone ou sur une longue période.
  • subprocess.call() : Exécute une commande et attend qu'elle se termine, renvoyant son code de retour. Bien qu'il existe toujours, subprocess.run() est généralement préféré car il capture également la sortie et lève des exceptions en cas d'erreur.

Utilisation de subprocess.run()

La fonction subprocess.run() est la méthode la plus simple et la plus courante pour exécuter des commandes externes et attendre leur achèvement. Sa signature de base est la suivante :

subprocess.run(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, check=False, encoding=None, text=None, capture_output=False)

Voici un aperçu des paramètres les plus couramment utilisés :

Paramètre Description
args La commende à exécuter. Il peut s'agir d'une séquence de chaînes de caractères (ex: ['ls', '-l']) ou d'une seule chaîne (ex: 'ls -l'). Si args est une chaîne, shell doit être défini sur True. Il est généralement recommandé d'utiliser une séquence de chaînes pour des raisons de sécurité.
stdin, stdout, stderr Contrôlent les flux d'entrée, de sortie et d'erreur du processus enfant. Par défaut, ils sont hérités du processus parent. Les valeurs courantes incluent subprocess.PIPE (pour capturer le flux), un descripteur de fichier ou un objet fichier. stderr=subprocess.STDOUT redirige l'erreur standard vers la sortie standard.
shell Si True, la commande spécifiée sera exécutée via un shell système. Cela peut être pratique pour des commandes shell complexes, mais présente des risques de sécurité si args provient d'une source non fiable.
cwd Définit le répertoire de travail courant pour le processus enfant. Si omis, le répertoire de travail du processus parent est utilisé.
timeout Définit une durée maximale en secondes pour l'exécution de la commande. Si la commande dépasse ce délai, le processus enfant est terminé et une exception TimeoutExpired est levée.
check Si True, une exception CalledProcessError est levée si le processus enfant se termine avec un code de retour non nul (indiquant une erreur). L'objet exception contient les arguments, le code de retour et les sorties capturées.
encoding ou text Si spécifié, stdin, stdout et stderr recevront/renverront des chaînes de caractères au lieu d'octets, encodées selon la valeur de encoding. Si text=True (équivalent à encoding=True dans les versions plus anciennes), l'encodage par défaut du système est utilisé.
capture_output Si True, stdout et stderr seront configurés sur subprocess.PIPE. C'est une manière pratique de capturer les sorties sans les spécifier individuellement.

Exemple de base avec subprocess.run()

Voici comment exécuter une commande simple et analyser son résultat :

import subprocess

# Exécution d'une commande système pour lister le contenu du répertoire courant
# Nous utilisons 'capture_output=True' pour obtenir la sortie et l'erreur
# et 'text=True' pour que les sorties soient des chaînes de caractères.
commande = ["ls", "-la"]
resultat_execution = subprocess.run(
    commande,
    capture_output=True, # Capture stdout et stderr
    text=True,           # Décode les sorties en chaînes de caractères
    timeout=10,          # Temps maximum de 10 secondes
    check=False          # Ne lève pas d'exception pour les codes de retour non nuls
)

print(f"Objet CompletedProcess : {resultat_execution}")
print(f"Code de retour de la commande : {resultat_execution.returncode}")

if resultat_execution.stdout:
    print(f"\n--- Sortie Standard (stdout) ---\n{resultat_execution.stdout}")

if resultat_execution.stderr:
    print(f"\n--- Erreur Standard (stderr) ---\n{resultat_execution.stderr}")

# Exemple avec une commande qui n'existe pas pour illustrer l'erreur
commande_invalide = ["commande_qui_n_existe_pas"]
resultat_erreur = subprocess.run(
    commande_invalide,
    capture_output=True,
    text=True
)

print(f"\n--- Résultat de la commande invalide (code {resultat_erreur.returncode}) ---")
print(f"Sortie standard : '{resultat_erreur.stdout.strip()}'")
print(f"Erreur standard :\n{resultat_erreur.stderr}")

Cas pratique : Récupération de l'état d'un processus

Ce cas montre comment exécuter une commande et récupérer son code de sortie ainsi que ses flux standard et d'erreur de manière structurée.

import subprocess

# Commande à exécuter pour obtenir des informations système (par exemple, le chemin actuel)
# Nous utilisons 'shell=True' ici pour montrer l'option, bien qu'une liste soit souvent préférée.
commande_shell_specifique = "pwd"
informations_processus = subprocess.run(
    commande_shell_specifique,
    shell=True,
    capture_output=True,
    text=True,
    check=True # Lèvera une CalledProcessError si la commande échoue
)

print("--- Analyse de l'exécution ---")
print(f"Code de sortie du processus : {informations_processus.returncode}")

if informations_processus.stdout:
    print(f"\nContenu de la sortie standard :\n{informations_processus.stdout.strip()}")

if informations_processus.stderr:
    print(f"\nMessages d'erreur standard :\n{informations_processus.stderr.strip()}")

# Un autre exemple avec une commande UNIX courante
def obtenir_details_fichiers(chemin='.'):
    """Exécute 'ls -lh' pour un chemin donné et renvoie les détails."""
    try:
        details_commande = ["ls", "-lh", chemin]
        process_info = subprocess.run(
            details_commande,
            capture_output=True,
            text=True,
            check=True # Vérifie le succès, lève une erreur sinon
        )
        return {
            "code_retour": process_info.returncode,
            "stdout": process_info.stdout,
            "stderr": process_info.stderr
        }
    except subprocess.CalledProcessError as e:
        return {
            "code_retour": e.returncode,
            "stdout": e.stdout,
            "stderr": e.stderr,
            "erreur": "La commande a échoué"
        }
    except FileNotFoundError:
        return {"erreur": "La commande 'ls' n'a pas été trouvée."}

print("\n--- Détails des fichiers du répertoire courant ---")
resultats_ls = obtenir_details_fichiers()
print(f"Code de retour : {resultats_ls['code_retour']}")
print(f"Sortie :\n{resultats_ls['stdout']}")
if resultats_ls.get('stderr'):
    print(f"Erreur :\n{resultats_ls['stderr']}")
if resultats_ls.get('erreur'):
    print(f"Statut : {resultats_ls['erreur']}")

Étiquettes: Python Subprocess processus Shell commande

Publié le 14 juillet à 04h14