Exécuter des commandes Shell

PythonBeginner
Pratiquer maintenant

Introduction

Dans le cadre des opérations, vous devez souvent appeler des commandes système existantes, même lorsque vous écrivez du Python. Il est donc important de comprendre les outils d’exécution des processus de Python.

Le module subprocess permet à Python de lancer d’autres programmes, de capturer leur sortie et d’examiner leur réussite ou leur échec en toute sécurité. Il s’agit de la méthode standard pour faire communiquer du code Python avec des commandes Shell.

Dans ce laboratoire, vous apprendrez à :

  1. Importer le module subprocess.
  2. Exécuter une commande Linux simple (ls -l) avec Python.
  3. Capturer la sortie de la commande pour la traiter ultérieurement.
  4. Décoder la sortie pour passer de bytes à une chaîne lisible.
  5. Gérer correctement les erreurs lorsqu’une commande échoue.

Vous travaillerez dans le répertoire ~/project d’un environnement Linux.

Importer subprocess

Dans cette étape, vous allez créer un script Python et importer le module nécessaire pour interagir avec le Shell du système.

Le module subprocess fait partie de la bibliothèque standard de Python : aucune installation supplémentaire n’est donc nécessaire. Il permet à votre script Python de communiquer avec le système d’exploitation sous-jacent.

Il constitue le pont standard entre le code Python et les outils Unix existants. Dans le cadre des opérations, ce pont est important, car vous remplacez rarement toutes les commandes Shell en une seule fois. Le plus souvent, Python joue le rôle de coordinateur : il appelle les outils système, capture leurs résultats et ajoute une logique de niveau supérieur autour de ces appels.

Commencez par vérifier que vous vous trouvez dans le bon répertoire :

cd ~/project

Créez maintenant un nouveau fichier Python nommé list_files.py à l’aide de l’éditeur nano ou de la méthode de votre choix :

nano list_files.py

Ajoutez la ligne suivante au début du fichier pour importer le module :

import subprocess

print("Module imported successfully")

Enregistrez le fichier, puis quittez l’éditeur (dans nano, appuyez sur Ctrl+X, puis sur Y et enfin sur Enter).

Exécutez maintenant le script pour vérifier que tout fonctionne correctement :

python3 list_files.py

Le message « Module imported successfully » doit s’afficher.

Exécuter une commande

Dans cette étape, vous allez modifier votre script pour exécuter réellement une commande Linux. Nous utiliserons la fonction subprocess.run(), introduite dans Python 3.5 comme méthode recommandée pour lancer des sous-processus.

Lorsque Python lance un autre programme, il crée un processus distinct. Ce processus enfant possède son propre code de sortie, sa sortie standard et sa sortie d’erreur, comme une commande exécutée directement dans le Shell. Comprendre cette séparation entre les processus facilite la gestion des erreurs par la suite.

Ouvrez de nouveau list_files.py :

nano list_files.py

Modifiez le fichier afin qu’il corresponde au code suivant. Nous utiliserons la commande ls -l pour afficher les fichiers du répertoire courant avec leurs détails :

import subprocess

## Run the 'ls -l' command
## The command and its arguments are passed as a list of strings
subprocess.run(["ls", "-l"])

Remarquez que la commande est transmise sous forme de liste : ["ls", "-l"]. Cette méthode est plus sûre et plus claire que le passage d’une chaîne unique, car elle évite les problèmes liés à l’injection Shell et aux espaces dans les noms de fichiers.

Enregistrez le fichier, puis quittez l’éditeur.

Exécutez maintenant le script :

python3 list_files.py

La sortie de la commande ls -l doit s’afficher directement dans votre terminal. Par défaut, subprocess.run envoie en effet la sortie de la commande vers la sortie standard, c’est-à-dire votre écran.

Capturer la sortie

À l’étape précédente, la sortie était affichée directement à l’écran. Dans les scripts d’automatisation, vous souhaitez généralement capturer cette sortie dans une variable afin que votre code Python puisse la traiter (par exemple, analyser les noms de fichiers ou vérifier la présence d’erreurs).

C’est la différence entre exécuter simplement une commande et l’intégrer dans un programme. Une fois la sortie capturée sous forme de données, votre script peut prendre des décisions à partir de celle-ci, au lieu de laisser le résultat uniquement dans le terminal pour qu’une personne le lise.

Pour cela, vous devez utiliser l’argument capture_output=True.

Ouvrez de nouveau list_files.py :

nano list_files.py

Modifiez le code pour capturer le résultat :

import subprocess

## Run the command and capture the output
result = subprocess.run(["ls", "-l"], capture_output=True)

## Print the captured result object
print(result)

Enregistrez le fichier, puis quittez l’éditeur.

Exécutez le script :

python3 list_files.py

La sortie devrait ressembler à ceci :

CompletedProcess(args=['ls', '-l'], returncode=0, stdout=b'total 0\n-rw-rw-r-- 1 labex labex 0 ...', stderr=b'')

Il s’agit d’un objet CompletedProcess. Remarquez la partie stdout=b'...'. Le b signifie bytes. Python a donc capturé les données brutes exactement comme l’ordinateur les voit, et non sous forme de texte lisible (chaîne de caractères). À l’étape suivante, vous convertirez ces données en texte lisible.

Décoder la sortie

Comme la sortie est capturée sous forme de bytes, vous devez la décoder en chaîne Python standard (UTF-8) pour pouvoir la lire et l’utiliser.

La distinction entre bytes et texte est fondamentale en Python. Les ordinateurs manipulent finalement des bytes bruts, tandis que les utilisateurs ont généralement besoin de texte. De nombreux problèmes déroutants dans les scripts d’automatisation viennent de l’oubli du format actuellement utilisé.

Ouvrez une nouvelle fois list_files.py :

nano list_files.py

Modifiez le code pour accéder à l’attribut stdout et le décoder :

import subprocess

## Run the command and capture the output
result = subprocess.run(["ls", "-l"], capture_output=True)

## Decode the bytes to a string
output_string = result.stdout.decode("utf-8")

## Print the clean string
print("--- Command Output ---")
print(output_string)

Enregistrez le fichier, puis quittez l’éditeur.

Exécutez le script :

python3 list_files.py

La sortie formatée de la commande ls -l doit maintenant s’afficher proprement, comme si vous l’aviez exécutée directement dans le Shell, mais elle est cette fois affichée par Python.

Remarque : vous pouvez également transmettre text=True (ou universal_newlines=True) à subprocess.run() afin que Python effectue automatiquement le décodage. Cependant, il est important de comprendre la méthode .decode() pour pouvoir traiter des données binaires.

Gérer les erreurs

Que se passe-t-il si la commande échoue ? Par exemple, si vous essayez d’afficher le contenu d’un répertoire qui n’existe pas ? Un script fiable doit gérer ce type d’erreur.

Dans les systèmes de type Unix, l’échec d’une commande est généralement signalé par un état de sortie différent de zéro. subprocess.run(..., check=True) transforme cette convention du système d’exploitation en exception Python. Votre script peut ainsi réagir en utilisant le flux de contrôle habituel de Python.

Créons un nouveau script nommé handle_error.py pour illustrer ce fonctionnement.

nano handle_error.py

Ajoutez le code suivant. Nous allons essayer d’afficher le contenu d’un répertoire inexistant nommé /nonexistent. Nous utilisons également check=True, qui demande à Python de lever une exception CalledProcessError lorsque la commande renvoie un code de sortie différent de zéro, ce qui indique un échec.

import subprocess

try:
    ## Try to list a directory that does not exist
    ## check=True raises an exception if the command fails
    subprocess.run(["ls", "/nonexistent"], check=True, capture_output=True)

except subprocess.CalledProcessError as e:
    print("An error occurred!")
    print(f"Return code: {e.returncode}")
    ## We can also print the error message generated by the command (stderr)
    print(f"Error message: {e.stderr.decode('utf-8')}")

Enregistrez le fichier, puis quittez l’éditeur.

Exécutez le script :

python3 handle_error.py

La sortie doit indiquer que l’erreur a été interceptée :

An error occurred!
Return code: 2
Error message: ls: cannot access '/nonexistent': No such file or directory

Sans le bloc try-except, le programme s’arrêterait avec une trace d’erreur difficile à lire. Cette approche permet à votre script de détecter les échecs et de décider de la suite à donner, par exemple enregistrer l’erreur, réessayer ou quitter proprement.

Résumé

Dans ce laboratoire, vous avez découvert les bases de l’exécution de commandes Shell avec le module subprocess de Python. Vous avez appris à :

  • Importer le module subprocess.
  • Exécuter des commandes avec subprocess.run().
  • Capturer la sortie standard avec capture_output=True.
  • Décoder une sortie sous forme de bytes en chaînes lisibles.
  • Gérer les échecs de commandes avec check=True et des blocs try-except.

Ces compétences sont essentielles pour créer des scripts d’automatisation, des outils de déploiement et des utilitaires d’administration système en Python.