Portée des variables et statut de retour

ShellBeginner
Pratiquer maintenant

Introduction

Lorsque vous écrivez des scripts, vous devez contrôler la manière dont les données sont accessibles et la façon dont les fonctions indiquent leur réussite ou leur échec.

Si vous utilisez le même nom de variable à deux endroits différents, une variable peut accidentellement écraser l’autre. C’est un bug fréquent en programmation. Pour l’éviter, nous utilisons la portée (scope), qui permet de conserver les variables dans le contexte d’une fonction.

Les fonctions doivent également pouvoir indiquer au script principal si elles ont réussi ou échoué. Dans Bash, cela se fait à l’aide des codes de statut de retour.

Ces deux notions deviennent essentielles dans les scripts volumineux : empêcher les variables d’avoir un effet en dehors de leur contexte prévu et utiliser le statut de réussite ou d’échec pour contrôler le déroulement de l’exécution.

Dans ce lab, vous apprendrez à :

  1. Gérer la portée des variables : utiliser local pour éviter les conflits entre variables.
  2. Gérer le statut de retour : utiliser les codes de sortie (0 pour une réussite, une valeur différente de zéro pour un échec) afin de contrôler le déroulement du script.

Portée des variables : globale ou locale

Par défaut, les variables Bash sont globales. Cela signifie que si vous définissez une variable dans une fonction, elle peut être consultée et modifiée partout dans le script. Cette situation peut provoquer des bugs difficiles à repérer : une partie du script peut modifier accidentellement des données utilisées par une autre partie.

Pour éviter cela, utilisez le mot-clé local. Une variable local existe uniquement dans la fonction où elle est créée.

C’est l’un des compromis liés aux scripts shell. Bash permet de commencer rapidement, mais cette simplicité facilite aussi la propagation inattendue des données dans le script. La portée définit la zone dans laquelle une variable est visible.

Observez cette différence.

Créez un script nommé scope.sh :

cd ~/project
nano scope.sh

Ajoutez le code suivant :

#!/bin/bash

## This is a Global variable
city="New York"

print_city() {
  ## This is a Local variable. It shares the name 'city', but it is a separate variable.
  local city="London"
  echo "Inside function: $city"
}

echo "Before function call: $city"

## Run the function
print_city

## Check if the Global variable was changed
echo "After function call: $city"

Enregistrez le fichier (Ctrl+O, puis Enter) et quittez l’éditeur (Ctrl+X).

Rendez le script exécutable, puis exécutez-le :

chmod +x scope.sh
./scope.sh

Résultat :

Before function call: New York
Inside function: London
After function call: New York

Remarquez que la variable city située en dehors de la fonction conserve la valeur "New York". La fonction utilise sa propre copie local de city ("London") sans modifier la variable globale.

Si nous n’avions pas utilisé local, la fonction aurait écrasé la variable globale et la dernière ligne aurait affiché "London".

C’est pourquoi local devient de plus en plus important lorsque les scripts grandissent. Dans un exemple court, les variables globales peuvent sembler inoffensives. Dans un script de maintenance plus long, la réutilisation d’un nom de variable peut créer des bugs subtils et difficiles à diagnostiquer.

Codes de statut de retour

Dans de nombreux langages de programmation, les fonctions renvoient des données, comme une chaîne de caractères ou un nombre. Dans Bash, return sert strictement à renvoyer des codes de statut indiquant une réussite ou un échec.

  • 0 : réussite.
  • 1-255 : échec (erreur).

Ne confondez pas return et echo.

  • echo affiche du texte à l’écran, ou dans une variable si sa sortie est capturée.
  • return transmet discrètement un nombre au système pour indiquer « j’ai réussi » ou « j’ai échoué ».

Cette distinction est fondamentale en programmation shell. La sortie texte et le signalement du statut sont deux canaux distincts. Une fonction peut afficher des informations lisibles avec echo, mais sa valeur de retour sert à contrôler le déroulement du script, et non à transporter des données générales.

Créons une fonction qui simule un échec en renvoyant le code 5.

Créez status.sh :

nano ~/project/status.sh

Ajoutez le code suivant :

#!/bin/bash

check_file() {
  echo "Checking for file..."
  ## We simulate a specific error by returning Code 5
  return 5
}

check_file

Enregistrez le fichier et quittez l’éditeur.

Rendez le script exécutable, puis exécutez-le :

chmod +x status.sh
./status.sh

Sortie :

Checking for file...

Vous voyez le texte, mais où se trouve le nombre 5 ? Il a été renvoyé silencieusement. À l’étape suivante, vous apprendrez à le récupérer et à l’utiliser.

Récupérer le code de retour ($?)

Pour consulter le code de retour de la dernière commande ou fonction exécutée, Bash fournit une variable spéciale : $?.

Lorsque check_file se termine, $? contient automatiquement la valeur 5.

Règle essentielle : vous devez lire $? immédiatement après l’exécution de la commande. Dès que vous exécutez une autre commande, même un simple echo, $? est remplacé par le statut de cette nouvelle commande.

Modifions status.sh pour récupérer le code.

Ouvrez le fichier :

nano status.sh

Remplacez son contenu par :

#!/bin/bash

check_file() {
  echo "Checking for file..."
  return 5
}

check_file

## Capture the return code immediately into a variable
ret_val=$?

echo "The function returned: $ret_val"

Enregistrez le fichier et quittez l’éditeur. Exécutez de nouveau le script :

./status.sh

Sortie :

Checking for file...
The function returned: 5

Vous avez maintenant récupéré le code d’erreur dans la variable ret_val.

La récupération du code de retour permet au reste du script de réagir de manière appropriée. Une instruction if ultérieure peut vérifier cette valeur et décider de poursuivre, de réessayer ou de s’arrêter. Les fonctions peuvent ainsi coopérer dans des scripts plus volumineux au lieu de rester des fragments de code isolés.

Agir en fonction du statut de retour

Maintenant que vous disposez du code de statut, vous pouvez utiliser une instruction if pour gérer les erreurs correctement.

  • Si le code vaut 0, tout fonctionne correctement.
  • Si le code vaut 5, vous savez précisément ce qui s’est produit, par exemple : « fichier introuvable ».

C’est ainsi que se comportent les commandes Linux standard. Par exemple, grep renvoie 0 lorsqu’il trouve une correspondance et 1 lorsqu’il n’en trouve aucune.

Modifiez status.sh pour y ajouter cette logique :

nano status.sh

Ajoutez la logique conditionnelle suivante :

#!/bin/bash

check_file() {
  echo "Checking for file..."
  return 5
}

check_file
ret_val=$?

## Logic based on the return code
if [ $ret_val -eq 0 ]; then
  echo "Success: File found."
elif [ $ret_val -eq 5 ]; then
  echo "Error: File missing (Code 5)."
else
  echo "Unknown Error: Code $ret_val"
fi

Enregistrez le fichier et quittez l’éditeur. Exécutez le script :

./status.sh

Sortie :

Checking for file...
Error: File missing (Code 5).

Vous avez maintenant mis en place une gestion basique des erreurs. Si la vérification du fichier échoue, le script le détecte et peut afficher un message d’erreur précis au lieu de s’arrêter brutalement ou de continuer sans contrôle.

Résumé

Dans ce lab, vous avez mis en pratique deux notions essentielles des scripts :

  1. Portée des variables (local) : utiliser local empêche les variables définies dans les fonctions d’écraser les variables globales, ce qui rend le comportement du script plus prévisible.
  2. Statut de retour (return, $?) : les codes de sortie permettent aux fonctions de communiquer une réussite ou un échec au script principal. Vous pouvez ainsi écrire des scripts capables de gérer intelligemment les erreurs.

Utilisez toujours local pour les variables définies dans les fonctions, sauf si vous devez explicitement modifier une variable globale.