CSC 3102 – Introduction aux systèmes d’exploitation

Portail informatique

TP3 – Les flux

Pour faire les exercices, vous avez besoin de connaître le langage bash. Vous pouvez vous référer à l'annexe shell. Vous pouvez aussi trouver une liste d'astuces ici. Tous les exercices sont obligatoires, sauf les exercices notés « défi » ou « optionnel » qui sont optionnels. En particulier, les exercices notés « hors présentiel » sont supposés fait d'une séance sur la suivante.

Manipuler les flux.

Un simple compteur (∼40mn)

Le but de cet exercice est d'écrire un script nommé cpt.sh qui incrémente chaque seconde le nombre contenu dans un fichier.

Le script que vous mettez en œuvre dans cet exercice utilise un idiome (c.-à-d. un patron de programmation bash) que vous allez souvent retrouver dans les exercices qui suivent. Ce canevas est le suivant :
#! /bin/bash # traite le cas dans lequel les arguments du script sont erronés if <les arguments sont incorrects>; then echo "Un message d'erreur adequat" >&2 # &2 est la sortie d'erreur exit 1 # un code de retour faux fi # ici, vous mettez le corps du script # le script s'est exécuté correctement, le script renvoie un code de retour vrai exit 0

Dans la question a, on vous demande de traiter le cas dans lequel les arguments sont erronés. Dans les questions b et c, on vous demande de remplir le corps du script : initialiser le fichier passé en argument en y écrivant un 0, puis incrémenter le compteur contenu dans le fichier chaque seconde.

Dans un premier temps, nous allons traiter le cas dans lequel les arguments du script sont invalides. Les arguments sont invalides si (i) il n'y a pas un et un seul argument ou (ii) si l'unique argument est un chemin vers un répertoire existant (le test correspondant est [ -d chemin ]). Écrivez un script nommé cpt.sh qui renvoie faux (valeur 1) après avoir affiché un message d'erreur adéquat si les arguments sont invalides, et qui renvoie vrai (valeur 0) sinon.

Dans le corps du script, écrivez la valeur 0 dans le fichier passé en argument ("$1").

Après avoir écrit 0 dans le fichier passé en argument, votre script doit maintenant effectuer une boucle infinie. Dans cette boucle, votre script doit, chaque seconde, incrémenter la valeur contenu dans le fichier. Pour cela, votre script doit :
  • lire le contenu du fichier passé en argument dans une variable n,
  • additionner 1 à n avec la commande expr,
  • écrire n dans le fichier passé en argument,
  • attendre une seconde en invoquant la commande sleep 1.
Une boucle infinie s'écrit de la façon suivante :
while true; do ... done
Pour arrêter un script qui ne se termine jamais comme celui que vous mettez en œuvre à cette question, il suffit de saisir la combinaison de touches control+c.

Découper des lignes (∼40mn)

Le but de cet exercice est d'écrire un petit programme capable de découper des lignes, c'est-à-dire d'extraire certains mots séparés par des blancs.

Pour commencer, nous allons vérifier que les arguments du script ne sont pas invalides. Les arguments sont invalides si :
  • le nombre d'arguments n'est pas égal à 2,
  • ou si le premier argument ne correspond pas à un fichier existant (test [ ! -e fic ]),
  • ou si le premier argument correspond à un répertoire (test [ -d fic ]).

En cas d'erreur, le script doit quitter en affichant un message adéquat et en renvoyant faux (valeur 1), sinon, il doit quitter en renvoyant vrai (valeur 0).

Complétez votre script pour qu'il lise, ligne à ligne, le fichier passé en argument (premier argument) et écrive ces lignes sur la sortie standard. On vous rappelle que pour lire un fichier ligne à ligne à partir du fichier fichier, il faut utiliser la construction while read line; do ... done <fichier.

Modifiez votre script pour qu'il itère sur chaque mot de chaque ligne non vide (indiqué par [ -n "$line" ]), et affiche le mot sur une ligne séparée. Pensez à utiliser une boucle for pour itérer sur les mots de la ligne précédemment lue.

Modifiez votre script pour qu'il n'affiche que le nième mot de chaque ligne, où n est le second argument du script.

Pour mettre en œuvre cet algorithme, nous vous conseillons d'utiliser une variable annexe, par exemple num, que vous initialiserez à zéro avant d'itérer sur les mots, et que vous incrémenterez à l'aide de la commande expr à chaque itération de la boucle sur les mots.
Si le nième mot d'une ligne contient un caractère de motif de filtre (*, ?, ou []), vous pourriez observer que votre script affiche des noms de fichiers au lieu du mot en question. Ici, vous ne pouvez pas désactiver cette interprétation avec des guillemets simples ' ni doubles ". La solution est d'ajouter au début de votre script set -f. La commande set permet de « régler » certains aspects du comportement de Bash. Si vous êtes curieux : man set !
L'algorithme proposé ne traite pas le cas dans lequel une ligne contient moins de n mots. Dans ce cas, l'algorithme va simplement ignorer la ligne et ne rien afficher. Vous pouvez, en hors présentiel, traiter ce cas et afficher une ligne vide lorsqu'une ligne contient moins de n mots.
La solution normale : La solution pour afficher la ligne vide :

Duplication de flux (∼30mn)

Le but de cet exercice est d'écrire un script qui prend en argument un fichier. Votre script doit lire les lignes provenant de l'entrée standard, et les écrire à la fois dans le fichier passé en argument et sur la sortie standard.
Vous n'aurez probablement pas le temps de finir cet exercice pendant la séance. Il vous ait demandé de le terminer en hors présentiel.

Dans un premier temps, nous allons traiter le cas où les arguments du script sont invalides. Les arguments sont invalides si (i) il n'y a pas un et un seul argument ou (ii) si l'unique argument est un chemin vers un répertoire existant (le test correspondant est [ -d chemin ]). Écrivez un script nommé tee.sh qui renvoie faux (valeur 1) après avoir affiché un message d'erreur adéquat si les arguments sont invalides, et qui renvoie vrai (valeur 0) sinon.

Modifiez votre script de façon (i) à lire l'entrée standard ligne à ligne et (ii) à afficher chaque ligne lue sur la sortie standard. On vous rappelle que pour lire l'entrée standard ligne à ligne, il faut utiliser la construction suivante : while read line; do ...; done.
Vous pouvez tester votre script de façon interactive en écrivant des lignes sur le terminal. Dans ce cas, vous pouvez fermer le flux associé à l'entrée standard avec la combinaison de touches control-d. Vous pouvez aussi tester votre script en redirigeant l'entrée standard à partir d'un fichier, par exemple avec ./tee.sh fic < /etc/passwd.

Nous allons maintenant dupliquer la sortie vers le fichier passé en argument. Ouvrez un flux associé au fichier passé en argument au début du corps du script. Vous devez ouvrir le flux en écriture et en écrasement. (commande exec 3>fichier).

Modifiez aussi votre boucle de façon à écrire chaque ligne lue à la fois sur la sortie standard (commande echo ligne) et dans le flux ouvert (commande echo ligne >&3).

Configuration de votre compte (∼30mn – optionnel mais fortement conseillé)

Mettre en place une configuration par défaut pour bash. Cet exercice, bien que non obligatoire, est fortement conseillé car il vous permet de personnaliser votre bash, ce qui, à l'usage, est très commode.
Pour les utilisateurs de MacOS, commencez par exécuter la commande suivante : ln -s ~/.bashrc ~/.bash_profile

Invite de commande

Un invite de commande, ou prompt, correctement positionné est un atout majeur pour se repérer dans le système de fichiers sans avoir à utiliser les commandes spécifiques. Nous vous proposons dans cet exercice de personnaliser votre invite de commande.
  1. Affichez la valeur actuelle de la variable d'environnement correspondant au prompt.
    $ echo $PS1
  2. Accédez à la page de manuel de bash, section « Invite » (ou « Printing-a-Prompt », ou « PROMPTING » si la documentation est en anglais), afin de consulter les caractères spéciaux d'encodage de l'invite de commande.
    man bash
  3. Faites en sorte que votre invite de commande s'affiche comme suit :
    [votre-login@nom-de-votre-machine dernier-élément-du-répertoire-courant]$ ̺, où « ̺ » représente un espace.

    Par exemple, si vous êtes dans le répertoire ~/cours/csc3102/bulbizarre/, que vous vous appelez sacha et que vous vous trouvez sur la machine argenta.imtbs-tsp.eu, votre invite de commande doit s'afficher comme suit : sacha@argenta.imtbs-tsp.eu:bulbizarre$ ̺
    PS1='\u@\h:\W\$ '
  4. On vous rappelle que pour pérenniser votre configuration, vous devez ajouter la commande de personnalisation de l'invite de commande dans le fichier ~/.bashrc. Ouvrez le fichier nommé ~/.bashrc avec votre éditeur de texte et copiez-y la commande précédente. On vous rappelle que la procédure pour copier/coller du texte avec la souris sous GNU/Linux est présentée ici.

Création d'un alias de commande

  1. Définissez l'alias rm comme étant la commande rm avec l'option qui demande à l'utilisateur de confirmer l'effacement de chaque fichier. Faîtes de même avec les commandes cp et mv.
    alias rm='rm -i' alias cp='cp -i' alias mv='mv -i'
  2. Créez un fichier nommé test.txt avec la commande touch test.txt.
    touch test.txt
  3. Supprimez le fichier test.txt avec la commande rm et constatez qu'une confirmation de suppression vous est bien demandée.
    rm test.txt
  4. Pérennisez les alias des commandes rm, cp et mv en les copiants/collants dans le fichier ~/.bashrc.

Droit d'accès par défaut

  1. Quelle est la commande permettant de connaître les droits d'accès positionnés sur les fichiers au moment de leur création ?
    $ umask 0022
  2. Quels sont les droits d'accès par défaut configurés sur votre compte ?
    On rappelle qu'en plus de ce que rapporte la commande adéquate, le droit d'exécution est par défaut retiré sur les fichiers, mais activé sur les répertoires pour en autoriser la traversée.
    u: rw, g: r, o: r
  3. Positionnez le masque de manière à supprimer les droits en lecture et en écriture pour le groupe et les autres.
    Ici aussi, on rappelle qu'en plus de ce que rapporte la commande adéquate, le droit d'exécution est par défaut retiré sur les fichiers, mais activé sur les répertoires pour en autoriser la traversée.
    umask 066
  4. Vérifiez que cette commande a fonctionné en créant un nouveau fichier (commande touch) et un nouveau répertoire (commande mkdir) pour en vérifier les droits d'accès.
    $ touch test.txt $ mkdir test $ ls -ld test.txt test drwx--x--x 2 ebrunet ebrunet 4096 août 6 13:17 test -rw------- 1 ebrunet ebrunet 0 août 6 13:13 test.txt
  5. Pérennisez votre masque en copiant/collant votre commande umask dans le fichier ~/.bashrc.

Lignes alternées (∼40mn – défi)

Le but de cet exercice est d'écrire les lignes provenant de deux fichiers de façon alternée sur la sortie standard.

Dans un premier temps, nous allons traiter le cas où les arguments du script sont invalides. Les arguments sont invalides si (i) il n'y a pas deux arguments ou (ii) si l'un des deux arguments n'est pas un chemin vers un fichier existant (le test correspondant est [ -f chemin ]). Écrivez un script nommé paste.sh qui renvoie faux (valeur 1) après avoir affiché un message d'erreur adéquat si les arguments sont invalides, et qui renvoie vrai (valeur 0) sinon.

Votre script doit maintenant ouvrir deux flux en lecture : l'un associé au fichier passé en premier argument et l'autre associé au fichier passé en second argument.

Pour simplifier, nous supposons pour le moment que les deux fichiers ont le même nombre de lignes. Affichez chacune des lignes des deux fichiers en les alternant. Pour vous guider, vous devez écrire une boucle qui affiche une ligne de chaque fichier, et ceci tant qu'il reste des lignes à lire dans le premier fichier et dans le second. L'algorithme est donc le suivant :
Tant que lecture premier flux renvoie vrai et (opérateur &&) lecture second flux renvoie vrai Écrit la ligne lue à partir du premier flux sur la sortie standard Écrit la ligne lue à partir du second flux sur la sortie standard

Testez votre script avec :
$ ./paste.sh ./paste.sh ./paste.sh

Nous allons maintenant gérer des fichiers de tailles différentes. Il faut commencer par transformer votre boucle en boucle infinie (on vous rappelle qu'une boucle infinie s'écrit while true; do ... done).

Dans votre boucle, commencez par lire une ligne du premier fichier :
  • si la lecture dans le premier fichier renvoie vrai, lisez une ligne du second fichier
    • si la lecture dans le second fichier renvoie vrai, affichez les lignes des premier et second fichiers
    • sinon afficher la ligne du premier fichier
  • sinon (c.-à-d., la lecture sur le premier fichier renvoie faux) lisez une ligne du second fichier
    • si la lecture dans le second fichier renvoie vrai, affichez la ligne du second fichier
    • sinon quittez votre script avec un exit 0

Testez votre script avec :
$ ./paste.sh ./paste.sh /etc/passwd