Nota: La respuesta refleja mi propia comprensión de estos mecanismos actualizados, acumulados durante la investigación y la lectura de las respuestas de los pares en este sitio y en unix.stackexchange.com , y se actualizará a medida que pase el tiempo. No dude en hacer preguntas o sugerir mejoras en los comentarios. También le sugiero que intente ver cómo funcionan las llamadas al sistema en shell con el strace
comando. Además, no se deje intimidar por la noción de elementos internos o syscalls: no tiene que saberlos ni poder usarlos para comprender cómo funciona Shell, pero definitivamente ayudan a comprender.
TL; DR
|
las tuberías no están asociadas con una entrada en el disco, por lo tanto, no tienen un número de inodo del sistema de archivos de disco (pero sí tienen un inodo en el sistema de archivos virtual de pipefs en el espacio del kernel), pero las redirecciones a menudo involucran archivos, que tienen entradas de disco y, por lo tanto, tienen el correspondiente inodo
- las tuberías no son
lseek()
capaces, por lo que los comandos no pueden leer algunos datos y luego retroceder, pero cuando se redirige >
o <
generalmente es un archivo lseek()
capaz de objetos, los comandos pueden navegar como quieran.
- las redirecciones son manipulaciones en los descriptores de archivos, que pueden ser muchas; las tuberías tienen solo dos descriptores de archivo: uno para el comando izquierdo y otro para el comando derecho
- La redirección en las corrientes y tuberías estándar se almacenan en búfer.
- las tuberías casi siempre implican bifurcación y, por lo tanto, intervienen pares de procesos; redirecciones: no siempre, aunque en ambos casos los subprocesos heredan los descriptores de archivo resultantes.
- las tuberías siempre conectan descriptores de archivo (un par), redirecciones, ya sea que utilicen un nombre de ruta o descriptores de archivo.
- las tuberías son un método de comunicación entre procesos, mientras que las redirecciones son solo manipulaciones en archivos abiertos u objetos similares
- ambos emplean
dup2()
syscalls debajo del capó para proporcionar copias de descriptores de archivos, donde ocurre el flujo real de datos.
- las redirecciones se pueden aplicar "globalmente" con el
exec
comando incorporado (vea esto y esto ), por lo que si lo hace, exec > output.txt
todos los comandos escribirán a output.txt
partir de ese momento. |
las tuberías se aplican solo para el comando actual (lo que significa un comando simple o como seq 5 | (head -n1; head -n2)
comandos de subshell o compuestos.
Cuando se realiza la redirección en los archivos, cosas como echo "TEST" > file
y echo "TEST" >> file
ambos usan open()
syscall en ese archivo ( ver también ) y obtienen un descriptor de archivo para pasarlo dup2()
. Las tuberías |
solo usan pipe()
y dup2()
syscall.
En lo que respecta a los comandos que se ejecutan, las canalizaciones y la redirección no son más que descriptores de archivos: objetos en forma de archivo, en los que pueden escribir a ciegas, o manipularlos internamente (lo que puede producir comportamientos inesperados; apt
por ejemplo, tiende a ni siquiera escribir en stdout si sabe que hay redirección).
Introducción
Para entender cómo difieren estos dos mecanismos, es necesario comprender sus propiedades esenciales, la historia detrás de los dos y sus raíces en el lenguaje de programación C. De hecho, saber qué son los descriptores de archivos, y cómo funcionan las llamadas al sistema dup2()
y también pipe()
es esencial lseek()
. Shell pretende ser una forma de hacer que estos mecanismos sean abstractos para el usuario, pero cavar más profundo que la abstracción ayuda a comprender la verdadera naturaleza del comportamiento de shell.
Los orígenes de las redirecciones y tuberías
Según el artículo de Dennis Ritche, Petroglifos proféticos , las tuberías se originaron en un memorando interno de 1964 de Malcolm Douglas McIlroy , cuando trabajaban en el sistema operativo Multics . Citar:
Para poner mis preocupaciones más fuertes en pocas palabras:
- Deberíamos tener algunas formas de conectar programas como la manguera de jardín: atornille otro segmento cuando sea necesario para masajear los datos de otra manera. Este es el camino de IO también.
Lo que es evidente es que en ese momento los programas eran capaces de escribir en el disco, sin embargo, eso era ineficiente si la salida era grande. Para citar la explicación de Brian Kernighan en el video de Unix Pipeline :
Primero, no tiene que escribir un gran programa masivo: tiene programas más pequeños existentes que ya pueden hacer parte del trabajo ... Otro es que es posible que la cantidad de datos que está procesando no encajaría si lo almacenó en un archivo ... porque recuerde, estamos de vuelta en los días en que los discos de estas cosas tenían, si tuvo suerte, un Megabyte o dos de datos ... Por lo tanto, la tubería nunca tuvo que instanciar toda la salida .
Así, la diferencia conceptual es evidente: las tuberías son un mecanismo para hacer que los programas se comuniquen entre sí. Redirecciones: son una forma de escribir en el archivo a nivel básico. En ambos casos, Shell hace que estas dos cosas sean fáciles, pero debajo del capó, están sucediendo muchas cosas.
Profundizando: syscalls y funcionamiento interno del shell
Comenzamos con la noción de descriptor de archivo . Los descriptores de archivo describen básicamente un archivo abierto (ya sea un archivo en el disco, en la memoria o un archivo anónimo), que está representado por un número entero. Los dos flujos de datos estándar (stdin, stdout, stderr) son descriptores de archivo 0,1 y 2 respectivamente. De dónde vienen ? Bueno, en los comandos de shell los descriptores de archivo se heredan de su padre - shell. Y es cierto en general para todos los procesos: el proceso hijo hereda los descriptores de archivo de los padres. Para los demonios , es común cerrar todos los descriptores de archivos heredados y / o redirigir a otros lugares.
De vuelta a la redirección. Que es realmente Es un mecanismo que le dice al shell que prepare los descriptores de archivo para el comando (porque el redireccionamiento lo realiza el shell antes de que se ejecute el comando) y los señala donde el usuario sugirió. La definición estándar de redirección de salida es
[n]>word
Que [n]
existe el número de descriptor de archivo. Cuando haces echo "Something" > /dev/null
el número 1 está implícito allí, y echo 2> /dev/null
.
Debajo del capó, esto se hace duplicando el descriptor de archivo a través de la dup2()
llamada al sistema. Vamos a tomar df > /dev/null
. El shell creará un proceso secundario donde se df
ejecuta, pero antes de eso se abrirá /dev/null
como descriptor de archivo # 3, y dup2(3,1)
se emitirá, lo que hace una copia del descriptor de archivo 3 y la copia será 1. Usted sabe cómo tiene dos archivos file1.txt
y file2.txt
, y cuando lo haga cp file1.txt file2.txt
, tendrá dos mismos archivos, pero puede manipularlos de forma independiente. Eso es lo mismo que sucede aquí. A menudo, puede ver que antes de ejecutar, bash
deberá dup(1,10)
hacer una copia del descriptor de archivo # 1 que es stdout
(y esa copia será fd # 10) para restaurarla más tarde. Es importante tener en cuenta que cuando se consideran los comandos integrados(que son parte del shell en sí y no tienen ningún archivo en ningún /bin
otro lado) o comandos simples en un shell no interactivo , el shell no crea un proceso hijo.
Y luego tenemos cosas como [n]>&[m]
y [n]&<[m]
. Esto es duplicar los descriptores de archivos, que el mismo mecanismo que dup2()
solo ahora tiene la sintaxis de shell, convenientemente disponible para el usuario.
Una de las cosas importantes a tener en cuenta sobre la redirección es que su orden no es fijo, pero es importante para la forma en que Shell interpreta lo que el usuario quiere. Compare lo siguiente:
# 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
El uso práctico de estos en scripts de shell puede ser versátil:
y muchos otros.
Fontanería con pipe()
ydup2()
Entonces, ¿cómo se crean las tuberías? A través de pipe()
syscall , que tomará como entrada una matriz (también conocida como lista) llamada pipefd
de dos elementos de tipo int
(entero). Esos dos enteros son descriptores de archivo. El pipefd[0]
será el fin de leer de la tubería y pipefd[1]
será el final de escritura. Entonces df | grep 'foo'
, grep
obtendrá una copia de pipefd[0]
y df
obtendrá una copia de pipefd[1]
. Pero cómo ? Por supuesto, con la magia de dup2()
syscall. En df
nuestro ejemplo, digamos que pipefd[1]
tiene el n. ° 4, por lo que el shell creará un hijo, hacer dup2(4,1)
(¿recuerda mi cp
ejemplo?) Y luego hacer execve()
para ejecutar realmente df
. Naturalmente,df
heredará el descriptor de archivo n. ° 1, pero no se dará cuenta de que ya no está apuntando a la terminal, sino en realidad fd n. ° 4, que en realidad es el extremo de escritura de la tubería. Naturalmente, ocurrirá lo mismo grep 'foo'
excepto con diferentes números de descriptores de archivo.
Ahora, una pregunta interesante: ¿podríamos hacer tuberías que redirijan fd # 2 también, no solo fd # 1? Sí, de hecho eso es lo que |&
hace en bash. El estándar POSIX requiere un lenguaje de comandos de shell para admitir la df 2>&1 | grep 'foo'
sintaxis para ese propósito, pero también lo bash
hace |&
.
Lo que es importante tener en cuenta es que las tuberías siempre tratan con descriptores de archivo. Existe FIFO
o tubería con nombre , que tiene un nombre de archivo en el disco y te permite utilizarlo como un archivo, pero se comporta como un tubo. Pero los |
tipos de tuberías son lo que se conoce como tubería anónima: no tienen nombre de archivo, porque en realidad son solo dos objetos conectados entre sí. El hecho de que no estamos tratando con archivos también tiene una implicación importante: las tuberías no son lseek()
capaces. Los archivos, ya sea en la memoria o en el disco, son estáticos: los programas pueden usar lseek()
syscall para saltar al byte 120, luego regresar al byte 10 y luego avanzar hasta el final. Las tuberías no son estáticas: son secuenciales y, por lo tanto, no puede rebobinar los datos que obtiene de ellas conlseek()
. Esto es lo que hace que algunos programas se den cuenta si están leyendo desde un archivo o desde una tubería y, por lo tanto, pueden hacer los ajustes necesarios para un rendimiento eficiente; en otras palabras, a prog
puede detectar si lo hago cat file.txt | prog
o no prog < input.txt
. Ejemplo de trabajo real de eso es la cola .
Las otras dos propiedades muy interesantes de las tuberías es que tienen un búfer, que en Linux tiene 4096 bytes , ¡y en realidad tienen un sistema de archivos como se define en el código fuente de Linux ! No son simplemente un objeto para pasar datos, ¡son una estructura de datos ellos mismos! De hecho, debido a que existe un sistema de archivos pipefs, que gestiona tuberías y FIFO, las tuberías tienen un número de inodo en su sistema de archivos respectivo:
# 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]'
En Linux, las canalizaciones son unidireccionales, al igual que la redirección. En algunas implementaciones tipo Unix, hay tuberías bidireccionales. Aunque con la magia de las secuencias de comandos de shell, también puede crear tuberías bidireccionales en Linux .
Ver también:
thing1 > temp_file && thing2 < temp_file
hacer más fácil con las tuberías. Pero, ¿por qué no reutilizar el>
operador para hacer esto, por ejemplo,thing1 > thing2
para comandosthing1
ything2
? ¿Por qué un operador extra|
?