Unix & Shell · L1 · Section 1/7
Bases du shell
Progression
#Bases du shell
Une commande a un nom, des arguments, et retourne un code de sortie. Ce chapitre installe les réflexes durables: lire l'aide, composer des flux, maîtriser les variables. Tout le reste du module s'appuie dessus.
#Prérequis et objectifs
Prérequis: savoir ouvrir un terminal. Objectifs: à la fin du chapitre, vous saurez prédire le code de sortie d'une commande, rediriger stdout et stderr séparément, et composer un pipeline de quatre étapes sans le tester à l'aveugle.
#Anatomie d'une commande et code de sortie
Chaque commande se termine par un code de sortie: 0 signifie succès, toute autre valeur signale une erreur. Ce code est disponible dans la variable spéciale $?. On ne le vérifie pas à chaque ligne en interactif, mais il devient crucial dans les scripts.
1true; echo $? # 0: succès2false; echo $? # 1: échec générique3ls inexistant; echo $? # 2 sur GNU/Linux: fichier introuvableDans un script, exit 3 choisit explicitement le code que verront les appelants. Les opérateurs && et || enchaînent selon ce code: cmd1 && cmd2 n'exécute cmd2 que si cmd1 réussit.
Deux réflexes avant de plonger: la complétion par tabulation (gagne du temps, évite les fautes) et la recherche dans l'historique avec Ctrl+R (retapez moins, réutilisez plus).
#Commandes intrinsèques et commandes externes
Le shell ne traite pas tous les noms de commande de la même façon. Pour savoir quoi exécuter, il applique un ordre de résolution strict: d'abord une fonction définie dans le shell, puis une opération prédéfinie, c'est-à-dire une commande intrinsèque interne au shell, puis un fichier exécutable trouvé grâce à la variable PATH. La notation par chemin absolu reste donc le moyen sûr de lever une ambiguïté.
La distinction est structurelle, pas cosmétique. Une commande intrinsèque s'exécute dans le processus du shell; une commande externe provoque la création d'un processus par le noyau. C'est ce qui impose cd comme intrinsèque: exécuté dans un processus fils, le changement de répertoire serait perdu au retour.
1whence cd # observation: cd est un builtin, chemin vide2whence ls # observation: /usr/bin/ls, donc un programme externeLes intrinsèques du cours: cd, pwd, fg, bg, exit, kill, echo, exec, . (et son synonyme source), break, continue, shift, eval, whence, alias, unalias, set, setopt, unsetopt, vared, read, test. Trois d'entre elles méritent un détour immédiat.
exec commande remplace le processus du shell par celui de la commande: le shell ne reprend pas la main. L'expérience est spectaculaire: lancez un terminal, tapez gnome-terminal, quittez-le par exit, vous retrouvez le shell d'origine; recommencez avec exec gnome-terminal et la fenêtre d'origine se ferme, puisqu'elle a été remplacée.
eval analyse deux fois sa commande: les expansions et substitutions sont évaluées une première fois, puis le résultat est relu comme une commande. C'est le mécanisme qui permet d'écrire un interpréteur en quelques lignes.
whence nom indique comment le shell interprète ce nom, et shift décale les paramètres positionnels en éliminant le premier — le geste type d'une boucle qui consomme ses arguments un par un.
#Les caractères spéciaux du shell
Certains caractères ont une signification particulière pour l'interpréteur. Le cours en donne la liste: *, ?, [, ], {, }, #, !, &, (, ), |, ;, \, <, >, $.
*et?sont les jokers, développés par le shell avant l'exécution de la commande;[...]désigne un ensemble de caractères,[^...]son complément.#introduit un commentaire dans un fichier de script;!sert à la négation et à l'historique;$introduit une variable ou une substitution.&,;,|,(,),&&et||sont les opérateurs d'enchaînement: arrière-plan, séquence, tube, regroupement, et les deux connecteurs logiques.\,<,>sont l'échappement et les redirections.
Ces caractères peuvent néanmoins figurer dans un nom de fichier, à condition d'être précédés de \. Un cas piège est le tiret: il annonce des options, et pour créer ou supprimer un fichier dont le nom commence par un tiret, il faut terminer la liste des options par -- ou préfixer le nom.
1touch -- -point!2rm -- -point!3touch "[Fa#]" # le quoting protège les crochets et le croisillon4ls # observation: le fichier [Fa#] apparaît tel quelEnfin, deux familles de guillemets ne jouent pas le même rôle, et un troisième mécanisme s'y ajoute.
- Apostrophes (
'...'): tout est figé, aucun caractère n'a de signification, pas même$. Le shell retire les apostrophes et transmet l'ensemble comme un seul mot. - Guillemets (
"..."): quatre caractères gardent leur signification —$, l'accent grave,\et le guillemet lui-même. Les substitutions et expansions s'y produisent donc encore. - Antislash (
\): annule la signification du caractère qui suit. Un blanc ne termine plus un mot, un passage à la ligne ne termine plus la commande, un opérateur redevient un caractère ordinaire.
1echo '$HOME' # observation: $HOME (littéral)2echo "$HOME" # observation: /home/antoine (développé)3echo \$HOME # observation: $HOME (littéral)#Naviguer et lister
pwd annonce le répertoire courant. ls -lah liste en affichant droits, propriétaire et taille lisible. Le globbing (*.md, img_??.png) cible un groupe de fichiers sans écrire leurs noms un par un.
1cd /var/log && pwd # observation attendue: /var/log2ls -d /etc/*/ # uniquement les sous-dossiers de /etc3echo mis*.md # le shell développe le motif avant d'exécuter#Rediriger et chaîner
Le shell manipule trois flux: entrée standard (stdin), sortie standard (stdout) et erreurs (stderr), identifiés par les descripteurs 0, 1 et 2. > redirige stdout vers un fichier en l'écrasant, >> ajoute à la fin, < lit depuis un fichier, 2> capture stderr.
1echo "rapport" > sortie.txt # crée ou écrase2echo "ligne 2" >> sortie.txt # ajoute3ls /tmp /inexistant 2> erreurs.log # stderr seule dans erreurs.log4ls /tmp /inexistant > tout.log 2>&1 # stdout et stderr dans le même fichier5wc -l < sortie.txt # lecture depuis un fichier: 2L'ordre compte: 2>&1 duplique stderr vers la destination actuelle de stdout. Écrit avant >, il pointerait encore vers le terminal.
Le tube | relie la sortie d'une commande à l'entrée de la suivante:
1# Top des clients d'un CSV, sans l'en-tête2cut -d, -f1 ventes.csv | tail -n +2 | sort | uniq -c | sort -nr | headOn compose une intention claire plutôt que de chercher une commande magique.
#Qui redirige, et pourquoi c'est décisif
La redirection est faite par le shell, indépendamment du programme qui s'exécute. C'est une propriété majeure: un programme qui se contente de lire sur son entrée standard et d'écrire sur sa sortie standard devient automatiquement un filtre utilisable dans un tube, sans avoir été écrit pour cela. Un filtre est par définition un programme qui transforme son entrée standard en sa sortie standard.
Deux règles du cours valent d'être connues par cœur, car elles distinguent zsh de bash. Le fichier en entrée doit exister. Le fichier en sortie ne doit pas exister — c'est vrai en zsh, mais pas nécessairement dans les autres shells, où > écrase. Ce comportement est commandé par l'option clobber; à Valrose elle est activée par défaut, et une question sur ce point a déjà été posée à une épreuve.
Le tableau complet des redirections utiles:
< fichierprend l'entrée standard sur un fichier;<> fichierl'utilise à la fois en entrée et en sortie.> fichierredirige la sortie standard;>! fichierremplace le fichier même s'il existe;>> fichierconcatène à la fin d'un fichier existant;>>! fichieraccepte un fichier inexistant.2> fichierredirige la sortie d'erreur;>& fichier(ou>&1) envoie les deux sorties au même endroit.<< motréalise une entrée sur place: le shell lit son entrée jusqu'à une ligne ne contenant que ce mot.
L'entrée sur place, ou heredoc, mérite un exemple, car elle évite d'écrire un fichier temporaire:
1cat << FIN > rapport.txt2Section 13Section 24FINLes deux formes se distinguent finement, et le TP 5 fait faire l'expérience:
1ls -l toto >& f1 # stdout ET stderr dans f1 : le message d'erreur y est2ls -l toto 1> f2 # stdout seule dans f2 : le fichier est VIDE, l'erreur reste à l'écran3ls -l toto 2> f3 # stderr seule dans f3 : rien à l'écran, l'erreur est dans f3#Le tube et les filtres
Le tube | connecte la sortie standard d'un processus à l'entrée standard du suivant, sans fichier auxiliaire. La différence avec l'enchaînement séquentiel ; est fondamentale: avec ; les commandes s'exécutent l'une après l'autre, sans rapport entre elles; avec | les processus sont exécutés simultanément et c'est le système qui gère leur synchronisation — un producteur plus rapide que son consommateur est suspendu temporairement. Le cours donne les deux écritures équivalentes pour illustrer le gain:
1ls -l /usr/bin > resultats ; more resultats ; rm resultats # fichier temporaire2ls -l /usr/bin | more # aucun fichier temporaireLes filtres simples à connaître sont cat, wc, head, tail, tr, cut, fold, split, paste, et les filtres élaborés sort, grep, sed et awk. Les idiomes qui reviennent dans tous les sujets d'épreuve:
1wc -l fichier # nombre de lignes2wc -w fichier # nombre de mots3head -n 20 fichier # 20 premières lignes4tail -n 20 fichier # 20 dernières lignes5head -n 50 f | tail -n 11 # lignes 40 à 506head -n 18 f | tail -n 1 # 18e ligne seule7grep -c motif f # nombre de LIGNES conformes8grep -o motif f | wc -l # nombre d'OCCURRENCES9cut -f 3 -d ";" f # 3e champ, séparateur point-virgule10cut -c 2,5-8,14- f # caractères 2, 5 à 8, puis 14 à la fin11tr 'A-Z' 'a-z' < f # minuscules12tr -d '0-9-' < f # supprimer chiffres et tirets13fold -w 16 f # lignes de 16 caractères exactement14paste -d ';' a.txt b.txt # concaténer ligne à ligne#Créer un fichier sans éditeur
Trois méthodes, trois niveaux de généralité.
1cat > premiertexte.txt # puis taper le texte, terminer par C-d (fin de fichier)cat recopie son entrée standard sur sa sortie standard; la redirection capte cette sortie. C-d envoie la fin de fichier qui clôt la saisie. C'est la méthode la plus générale, et la seule qui accepte un texte de plusieurs lignes sans astuce.
1echo "ma premiere phrase\nma seconde phrase ..." > premiertexte.txtCette forme n'est correcte qu'en zsh, dont l'echo interprète les séquences d'échappement. Le même echo en bash afficherait le texte ma premiere phrase\nma seconde phrase ... avec l'antislash visible. C'est une raison concrète pour laquelle l'UE impose zsh.
1printf 'ma premiere phrase\nma seconde phrase ...\n' > premiertexte.txtprintf prend un modèle puis des arguments: les caractères ordinaires sont affichés tels quels, les caractères spéciaux sont remplacés (\n passe à la ligne, \r revient en début de ligne, \b recule d'un caractère), et les codes de conversion ont la forme %[indicateurs][largeur][.précision]conversion. C'est la forme portable, identique dans tous les shells.
Et la commande qui piège tout le monde: cat PREMIERTEXTE.txt échoue, parce qu'Unix distingue majuscules et minuscules. Le nom PREMIERTEXTE.txt n'existe pas, seul premiertexte.txt existe.
#Corriger, compléter et retrouver une ligne
Le shell interactif est un éditeur de ligne, avec les mêmes raccourcis qu'Emacs: C-a va en début de ligne, C-e en fin, C-w efface le mot précédent, C-k efface tout ce qui suit le curseur, C-u efface toute la ligne, C-c abandonne la commande en cours de frappe. Un point à retenir du cours: la ligne n'est soumise qu'à l'appui sur Entrée, mais cette touche peut être tapée n'importe où dans la ligne — inutile d'aller à la fin.
La touche TAB demande l'achèvement automatique. Si c'est le premier mot de la commande, le shell cherche dans les fichiers exécutables de PATH; sinon, cela dépend de la commande. En cas d'ambiguïté il affiche les choix possibles; en cas de faute de frappe, il peut proposer une correction, que l'on refuse (n), accepte (y), ou que l'on corrige soi-même (e).
L'historique est accessible de plusieurs manières: C-p ou la flèche haut remonte la commande précédente, C-n ou la flèche bas redescend, C-r effectue une recherche incrémentale vers le début. Le cours cite aussi M-a, qui soumet la commande puis la repropose, C-o, qui soumet la commande puis propose la suivante de l'historique, C-l, qui efface la fenêtre et réaffiche l'invite, et M-h, qui appelle man sur la commande en cours de frappe.
#Les variables prédéfinies du shell
Au-delà de PATH et HOME, un certain nombre de variables sont fournies par le shell lui-même. Elles existent toujours, et de nombreux scripts se basent sur l'hypothèse de leur existence: les connaître est le préalable à tout script générique.
1echo $HOST # nom de la machine2echo $USERNAME # nom de l'utilisateur3echo $PWD # répertoire courant4echo $OLDPWD # répertoire précédent (ce que restaure cd -)5echo $TERM # type de terminal6echo $DISPLAY # terminal graphique du serveur X, vide en TTY7echo $RANDOM # entier pseudo-aléatoire entre 0 et 327678echo $LINENO # numéro de ligne courant dans un scriptÀ côté de ces variables, le shell expose des paramètres spéciaux, distincts des variables ordinaires: $0 est le nom de la commande, $1 à $n les paramètres positionnels, $# leur nombre, $* leur concaténation en un seul mot, $@ la liste des paramètres en autant de mots, $$ le numéro de processus du shell, $! le PID du dernier processus lancé en arrière-plan, et $? le code de retour de la dernière commande.
1echo "$# paramètres" # nombre d'arguments reçus2echo "$1" "$2" # les deux premiers3shift # élimine $1 et décale les suivantsvared ouvre une édition interactive de la valeur d'une variable, avec les raccourcis de correction habituels: c'est la façon dont le cours présente la modification de PATH.
1vared PATH2vared HOMELa commande set liste toutes les variables, env donne la liste plus courte des variables d'environnement exportées, et unset supprime une variable.
#Options du shell et fichiers de configuration
Le shell est paramétrable, et le cours insiste: avant d'écrire du code, il faut connaître les caractéristiques de son shell. Les options sont toutes booléennes; setopt les met en fonction, unsetopt les met hors fonction, le préfixe no les nie.
all_exportexporte automatiquement toutes les variables créées.auto_listliste les choix possibles lors d'un achèvement automatique ambigu.clobberautorise>à écraser un fichier existant; si l'option est désactivée, la redirection provoque une erreur.correctetcorrect_allproposent une correction des fautes de frappe, sur les commandes puis sur les paramètres.globfait fonctionner les jokers et les modèles étendus. Attention: c'est le shell qui interprètels *[0-9]*, alors quegrep -E ".*[0-9].*"est interprété pargrep.ignore_eofempêcheC-dde terminer le shell.notifysignale immédiatement les changements d'état des tâches.
Ces options, comme les alias, ne survivent pas à la fermeture d'un terminal: elles doivent être écrites dans un fichier de configuration. zsh en charge plusieurs, dans un ordre précis.
/etc/zshenvpuis~/.zshenv, toujours exécutés./etc/zprofilepuis~/.zprofile, pour tout shell de connexion./etc/zshrcpuis~/.zshrc, pour tout shell interactif./etc/zloginpuis~/.zlogin, pour tout shell de connexion./etc/zlogoutpuis~/.zlogout, en fin de shell de connexion.
En pratique: zshenv définit alias et fonctions et choisit les options, zprofile ou zlogin définit les variables d'environnement, zshrc définit le reste, notamment l'invite PS1. L'invite elle-même est une chaîne contenant des codes: %d pour le répertoire courant, %~ pour sa forme abrégée, %c pour son dernier composant, %m pour le nom de la machine, %n pour le nom de l'utilisateur, %T pour l'heure, %B et %b pour encadrer du gras, %U et %u pour du souligné.
#Exercices complémentaires
1. Écrire la commande qui crée un fichier premiertexte.txt contenant deux phrases, sans éditeur, puis celle qui affiche sa taille en kilo-octets sans utiliser ls -l.
2. Écrire la ligne de commandes qui affiche, page par page, le contenu du répertoire /usr/bin sans créer de fichier temporaire. Puis celle qui compte simplement le nombre de fichiers de ce répertoire.
3. Expliquer pourquoi echo $DISPLAY n'affiche rien dans un terminal textuel.
Corrections
1.
1echo "ma premiere phrase\nma seconde phrase ..." > premiertexte.txt2du -hs premiertexte.txtecho avec \n fonctionne en zsh; pour une solution portable, printf 'ma premiere phrase\nma seconde phrase ...\n' > premiertexte.txt. Pour la taille, du -hs donne l'occupation disque sous forme lisible; -s la résume au seul fichier. Attention: du mesure la place occupée, arrondie au bloc, alors que ls -l affiche la taille logique — les deux valeurs diffèrent pour un petit fichier.
2.
1ls -l /usr/bin | more2ls /usr/bin | wc -lLa première ligne remplace ls -l /usr/bin > resultats ; more resultats ; rm resultats en supprimant le fichier auxiliaire. La seconde compte les entrées: comme la sortie de ls est redirigée, ls écrit un nom par ligne, donc wc -l compte bien les fichiers.
3. DISPLAY contient le nom du terminal graphique auprès duquel les clients X doivent s'enregistrer, par exemple :0. Dans un terminal purement textuel, aucun serveur X n'est en écoute: la variable n'est pas définie, donc echo $DISPLAY n'affiche qu'une ligne vide. C'est aussi la raison pour laquelle lancer une application graphique depuis un TTY échoue avec un message d'erreur d'affichage.
#Variables d'environnement utiles
PATH indique où chercher les exécutables, HOME pointe vers votre dossier utilisateur, EDITOR choisit l'éditeur par défaut. Pour définir une variable valable pour les programmes lancés ensuite, utilisez export:
1export EDITOR=vim2echo "$EDITOR" # observation attendue: vimLes scripts d'initialisation (~/.bashrc, ~/.zshrc) rendent vos alias et exports durables. Allez-y progressivement, commentez vos ajouts et évitez les modifications obscures copiées sur Internet.
#Exercice (15 minutes)
Créez un dossier atelier, placez-vous dedans, créez en une ligne trois fichiers (a.txt, b.txt, c.txt) contenant chacun « bonjour », puis vérifiez le résultat.
Correction:
1mkdir -p atelier && cd atelier2for f in a b c; do echo "bonjour" > "$f.txt"; done3ls -l4cat a.txt b.txt c.txt | wc -lObservation attendue: trois fichiers d'une ligne, et wc -l affiche 3. Retenez l'idée de « chaîne d'actions lisible » plutôt que la syntaxe exacte du for.
#Animation: pipeline de commandes
#Atelier interactif: composer un pipeline
Construire une pipeline shell
Composez source, filtres et destination. Le shell enchaîne les commandes via | : chacune lit sur son stdin et écrit sur son stdout.
Astuce : activez set -euo pipefail dans vos scripts pour propager les erreurs dans les pipelines.
Source
Filtres (ordre d’exécution)
L’ordre dépend des colonnes disponibles : uniq -c attend un flux trié.
Destination
Flux résultant
cat app.log | grep "ERROR" | cut -d, -f1 | sort -nr | uniq -c | head -n 10 | less -RConseil : encapsulez la pipeline dans une fonction shell pour la réutiliser et testez-la avec des jeux de données réduits avant de l’exécuter sur un gros fichier.