Arborescence et commandes de base, redirections et tubes, variables d'environnement, premiers scripts.
En 1986, une revue demande à Donald Knuth d'illustrer la programmation lettrée. L'exercice : lire un texte, trouver les mots les plus fréquents, les afficher triés. Knuth rend un programme en Pascal d'une dizaine de pages, avec sa propre structure de données de hachage. Douglas McIlroy — l'inventeur des tubes Unix — est chargé de la critique. Il répond par six commandes :
tr -cs A-Za-z '\n' | tr A-Z a-z | sort | uniq -c | sort -rn | head -n 10L'anecdote n'est pas là pour humilier Knuth, dont le propos était autre. Elle dit ce qu'est un système Unix : non pas un gros programme qui sait tout faire, mais une collection de petits outils qui font une chose, et un moyen de les brancher les uns aux autres.
Ce chapitre est placé avant toute théorie parce que les TP des quatre blocs suivants se font sous Linux. On ne programme pas un système dans lequel on ne sait pas se déplacer.
Une seule arborescence
Contrairement aux systèmes à lettres de lecteur, Unix n'a qu'un seul arbre, dont la racine
est /. Un second disque, une clé USB, un partage réseau : tous apparaissent comme un
répertoire quelque part dans cet arbre, opération appelée montage et étudiée au chapitre 7.
| Répertoire | Contenu |
|---|---|
/bin, /usr/bin | les exécutables |
/etc | la configuration, en fichiers texte |
/home/nom | le répertoire personnel d'un utilisateur |
/tmp | les fichiers temporaires, effacés au redémarrage |
/dev | les périphériques, vus comme des fichiers |
/proc | l'état du noyau, vu comme des fichiers |
Les deux dernières lignes méritent qu'on s'y arrête, parce qu'elles illustrent le principe
« tout est fichier ». Le disque est accessible par /dev/sda, la carte son par /dev/snd, et
lire /proc/cpuinfo affiche les caractéristiques du processeur — alors qu'aucun de ces
fichiers n'existe sur un disque. Ce sont des vues, produites par le noyau au moment de la
lecture. L'intérêt est immense : les mêmes commandes, les mêmes appels système et les mêmes
droits s'appliquent aux fichiers réels et aux périphériques.
Un chemin absolu part de la racine ; un chemin relatif part du répertoire courant. Trois
raccourcis servent constamment : . désigne le répertoire courant, .. son parent, ~ le
répertoire personnel.
Les commandes qu'il faut avoir dans les doigts
| Commande | Rôle |
|---|---|
pwd, cd, ls -l | où suis-je, aller, lister avec les droits |
cat, less, head, tail | afficher en entier, page à page, le début, la fin |
cp, mv, rm, mkdir | copier, déplacer ou renommer, supprimer, créer |
grep motif fichier | ne garder que les lignes contenant le motif |
wc -l | compter les lignes |
sort, uniq -c | trier, compter les doublons consécutifs |
find . -name "*.c" | chercher des fichiers dans une arborescence |
ps, kill, top | lister les processus, en tuer un, les observer |
chmod, chown | changer droits et propriétaire (chapitre 7) |
man commande | le manuel, à consulter avant de chercher ailleurs |
Deux remarques d'usage. rm ne demande pas confirmation et il n'existe pas de corbeille : la
suppression est immédiate et définitive. Et uniq ne supprime que les doublons consécutifs,
d'où le sort qui le précède toujours dans la ligne de McIlroy.
Redirections et tubes
C'est le cœur du chapitre, et il se comprend beaucoup mieux avec le chapitre 1 en tête.
Tout processus démarre avec trois descripteurs de fichier ouverts, désignés par un numéro — exactement les descripteurs que l'exercice du chapitre 1 validait :
0 entrée standard (stdin) par défaut : le clavier1 sortie standard (stdout) par défaut : l'écran2 sortie d'erreur (stderr) par défaut : l'écran aussiUn programme bien écrit ne sait pas d'où viennent ses données ni où vont ses résultats : il lit 0 et écrit 1. C'est le shell qui décide à quoi ces numéros sont raccordés, avant de lancer le programme.
commande > fichier la sortie va dans le fichier (écrase)commande >> fichier la sortie s'ajoute à la fincommande < fichier l'entrée vient du fichiercommande 2> erreurs seules les erreurs vont dans le fichiercommande > tout 2>&1 les erreurs suivent la sortieLa séparation de 1 et de 2 n'est pas un détail : elle permet de rediriger un résultat dans un fichier tout en continuant à voir les messages d'erreur à l'écran. Un programme qui écrit ses erreurs sur la sortie standard les mélange aux données, et pollue le fichier produit.
Le tube (|) va plus loin : il branche la sortie standard d'une commande directement sur
l'entrée standard de la suivante, sans fichier intermédiaire. Les deux processus s'exécutent
en même temps, et le noyau régule leur allure — si le lecteur est plus lent, l'écrivain est
suspendu jusqu'à ce que le tampon se vide. C'est déjà du producteur-consommateur, celui du
chapitre 5, réalisé par le système.
D'où la relecture de la ligne de McIlroy :
tr -cs A-Za-z '\n' remplace tout ce qui n'est pas une lettre par un saut de lignetr A-Z a-z passe en minusculessort regroupe les mots identiques (uniq exige des doublons voisins)uniq -c compte chaque groupesort -rn trie par nombre décroissanthead -n 10 garde les dix premiersSix outils qui ignorent tout les uns des autres, assemblés en une phrase.
Quelle est la différence entre commande > sortie.txt et commande | autre ?
L'environnement, et comment une commande est trouvée
Chaque processus porte un ensemble de variables appelé son environnement, hérité de son
parent. Le shell les affiche par env et en définit par export.
Les plus utiles sont HOME, le répertoire personnel, PWD, le répertoire courant, USER,
LANG — et surtout PATH, qui mérite une explication détaillée parce qu'elle éclaire ce que
fait le shell.
Quand vous tapez ls, le shell ne cherche pas partout. Il parcourt dans l'ordre les
répertoires listés dans PATH, séparés par des deux-points, et prend le premier exécutable
portant ce nom. D'où trois conséquences quotidiennes : une commande installée dans un
répertoire absent de PATH reste introuvable alors que le fichier existe ; l'ordre décide
laquelle de deux versions homonymes l'emporte ; et un programme du répertoire courant ne
s'exécute que par ./programme, parce que . ne figure volontairement pas dans PATH —
sans quoi un fichier nommé ls déposé dans un répertoire partagé s'exécuterait à la place de
la vraie commande.
Une distinction revient sans cesse en TP : variable=valeur ne crée la variable que pour le
shell courant, export variable=valeur la transmet aussi aux processus enfants. Le chapitre 3
expliquera pourquoi — l'environnement est copié au moment du fork, donc ce qui n'est pas
exporté n'est pas dans la copie.
Premiers scripts
Un script shell est un fichier de commandes, avec quelques constructions de programmation.
#!/bin/bash# Compte les fichiers .c d'un répertoire donné en argument. repertoire=${1:-.} # premier argument, ou "." s'il manque if [ ! -d "$repertoire" ]; then echo "erreur : $repertoire n'est pas un répertoire" >&2 exit 1fi n=$(find "$repertoire" -name "*.c" | wc -l)echo "$n fichiers C dans $repertoire" for f in "$repertoire"/*.c; do echo " $(wc -l < "$f") lignes $f"doneQuatre points à retenir de ce squelette. La première ligne, le shebang, dit au noyau quel
interpréteur lancer — c'est lui qui permet d'exécuter le script directement. Les arguments
s'appellent $1, $2, et $0 est le nom du script. Le code de retour compte : 0 pour
succès, autre chose pour un échec, et c'est cette valeur que le shell teste dans un if ou
après un &&. Enfin, les guillemets autour des variables ne sont pas facultatifs : sans
eux, un nom de fichier contenant une espace est découpé en deux arguments, et c'est la source
d'erreur numéro un des scripts débutants.
Un étudiant compile un programme dans son répertoire courant, tape monprog et obtient « commande introuvable », alors que le fichier est bien là et exécutable. Pourquoi, et pourquoi ce comportement est-il volontaire ?
À vous
L'exercice reconstruit le tube. Vous disposez d'une poignée de commandes écrites comme des fonctions qui prennent un tableau de lignes et en rendent un autre — c'est exactement le contrat d'un outil Unix — et vous devez écrire l'évaluateur qui les enchaîne.
L'objectif final est de faire tourner la ligne de McIlroy sur un texte, et d'obtenir les mots
les plus fréquents. Deux commandes manquent volontairement, uniq -c et sort -rn : ce sont
celles qui font comprendre pourquoi sort doit précéder uniq.
Écrivez uniq -c et sort -rn, puis faites tourner la ligne de McIlroy.
// Chaque commande : (lignes, arguments) -> lignes. C'est tout le contrat. const COMMANDES = { // Découpe sur tout ce qui n'est pas une lettre : un mot par ligne. mots: (l) => l.join(" ").split(/[^A-Za-zÀ-ÿ]+/).filter(Boolean), minus: (l) => l.map((x) => x.toLowerCase()), sort: (l) => [...l].sort(), head: (l, n) => l.slice(0, Number(n)), grep: (l, motif) => l.filter((x) => x.includes(motif)), wc: (l) => [String(l.length)], // uniq -c : compte les répétitions CONSÉCUTIVES et préfixe le compte. uniqc: (l) => { return l; // ← à écrire }, // sort -rn : trie par le nombre placé en tête de ligne, décroissant. sortrn: (l) => { return l; // ← à écrire }, }; // L'évaluateur : "mots | minus | sort" appliqué à un texte. function tube(entree, ligneDeCommande) { const etapes = ligneDeCommande.split("|").map((s) => s.trim().split(/\s+/)); let flux = entree; for (const [nom, ...args] of etapes) { if (!COMMANDES[nom]) throw new Error("commande inconnue : " + nom); flux = COMMANDES[nom](flux, ...args); } return flux; } // ── À VOUS ──────────────────────────────────────────────────────────────── // 1. Écrivez uniqc et sortrn. // 2. Expliquez pourquoi « mots | minus | uniqc » sans sort donne un résultat // faux, et vérifiez-le en lançant les deux versions. const TEXTE = [ "Le système gère la mémoire et le processeur", "le processeur exécute le programme que le système a chargé", "la mémoire contient le programme et ses données", ]; console.log("— sans sort —"); console.log(tube(TEXTE, "mots | minus | uniqc | sortrn | head 5").join("\n")); console.log("— avec sort, la ligne de McIlroy —"); console.log(tube(TEXTE, "mots | minus | sort | uniqc | sortrn | head 5").join("\n"));
En travaux pratiques
Une chaîne de traitement, sans écrire de programme
Traiter un vrai fichier de journaux uniquement en composant des commandes, et comprendre en quoi la redirection est une opération du système et non du langage.
- Le TP 1
- Un fichier de journaux d'au moins 100 000 lignes — le vôtre, ou un généré
- 1. Explorer sans tout lire
Sans ouvrir le fichier dans un éditeur, donnez son nombre de lignes, ses dix premières, ses dix dernières, et sa taille en octets.
- 2. Les dix adresses les plus actives
Écrivez une seule ligne de commandes qui extrait le premier champ, compte les occurrences et affiche les dix premières. Chronométrez-la.
- 3. Filtrer et compter
Comptez les réponses en erreur, puis la part qu'elles représentent, puis leur répartition par heure. Une ligne par question.
- 4. Les trois flux
Lancez une commande qui échoue sur la moitié de ses arguments. Redirigez la sortie normale vers un fichier et les erreurs vers un autre, puis les deux vers le même. Vérifiez l'ordre des lignes obtenues.
- 5. Un script réutilisable
Transformez votre chaîne en script prenant le fichier en argument, avec une valeur par défaut, un contrôle des arguments et un code de retour correct.
- 6. Les droits
Rendez le script exécutable pour vous seul. Testez-le sous un autre utilisateur. Puis expliquez pourquoi chmod 744 sur un RÉPERTOIRE ne fait pas ce qu'on croit.
- 7. Ce que le shell fait avant de lancer
Créez des fichiers nommés a.txt, b.txt, puis lancez une commande avec l'argument étoile point txt entre guillemets et sans guillemets. Expliquez la différence avec echo.
- Votre chaîne des dix adresses tient sur une ligne et tourne en moins de deux secondes
- Votre script renvoie un code non nul quand le fichier n'existe pas
- Vous savez dire quel programme développe l'étoile, et quand
Ce que la suite en fait
Tout ce que le shell fait, il le fait par appels système, et le chapitre 3 va les nommer.
Lancer une commande, c'est fork puis exec ; attendre sa fin, c'est wait ; le code de
retour testé par un if est l'argument passé à exit. Le shell n'a aucun privilège
particulier : c'est un programme ordinaire, et vous pourriez en écrire un.
Le tube, lui, revient deux fois. Au chapitre 3 comme moyen de communication entre processus, et au chapitre 5 comme cas d'école du producteur-consommateur — avec son tampon borné, son écrivain suspendu quand c'est plein et son lecteur suspendu quand c'est vide. Vous l'aurez utilisé cent fois avant de le programmer.
À retenir
Vous avez parcouru les 9 sections.
Marquez-la terminée pour faire avancer votre parcours, ou revenez sur un point avant de passer à la suite.