Cours Linux / Écrire un script Bash avec variables, conditions et boucle

Un script Bash rassemble des commandes et des décisions dans un fichier exécutable. Pour créer votre premier script, vérifiez son argument, protégez les chemins par des guillemets, parcourez les fichiers avec une boucle, puis contrôlez sa sortie et son code de fin. L’exercice ci-dessous fonctionne aussi avec un nom de dossier contenant une espace.

Il vise Bash sur Ubuntu ou Rocky Linux et n’écrit que de petits fichiers dans un nouveau dossier de pratique. Pour les règles de développement plus avancées, commencez par le cours sur le fonctionnement du shell, les variables et les états de sortie.

Choisir Bash et préparer les fichiers de pratique

Vérifiez que bash --version affiche Bash. La construction [[ ... ]] du programme et d’autres possibilités de Bash ne sont pas garanties par un interpréteur sh différent : lancez donc bash list-files.sh, pas sh list-files.sh. Enregistrez les scripts avec des fins de ligne LF, notamment après une édition sur Windows.

Dans un même terminal, créez un répertoire neuf et deux fichiers ordinaires. Chaque commande doit réussir avant de poursuivre ; mktemp -d affiche un chemin unique sous votre dossier personnel.

bash --version
lab=$(mktemp -d "$HOME/linux-script-lab.XXXXXX") &&
  cd "$lab" &&
  mkdir -- "sample files" &&
  printf 'one\n' > "sample files/alpha.txt" &&
  printf 'two\n' > "sample files/beta.txt" &&
  printf 'Dossier : %s\n' "$lab"

Après le changement de dossier, votre terminal doit se trouver dans le chemin affiché. Les commandes suivantes supposent que vous restez dans ce terminal : lancer chaque bloc dans un nouveau processus perdrait le répertoire courant et la variable lab. Si la création échoue, arrêtez-vous et vérifiez le chemin plutôt que de continuer dans un autre dossier.

Écrire un programme Bash complet

Dans le dossier de pratique, le bloc suivant crée list-files.sh et le lance. Il ne modifie aucun fichier reçu en argument ; il affiche le nom de chaque fichier ordinaire directement présent dans le dossier donné. Le marqueur EOF entre apostrophes empêche votre shell de développer $directory lors de la création : ces variables seront lues plus tard par le script.

cat > list-files.sh <<'EOF'
#!/usr/bin/env bash
if (( $# != 1 )); then
  printf 'Utilisation : %s RÉPERTOIRE\n' "$0" >&2
  exit 2
fi
directory=$1
if [[ ! -d "$directory" ]]; then
  printf 'Ce répertoire est absent : %s\n' "$directory" >&2
  exit 1
fi
count=0
for path in "$directory"/*; do
  [[ -f "$path" ]] || continue
  printf '%s\n' "${path##*/}"
  count=$((count + 1))
done
printf 'fichiers=%s\n' "$count"
EOF
bash -n list-files.sh
bash list-files.sh "sample files"

Vous devez obtenir alpha.txt, beta.txt et fichiers=2. L’ordre des noms peut varier avec la locale. bash -n ne vérifie que la syntaxe, sans démontrer que le chemin existe ni que les permissions conviennent. La première ligne #!/usr/bin/env bash est un shebang qui désigne l’interpréteur lorsque le fichier est lancé directement ; le symbole # ne représente pas ici une invite d’administrateur.

Comprendre les arguments, les guillemets et le glob

$# contient le nombre d’arguments ; le programme exige exactement un chemin. directory=$1 assigne le premier argument, sans espace autour de =. Les expansions "$directory" gardent une espace du chemin dans un seul argument. Les apostrophes simples empêchent normalement l’expansion de variables, alors que les guillemets doubles la permettent sans découper la valeur en plusieurs mots ni traiter ses caractères comme un motif. Le manuel de l’expansion des paramètres Bash détaille ce comportement.

Dans "$directory"/*, le chemin est protégé, mais * reste libre de développer les noms du dossier. En configuration Bash ordinaire, un motif sans correspondance peut rester littéral : [[ -f "$path" ]] || continue l’ignore alors. Le motif ne descend pas dans les sous-dossiers et ignore les noms commençant par un point, sauf si les options de glob du shell ont été modifiées. Un lien symbolique vers un fichier ordinaire peut satisfaire -f ; cet exemple liste des chemins, il n’est pas un inventaire de sécurité.

Lire les conditions, la boucle et les codes de fin

Construction Rôle dans cet exemple
(( $# != 1 )) Refuse un nombre d’arguments différent de un.
[[ ! -d "$directory" ]] Refuse un chemin qui n’est pas un dossier.
for …; do …; done Traite chaque chemin développé par le motif.
${path##*/} Retire le plus long préfixe finissant par une barre oblique et affiche seulement le nom.
exit 2 / exit 1 Signale une erreur d’usage / un dossier absent, selon les choix de ce programme.

Quand le dernier printf réussit, le script se termine normalement avec l’état 0. Les états 1 et 2 ont ici une signification définie par l’auteur du script ; ils ne sont pas une classification universelle de toutes les commandes. $? conserve l’état de la commande immédiatement précédente et doit être lu avant toute autre commande. Le manuel des conditions Bash et celui des boucles donnent les règles complètes.

bash list-files.sh
printf 'état=%s\n' "$?"
bash list-files.sh missing-directory
printf 'état=%s\n' "$?"
chmod u+x list-files.sh
./list-files.sh "sample files"

Les deux appels sans dossier valide doivent afficher leur message d’erreur, puis les états 2 et 1. Le dernier appel retrouve les deux noms : chmod u+x autorise l’exécution directe pour le propriétaire, à condition que le shebang et son interpréteur soient utilisables. Cette commande de permission ne donne aucun droit particulier au programme.

Faire varier une entrée et vérifier la sortie

  1. Lancez le programme sur "sample files" et confirmez fichiers=2.
  2. Ajoutez un seul fichier avec printf 'three\n' > "sample files/gamma.txt" et vérifiez fichiers=3.
  3. Supprimez seulement "sample files/gamma.txt", le fichier créé à l’étape précédente, puis vérifiez que le total redevient 2.
  4. Créez un dossier vide neuf ; il doit produire fichiers=0. Un chemin absent doit provoquer un message d’erreur et l’état 1.

La boucle ne trie pas elle-même ; seul le compte est l’observation stable de cette comparaison. Vérifiez le dossier courant avec pwd avant de retirer des fichiers de pratique. Ne remplacez pas une suppression ciblée par une commande récursive sur un chemin que vous n’avez pas contrôlé.

Corriger une fin de ligne CRLF si le script refuse de démarrer

Après une édition sur Windows, un lancement direct peut mentionner bash\r ou ^M lorsque le shebang contient CRLF. Dans VS Code, le menu CRLF de la barre d’état permet de choisir LF puis d’enregistrer. bash list-files.sh lit directement le fichier et peut éviter l’erreur de shebang, mais il vaut mieux corriger les fins de ligne pour l’exécution directe.

Si GNU sed est disponible, vous pouvez copier uniquement le fichier de cet exercice puis retirer le caractère de retour chariot en fin de ligne. cp -i demande avant d’écraser une ancienne copie ; inspectez la cible si une copie existe déjà.

cp -i -- list-files.sh list-files.sh.crlf-backup
sed -i 's/\r$//' list-files.sh
bash -n list-files.sh
bash list-files.sh "sample files"

La vérification de syntaxe doit se terminer sans message et le programme doit afficher le nombre de fichiers réellement présents. Pour revenir en arrière, inspectez la copie puis exécutez cp -i -- list-files.sh.crlf-backup list-files.sh. Le script original et son dossier de pratique restent sous votre contrôle ; aucun nettoyage automatique n’est nécessaire.