Aller au contenu principal

Unix & Shell · L1 · Section 6/7

Annales corrigées

Progression

Points d’expérience : XPSérie de jours consécutifs : · —Progression du module : — / —compris

#Annales corrigées

Cette page rassemble des exercices réellement posés à l'épreuve de l'UE SPUF201 « Système 1 : utilisation d'Unix et programmation shell » (Licence, portail Sciences et technologies, semestre 2, Université Côte d'Azur, campus Valrose), ainsi que les questions des feuilles de travaux pratiques qui les préparent.

Sources exactes. Quatre sujets d'épreuve sont repris :

  • Partiel blanc 2023-2024 — sujet de révision distribué par l'équipe pédagogique (explicitement non officiel), 1 h 30, questions de cours, exercices de filtres, droits, lecture de deux scripts.
  • Partiel du 23/03/2022 — 30 points en trois parties : questions de cours, utilisation des commandes, problème d'arborescence et lecture d'un script.
  • Contrôle continu terminal du 9/05/2022 — 42 points : questions de cours, commandes, écriture de scripts et problème notes.txt.
  • Contrôle continu terminal du 9/05/2023 — 35 points : questions de cours et exercices, puis les scripts ord.sh, 421.sh, fact.sh et le problème notes.txt.

Origine des corrigés. Pour les sessions 2022 et 2023, les corrigés des scripts sont ceux distribués par l'UE dans ses archives de révision et dans le sujet annoté : ils font foi. Les autres corrigés sont rédigés ici à partir des chapitres de cours officiels et des feuilles de TP. Chaque correction indique sa provenance.

#Exercice 1 — Questions de cours : système, shell et fichiers

Énoncé (partiel blanc, partie 1, questions a à e et g ; CCT du 9/05/2022, questions a à d ; CCT du 9/05/2023, question g).

  1. Qu'est-ce que Linux ? Qu'est-ce que GNU ? Qu'est-ce qu'un système d'exploitation Linux ? Citez l'autre nom.
  2. Qu'appelle-t-on le shell ? Quel lien a-t-il avec le noyau ? Citez trois exemples de shell.
  3. Qu'est-ce qu'un serveur X ?
  4. Quelle différence entre une commande intrinsèque et une autre commande dans un shell ?
  5. Quelle est la structure de données utilisée sous les systèmes de fichiers Unix/Linux ? Donnez les principales caractéristiques.
  6. Qu'est-ce qu'une distribution Linux ? Quel est son autre nom plus complet ? Quel est le point commun des différentes distributions ? Citez quatre distributions.
  7. Citez un système de fichiers utilisé sous Linux et un utilisé sous Windows.
  8. À quoi sert la commande sudo ? Comment appelle-t-on la partition d'échange d'un système Unix ?
Correction détaillée

1. Linux, GNU, GNU/Linux. Linux est le noyau : le composant logiciel qui masque le matériel et fournit les primitives (lancer un processus, lire un fichier, obtenir de la mémoire). GNU est le projet de logiciel libre lancé en 1984 par Richard Stallman pour reconstruire entièrement tous les composants d'Unix : compilateur, éditeur, utilitaires, shell. Un système d'exploitation complet associe les deux : le noyau Linux fournit la couche basse, les outils GNU fournissent tout le reste — et le cours précise qu'ils représentent 90 % du total. C'est pourquoi le nom recommandé est GNU/Linux ; « Linux » seul ne désigne que le noyau, qui n'est qu'un des noyaux utilisables par le système GNU. Autre nom à citer : GNU/Linux (ou, de façon générique, Unix, dont le nom n'est plus une marque déposée).

2. Le shell. Le shell est le programme de démarrage et l'interpréteur de commandes : il est « à l'écoute » de l'utilisateur, il interprète et exécute les commandes tapées, et lorsque le processus appelé se termine, le processus du shell redevient actif. Son lien avec le noyau est un lien de couches : le noyau (l'« amande », kernel) est au-dessus du matériel, le shell est au-dessus du noyau. Le shell est indépendant du noyau, ce qui explique qu'on puisse utiliser plusieurs shells sur un même système ; il n'accède aux ressources matérielles que par les appels système du noyau. Trois exemples : sh (le shell de Steven Bourne, présent dans toute installation Unix), bash (Bourne Again Shell, version améliorée de sh, shell par défaut de la plupart des distributions GNU/Linux), zsh (celui qui englobe tous les autres et qui est utilisé à Valrose) ; on peut aussi citer csh, tcsh et ksh.

3. Le serveur X. X (ou X11) est le système de fenêtrage d'Unix, construit au MIT et librement disponible. Il repose sur une relation client-serveur : le serveur X gère le terminal graphique dans sa totalité (affichage à l'écran, reconnaissance des événements clavier et souris, événements graphiques comme le recouvrement de fenêtres) ; les clients sont les programmes qui envoient au serveur des requêtes d'affichage et reçoivent la notification des événements qui les concernent. Le système est indépendant des machines : un même serveur X peut satisfaire les requêtes de plusieurs machines, et clients comme serveur ignorent le fonctionnement de l'autre. X n'impose aucun comportement d'interface : c'est un client particulier, le gestionnaire de fenêtres, puis un environnement de bureau, qui codifient l'apparence et les comportements.

4. Commande intrinsèque contre commande externe. Une commande intrinsèque (builtin) est interne au shell : elle ne correspond pas au lancement d'un processus, elle s'exécute dans le processus du shell lui-même. Une commande externe est un fichier exécutable que le shell cherche dans les chemins de la variable PATH et dont il demande l'exécution au noyau — donc un nouveau processus. La distinction est observable : cd doit être intrinsèque, sinon le changement de répertoire n'affecterait que le processus fils et serait perdu au retour. Exemples intrinsèques cités par le cours : cd, pwd, fg, bg, exit, kill, echo, exec, . (ou source), break, continue, shift, eval, whence, alias, unalias, set, unsetopt. L'ordre de résolution du shell est : fonction du shell, puis opération prédéfinie (intrinsèque), puis fichier exécutable trouvé grâce à PATH.

5. La structure de données : l'inode. Les systèmes de fichiers Unix/Linux reposent sur l'inode (index node). Chaque fichier et chaque répertoire reçoit un numéro d'inode unique, qui localise sur le disque un ensemble d'informations : le bloc de disque où la donnée est stockée, le type de fichier, la taille, l'identifiant du propriétaire, les autorisations de lecture, d'écriture et d'exécution, ainsi que les dates de dernière modification et de dernier accès. Deux caractéristiques sont à retenir absolument :

  • le nom du fichier n'est pas dans l'inode : il est stocké dans l'inode du répertoire, sous forme d'association nom vers numéro d'inode. C'est ce qui rend possibles les liens durs, où deux noms différents désignent le même inode ;
  • le nombre d'inodes est défini au formatage du système de fichiers, et ne peut pas être augmenté ensuite sans reformater.

6. Distribution Linux. Une distribution est un assemblage cohérent du noyau et d'un ensemble de logiciels — c'est l'autre nom plus complet : un système d'exploitation GNU/Linux. Le point commun des différentes distributions est le noyau Linux et les outils GNU ; ce qui change, c'est le gestionnaire de paquets, les logiciels installés par défaut et l'environnement de bureau. Quatre exemples, avec leur filiation : Debian (1993), Ubuntu (2004, dérivée de Debian), Fedora (2004, issue de Red Hat Linux de 1995), openSUSE (2005, issue de Suse 1992) ; on peut aussi citer Slackware (1992), Gentoo (2000), Arch Linux (2002), CentOS (2003), Linux Mint (2006). La distribution installée dans les salles de TP du PV3 est Ubuntu, avec l'environnement de bureau GNOME ; ses variantes sont Kubuntu (KDE), Xubuntu (Xfce), Lubuntu (LXQt) et Ubuntu Studio.

7. Systèmes de fichiers. Sous Linux : ext3, ext4 (on peut aussi citer ext2, ReiserFS v3, F2FS). Sous Windows : NTFS (on peut aussi citer FAT et FAT32). Autres exemples utiles : ISO9660 pour les CD-ROM et DVD, HFSX sous Mac OS à partir de 10.4.

8. sudo et la partition d'échange. sudo permet de lancer une commande en se substituant à root : le compte root, aussi appelé administrateur ou super-utilisateur, a tous les droits sur le système ; on préfère donc ne pas s'y installer durablement et élever ponctuellement les privilèges pour la seule commande concernée. La variante su ouvre un shell root, gksudo et kdesudo en sont les équivalents graphiques. La « partition d'échange » s'appelle la partition swap (ou zone d'échange) : c'est la zone dédiée à l'échange entre la mémoire vive et le disque dur.

Ce qui est évalué ici. Les questions de cours des trois sujets tournent toutes autour des mêmes distinctions : noyau contre outils, shell contre noyau, intrinsèque contre processus externe, inode contre nom de fichier. Ce sont les quatre frontières du modèle en couches du chapitre 1.

#Exercice 2 — Questions de cours : réseau, protocoles et accès distant

Énoncé (partiel blanc, questions c à f ; CCT du 9/05/2022, questions d à g ; CCT du 9/05/2023, questions h et i).

  1. Définir IRC et décrire brièvement à quoi cela sert.
  2. Combien peut-on avoir d'adresses IPv6 théoriques ? Pourquoi l'IPv6 remplace-t-il l'IPv4 ?
  3. Définir TCP et décrire brièvement à quoi cela sert.
  4. Que fait un serveur de noms de domaine ?
  5. Quelle commande permet de faire une copie sécurisée sur une machine distante ? Quel protocole utilise-t-elle ? Décrire brièvement ce que fait ce protocole.
  6. Quel objet mathématique ou informatique permet de modéliser un réseau ? Expliquer.
  7. Comment est identifiée une machine sur un réseau Ethernet ? Et sur Internet ?
Correction détaillée

1. IRC. IRC signifie Internet Relay Chat : c'est un protocole de communication entre plusieurs personnes, sous forme immédiate. Son architecture est élaborée : des serveurs IRC sont reliés entre eux, un utilisateur se connecte à un serveur et choisit un canal parmi un très grand nombre, avec la liberté d'en définir de nouveaux. Tout message tapé sur un canal est transmis par le serveur à tous les autres serveurs qui servent ce canal. La conséquence pratique, que le cours souligne sans détour : « tout le monde parle ensemble, comme dans une foule, peu de gens écoutent ». À distinguer des protocoles simples comme write (deux personnes sur la même machine, simultanément) et du protocole différé NNTP, qui fonctionne comme des panneaux d'affichage publics.

2. IPv6. Une adresse IPv6 est longue de 128 bits, soit 16 octets, notée en hexadécimal en 8 groupes de 16 bits séparés par des deux-points, par exemple 2001:0db8:0000:85a3:0000:0000:ac1f:8001. Le nombre d'adresses théoriques est donc 21283,4×10382^{128} \approx 3{,}4 \times 10^{38}, à comparer aux 2324,3×1092^{32} \approx 4{,}3 \times 10^{9} de l'IPv4. IPv6 remplace IPv4 parce que l'IPv4 a atteint ses limites : l'adressage par classes (A, B, C) a été abandonné en 1993 au profit du Classless Inter-Domain Routing (CIDR), beaucoup d'entreprises masquent leurs adresses internes derrière du NAT (network address translation), et cela n'a pas suffi — la pénurie d'adresses est la raison même de la transition.

3. TCP. TCP (Transmission Control Protocol) intervient aux deux extrémités de la communication. Au départ il découpe le message en paquets et associe à chaque paquet une enveloppe identifiant l'expéditeur, le destinataire, le message lui-même et l'ordre des paquets ; à l'arrivée il réassemble les paquets. Il assure la sécurité et l'intégrité de la transmission en dialoguant avec l'expéditeur pour redemander les paquets manquants ou mal transmis. Il sert de support à de très nombreuses applications, chacune identifiée par un numéro de port spécialisé, ce qui permet d'acheminer le message à la bonne application à l'arrivée. Avec IP, il forme le couple de protocoles développé vers la fin des années 1970 sur lequel repose Internet.

4. Serveur de noms de domaine. L'adresse IP est malcommode pour l'utilisateur : on définit donc des adresses symboliques hiérarchisées en domaines, et des serveurs de noms — ordinateurs ou services spécialisés — établissent la correspondance depuis et vers les adresses IP. La hiérarchie se lit de droite à gauche : le niveau le plus élevé apparaît en dernier (domaines par pays comme fr, uk, za ; domaines « apatrides » comme org, net, com, biz ; domaines réservés aux États-Unis comme mil, gov, edu). En France, l'Afnic gère le domaine .fr. Les niveaux inférieurs sont gérés par l'organisme propriétaire du domaine, et il existe souvent des alias : sesame-mips.unice.fr et www-mips.unice.fr sont deux alias de polymnie.unice.fr.

5. Copie sécurisée. La commande est scp (secure copy), et elle utilise le protocole SSH. SSH fait le même travail que l'ancien rlogin mais de manière sécurisée : authentification du client par le serveur, cryptage de la transmission, garantie d'intégrité de cette transmission. Son fonctionnement est client-serveur. L'authentification du client par le serveur et du serveur par le client se fait par une clé cryptée propre à chaque machine ; ces clés d'hôte sont conservées dans le répertoire ~/.ssh du client et permettent de reconnaître un hôte connu. À la première connexion, l'utilisateur doit approuver l'identité annoncée par le serveur ; cette démarche ne doit être répétée que si le serveur change de clé d'hôte. Le mécanisme de mot de passe peut être remplacé par la signature par clé publique, plus sûre : on crée une paire de clés avec ssh-keygen, la clé privée servant à déchiffrer et la clé publique à chiffrer.

6. Modéliser un réseau. Un réseau se modélise par un graphe : un ensemble de nœuds (sommets) et de liens (arêtes). Les nœuds représentent les ordinateurs, les serveurs, les imprimantes, les nœuds de communication ; les arêtes représentent les liens entre eux. Le cours file l'analogie avec un réseau routier : routes, échangeurs, convois, les sous-réseaux correspondant à des opérateurs distincts reliés par leurs nœuds centraux.

7. Identifier une machine. Sur un réseau Ethernet, une machine est identifiée par son adresse Ethernet, aussi appelée adresse MAC, caractéristique dans le monde entier : Ethernet met en œuvre le principe de diffusion, chaque organe est à l'écoute de ce qui le concerne. Sur Internet, une machine est identifiée par son adresse IP, portée par le message ; sur le réseau local, c'est le réseau local qui fait correspondre l'adresse physique et l'adresse IP, et un routeur envoie le message vers le routeur voisin capable de traiter cette adresse IP.

#Exercice 3 — Filtres, redirections et création de fichiers sans éditeur

Énoncé (partiel blanc, partie 2, questions a à e, h et i ; CCT du 9/05/2022, partie 2, questions a à f, h et i ; CCT du 9/05/2023, partie 1, questions e et f).

On dispose d'un fichier villes.txt dont chaque ligne commence par le numéro de département suivi du nom de la ville, et d'un fichier animaux.txt.

  1. On veut compter le nombre de mots contenant anaconda dans animaux.txt.
  2. Afficher uniquement les 6 premiers noms de villes.txt.
  3. Afficher tous les noms entre les lignes 30 et 41.
  4. Afficher uniquement le nom de la ligne 50.
  5. Expliquer ce que produit la commande grep -E "^0[0-9]" villes.txt > d.txt.
  6. Écrire la commande qui affiche le nombre de villes qui commencent par M.
  7. Afficher les lignes 7 et 8 du fichier foo.txt.
  8. Je veux créer un fichier index composé du contenu de ls -L suivi de la date. Quelles commandes utiliser ?
  9. Comment ajouter le calendrier à la fin du fichier ainsi créé ?
  10. Dans un répertoire vide, créer un fichier nommé premiertexte.txt contenant deux phrases, sans utiliser d'éditeur. Que fait alors cat PREMIERTEXTE.txt ? Comment afficher la taille du fichier en kilo-octets sans utiliser ls -l ?
Correction détaillée

1. Compter les mots contenant anaconda. Le piège est là : grep -c compte les lignes conformes, pas les occurrences. Sur un fichier où un mot peut apparaître deux fois sur la même ligne, les deux commandes donnent deux résultats différents :

bashbash

1grep -o anaconda animaux.txt | wc -l    # nombre d'occurrences (mots)2grep -c anaconda animaux.txt            # nombre de lignes contenant le motif

grep -o n'affiche que la partie conforme, une par ligne ; wc -l compte ensuite les lignes, donc les occurrences. Vérification sur deux lignes contenant trois fois le mot : grep -o compte 3, grep -c compte 2. Si l'énoncé dit « nombre de mots », la réponse attendue est la première ; si le correcteur attend une réponse courte, grep -c reste la commande de comptage canonique.

2. Les 6 premiers noms.

bashbash

1head -n 6 villes.txt

head affiche par défaut les 10 premières lignes ; l'option -n nombre change ce nombre. Si l'on veut réellement n'extraire que le champ nom et pas la ligne entière, il faut couper la ligne, par exemple avec cut : head -n 6 villes.txt | cut -d' ' -f2-. C'est la lecture littérale de « uniquement les noms ».

3. Les lignes 30 à 41. Le réflexe est de composer deux filtres, head puis tail :

bashbash

1head -n 41 villes.txt | tail -n 12

head -n 41 garde les 41 premières lignes, tail -n 12 garde les 12 dernières de ce flux, soit les lignes 30 à 41 incluses. C'est l'idiome attendu à l'examen ; on peut aussi écrire sed -n '30,41p' villes.txt.

4. La ligne 50 seule.

bashbash

1head -n 50 villes.txt | tail -n 1

ou sed -n '50p' villes.txt. La composition head puis tail est le même raisonnement que ci-dessus avec un intervalle réduit à un point.

5. Que produit grep -E "^0[0-9]" villes.txt > d.txt ? La commande n'affiche rien sur la sortie standard : tout ce qu'elle produit part dans le fichier d.txt. Elle y écrit les lignes de villes.txt qui commencent par un 0 suivi d'un chiffre, c'est-à-dire les villes des départements 01 à 09 — la Corse (2A, 2B) et les départements à deux chiffres ne sont pas concernés. Trois éléments sont à commenter séparément : ^ ancre le motif en début de ligne ; [0-9] désigne un chiffre pris dans l'intervalle ; l'option -E active les expressions rationnelles étendues ; et > redirige la sortie standard vers un fichier, ce qui explique l'absence d'affichage.

6. Nombre de villes commençant par M. Attention : les lignes commencent par le numéro de département, pas par le nom. grep -c "^M" villes.txt renverrait donc le plus souvent 0. Il faut isoler le champ nom :

bashbash

1grep -cE "^[0-9A-Z]+ M" villes.txt

Le motif cherche des lignes commençant par un ou plusieurs caractères de département, puis un espace, puis un M. Variante en deux temps, plus lisible : cut -d' ' -f2 villes.txt | grep -c "^M". Le cours insiste sur ce point : il est fortement recommandé d'utiliser l'option -E avec grep pour être certain que tous les motifs sont correctement reconnus, et de n'utiliser grep qu'avec des expressions rationnelles.

7. Les lignes 7 et 8 de foo.txt.

bashbash

1head -n 8 foo.txt | tail -n 2

8. Créer index avec ls -L puis la date. Deux commandes suffisent, la première en écrasement, la seconde en ajout :

bashbash

1ls -L > index2date >> index

> crée ou écrase le fichier, >> concatène à la fin d'un fichier existant. On peut aussi regrouper les deux commandes dans un même bloc dont la sortie est redirigée une seule fois : { ls -L; date; } > index — le point-virgule est l'opérateur d'exécution séquentielle, la fin de ligne d'une commande complète lui étant équivalente.

9. Ajouter le calendrier.

bashbash

1cal >> index

cal affiche le calendrier du mois courant, ou celui d'un mois précis si on lui donne des paramètres (cal 4 1972). L'usage de >> est indispensable : avec >, le contenu précédent serait perdu.

10. Créer un fichier sans éditeur, puis le relire. Trois méthodes valent selon ce que l'on veut montrer.

bashbash

1cat > premiertexte.txt          # puis taper les lignes, et terminer par C-d2echo "ma premiere phrase\nma seconde phrase ..." > premiertexte.txt3printf 'ma premiere phrase\nma seconde phrase ...\n' > premiertexte.txt

La première est la plus générale : cat envoie son entrée standard sur sa sortie standard, et la redirection > capte cette sortie ; C-d envoie la fin de fichier (EOF) qui termine la saisie. La deuxième exploite une particularité de zsh : son echo interprète les séquences d'échappement, donc \n produit un vrai passage à la ligne. La troisième est la plus portable, printf interprétant \n dans tous les shells.

Puis cat PREMIERTEXTE.txt affiche une erreur : le fichier n'existe pas, car Unix distingue majuscules et minuscules. La commande est sensible à la casse ; premiertexte.txt et PREMIERTEXTE.txt sont deux noms différents. Enfin, la taille en kilo-octets sans ls -l :

bashbash

1du -hs premiertexte.txt

-s résume l'occupation au seul fichier au lieu de détailler, -h affiche la taille sous une forme lisible (octets, kilo-octets, méga-octets). À noter : du mesure la place occupée sur le disque, arrondie au bloc, tandis que ls -l affiche la taille logique du fichier — les deux valeurs diffèrent pour les petits fichiers.

#Exercice 4 — Expressions rationnelles avec grep

Énoncé (partiel blanc, questions 2f et 2g ; CCT du 9/05/2022, question 2g ; CCT du 9/05/2023, question 1c).

  1. Écrire la commande qui affiche le nombre de villes dont le nom commence par la lettre L.
  2. Afficher tous les noms de villes qui ont un numéro de département supérieur à 10 et dont le nom ne contient pas de voyelle.
  3. Afficher tous les noms de fichiers qui commencent par un F ou un G ou un T ou un H et qui contiennent au moins une voyelle suivie de deux chiffres.
  4. Afficher le contenu de /etc/fichier1.txt en y remplaçant la lettre a par la lettre c et le chiffre 5 par le chiffre 6.
Correction détaillée

1. Compter les noms commençant par L.

bashbash

1grep -c "^L" villes.txt              # si la ligne commence par le nom2cut -d' ' -f2 villes.txt | grep -c "^L"   # si la ligne commence par le numéro

La question est la même que dans l'exercice précédent : tout dépend du format du fichier. L'ancre ^ signifie « en début de ligne » — le cours rappelle que ^ et $ sont des métacaractères correspondant respectivement à une chaîne vide au début et en fin de ligne. L'option -c retourne le nombre de lignes conformes au lieu des lignes elles-mêmes : c'est l'option que les fiches de révision signalent comme « à savoir par cœur ».

2. Département supérieur à 10 et nom sans voyelle. Le motif doit combiner trois contraintes : un numéro à deux ou trois chiffres ne commençant pas par 0, un espace, puis un nom composé uniquement de non-voyelles.

bashbash

1grep -E "^[1-9][0-9]+ +[^aeiouyAEIOUY]*$" villes.txt

Lecture du motif : [1-9] interdit le zéro initial, [0-9]+ exige au moins un chiffre de plus (donc un département à deux chiffres ou plus, ce qui exclut 01 à 09), + est l'opérateur de répétition « une fois ou plus », [^aeiouyAEIOUY]* prend le complémentaire de l'ensemble des voyelles, et $ ancre en fin de ligne. Le point délicat est la définition exacte de « supérieur à 10 » : si l'énoncé considère que 10 lui-même est exclu, le motif ci-dessus convient ; s'il faut l'inclure, écrire ^(1[0-9]|[2-9][0-9]|[0-9]{3}) à la place. Les noms propres comportant une apostrophe ou un tiret demandent d'ajouter ces caractères à la classe.

3. Noms de fichiers commençant par F, G, T ou H. Le motif porte ici sur des noms de fichiers, donc sur une sortie de ls, pas sur un contenu.

bashbash

1ls | grep -E "^[FGTH].*[aeiouyAEIOUY][0-9]{2}"

^[FGTH] impose la première lettre, .* laisse passer un nombre quelconque de caractères, [aeiouyAEIOUY] impose une voyelle, [0-9]{2} impose exactement deux chiffres. Les accolades {n} sont les opérateurs de répétition décrits dans le manuel de grep fourni en annexe du sujet : {n} signifie « exactement n fois », {n,} « n fois ou plus », {n,m} « entre n et m fois ». Selon la place exigée de la voyelle et des chiffres, on encadre le motif par .* de part et d'autre : ^[FGTH].*[aeiouy].*[0-9]{2}.* est la forme la plus permissive, et donc la plus sûre si l'énoncé ne précise pas la position.

4. Remplacer des caractères dans un fichier. Le remplacement caractère par caractère est le travail de tr, pas de grep :

bashbash

1cat /etc/fichier1.txt | tr "a" "c" | tr "5" "6"2cat /etc/fichier1.txt | tr "a5" "c6"

Les deux formes sont équivalentes, la seconde étant plus efficace car elle ne lance qu'un processus. tr effectue des remplacements ou des suppressions de caractères de son entrée standard vers sa sortie standard : sans option -d, il remplace les caractères de la première chaîne par les caractères de même rang de la seconde. Il remplace donc toutes les occurrences. Deux remarques de fond : tr travaille caractère par caractère et ne sait pas faire des remplacements de chaînes ; et la redirection manquante signifie que le résultat s'affiche à l'écran sans modifier le fichier — pour l'écrire, il faudrait ajouter > resultat.txt.

Le point de méthode. Deux outils, deux grammaires : les jokers (*, ?, [ensemble]) sont interprétés par le shell et portent sur des noms de fichiers ; les expressions rationnelles sont interprétées par la commande (grep, sed, tr, Emacs) et portent sur des contenus. Le cours met en garde explicitement : « ne pas confondre jokers et expressions régulières », car * n'a pas du tout la même signification dans les deux langages.

#Exercice 5 — Variables, alias et commandes intrinsèques

Énoncé (partiel blanc, questions 2j et 2k ; CCT du 9/05/2022, questions 2j, 2k et 1d ; CCT du 9/05/2023, questions 1g et 1j).

  1. Qu'affiche la commande echo $DISPLAY invoquée dans un terminal ? Et echo $PWD ?
  2. Écrire la commande qui édite le contenu de la variable PATH, puis celle qui édite la variable HOME.
  3. Qu'est-ce qu'une commande intrinsèque ? Donner deux exemples.
  4. Définir un alias exe qui ajoute les droits d'exécution au propriétaire et fixe les droits du groupe et des autres à la lecture. Utiliser cet alias sur le fichier monscript.sh. Dans quel fichier sont stockés les alias pour qu'ils soient persistants ?
Correction détaillée

1. $DISPLAY et $PWD. DISPLAY contient le nom du terminal graphique pour le serveur X auquel les clients graphiques doivent s'adresser. Dans une session graphique locale, elle vaut typiquement :0 ; dans un terminal textuel (TTY) ou une session sans X, elle est vide, et un programme graphique échoue alors avec un message d'erreur sur l'affichage. PWD contient le répertoire courant du shell : c'est la variable que la commande intrinsèque pwd affiche. Elle est mise à jour automatiquement par cd, et OLDPWD conserve le répertoire précédent, ce qui est exactement ce que restaure cd -.

2. Éditer PATH et HOME. La commande prévue par le cours est l'intrinsèque vared, qui ouvre une édition interactive de la valeur de la variable :

bashbash

1vared PATH2vared HOME

vared utilise les commandes de correction ordinaires du shell : C-a va en début de ligne, M-d efface le mot à droite du curseur, et la touche Entrée valide sans qu'il soit nécessaire d'être en fin de ligne. Rappel du rôle de PATH : c'est la liste de chemins absolus séparés par le caractère : dans laquelle le shell cherche le premier répertoire contenant un fichier exécutable du nom demandé. Une autre voie, dans un script, consiste à réaffecter la variable : PATH="$PATH:/opt/bin".

3. Commande intrinsèque. Une commande intrinsèque est intégrée au shell et s'exécute dans le même processus que lui : ce n'est pas un programme séparé. Deux exemples parmi la liste officielle : cd et echo (ou alias, set, exit, unalias, clear). La conséquence pratique : cd doit modifier l'état du shell lui-même, donc il ne peut pas être un processus externe.

4. Alias exe.

bashbash

1alias exe="chmod u+x,g=r,o=r "2exe monscript.sh

L'alias doit se terminer par un blanc : le cours précise que si la définition d'un alias se termine par un blanc, le shell cherche d'éventuels alias dans ce qui suit. Le fichier de persistance est ~/.zshrc pour un shell interactif — c'est la réponse attendue à l'examen, et c'est bien ce que dit l'ordre de chargement de zsh : /etc/zshrc puis ~/.zshrc pour tout shell interactif. unalias exe supprime la définition, alias seul en donne la liste. Pour utiliser la commande redéfinie par un alias, on la précède d'un antislash, par exemple \date.

Pourquoi g=r,o=r et non g+r,o+r. La notation = établit exactement les droits indiqués, tandis que + les ajoute à l'existant. Comme la question demande de fixer les droits du groupe et des autres à la lecture, = est la seule notation qui garantit le résultat quel que soit l'état initial du fichier.

Après avoir défini l'alias exe comme chmod u+x,g=r,o=r, que produit l'appel exe script.sh ?
Après avoir défini l'alias exe comme chmod u+x,g=r,o=r, que produit l'appel exe script.sh ?

#Exercice 6 — Droits, umask et notations chmod

Énoncé (CCT du 9/05/2022, questions 2l à 2o ; partiel blanc, questions 2l et 2m ; TP séance 4, questions 9 à 12).

  1. On est dans son répertoire personnel, qui contient un dossier contenant des scripts *.sh. Ajouter au groupe la possibilité d'exécuter ces scripts.
  2. Déterminer la valeur du umask avant la création des répertoires drwxr-xr-x rep1 et drw-r----x rep2.
  3. Déterminer les valeurs possibles du umask avant la création du fichier -r---w---- fic.
  4. Quels sont les droits obtenus sur des répertoires pour les valeurs de umask suivantes : 345, 136 ?
  5. Un fichier fic a les droits -rwxr-xr-x. Écrire la commande qui les passe à -r--rw-rw-, puis celle qui les passe à -rw-r-xr-- sans connaître les droits courants du fichier.
  6. Le type affiché -rw-rw-r-- répertoire1 : s'agit-il d'un fichier ou d'un répertoire ? Donner les umask possibles.
  7. Avec umask 072, créer le répertoire TP04_1 et le fichier vide. Quelles sont leurs permissions respectives, et pourquoi diffèrent-elles ?
  8. Créer un répertoire TP04_2 directement avec les droits rwxr-----.
Correction détaillée

1. Ajouter l'exécution au groupe.

bashbash

1chmod g+x *.sh

g désigne le groupe, +x ajoute la permission d'exécution ; les autres droits ne sont pas touchés. À noter que le droit x sur un répertoire signifie « traverser », c'est-à-dire accéder aux fichiers répertoriés, et qu'il faut à la fois r et x pour pouvoir explorer un répertoire. Ici, les fichiers étant déjà dans un dossier accessible, seule la permission des fichiers est à changer.

2. umask devant drwxr-xr-x et drw-r----x. Le droit de référence d'un répertoire est le droit global rwxrwxrwx (777) ; le masque se soustrait bit à bit.

  • drwxr-xr-x vaut 755 : 777 moins le masque donne 755, donc umask 022.
  • drw-r----x vaut 641 : en soustrayant bit à bit, 777 \& ~umask = 641 impose ~umask = 641, donc umask 136. Vérification : umask 136 puis mkdir donne bien drw-r----x.

3. Valeurs possibles du umask devant -r---w----. Le fichier vaut 420. Cette fois, comme pour tout fichier ordinaire, le droit de référence n'est pas 777 mais 666 : pour des raisons de sécurité, Linux n'autorise pas le droit d'exécution lors de la création d'un fichier. Il faut donc résoudre 666 \& ~umask = 420 chiffre octal par chiffre octal, en notant que le bit x du masque est libre puisqu'il n'a aucun effet sur un fichier :

  • propriétaire : 6 \& ~u = 4 impose le bit 2 du masque, le bit 4 étant libre d'être à 0 ou 1 ;
  • groupe : 6 \& ~g = 2 impose le bit 4 du masque ;
  • autres : 6 \& ~o = 0 impose les bits 4 et 2 du masque.

Les valeurs possibles sont donc 246, 247, 256, 257, 346, 347, 356, 357 (le troisième chiffre hésitant entre 6 et 7 selon que le bit x du masque est posé ou non). C'est la réponse complète ; en pratique, la valeur « naturelle » est 246.

4. Droits d'un répertoire pour umask 345 et umask 136. On calcule 777 \& ~umask.

  • umask 345 : ~345 = 432, et 777 \& 432 = 432, soit dr---wx-w- (propriétaire en lecture seule, groupe en écriture et exécution, autres en écriture).
  • umask 136 : ~136 = 641, et 777 \& 641 = 641, soit drw-r----x.

Ces résultats, apparemment exotiques, sont exactement ceux que l'on obtient avec la commande mkdir après avoir posé le masque : ils montrent qu'un umask n'est pas une valeur « interdite » mais un masque de bits, dont les combinaisons n'ont aucune raison d'être intuitives.

5. Passer de -rwxr-xr-x à -r--rw-rw-, puis à -rw-r-xr--.

bashbash

1chmod 466 fic                  # ou : chmod u=r,g=rw,o=rw fic2chmod 654 fic                  # ou : chmod u=rw,g=rx,o=r fic

La seconde question précise « sans connaître les droits sur le fichier » : c'est le signal qu'il faut utiliser la notation absolue (=), qui remplace les droits au lieu de les modifier. Avec + ou -, le résultat dépendrait de l'état initial ; avec =, il est déterminé. Les deux notations, octale et symbolique, sont équivalentes pour une affectation complète.

6. -rw-rw-r-- répertoire1. Le premier caractère de la ligne de ls -l donne le type : un tiret signifie fichier ordinaire, d un répertoire, l un lien symbolique. Le nom répertoire1 est donc trompeur : c'est un fichier de droits rw-rw-r--, soit 664. Comme il s'agit d'un fichier, la base est 666, et la résolution de 666 \& ~umask = 664 n'impose qu'un seul bit : le bit « écriture pour les autres » (002) doit être posé dans le masque ; les bits de lecture (444) et d'écriture du propriétaire et du groupe (220) doivent être à 0, et les trois bits d'exécution sont libres. Les masques possibles sont donc 002, 003, 012, 013, 102, 103, 112, 113, la valeur usuelle étant 002.

7. umask 072, puis mkdir TP04_1 et touch vide.

bashbash

1umask 0722mkdir TP04_13touch vide4ls -ld TP04_1 vide

Résultats : TP04_1 est un répertoire de base 777, donc 777 \& ~072 = 705, soit drwx---r-x ; vide est un fichier de base 666, donc 666 \& ~072 = 604, soit -rw----r--. Les permissions diffèrent parce que les bases de calcul diffèrent : le droit global d'un répertoire est 777, celui d'un fichier est 666, le noyau n'accordant jamais le droit d'exécution à la création d'un fichier. C'est la question classique : le masque est le même, les droits obtenus ne le sont pas.

8. Créer un répertoire avec des droits imposés. Le manuel de mkdir prévoit l'option -m, qui prend une notation octale :

bashbash

1mkdir -m 750 TP04_2

Vérification : ls -ld TP04_2 affiche drwxr-x---. L'autre voie, en deux temps, consiste à créer puis à corriger : mkdir TP04_2 && chmod 750 TP04_2.

Rappels de synthèse. chmod n'est possible que si l'on a la permission d'écriture sur le répertoire contenant le fichier ; seul le propriétaire et le super-utilisateur peuvent définir les droits ; le umask n'a aucune influence sur les fichiers déjà créés ; umask sans argument affiche la valeur courante, umask -S l'affiche sous forme symbolique.

Le noyau crée un fichier ordinaire alors que le umask vaut 027. Quels droits obtient-il ?
Le noyau crée un fichier ordinaire alors que le umask vaut 027. Quels droits obtient-il ?

#Exercice 7 — Arborescence : déplacements, liens et archivage

Énoncé (partiel du 23/03/2022, partie 3, questions a à g). On part de l'arborescence suivante, ~ désignant le répertoire personnel, qui est aussi le répertoire courant : MesPDFs (contenant coursMaths1.pdf, coursMaths2.pdf, coursInfo1.pdf, coursInfo2.pdf, script3.sh), MesScripts (contenant Rapport.ppt, script1.sh, script2.sh), Perso (contenant photo.jpg, film.avi), Autres (contenant cv.html, index.html).

  1. Sans changer de répertoire courant et en une seule commande, créer un répertoire Maths et un répertoire Info tous les deux dans MesPDFs.
  2. Créer un répertoire MesPresentations dans le répertoire personnel et y déplacer les fichiers .ppt.
  3. En utilisant des jokers, déplacer coursMaths1.pdf et coursMaths2.pdf dans Maths, puis coursInfo1.pdf et coursInfo2.pdf dans Info. Déplacer script3.sh dans le bon répertoire.
  4. Créer un lien dur dans Perso nommé moncv.html à partir du fichier cv.html.
  5. Déplacer Autres en le renommant WWW dans Perso.
  6. Archiver la nouvelle arborescence dans le fichier newHome.tar.
  7. Dessiner cette nouvelle arborescence sans afficher les fichiers.
Correction détaillée

1. Deux répertoires en une commande. mkdir accepte plusieurs arguments, le dernier n'étant un répertoire destination que pour cp, mv et ln — pour mkdir, chaque argument est un répertoire à créer :

bashbash

1mkdir MesPDFs/Maths MesPDFs/Info

2. Créer MesPresentations et y déplacer les .ppt.

bashbash

1mkdir MesPresentations2mv MesScripts/*.ppt MesPresentations/

mv a ici deux arguments : les fichiers à déplacer (le joker *.ppt est développé par le shell, donc la commande reçoit en réalité la liste des noms concernés) et le répertoire destination. Comme pour cp, mv accepte n arguments, les n − 1 premiers étant les sources et le dernier le répertoire destination. Si le fichier destination existe déjà, il est remplacé.

3. Déplacer avec des jokers. C'est le cœur de la question : les jokers permettent d'écrire une seule commande là où il faudrait deux noms.

bashbash

1mv MesPDFs/coursMaths*.pdf MesPDFs/Maths/2mv MesPDFs/coursInfo*.pdf MesPDFs/Info/3mv MesPDFs/script3.sh MesScripts/

script3.sh est un script shell : sa place est dans MesScripts, avec les autres. Le cours précise les limites des jokers : le caractère / ne peut pas être décrit par un joker, et le caractère . en début de nom de fichier non plus — ce qui explique qu'un joker comme * ne voie jamais les fichiers cachés. Si aucun fichier ne correspond, c'est une erreur et la commande n'est pas appelée ; si plusieurs correspondent, la commande reçoit une liste de noms.

4. Lien dur. On ne change pas de répertoire courant, donc les deux chemins sont relatifs au répertoire personnel :

bashbash

1ln Autres/cv.html Perso/moncv.html

Créer un fichier, c'est d'abord placer un lien dur dans le répertoire correspondant : le nom est une entrée du répertoire, l'identité est l'inode. Plusieurs liens durs peuvent pointer sur le même fichier, et un fichier n'est donc pas « dans » un répertoire. Un lien dur ne peut pas traverser deux volumes différents, puisqu'il désigne un bloc de données dans un espace physique donné.

5. Renommer en déplaçant.

bashbash

1mv Autres Perso/WWW

Un déplacement dans le même répertoire change simplement le nom ; un déplacement entre répertoires change à la fois le nom et l'emplacement. Ici les deux se combinent.

6. Archiver. tar construit une archive à partir d'une arborescence ; l'option -f est obligatoire et doit apparaître en dernier, car elle introduit le nom du fichier d'archive.

bashbash

1tar cf newHome.tar .

Deux remarques qui valent des points. D'abord, tar cf archive . inclut le répertoire courant, donc l'archive elle-même si elle est créée dedans : mieux vaut l'écrire ailleurs (tar cf /tmp/newHome.tar .). Ensuite, pour archiver et compresser en une passe, on ajoute z (gzip) ou j (bzip2) : tar czf newHome.tar.gz .. Les noms, permissions et dates sont conservés tels quels ; les liens symboliques sont conservés tels quels, sauf si l'on ajoute h pour les suivre.

7. Dessiner l'arborescence sans les fichiers. Deux voies :

bashbash

1tree -d2find . -type d

tree affiche l'arborescence en arbre et -d ne garde que les répertoires ; find avec -type d sélectionne les éléments de type répertoire. find est la commande générale de parcours : son premier argument est le répertoire de départ, puis viennent des mots-clés suivis de valeurs (-type, -name, -user, -group, -printf), puis une action (-print, -exec).

Ce que l'exercice teste. La distinction entre chemin absolu (depuis la racine), chemin relatif (par rapport au répertoire courant) et chemin local (par rapport au répertoire personnel, par exemple ~/TP05/Exercice1.py), plus la maîtrise des jokers et des liens. Le cours note que ./ désigne le répertoire courant et est considéré à la fois comme un chemin relatif et comme un chemin local.

#Exercice 8 — Lire et exécuter un script : calcul.sh et go.sh

Énoncé (partiel blanc, partie 3, questions a et b ; partiel du 23/03/2022, partie 3, questions h et i).

Script calcul.sh :

zshzsh

1#!/bin/zsh2u=03u1=04u2=15while [[ $u -le $1 ]]6do7        echo -n "$u1 "8        u=$(($u1 + $u2))9        u1=$u210        u2=$u11done
  1. Décrire ce que fait ce script, sans expliquer chaque ligne.
  2. Expliquer l'argument de la commande echo. Qu'affiche sur la sortie standard l'appel ./calcul.sh 30 lancé dans le répertoire du script ?

Script go.sh :

zshzsh

1#/bin/zsh2d=03s=04while [[ $d -le $1 ]]5do6echo -n "$d "7s=$(( $s + $d ))8d=$(( $d + 2 ))9done10echo "\n$s"
  1. Décrire ce que fait ce script.
  2. Qu'affiche la commande ./go.sh 15 ?
Correction détaillée

1. calcul.sh. Le script affiche la suite de Fibonacci tant que les termes restent inférieurs ou égaux au nombre passé en paramètre. Trois variables circulent : u1 est le terme courant affiché, u2 le terme suivant, et u la somme des deux — c'est u qui sert de compteur de boucle. À chaque tour on affiche u1, puis on décale la fenêtre : le nouveau terme vaut u1 + u2, l'ancien u2 devient u1, et la somme devient u2. La condition [[ $u -le $1 ]] teste cette somme, donc la boucle s'arrête dès que le prochain terme dépasserait la borne.

2. L'argument de echo, et le résultat de ./calcul.sh 30. L'argument en question est l'option -n, qui demande à echo de ne pas terminer la sortie par un passage à la ligne. Le script enchaîne donc les termes sur une seule ligne, séparés par un espace, et l'invite de commande réapparaîtra à la suite du dernier nombre.

Trace de ./calcul.sh 30 : le script parcourt les valeurs u successives 1, 2, 3, 5, 8, 13, 21, puis 34, valeur qui dépasse 30 et arrête la boucle. Les termes affichés sont donc ceux de u1 à chaque tour :

texttext

10 1 1 2 3 5 8 13

Huit termes, sans retour à la ligne final. Vérifié en exécutant le script sous zsh 5.9.

3. go.sh. Le script additionne les entiers pairs de 0 au paramètre inclus, en affichant chaque terme au fur et à mesure. d est le terme courant, initialisé à 0 et incrémenté de 2 ; s accumule la somme. La boucle s'arrête dès que d dépasse le paramètre. À la fin, le script affiche un passage à la ligne suivi de la somme.

4. Résultat de ./go.sh 15.

texttext

10 2 4 6 8 10 12 14 256

Les termes pairs s'arrêtent à 14 (16 dépasserait 15) et la somme vaut 0+2+4+6+8+10+12+14=560+2+4+6+8+10+12+14 = 56. Vérifié en exécution.

Le piège de ce sujet : le shebang du script est faux. La première ligne du sujet est #/bin/zsh — il manque le point d'exclamation. Sans #!, le noyau ne sait pas quel interpréteur lancer : il retombe sur sh. Or sh ne connaît pas la construction [[ ... ]], et l'exécution produit un message d'erreur [[: not found avant de sortir de la boucle. Le script n'affiche alors que 0 et 0. Il faut donc écrire #!/bin/zsh. Le cours est explicite sur ce mécanisme : quand on demande au noyau d'exécuter un fichier qui est un script, le noyau appelle le shell pour l'interpréter si la première ligne est #!chemin_absolu_de_shell ; sinon c'est Sh. Vérifié : avec #/bin/zsh le script échoue sous dash, avec #!/bin/zsh il produit exactement la sortie ci-dessus.

Deuxième piège : echo "\n$s" dépend du shell. En zsh, echo interprète les séquences d'échappement, donc \n produit un vrai passage à la ligne. En bash, le même echo afficherait littéralement \n56, avec l'antislash visible. C'est l'une des raisons pour lesquelles le cours impose zsh comme shell officiel de l'UE. Vérifié : od -c montre bien un octet 0x0a en zsh et les deux caractères \ et n en bash.

#Exercice 9 — Écrire des scripts : ord.sh, 421.sh et fact.sh

Énoncé (CCT du 9/05/2023, partie 2, questions a à c).

  1. Écrire un script ord.sh qui prend en paramètre un nombre de trois chiffres et affiche ce nombre avec ses chiffres ordonnés du plus grand au plus petit. Le script vérifie que le nombre contient bien trois chiffres et renvoie un message d'erreur sinon. Indice du sujet : la chaîne de caractères est mutable et on y accède comme dans un tableau, par exemple a=123 puis echo $a[1] affiche 1 et a[1]=9 remplace le premier caractère.
  2. Écrire un script 421.sh qui simule le jeu du « 421 » simplifié : le joueur humain lance trois dés, puis l'ordinateur ; chacun a droit à dix lancers ; le premier qui fait 421 a gagné et le jeu s'arrête ; si personne ne gagne, le jeu se termine par « Personne n'a gagné ! ». Le format de sortie doit être respecté.
  3. Écrire un script fact.sh qui calcule et affiche la factorielle d'un nombre donné en paramètre, au format 5!=5*4*3*2*1=120.
Correction détaillée (corrigé officiel de l'UE)

Les trois scripts ci-dessous sont ceux distribués par l'UE dans son archive de révision.

ord.sh. L'idée directrice est de traiter le nombre comme une chaîne de trois caractères et de faire un tri à trois éléments par comparaisons successives, en échangeant les caractères deux à deux.

zshzsh

1#!/bin/zsh2nb=$13 4if [[ ${#nb} -ne 3 ]]5then6   echo "Le parametre doit etre un nombre de 3 chiffres !"7   exit 18fi9 10temp=11if (( $nb[1] < $nb[2] ))12then13  temp=$nb[1]14  nb[1]=$nb[2]

Points à comprendre : ${#nb} donne la longueur de la valeur du paramètre, ce qui réalise le contrôle « trois chiffres » ; nb[1] est le premier caractère (l'indexation des chaînes commence à 1 en zsh, pas à 0) ; les trois comparaisons croisées constituent un tri décroissant à trois éléments — après les deux premières, le plus grand est en position 1, et la troisième ordonne les deux restants. Exécution vérifiée : ./ord.sh 569 affiche 965, ./ord.sh 569123 affiche le message d'erreur et sort avec le code 1. Le message du script officiel est écrit sans accents ; à l'épreuve, reproduire exactement celui de l'énoncé : « Le paramètre doit être un nombre de 3 chiffres ! ».

421.sh. Le script réutilise ord.sh pour trier les trois dés avant comparaison, ce qui évite d'énumérer les six permutations de 4-2-1.

zshzsh

1#!/bin/zsh2de1=3de2=4de3=5humain=6ordi=7for (( i=1; i<=10; i++ ))8do9  de1=$(( $RANDOM % 6 +1 ))10  de2=$(( $RANDOM % 6 +1 ))11  de3=$(( $RANDOM % 6 +1 ))12  humain=$de1$de2$de313  de1=$(( $RANDOM % 6 +1 ))14  de2=$(( $RANDOM % 6 +1 ))

Points à comprendre : $RANDOM est une variable fournie par le shell, qui donne un entier pseudo-aléatoire entre 0 et 32767 ; $(( $RANDOM % 6 + 1 )) ramène ce tirage dans l'intervalle 1 à 6 ; la concaténation humain=$de1$de2$de3 fabrique le nombre à trois chiffres ; la substitution de commande $( ./ord.sh $humain ) récupère le résultat du script précédent pour le ranger dans une variable ; enfin exit 0 termine le script immédiatement, ce qui arrête le jeu. Le for (( ... )) est la forme de boucle « à la C » que zsh accepte en plus de for nom in liste.

fact.sh. Le script affiche la factorielle sous la forme demandée, en construisant l'affichage et le calcul dans la même boucle descendante.

zshzsh

1#!/bin/zsh2fact="$1!="3nb=14echo -n $fact5for (( i=$1; i>=2; i-- ))6do7  echo -n "$i*"8  nb=$(( $i*$nb ))9done10echo -n "1="$nb

Points à comprendre : echo -n empile les fragments sur une seule ligne, d'abord 5!=, puis 5*, 4*, 3*, 2*, et enfin 1=120 ; la boucle s'arrête à 2 pour que le facteur 1 soit écrit une seule fois, à la main ; nb accumule le produit. Exécution vérifiée : ./fact.sh 5 affiche 5!=5*4*3*2*1=120. Le cas limite ./fact.sh 1 affiche 1!=1=1, ce que l'énoncé n'interdit pas.

Ce que ces trois scripts évaluent. La manipulation d'une chaîne indexable et d'un tri par échanges ; le tirage pseudo-aléatoire et la substitution de commande ; l'accumulation dans une boucle descendante avec une sortie au format imposé. Ce sont les trois briques de script les plus rentables à maîtriser : elles reviennent d'une session à l'autre.

#Exercice 10 — Écrire des scripts : impair.sh et nbssremise.sh

Énoncé (CCT du 9/05/2022, partie 3, questions b et c).

  1. Écrire un script impair.sh qui affiche tous les nombres entiers impairs inférieurs ou égaux au nombre entier positif donné en paramètre. Si l'utilisateur ne donne pas de paramètre, le script lui demande de saisir un nombre entier. Exemples : impair.sh 10 affiche 1 3 5 7 9, impair.sh demande « Saisir un entier positif : » puis affiche les impairs.
  2. Écrire un script nbssremise.sh qui génère n nombres aléatoires sans remise dans l'intervalle [0, S], S étant le deuxième paramètre et n le premier. Le script affiche la liste puis le nombre d'essais utilisés, et gère la compatibilité des paramètres. Algorithme proposé par le sujet : gérer les paramètres ; créer une variable present qui contient S fois la lettre N ; tant que les n nombres ne sont pas générés, tirer un nombre dans [0,S], gérer le compteur d'essais, vérifier à l'aide de present si le nombre est déjà sorti, et si ce n'est pas le cas l'ajouter au résultat et mettre à jour present ; enfin afficher le résultat.
Correction détaillée

1. impair.sh. La structure attendue combine un test de présence de paramètre, une saisie interactive et une boucle sur les entiers.

zshzsh

1#!/bin/zsh2if [[ $# -eq 0 ]]3then4  echo -n "Saisir un entier positif : "5  read n6else7  n=$18fi9 10i=111while [[ $i -le $n ]]12do13  echo -n "$i "14  i=$(( $i + 2 ))

Points à comprendre : $# est le nombre de paramètres positionnels ; read n lit une ligne sur l'entrée standard et l'affecte à n — c'est le mécanisme que le TP 6 fait manipuler dans un script d'identification ; la boucle part de 1 et avance de 2 pour n'énumérer que les impairs ; le echo final sans argument ajoute le retour à la ligne que les echo -n ont supprimé. Pour ./impair.sh 10, la sortie est 1 3 5 7 9. Si l'énoncé exige de refuser un paramètre non entier, on ajoute un contrôle de motif sur la valeur de n.

2. nbssremise.sh — le corrigé officiel.

zshzsh

1#!/bin/zsh2if [[ $# = 0 ]]3then4  echo "Il faut deux paramètres !"5  exit6fi7if (( $1 > $2 ))8then9   echo "Trop d'éléments à générer"10   exit11fi12present="$2"13res=""14essai=0

La structure est conforme à l'algorithme demandé : contrôle du nombre de paramètres, contrôle de compatibilité (n ne peut pas dépasser le nombre de valeurs disponibles), boucle de tirage avec compteur d'essais, et rejet des doublons. Trois défauts subsistent dans ce corrigé, et les signaler vaut mieux que les recopier :

  • present="$2" initialise present avec la borne (par exemple la chaîne 20), et non avec S fois la lettre N comme le demande l'algorithme ;
  • le test [[ ! "$present" = "*$rand*" ]] utilise l'opérateur = avec un motif entre guillemets, donc comparé littéralement : la condition est vraie presque toujours, et les doublons ne sont pas éliminés. La ligne correcte, présente en commentaire dans le corrigé, est [[ ! "$present" =~ "$rand" ]], qui active l'opérateur de comparaison par expression rationnelle ;
  • $RANDOM%$2 produit un entier de 0 à S−1, alors que l'intervalle demandé est [0,S] bornes incluses.

Voici la version corrigée, fidèle à l'algorithme du sujet et vérifiée en exécution :

zshzsh

1#!/bin/zsh2if (( $# != 2 )); then3  echo "Il faut deux paramètres !"4  exit 15fi6n=$17S=$28if (( n > S + 1 )); then9  echo "Trop d'éléments à générer."10  exit 111fi12 13present=""14for (( i=0; i<=S; i++ )); do present="${present}N"; done

La variable present est ici une chaîne de S+1 caractères, un par valeur de l'intervalle : le caractère d'indice rand+1 vaut N si la valeur n'est pas encore sortie, O sinon. La mise à jour remplace le caractère par découpage et recollage. Exécution vérifiée : ./nbssremise.sh affiche « Il faut deux paramètres ! », ./nbssremise.sh 20 10 affiche « Trop d'éléments à générer. », et ./nbssremise.sh 10 20 affiche dix valeurs distinctes de [0,20] suivies du nombre d'essais. Le nombre d'essais est une variable aléatoire : c'est le problème du collectionneur de coupons, dont l'espérance croît comme nlnnn \ln n lorsque l'on veut n valeurs parmi n.

#Exercice 11 — Le problème notes.txt

Énoncé (CCT du 9/05/2022, problème, questions i à vi ; CCT du 9/05/2023, problème, questions i à v). Les notes d'examen sont dans un fichier notes.txt au format NomEtudiant;Prénom;numéroEtudiant;NBnotes;(codeUE;NomUE;note/20)+. Exemple :

texttext

1Dupont;Maurice;dm123456;2;SPUF20;Système_1;15;SPUF21;Programmation_impérative;102Martin;Pierre;mp012345;1;SPUF21;Programmation_impérative;183Titi;Toto;tt987654;3;SPUF20;Système_1;8;SPUP20;Optique;12;SPUM21;Fondements_2;14
  1. Écrire un script makeArbo.sh qui crée l'arborescence du répertoire Notes contenant tous les répertoires des numéros d'étudiants listés dans notes.txt.
  2. Écrire un script qui dépose dans chaque répertoire étudiant un fichier NomEtudiant.txt contenant ses notes aux différentes UE.
  3. On est root sur le système et tous les étudiants ont un compte. Écrire un script qui permet à chaque étudiant de consulter ses notes et seulement les siennes.
  4. Écrire un script qui compte et affiche le nombre total de notes égales à 10/20, 12/20 ou 14/20.
  5. Écrire un script apprecie.sh qui prend une note en paramètre et affiche « reçu » si la note est supérieure ou égale à 10, « ajourné » sinon, en gérant la validité du paramètre.
  6. (bonus) Écrire un script moyenneUE.sh qui prend un code UE en paramètre et affiche la moyenne, la note minimale et la note maximale de cette UE.
Correction détaillée (corrigés officiels de l'UE)

1. makeArbo.sh. Le numéro d'étudiant est le troisième champ du fichier, les champs étant séparés par des points-virgules.

zshzsh

1#!/bin/zsh2mkdir Notes3num=$(cat notes.txt | wc -l)4for i in {1..$num}5do6  name=$(cat notes.txt | cut -f 3 -d ";" | head -n $i | tail -n 1)7  mkdir -p Notes/$name8done

Trois idiomes à retenir, qui reviennent dans tout le problème : cut -f 3 -d ";" extrait le troisième champ en utilisant le point-virgule comme délimiteur (la tabulation est le délimiteur par défaut de cut) ; head -n $i | tail -n 1 isole la i-ème ligne d'un flux ; la substitution de commande $( ... ) range le résultat dans une variable. mkdir -p évite l'erreur si le répertoire existe déjà.

2. Créer les fichiers de notes. Le script officiel réutilise le même parcours ligne à ligne, puis boucle sur les triplets (codeUE, NomUE, note).

zshzsh

1#!/bin/zsh2num=$(cat notes.txt | wc -l)3notes=4for i in {1..$num}5do6 notes=$(cut -f 4 -d ";" notes.txt | head -n $i | tail -n 1)7 nom=$(cut -f 1 -d ";" notes.txt | head -n $i | tail -n 1)8 prenom=$(cut -f 2 -d ";" notes.txt | head -n $i | tail -n 1)9 numEtudiant=$(cut -f 3 -d ";" notes.txt | head -n $i | tail -n 1)10 touch Notes/$numEtudiant/$nom11 echo "Les notes de $nom $prenom sont :" >> Notes/$numEtudiant/$nom12 for (( k=1; k <= $notes*3; k=$k+3 ))13 do14  echo -n "$(cut -f $(($k+4)) -d ";" notes.txt | head -n $i | tail -n 1) --> " >> Notes/$numEtudiant/$nom

La clé du raisonnement est le calcul des numéros de champ : les quatre premiers champs étant fixes, le triplet numéro m commence au champ 4 + 3m − 2, soit pour m allant de 1 à NBnotes, les champs 4+3m−2, 4+3m−1, 4+3m. Le script parcourt k = 1, 4, 7, … jusqu'à 3 × NBnotes et lit donc les champs k+4, k+5, k+6, c'est-à-dire 5, 6, 7 puis 8, 9, 10, et ainsi de suite. Le nombre de triplets vient du quatrième champ, NBnotes, ce qui rend le script indépendant du nombre d'UE. La boucle for (( ... )) est la forme « à la C ».

3. Restreindre la consultation au propriétaire. On opère directement depuis le répertoire Notes.

zshzsh

1#!/bin/zsh2num=$(ls | wc -l)3for i in {1..$num}4do5 user=$(ls | head -n $i | tail -n 1)6 chown $user $user7 chmod 700 $user8done

chown $user $user attribue le répertoire à l'étudiant (et non plus à root, qui l'a créé), et chmod 700 donne rwx au seul propriétaire : personne d'autre ne peut ni lister ni traverser. C'est l'application directe des règles de droits vues au chapitre 2 : r permet de consulter la liste des fichiers, w d'ajouter ou d'enlever des références, x d'accéder aux fichiers répertoriés — et il faut à la fois r et x pour explorer un répertoire. La variante chmod u=rwx,g=,o= est équivalente.

4. Compter les notes égales à 10, 12 ou 14.

zshzsh

1#!/bin/zsh2numListe=$(ls Notes | wc -l)3c=04for (( i=1; i<=$numListe; i++ ))5do6  user=$(ls Notes | head -n $i | tail -n 1)7  c=$(( $(grep -cE "(10|12|14)/20" Notes/$user/*) + $c))8done9echo $c

Le motif (10|12|14)/20 utilise l'alternance | du mode étendu, activé par -E ; -c compte les lignes conformes ; le joker Notes/$user/* passe tous les fichiers de l'étudiant à grep. C'est la seule question du problème où une expression rationnelle avec groupe et alternance est indispensable — l'option -E est explicitement recommandée par le cours.

5. apprecie.sh. Deux écritures officielles circulent, l'une avec if, l'autre avec les connecteurs logiques.

zshzsh

1#!/bin/zsh2if (( $1 >= 10 ))3then4  echo "recu"5else6  echo "ajourné"7fi
zshzsh

1#!/bin/zsh2(( $1 >= 10 && $1 <= 20 )) && echo "recu" || echo "ajourné"

La seconde version exploite deux idées du cours : (( )) est la commande let, qui ne renvoie rien sur la sortie standard mais fournit un code de retour ; et les opérateurs && et || enchaînent selon ce code — cmd1 && cmd2 n'exécute cmd2 que si cmd1 réussit. Attention à ne pas confondre les deux notations : $(( )) calcule et renvoie une valeur, (( )) compare et renvoie un code de retour.

6. moyenneUE.sh (bonus). Le corrigé officiel extrait les triplets de l'UE demandée, puis parcourt le fichier intermédiaire pour calculer moyenne, minimum et maximum.

zshzsh

1#!/bin/zsh2grep -oE "$1;+[^;]+;+[0-9]+" notes.txt > notesUE.txt3max=04min=215sum=06nombre_notes=$(wc -l < notesUE.txt)7cat notesUE.txt8while IFS=';' read -r codeUE nomUE noteUE9do10        sum=$(($sum+$noteUE))11        if [[ $noteUE -gt $max ]]12        then13                max=$noteUE14        fi

Trois techniques méritent d'être isolées. D'abord, grep -oE avec le motif $1;+[^;]+;+[0-9]+ sélectionne la partie conforme — le code d'UE, son nom et sa note — au lieu de la ligne entière : c'est la façon d'extraire une sous-chaîne avec grep. Ensuite, IFS=';' read -r a b c découpe une ligne en champs sur le point-virgule : IFS est la variable qui contient les séparateurs de mots, et la redirection done < notesUE.txt alimente la boucle while depuis le fichier. Enfin, les bornes initiales max=0 et min=21 exploitent le fait que toutes les notes sont dans [0,20] : le premier passage corrige forcément les deux. On notera que la division entière tronque la moyenne, et que le cas « aucune note trouvée pour cette UE » provoquerait une division par zéro : l'énoncé demande justement de le traiter.

Le fil du problème. Les cinq scripts partagent le même squelette : parcourir un fichier structuré, en extraire des champs par cut, compter et accumuler. Une fois que l'on sait isoler la i-ème ligne (head puis tail) et le m-ième champ (cut -f), les cinq questions deviennent des variations. C'est exactement le programme des TP 2 et 6 : cut, wc, head, tail, grep, sort, uniq.

#Exercice 12 — Travaux pratiques : recherche, filtres et alias

Énoncé (TP séance 2, questions 15 à 19 et 27 à 35 ; TP séance 4, questions 6 à 8 ; TP séance 5, questions 20 à 25 ; TP séance 6, questions 10 à 15 et 41 à 47).

  1. Quel joker représente presque tous les noms de fichiers d'un répertoire ? Lesquels sont omis ?
  2. Rechercher tous les fichiers ou répertoires dont le nom contient la lettre s à partir du répertoire courant ; puis seulement les fichiers ordinaires dont le nom contient la lettre e ; puis seulement ceux dont la taille dépasse 3 ko.
  3. Rechercher tous les fichiers contenant la chaîne python dans leur nom à partir de /usr/bin, en ne gardant que les fichiers normaux, puis en affichant aussi leur date de dernière modification.
  4. Combien le fichier /etc/services comporte-t-il de lignes ? Afficher les 20 dernières lignes, les lignes 40 à 50, la 18e ligne, et compter les mots entre les lignes 30 et 40.
  5. Créer un fichier udp_services à partir de services, contenant uniquement les services utilisant le protocole udp et trié par ordre alphabétique des services.
  6. Créer un fichier extremes constitué des 10 premières lignes de services suivies des 10 dernières, en une seule ligne de commande et sans fichier intermédiaire.
  7. Définir un alias date pour la commande ls, expliquer pourquoi la commande date d'origine « disparaît », puis rendre persistants les alias ll, la, psu et exe.
Correction détaillée

1. Le joker *. Il représente une chaîne quelconque, éventuellement vide. Sont omis : les fichiers dont le nom commence par un point — les fichiers cachés — car le caractère . en début de nom ne peut pas être reconnu par un joker ; et le caractère / ne peut pas davantage être décrit par un joker, ce qui interdit de traverser une arborescence avec *. Pour atteindre les fichiers cachés, il faut le point explicite : .* désigne les fichiers cachés du répertoire courant.

2. Recherche de fichiers avec find. find prend en premier argument un répertoire, puis des mots-clés suivis de valeurs, puis une action.

bashbash

1find . -name "*s*"                       # fichiers et répertoires contenant s2find . -type f -name "*e*"               # seulement les fichiers ordinaires3find . -type f -name "*e*" -size +3k     # et de taille supérieure à 3 ko

-name prend un motif en expression générique, pas en expression rationnelle ; -type f restreint aux fichiers ordinaires (d pour répertoire, l pour lien) ; -size +3k sélectionne les fichiers de plus de 3 kilo-octets. Le cours rappelle que find affiche aussi les fichiers cachés, contrairement aux jokers du shell.

3. Recherche dans /usr/bin.

bashbash

1find /usr/bin -name "*python*"2find /usr/bin -type f -name "*python*"3find /usr/bin -type f -name "*python*" -printf "%T+ %p\n"

L'action -printf affiche le résultat selon un formatage précis : %p est le nom du fichier, %T sa date de dernière modification — c'est l'option que le TP désigne explicitement comme celle à consulter dans le manuel pour « afficher aussi le nom du fichier ».

4. Le fichier /etc/services.

bashbash

1wc -l /etc/services                  # nombre de lignes2tail -n 20 /etc/services             # 20 dernières lignes3head -n 50 /etc/services | tail -n 11   # lignes 40 à 504head -n 18 /etc/services | tail -n 1    # 18e ligne5head -n 40 /etc/services | tail -n 11 | wc -w   # mots entre les lignes 30 et 40

wc compte lignes, mots et caractères ; l'option -l ne retient que les lignes, -w les mots. La vérification demandée par le TP consiste à afficher le fichier avec cat puis à le numéroter avec nl -ba, ce qui matérialise les numéros de ligne.

5. Extraire et trier les services udp.

bashbash

1grep udp /etc/services | sort > udp_services

grep filtre les lignes contenant le motif, sort les trie en ordre alphabétique des chaînes — et comme le nom du service est en début de ligne, le tri porte bien sur le nom du service. sort dispose de nombreuses options : -u pour éliminer les doublons de clé, -t pour choisir le délimiteur de champ, -k pour définir une clé de tri, -r pour inverser l'ordre, -n pour un tri numérique.

6. extremes en une seule ligne.

bashbash

1head -n 10 /etc/services > extremes && tail -n 10 /etc/services >> extremes

C'est la même mécanique que dans l'exercice 3 : > pour créer, >> pour concaténer, && pour enchaîner les deux commandes dans une seule ligne. La variante avec un regroupement évite d'écrire deux fois le nom du fichier : { head -n 10 /etc/services; tail -n 10 /etc/services; } > extremes.

7. Alias.

bashbash

1alias date=ls2alias3\date                                # appelle la commande d'origine4unalias date5alias ll="ls -l"6alias la="ls -la"7alias psu="ps u"8alias exe="chmod u+x "

La commande date « disparaît » parce que le shell résout d'abord les alias avant de chercher une fonction, puis une opération prédéfinie, puis un fichier exécutable dans PATH. L'alias porte le même nom, il est donc trouvé en premier ; l'appel \date neutralise l'alias en échappant le nom. Pour rendre les alias persistants, on les place dans ~/.zshrc, fichier lu automatiquement à l'initialisation de tout shell interactif — c'est le deuxième étage de l'ordre de chargement de zsh, après /etc/zshenv et ~/.zshenv, avant /etc/zlogin et ~/.zlogin. Les alias définis dans un terminal ne survivent pas à la fermeture de la session : seule l'écriture dans le fichier de configuration les rend durables.

#Ce que ces annales vérifient

  • Modèle Unix : noyau, outils GNU, shell, intrinsèques contre processus externes, inode contre nom de fichier (exercice 1).
  • Réseau : graphe, Ethernet et MAC, IP, IPv6, DNS, TCP, SSH et scp (exercice 2).
  • Ligne de commande : filtres (head, tail, wc, cut, tr, sort), redirections, tubes, création de fichier sans éditeur (exercice 3).
  • Expressions rationnelles : ancres, classes, alternance, répétitions, option -E, distinction avec les jokers (exercice 4).
  • Variables et alias : PATH, PWD, DISPLAY, vared, alias et fichiers de configuration (exercice 5).
  • Droits : calcul du umask dans les deux sens, notations octale et symbolique, différence de base entre fichiers et répertoires (exercice 6).
  • Arborescence : chemins absolus, relatifs et locaux, jokers, liens durs et symboliques, archivage (exercice 7).
  • Lecture de script : trace d'exécution, echo -n, substitution de commande, shebang (exercice 8).
  • Écriture de script : chaînes indexables, $RANDOM, boucles for (( )) et while, contrôle des paramètres, saisie (exercices 9 et 10).
  • Traitement de fichier structuré : cut, head et tail pour isoler une ligne, grep -E, IFS et read (exercice 11).
  • Outils du quotidien : find, wc, sort, alias, fichiers de configuration (exercice 12).
Sur un fichier dont chaque ligne commence par un numéro de département, quelle commande compte les villes dont le nom commence par M ?
Sur un fichier dont chaque ligne commence par un numéro de département, quelle commande compte les villes dont le nom commence par M ?