L'un des principes fondamentaux d'Unix prévoit de découper les traitements complexes en un certain nombre de commandes simples qui seront coordonnées ou juxtaposées, voire imbriquées, afin de fournir un résultat prévisible et constant pour un contexte donné. [1] :
Pour ce faire, Unix a été conceptuellement doté (et ce au niveau de la librairie C sous-jacente) de mécanismes permettant la collaboration des différentes commandes, unifiées par l'environnement controlant l'exécution de ce flux (généralement un shell), qui vont ainsi transporter d'une commande à l'autre les données textuelles traitées : l'information est transportée sous forme d'un flux textuel
Les descripteurs automatiquement créés pour chaque nouveau processus. |
Les redirections. |
Les tubes (ou pipes). |
Lors de la création d'un nouveau processus, trois descripteurs de fichiers sont automatiquement créés. Pour s'en assurer, il suffira de compiler et exécuter le petit fichier source C suivant :
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> int main(int argc, char **argv) { int i, status; pid_t pid, fils; char c; char fic[256]; struct stat fs; pid = getpid(); printf("PID du processus : %d \n",pid); for (i = getdtablesize(); i >= 0; --i) { if ( ! fstat(i, &fs) ) { printf("- Descripteur %d (inode %d%d - ", i, fs.st_dev, fs.st_ino); memset(&fic, 0, sizeof(fic)); sprintf((char *)&fic, "/proc/%d/fd/%d", pid, i); switch (fs.st_mode & S_IFMT) { case S_IFBLK: printf("périphérique bloc) :"); break; case S_IFCHR: printf("périphérique caractère) :"); break; case S_IFDIR: printf("répertoire) :"); break; case S_IFIFO: printf("FIFO/tube) :"); break; case S_IFLNK: printf("lien symbolique) :"); break; case S_IFREG: printf("fichier ordinaire) :"); break; case S_IFSOCK: printf("socket) :"); break; default: printf("type inconnu...) :"); } printf("\n"); fils = fork(); if (fils == 0) { execl("/bin/ls", "/bin/ls", "-l", fic, NULL); _exit (EXIT_FAILURE); } else if (fils < 0) printf("\nErreur lors de l'exécution de la commande ls.\n"); else if (waitpid (fils, &status, 0) != fils) status = -1; printf("\n"); } } printf("Appuyez sur une touche pour terminer l'exécution du programme."); c = tolower (fgetc (stdin)); return 0; }
Compilez le code via une commande du style :
td_scripts$ gcc -o descripteurs descripteurs.c
Puis exécutez le programme ainsi obtenu :
td_scripts$ ./descripteurs
PID du processus : 5085 - Descripteur 2 (inode 110 - périphérique caractère) : lrwx------ 1 francois francois 64 sep 21 17:24 /proc/5085/fd/2 -> /dev/pts/1 - Descripteur 1 (inode 110 - périphérique caractère) : lrwx------ 1 francois francois 64 sep 21 17:24 /proc/5085/fd/1 -> /dev/pts/1 - Descripteur 0 (inode 110 - périphérique caractère) : lrwx------ 1 francois francois 64 sep 21 17:24 /proc/5085/fd/0 -> /dev/pts/1 Appuyez sur une touche pour terminer l'exécution du programme.
Lors de son exécution, ce petit programme parcourt sa table des descripteurs à l'envers, et affiche ceux qui ont été affectés. Pour chacun des descripteurs ouverts (via la fonction open(...) de la libc), il procède à l'affichage du fichier correspondant par un appel à la commande unix ls. Ce qui nous montre bien, que par défaut, un processus lancé depuis un shell dispose de 3 descripteurs de fichiers vers la console d'appel (dans l'exemple, il s'agit du pseudo-terminal /dev/pts/1) : ces descripteurs sont hérités du shell père qui les a créés lors de son initialisation (login).
En conclusion, par défaut tout nouveau processus dispose de 3 descripteurs de fichiers ouverts sur la même console :
descripteur 0 : l'entrée standard (stdin en C), ouvert en mode lecture, permet d'alimenter le processus en données via le clavier.
descripteur 1 : la sortie standard (stdout en C), ouvert en mode écriture, permet d'afficher du texte à l'écran.
descripteur 2 : la sortie d'erreur (stderror en C), ouvert en mode écriture, permet d'afficher les messages d'erreur rencontrées durant l'exécution à l'écran.
La redirection est le mécanisme offert par le shell permettant d'affecter un flux de données vers un fichier particulier[2] C'est ainsi qu'il sera possible de récupérer le résultat d'une commande dans un fichier, ou encore d'envoyer un fichier vers une commande.
Elle permet donc d'envoyer les affichages normalement affectés à un descripteur donné vers un fichier; et peut prendre deux formes :
La simple redirection, qui s'écrit via le symbole : >, et crée ou écrase le fichier de destination (s'il existe). |
La double redirection, qui s'écrit via le symbole : >>, et crée ou concatène le flux sortant vers le fichier de destination. |
macommande [descripteur]>[>] nomfichier où les termes entre crochets sont optionnels.
Exemples :
td_scripts$ ls > toto.txt envoie le résultat de la commande ls dans le fichier toto.txt.
Qu'il est également possible d'écrire sous la forme :
td_scripts$ ls 1> toto.txt dans la mesure où à défaut d'indication, c'est le descripteur 1 qui est utilisé par la redirection de sortie.
td_scripts$ ls > toto.txt; ls -l >> toto.txt envoie le résultat de la commande ls dans le fichier toto.txt, puis y ajoute le résultat de la commande ls -l.
td_scripts$ ls > toto.txt; ls -l > toto.txt envoie le résultat de la commande ls dans le fichier toto.txt, puis l'écrase par le résultat de la commande ls -l.
td_scripts$ ls -l monfichierquinexistepas > toto.txt écrase le fichier toto.txt en n'y écrivant rien dedans (puisque le fichier visé n'existe pas), et affiche le message d'erreur suivant à l'écran :
ls: ne peut accéder monfichier: Aucun fichier ou répertoire de ce type
td_scripts$ ls -l monfichierquinexistepas > toto.txt 2>/dev/null écrase le fichier toto.txt en n'y écrivant rien dedans (puisque le fichier visé n'existe toujours pas), et redirige le message d'erreur vers /dev/null, ce qui en supprime l'affichage.
td_scripts$ ls -l monfichierquinexistepas * > toto.txt 2>toto.txt envoie le résultat de la commande ls et le message d'erreur dans le fichier toto.txt.
td_scripts$ ls -l monfichierquinexistepas * > toto.txt 2>&1 est identique à la précédente forme, si ce n'est que dans ce cas, la syntaxe 2>&1 indique au shell de rediriger les flux destinés au descripteur 2 vers le descripteur 1.
Elle permet donc d'affecter le contenu d'un fichier en tant que saisie d'une commande; et peut également prendre deux formes :
La simple redirection, qui s'écrit via le symbole : <, et lit des données depuis le fichier spécifié. |
La double redirection, qui s'écrit via le symbole : <<, et lit les données présentes entre deux occurences d'une étiquette. |
Cette forme est essentiellement utilisée dans les script pour utiliser une portion de celui-ci en tant qu'entrée d'une commande ou encore pour transmettre une transcription littérale d'une saisie. |
macommande [0]<[<][-][ETIQUETTE] nomfichier où les termes entre crochets sont optionnel.
Exemples :
td_scripts$ mail -s "un sujet" lepape@vatican.com < toto.txt envoie le contenu du fichier toto.txt dans le corps du message électronique à destination du pape.
td_scripts$ mail -s "un sujet" lepape@vatican.com <MSG
>bla >bla >bla >MSGest équivalente à la précédente, si ce n'est que la commande mail attend cette fois-ci la saisie du message depuis la console. Le saisie se termine par la saisie en début de ligne de l'étiquette, ici le mot MSG (C'est d'ailleurs le comportement normal de cette commande si rien ne lui est spécifiée - rappelez vous la ligne se terminant par . pour finir un message !).
Soit le bout de script suivant dans toto.sh :
cat <<FIN Ceci est message sur plusieurs lignes... FIN cat <<-EOF Ceci est un message sur plusieurs lignes... dans lequel les tabulations seront omises EOFSon exécution par td_scripts$ . toto.sh affiche le contenu :
Ceci est message sur plusieurs lignes... Ceci est un message sur plusieurs lignes... dans lequel les tabulations seront omises
L'utilisation du symbole - suivant la redirection << permet de supprimer les éventuelles tabulations ayant servi à indenter proprement le code du script. |
Un tube est un autre mécanisme du shell permettant de faire communiquer entre eux deux processus, sa syntaxe est la suivante :
macommande1 | macommande2 [| macommande3] [...]
Le tube a donc pour effet d'affecter la sortie standard de la commande située à gauche de l'expression à l'entrée standard de la commande de droite de l'expression ;
Le tube n'a de sens que si les commandes utilisées à droite de l'expression utilisent leur entrée standard, ce qui est notamment le cas pour les commandes ls, echo, cp, rm, mv, etc... |
Exemples :
td_scripts$ ls | tee toto.txt envoie le résultat de la commande ls vers la commande tee laquelle affiche sur la console ce qu'elle reçoit sur son entrée standard et l'insère également dans le fichier toto.txt.
td_scripts$ ls | wc -l wc -l compte le nombre de lignes reçues sur son entrée standard (en l'occurence la sortie de ls).
td_scripts$ dpkg -L coreutils | grep bin | pr -4 | sed -e '/^$/d' | sed -e '/^[1-9]/d' affiche sur 4 colonnes les binaires contenus dans le paquet debian coreutils
dont voici le résultat :
/usr/sbin /usr/bin/head /usr/bin/sort /bin/pwd /usr/sbin/chroot /usr/bin/tsort /usr/bin/du /bin/echo /usr/bin /usr/bin/chcon /usr/bin/paste /bin/ls /usr/bin/tail /usr/bin/expand /usr/bin/cksum /bin/df /usr/bin/cut /usr/bin/tac /usr/bin/factor /bin/mv /usr/bin/users /usr/bin/dirname /usr/bin/pr /bin/chown /usr/bin/printenv /usr/bin/csplit /usr/bin/pathchk /bin/cp /usr/bin/sha384su /usr/bin/printf /usr/bin/link /bin/dd /usr/bin/sha1sum /usr/bin/who /usr/bin/ptx /bin/readlink /usr/bin/groups /usr/bin/fold /usr/bin/sha256su /bin/chgrp /usr/bin/mkfifo /usr/bin/dircolor /usr/bin/sha224su /bin/false /usr/bin/[ /usr/bin/tee /usr/bin/base64 /bin/rm /usr/bin/md5sum /usr/bin/tty /usr/bin/pinky /bin/true /usr/bin/whoami /usr/bin/logname /usr/bin/stat /bin/date /usr/bin/runcon /usr/bin/od /usr/bin/install /bin/sleep /usr/bin/yes /usr/bin/nl /usr/bin/expr /bin/mkdir /usr/bin/shuf /usr/bin/env /usr/bin/uniq /bin/rmdir /usr/bin/seq /usr/bin/join /usr/bin/wc /bin/mknod /usr/bin/tr /usr/bin/sha512su /usr/bin/basename /bin/sync /usr/bin/nohup /usr/bin/sum /bin/vdir /bin /usr/bin/unexpand /usr/bin/shred /bin/ln /bin/dir /usr/bin/id /usr/bin/fmt /bin/uname /bin/chmod /usr/bin/hostid /usr/bin/comm /bin/stty /usr/bin/md5sum.t /usr/bin/nice /usr/bin/split /bin/cat /usr/bin/touch /usr/bin/unlink /usr/bin/test /bin/touch
C'est avec l'utilisation des tubes de communication que le notion de filtres Unix prend tout son sens (cf. chapite 5) ! |
Il peut être parfois utile de regrouper un certain nombre de commandes, qui seront alors séparées par un caractère point-virgule (;), soit pour en rediriger les sorties vers un même fichier ou vers un tube, soit pour les faire exécuter dans un même environnement.
Ce regroupement permet d'agréger les sorties standard (et les sorties d'erreurs) de chaque commande, comme s'il s'agissait d'un seul et unqiue descripteur. Dans ce cas, les commandes sont exécutées classiquement, dans l'ordre d'apparition dans l'expression, par des shells enfants.
Exemple :
td_scripts$ (echo "DEBUT DU FICHIER" ; cat toto.txt ; echo "FIN DU FICHIER) > titi.txt envoie le contenu du fichier toto.txt encadré des lignes "DEBUT..." et "FIN..." dans le fichier titi.txt.
Cet autre type de regroupement permet de faire exécuter la séquence de commandes par le shell courant, et également d'agréger les sorties.
Dans ce cas toutes les commandes (y compris la dernière) doivent être suivies d'un point-virgule (;), de plus un espace est nécessaire après l'accolade ouvrante et avant l'accolade fermante. |
Exemple :
td_scripts$ { cd monnouveaurepertoire ; echo "DEBUT DU FICHIER" ; cat toto.txt ; echo "FIN DU FICHIER ; } > titi.txt envoie le contenu du fichier toto.txt encadré des lignes "DEBUT..." et "FIN..." dans le fichier titi.txt, en outre l'environnement du shell sera modifié, le répertoire courant étant maintenant ./monnouveaurepertoire/ .
[1] | Une commande ne fait qu'une seule chose... mais le fait bien ! |
[2] | Or, sous Unix, tout est fichier ... |