• # Qu'est-ce qu'un pipe ?

    Posté par . En réponse au message Incompréhension des pipes sur Linux. Évalué à 10. Dernière modification le 26 août 2024 à 23:44.

    Pour savoir exactement ce que ça fait, il faut savoir comment fonctionne un "tube", et connaitre le principe d'entrée et de sortie standard + erreur sous Unix.

    Tout processus lorsqu'il est créé possède par défaut trois descripteurs de fichiers ouverts qui lui serviront à communiquer : l'entrée standard (le clavier pour un processus interactif dans un shell), la sortie standard (le terminal par défaut), et la sortie d'erreur (le terminal également). Petit exemple : exécute la commande cat sans rien lui passer : tu verras qu'elle t'affichera tout ce que tu lui entres au clavier:

    $ cat
    abc
    abc
    def
    def
    ghi
    ghi
    

    Note : pour arrêter la saisie sur l'entrée standard, tu tapes sur les touches CTRL+D

    En fait la commande cat est une commande qui, si on ne lui spécifie pas de paramètres, va prendre tout ce qui arrive sur l'entrée standard pour l'afficher sur la sortie standard (petite parenthèse : le nom cat vient à l'origine du mot con*cat*, et cette commande permet d'afficher sur sa sortie standard les fichiers qu'on lui passe en paramètre).

    Le descripteur de fichier de l'entrée standard porte le numéro 0, celui de la sortie standard le numéro 1, et celui de la sortie d'erreur le numéro 2 (on y reviendra plus tard).

    Cependant, il est possible de faire des redirections des divers périphériques par défaut. Par exemple, tu peux exécuter la commade cat < /dev/random : en exécutant cette commande tu dis à cat que l'entrée standard n'est plus le clavier, mais le fichier que tu as passé en paramètre (ici /dev/randpm génère des nombres aléatoires : tu verras une bouillie binaire s'afficher à ton écran).

    De même qu'il est possible de changer le périphérique d'entrée, il est possible de changer le périphérique de sortie. Par exemple, si tu veux rediriger la sortie de la commande cat dans un fichier :

    totof@superbipbip:~$ cat >/tmp/toto.txt
    abc
    def
    ghi
    la vie est belle
    

    (taper control+d pour faire cesser la saisie)

    Là tu as généré un fichier que tu peux visualiser ainsi :

    totof@superbipbip:~$ more /tmp/toto.txt
    abc
    def
    ghi
    la vie est belle
    totof@superbipbip:~$ 
    

    Le troisième descripteur est la sortie d'erreur. Cette sortie affiche le message d'erreur. Faisons en sorte que cat génère une erreur en lui donnant un fichier bidon en paramètre d'entrée :

    totof@superbipbip:~$ cat /tmp/je_nexiste_pas
    cat: /tmp/je_nexiste_pas: Aucun fichier ou dossier de ce nom
    

    Une erreur qui s'affiche à l'écran, mais tu peux également la rediriger :

    totof@superbipbip:~$ cat /tmp/je_nexiste_pas 2>/tmp/erreur.txt
    

    La commande est "muette", mais elle génère bien un message d'erreur qu'on a redirigé vers un fichier

    totof@superbipbip:~$ more /tmp/erreur.txt 
    cat: /tmp/je_nexiste_pas: Aucun fichier ou dossier de ce nom
    

    Maintenant venons-en aux pipes. Quand tu enchaines les commandes via un pipe, tu demandes à ce que la sortie standard de la commande à gauche du pipe soit redirigée vers l'entrée standard de la commande à droite du pipe. Décomposons cet enchaînement :

    totof@superbipbip:~$ echo "coucou" | cat
    coucou
    

    Ici, la commande écho affiche sur la sortie standard la chaîne "coucou" et la commande cat la récupère depuis son entrée standard pour l'afficher à son tour sur sa sortie standard. A noter que les deux commandes tournent en parallèle. Le traitement de la commande de droite n'attend pas que la commande de gauche soit terminée pour s'exécuter. Elles tournent ensemble et les données sont traitées par la commande de droite au fur à mesure qu'elles arrivent de la commande de gauche.

    En redirigeant vers un fichier :

    totof@superbipbip:~$ echo "coucou" | cat >/tmp/coucou.txt
    totof@superbipbip:~$ more /tmp/coucou.txt 
    coucou
    

    Si tu veux récupérer la sortie standard et la sortie d'erreur pour la rediriger vers ton pipe tu peux : il suffit de le demander. Voici ce qui se passe sans redirection :

    totof@superbipbip:~$ cat /tmp/jenexistepas | cat >/tmp/coucou.txt
    cat: /tmp/jenexistepas: Aucun fichier ou dossier de ce nom
    

    Le premier cat essaie de récpérer le contenu du fichier en paramètre, mais comme il n'existe pas, le processus affiche un message d'erreur. ( et si tu vas voir le contenu de /tmp/coucou.txt, tu auras un fichier vide car la commande n'a rien retourné sur sa sortie standard).

    Si on veut récupérer ce message d'erreur dans le pipe, il faut rediriger la sortie d'erreur (descripteur N° 2) vers la sortie standard (2>&1 permet de le faire dans la commande suivante) :

    totof@superbipbip:~$ cat /tmp/jenexistepas 2>&1 | cat >/tmp/coucou.txt
    

    Là encore, à la saisie on a l'impression qui ne se passe rien .. mais si on va voir :

    totof@superbipbip:~$ more /tmp/coucou.txt 
    cat: /tmp/jenexistepas: Aucun fichier ou dossier de ce nom
    

    Pour résumer, quand on exécute commmande 1 | commande 2, on demande à commande 2 de récupérer sur son entrée standard le contenu du canal de sortie standard de commande 1. Et si tu veux rediriger la sortie d'erreur vers le pipe, il faut que tu précises à la commande 1 (à gauche du pipe) de rediriger sa sortie d'erreur vers la sortie standard (je précise parce que c'est facile d'oublier).

    Maintenant pourquoi ce que tu as fait n'a pas fonctionné ?

    tail -2 fichier.txt | nl fichier.txt
    

    En gros tu dis à tail de récupérer les deux dernières lignes du fichier en paramètre. Tail les envoie sur sa sortie standard qui est capturée par le pipe. Sauf que à droite tu dis à la commande nl de ne pas récupérer la sortie standard, mais de récupérer les données du fichier passé en paramètre. Donc la sortie standard de la commande de gauche est ignorée. Tout ce que tu fais c'est exécuter des commandes en parallèle sans qu'elles n'interagissent entre elles.

    Maintenant un petit mot : j'ai utilisé cat pour l'exemple. Cependant beaucoup de monde croit que faire un cat est nécessaire pour pouvoir ensuite le traiter par une autre commande (tu verras beaucoup de cat fichier | grep par exemple). Le cat ne sert à rien dans ce cas. Démonstration avec le fichier généré précédemment :

    totof@superbipbip:~$ more /tmp/toto.txt
    abc
    def
    ghi
    la vie est belle
    

    Beaucoup de gens vont faire ceci :

    totof@superbipbip:~$ cat /tmp/toto.txt | grep def
    def
    

    Le cat ne sert à rien. On appelle ça UUOC ou Useless use of cat. Dans ce cas il vaut mieux faire ceci:

    totof@superbipbip:~$ grep def /tmp/toto.txt
    def
    

    Et si ta commande ne prend pas de fichier en paramètre mais ne prend ses données sur l'entrée standard (oui ça existe) :

    totof@superbipbip:~$ grep def < /tmp/toto.txt
    def