Remarque: la réponse reflète ma propre compréhension de ces mécanismes actualisés, accumulés au cours de la recherche et de la lecture des réponses par les pairs sur ce site et sur unix.stackexchange.com , et sera mise à jour au fil du temps. N'hésitez pas à poser des questions ou à suggérer des améliorations dans les commentaires. Je vous suggère également d'essayer de voir comment les appels système fonctionnent en shell avec strace
commande. Veuillez également ne pas être intimidé par la notion d'internes ou d'appels système - vous n'avez pas besoin de savoir ou de pouvoir les utiliser pour comprendre comment fonctionne un shell, mais ils aident vraiment à la compréhension.
TL; DR
|
Les pipes ne sont pas associées à une entrée sur le disque, donc n’ont pas de numéro de système de fichiers sur le disque (mais ont un inode dans le système de fichiers virtuel pipefs dans l’espace noyau), mais les redirections impliquent souvent des fichiers qui ont des entrées de inode.
- Les pipes ne sont pas
lseek()
'capables, donc les commandes ne peuvent pas lire certaines données et ensuite revenir en arrière, mais quand vous redirigez avec >
ou <
habituellement c'est un fichier qui est un lseek()
objet capable, les commandes peuvent donc naviguer comme bon leur semble.
- les redirections sont des manipulations sur les descripteurs de fichiers, qui peuvent être nombreux; les pipes ont seulement deux descripteurs de fichier - un pour la commande gauche et un pour la commande droite
- la redirection sur les flux standard et les pipes sont tous deux tamponnés.
- les tuyaux impliquent presque toujours un forgeage et par conséquent des paires de processus sont impliqués; redirections - pas toujours, bien que dans les deux cas les descripteurs de fichiers résultants soient hérités par des sous-processus.
- Les pipes connectent toujours des descripteurs de fichier (une paire), des redirections - utilisez un nom de chemin ou des descripteurs de fichier.
- les tubes sont une méthode de communication interprocessus, tandis que les redirections ne sont que des manipulations sur des fichiers ouverts ou des objets de type fichier
- les deux utilisent des
dup2()
appels système sous le capot pour fournir des copies des descripteurs de fichier, où le flux réel de données se produit.
- Les redirections peuvent être appliquées "globalement" avec la
exec
commande intégrée (voir ceci et cela ), donc si vous le faites, exec > output.txt
chaque commande écrit à output.txt
partir de là. |
Les tuyaux ne sont appliqués que pour la commande en cours (ce qui signifie soit une commande simple, soit des commandes semblables à un sous-shell seq 5 | (head -n1; head -n2)
ou composées.
Lorsque la redirection est effectuée sur des fichiers, des choses comme echo "TEST" > file
et echo "TEST" >> file
toutes les deux utilisent open()
syscall sur ce fichier ( voir aussi ) et en tirent le descripteur de fichier dup2()
. Les pipes |
n'utilisent que pipe()
et dup2()
syscall.
En ce qui concerne les commandes en cours d'exécution, les canaux et la redirection ne sont rien de plus que des descripteurs de fichier - des objets de type fichier, sur lesquels ils peuvent écrire en aveugle ou les manipuler en interne (ce qui peut produire des comportements inattendus; apt
par exemple, a tendance à ne même pas écrire sur stdout s'il sait qu'il y a redirection).
introduction
Afin de comprendre en quoi ces deux mécanismes diffèrent, il est nécessaire de comprendre leurs propriétés essentielles, leur histoire et leurs racines dans le langage de programmation C. En fait, savoir ce que sont les descripteurs de fichier, comment dup2()
et comment les pipe()
appels système fonctionnent est essentiel lseek()
. Shell est conçu comme un moyen de rendre ces mécanismes abstraits pour l'utilisateur, mais creuser plus profondément que l'abstraction permet de comprendre la vraie nature du comportement de shell.
Les origines des redirections et des pipes
Selon l'article de Dennis Ritche, Prophetic Petroglyphs , les tuyaux proviennent d'une note interne de Malcolm Douglas McIlroy , datant de 1964 , à l'époque où ils travaillaient sur le système d'exploitation Multics . Citation:
Pour résumer mes plus grandes préoccupations:
- Nous devrions avoir des moyens de connecter des programmes tels que tuyau d’arrosage - vissez un autre segment quand il devient nécessaire de masser les données d’une autre manière. C’est aussi la manière d’IO.
Ce qui est évident, c’est qu’à l’époque, les programmes étaient capables d’écrire sur disque, mais c’était inefficace si la sortie était volumineuse. Pour citer l'explication de Brian Kernighan dans la vidéo Unix Pipeline :
Premièrement, vous n’avez pas à écrire un gros programme volumineux - vous avez des programmes plus petits existants qui font peut-être déjà une partie du travail ... Un autre problème est que la quantité de données que vous traitez ne correspondrait pas si vous l'avez stocké dans un fichier ... parce que, rappelez-vous, nous étions de retour à l'époque où les disques contenant ces choses avaient, si vous aviez de la chance, un ou plusieurs mégaoctets de données ... Ainsi, le pipeline n'a jamais eu à instancier l'intégralité de la sortie. .
La différence conceptuelle est donc évidente: les pipes sont un mécanisme permettant aux programmes de se parler. Les redirections - sont une façon d'écrire dans un fichier au niveau de base. Dans les deux cas, Shell facilite ces deux choses, mais sous le capot, il se passe beaucoup de choses.
Aller plus loin: appels système et fonctionnement interne du shell
Nous commençons par la notion de descripteur de fichier . Les descripteurs de fichier décrivent essentiellement un fichier ouvert (qu'il s'agisse d'un fichier sur disque, en mémoire ou anonyme), qui est représenté par un nombre entier. Les deux flux de données standard (stdin, stdout, stderr) sont respectivement les descripteurs de fichier 0,1 et 2. D'où viennent-ils ? Dans les commandes shell, les descripteurs de fichier sont hérités de leur parent - shell. Et c'est vrai en général pour tous les processus - le processus enfant hérite des descripteurs de fichier du parent. Pour les démons, il est courant de fermer tous les descripteurs de fichiers hérités et / ou de les rediriger vers d'autres emplacements.
Retour à la redirection. C'est quoi vraiment? C'est un mécanisme qui indique au shell de préparer les descripteurs de fichier pour la commande (car les redirections sont effectuées par le shell avant l'exécution de la commande), et de les indiquer à l'endroit suggéré par l'utilisateur. La définition standard de la redirection de sortie est
[n]>word
Qu'il [n]
y ait le numéro de descripteur de fichier. Lorsque vous faites echo "Something" > /dev/null
le numéro 1 est implicite là, et echo 2> /dev/null
.
Sous le capot, cela se fait en dupliquant le descripteur de fichier via dup2()
un appel système. Prenons df > /dev/null
. Le shell créera un processus enfant où df
s'exécutera, mais avant cela, il s'ouvrira en /dev/null
tant que descripteur de fichier n ° 3 et dup2(3,1)
sera publié. Il créera une copie du descripteur de fichier 3 et la copie sera 1. Vous savez comment vous avez deux fichiers file1.txt
et file2.txt
et quand vous cp file1.txt file2.txt
aurez deux fichiers identiques, mais que vous pourrez les manipuler indépendamment? C'est un peu la même chose qui se passe ici. Souvent, vous pouvez voir que, avant de lancer la tâche, bash
il est dup(1,10)
nécessaire de créer un descripteur de fichier de copie n ° 1 qui est stdout
(et que cette copie sera le disque n ° 10) afin de le restaurer ultérieurement. Il est important de noter que lorsque vous considérez les commandes intégrées(qui font partie du shell proprement dit, et n'ont aucun fichier /bin
dedans ou ailleurs) ou de simples commandes dans un shell non interactif , le shell ne crée pas de processus enfant.
Et puis nous avons des choses comme [n]>&[m]
et [n]&<[m]
. Cela duplique les descripteurs de fichier, dont le mécanisme est identique à dup2()
celui de la syntaxe du shell, disponible pour l'utilisateur.
Un des points importants à noter à propos de la redirection est que leur ordre n’est pas fixe, mais qu’il est important pour l’interprétation que shell donne à ce que l’utilisateur souhaite. Comparez ce qui suit:
# Make copy of where fd 2 points , then redirect fd 2
$ ls -l /proc/self/fd/ 3>&2 2> /dev/null
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
lrwx------ 1 runner user 64 Sep 13 00:08 3 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/29/fd
# redirect fd #2 first, then clone it
$ ls -l /proc/self/fd/ 2> /dev/null 3>&2
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
l-wx------ 1 user user 64 Sep 13 00:08 3 -> /dev/null
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/31/fd
L'utilisation pratique de ceux-ci dans les scripts shell peut être polyvalente:
et beaucoup d'autres.
Plomberie avec pipe()
etdup2()
Alors, comment les tuyaux sont-ils créés? Via pipe()
syscall , qui prendra en entrée un tableau (ou liste) appelé pipefd
de deux éléments de type int
(entier). Ces deux entiers sont des descripteurs de fichier. Le pipefd[0]
sera l'extrémité de lecture du tuyau et pipefd[1]
sera l'extrémité d'écriture. So in df | grep 'foo'
, grep
obtiendra une copie pipefd[0]
et df
obtiendra une copie de pipefd[1]
. Mais comment ? Bien sûr, avec la magie de l' dup2()
appel système. Par df
exemple, dans notre exemple, pipefd[1]
a # 4, le shell fera un enfant, do dup2(4,1)
(souvenez-vous de mon cp
exemple?), Puis execve()
exécutez df
. Naturellement,df
héritera du descripteur de fichier n ° 1, mais ne saura pas qu'il ne pointe plus sur le terminal, mais sur le fichier fd n ° 4, qui correspond en réalité à la fin du tuyau. Naturellement, la même chose se produira grep 'foo'
sauf avec des nombres différents de descripteurs de fichiers.
Maintenant, question intéressante: pourrions-nous aussi créer des pipes qui redirigeront fd # 2, pas seulement fd # 1? Oui, en fait, c'est ce qui se |&
passe à Bash. La norme POSIX exige que le langage de commande shell prenne en charge la df 2>&1 | grep 'foo'
syntaxe à cette fin, mais il en bash
va |&
de même.
Il est important de noter que les pipes traitent toujours avec des descripteurs de fichier. Il existe FIFO
ou pipe nommée , qui a un nom de fichier sur le disque et vous permet de l'utiliser comme un fichier, mais se comporte comme une pipe. Mais les |
types de canaux sont ce qu’on appelle des tubes anonymes - ils n’ont pas de nom de fichier, car ce ne sont en réalité que deux objets reliés entre eux. Le fait que nous n'ayons pas affaire à des fichiers a également une implication importante: les pipes ne sont pas lseek()
capables. Les fichiers, en mémoire ou sur disque, sont statiques - les programmes peuvent utiliser lseek()
syscall pour passer à l'octet 120, puis revenir à l'octet 10, puis le transférer à la fin. Les pipes ne sont pas statiques - elles sont séquentielles, et vous ne pouvez donc pas rembobiner les données que vous obtenez aveclseek()
. C’est ce qui permet à certains programmes de savoir s’ils lisent à partir d’un fichier ou d’un tuyau, et peuvent donc effectuer les ajustements nécessaires pour obtenir des performances efficaces; en d'autres termes, un prog
peut détecter si je le fais cat file.txt | prog
ou prog < input.txt
. Le vrai exemple de travail est la queue .
Les deux autres propriétés très intéressantes des pipes sont qu’elles ont un tampon, ce qui sous Linux est de 4096 octets , et qu’elles ont un système de fichiers tel que défini dans le code source de Linux ! Ils ne sont pas simplement un objet de transmission de données, ils sont eux-mêmes une structure de données! En fait, comme il existe un système de fichiers pipefs, qui gère à la fois les tubes et les FIFO, les tubes ont un numéro inode sur leur système de fichiers respectif:
# Stdout of ls is wired to pipe
$ ls -l /proc/self/fd/ | cat
lrwx------ 1 user user 64 Sep 13 00:02 0 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:02 1 -> pipe:[15655630]
lrwx------ 1 user user 64 Sep 13 00:02 2 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:02 3 -> /proc/22/fd
# stdin of ls is wired to pipe
$ true | ls -l /proc/self/fd/0
lr-x------ 1 user user 64 Sep 13 03:58 /proc/self/fd/0 -> 'pipe:[54741]'
Sur Linux, les canaux sont unidirectionnels, tout comme la redirection. Sur certaines implémentations de type Unix, il existe des tuyaux bidirectionnels. Bien qu'avec la magie des scripts shell, vous pouvez également créer des pipes bidirectionnelles sur Linux .
Voir également:
thing1 > temp_file && thing2 < temp_file
de faire plus facilement avec des pipes. Mais pourquoi ne pas réutiliser l'>
opérateur pour le faire, par exemplething1 > thing2
pour les commandesthing1
etthing2
? Pourquoi un opérateur supplémentaire|
?