Nous avons vu comment écrire des commandes simples, où une commande est suivie d'arguments. Nous allons maintenant voir comment construire des commandes plus complexes en composant des commandes simples.
Avant de commencer, une brève introduction aux entrées/sorties.
Les processus disposent de « canaux d'E/S standard ».
La commande tr en est un exemple très pur.
Tout ce qu'elle fait, c'est lire du texte depuis son entrée, effectuer des translittérations de caractères, puis afficher le texte obtenu sur sa sortie.
Nous verrons plus en détail la manipulation des canaux « stdio » plus loin dans le programme.
C'est l'une des « fonctionnalités phares » de la programmation en shell. Les pipelines permettent de créer des transformations élaborées sur un flux de texte.
Pour produire une liste triée d'utilisateurs :
cat /etc/passwd | cut -d : -f 1 | sort
Le symbole pipe (|) relie la sortie d'une commande à l'entrée d'une autre.
cut lit la sortie de cat, et sort lit la sortie de cut.
Par défaut, chaque commande d'un pipeline s'exécute dans un sous-shell distinct. (Un sous-shell est un processus enfant qui est une copie du shell en cours d'exécution.)
L'exécution de pipelines a un coût en performance.
Si tu te retrouves avec de longs pipelines de commandes similaires, envisage de les regrouper en une seule commande.
Par exemple, les pipelines qui utilisent plusieurs instances de grep, cut, sed, awk et tr peuvent généralement être regroupés en une seule commande awk pour plus d'efficacité.
Le code de sortie d'un pipeline est le code de sortie de la dernière commande du pipeline. Cela signifie que si une commande précédente a échoué mais que la dernière a réussi, le code de sortie du pipeline entier est zéro.
Il existe un paramètre du shell qui peut changer cela ;
si le paramètre « pipefail » est activé (avec set -o pipefail), le code de sortie du pipeline sera :
Une liste de commandes est une suite de pipelines séparés par ; (ou un saut de ligne), && ou ||.
A; B est une liste de commandes où B s'exécute après la fin de A.A && B, où B ne s'exécute que si A réussit (se termine avec un code de sortie zéro).A || B, où B ne s'exécute que si A échoue (se termine avec un code de sortie non nul).Le code de sortie d'une liste de commandes est le code de sortie de la dernière commande exécutée.
Les opérateurs && et || peuvent être enchaînés pour que la commande suivante s'exécute conditionnellement, en fonction du code de sortie des commandes précédentes.
Par exemple
A && B && C && D || E
Utilise ces opérateurs logiques avec parcimonie. Ils peuvent rapidement mener à du code illisible, ou à une logique difficile à comprendre.
Par exemple, tu pourrais penser que ces deux extraits sont identiques :
if A; then B; else C; fi
A && B || C
La différence est la suivante : quand C s'exécute-t-il ?
if), C ne s'exécute que si A échoue.Voici quelques exemples où les listes de commandes permettent de simplifier le code.
Imaginons que tu disposes d'un fichier de données contenant des informations sur des triangles, et où un triangle est représenté par trois lignes distinctes contenant ses côtés.
$ cat triangle.dat
3
4
5
9
12
14
Tu peux utiliser une boucle while dont la condition est constituée de trois commandes read distinctes :
while read a && read b && read c; do
if is_pythagorean "$a" "$b" "$c"; then
echo "$a:$b:$c is pythagorean"
else
echo "$a:$b:$c is not pythagorean"
fi
done < triangle.dat
En supposant que is_pythagorean soit une commande qui détermine si les trois côtés satisfont l'équation de Pythagore, la sortie serait :
3:4:5 is pythagorean
9:12:14 is not pythagorean
Beaucoup de langages de programmation proposent une forme d'assertion qui lève une exception si une condition n'est pas satisfaite
assert(user == "Administrator", "wrong user");
On peut utiliser un opérateur OU en Bash pour simuler cette fonctionnalité :
die () { # a little convenience function
echo "$*" >&2
exit 1
}
[[ $user == "Administrator" ]] || die "wrong user"
[[ $password == "secret!" ]] || die "incorrect password"
Les longues listes de commandes deviennent vite difficiles à lire. Un usage généreux des sauts de ligne peut beaucoup aider.
Prenons cet exemple où un mot est ajouté à un tableau si deux conditions sont remplies.
[[ "$word" != "$topic" ]] && [[ "$key" == "$(sorted "$topic")" ]] && anagrams+=("$candidate")
Bash permet d'ajouter un saut de ligne après un pipe ou un opérateur logique.
[[ "$word" != "$topic" ]] &&
[[ "$key" == "$(sorted "$topic")" ]] &&
anagrams+=("$candidate")
Cependant, l'opérateur peut facilement passer inaperçu en fin de ligne. Utiliser une continuation de ligne permet de placer l'opérateur en début de ligne, ce qui rend plus évident que la liste se poursuit :
[[ "$word" != "$topic" ]] \
&& [[ "$key" == "$(sorted "$topic")" ]] \
&& anagrams+=("$candidate")
Une continuation de ligne est la séquence de deux caractères « barre oblique inversée » et « saut de ligne » (\ + \n).
Quand Bash rencontre cette séquence, elle est simplement supprimée du code, ce qui prolonge la ligne courante avec la ligne suivante.
Fais attention à ne laisser aucun espace entre la barre oblique inversée et le saut de ligne.
Voici un autre exemple
printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort
ou
printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort