¿Cuál es la diferencia entre <<, <<< y <<en bash?

102

¿Cuál es la diferencia entre <<, <<<y < <en bash?

Searene
fuente
20
Al menos en estos tiempos centrados en Google, es difícil buscar estos operadores basados ​​en símbolos. ¿Existe un motor de búsqueda en el que pueda conectar "<< <<< <<" y obtener algo útil?
Daniel Griscom
11
@DanielGriscom Hay SymbolHound .
Dennis
1
@DanielGriscom Stack Exchange solía admitir símbolos de búsqueda, pero luego algo se rompió y nadie lo solucionó.
muru
Ya está allí (y lo ha estado durante casi un año): ¿Cuáles son los operadores de control y redirección del shell?
Scott

Respuestas:

115

Aquí documenta

<< que se conoce como here-document estructura. Dejas que el programa sepa cuál será el texto final, y cada vez que se ve ese delimitador, el programa leerá todo lo que le has dado al programa como entrada y realizará una tarea sobre él.

Esto es lo que quiero decir:

$ wc << EOF
> one two three
> four five
> EOF
 2  5 24

En este ejemplo, le decimos al wcprograma que espere la EOFcadena, luego escriba cinco palabras y luego escriba EOFpara indicar que hemos terminado de dar entrada. En efecto, es similar a correrwc solo, escribir palabras y luego presionarCtrlD

En bash, estos se implementan a través de archivos temporales, generalmente en forma /tmp/sh-thd.<random string>, mientras que en el tablero se implementan como canalizaciones anónimas. Esto se puede observar a través de las llamadas del sistema de seguimiento con stracecomando. Reemplace bashcon shpara ver cómo se /bin/shrealiza esta redirección.

$ strace -e open,dup2,pipe,write -f bash -c 'cat <<EOF
> test
> EOF'

Aquí cadena

<<<que se conoce como here-string. En lugar de escribir texto, le da una cadena de texto prefabricada a un programa. Por ejemplo, con el programa bcque podemos hacer bc <<< 5*4para obtener resultados para ese caso específico, no es necesario ejecutar bc de forma interactiva.

Las cadenas aquí en bash se implementan a través de archivos temporales, generalmente en el formato /tmp/sh-thd.<random string>, que luego se desvinculan, lo que hace que ocupen temporalmente un espacio de memoria pero no se muestren en la lista de /tmpentradas de directorio, y efectivamente existan como archivos anónimos, que aún pueden se hará referencia a través del descriptor de archivo por el propio shell, y el descriptor de archivo será heredado por el comando y luego duplicado en el descriptor de archivo 0 (stdin) a través de la dup2()función. Esto se puede observar a través de

$ ls -l /proc/self/fd/ <<< "TEST"
total 0
lr-x------ 1 user1 user1 64 Aug 20 13:43 0 -> /tmp/sh-thd.761Lj9 (deleted)
lrwx------ 1 user1 user1 64 Aug 20 13:43 1 -> /dev/pts/4
lrwx------ 1 user1 user1 64 Aug 20 13:43 2 -> /dev/pts/4
lr-x------ 1 user1 user1 64 Aug 20 13:43 3 -> /proc/10068/fd

Y a través de rastreo de llamadas al sistema (salida acortada para facilitar la lectura; observe cómo se abre el archivo temporal como FD 3, los datos grabados en ellos, entonces se vuelve a abrir con O_RDONLYmarcar como fd 4 y posteriores no enlazado, a continuación, dup2()en fd 0, que se hereda por la cattarde ):

$ strace -f -e open,read,write,dup2,unlink,execve bash -c 'cat <<< "TEST"'
execve("/bin/bash", ["bash", "-c", "cat <<< \"TEST\""], [/* 47 vars */]) = 0
...
strace: Process 10229 attached
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
[pid 10229] write(3, "TEST", 4)         = 4
[pid 10229] write(3, "\n", 1)           = 1
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDONLY) = 4
[pid 10229] unlink("/tmp/sh-thd.uhpSrD") = 0
[pid 10229] dup2(4, 0)                  = 0
[pid 10229] execve("/bin/cat", ["cat"], [/* 47 vars */]) = 0
...
[pid 10229] read(0, "TEST\n", 131072)   = 5
[pid 10229] write(1, "TEST\n", 5TEST
)       = 5
[pid 10229] read(0, "", 131072)         = 0
[pid 10229] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=10229, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

Opinión: potencialmente porque las cadenas aquí utilizan archivos de texto temporales, es la posible razón por la cual las cadenas aquí siempre insertan una nueva línea final, ya que el archivo de texto por definición POSIX debe tener líneas que terminen en un carácter de nueva línea.

Proceso de sustitución

Como tldp.org explica ,

La sustitución de procesos alimenta la salida de un proceso (o procesos) en el stdin de otro proceso.

En efecto, esto es similar a la canalización estándar de un comando al otro, por ejemplo echo foobar barfoo | wc. Pero aviso: en la página de manual de bash tenga verá que se denota como <(list). Entonces, básicamente, puede redirigir la salida de múltiples (!) Comandos.

Nota: técnicamente cuando dice < <que no se refiere a una cosa, sino a dos redirecciones con una sola < redirección de salida y de proceso <( . . .).

¿Qué sucede si solo procesamos la sustitución?

$ echo <(echo bar)
/dev/fd/63

Como puede ver, el shell crea un descriptor de archivo temporal /dev/fd/63donde va la salida (que según la respuesta de Gilles , es una tubería anónima). Eso significa <que redirige ese descriptor de archivo como entrada en un comando.

Entonces, un ejemplo muy simple sería hacer la sustitución del proceso de salida de dos comandos echo en wc:

$ wc < <(echo bar;echo foo)
      2       2       8

Así que aquí hacemos que Shell cree un descriptor de archivo para toda la salida que ocurre en el paréntesis y lo redirige como entrada a wc . Como se esperaba, wc recibe esa secuencia de dos comandos de eco, que por sí solo generaría dos líneas, cada una con una palabra, y apropiadamente tenemos 2 palabras, 2 líneas y 6 caracteres más dos líneas nuevas contadas.

Nota al margen : La sustitución del proceso puede denominarse bashism (un comando o estructura que se puede usar en shells avanzados bash, pero no especificado por POSIX), pero se implementó kshantes de la existencia de bash como la página de manual de ksh y esta respuesta sugiere. Conchas como tcshy mkshsin embargo no tienen sustitución proceso. Entonces, ¿cómo podríamos redirigir la salida de múltiples comandos a otro comando sin sustitución del proceso? Agrupación más tuberías!

$ (echo foo;echo bar) | wc
      2       2       8

Efectivamente, esto es lo mismo que el ejemplo anterior. Sin embargo, esto es diferente bajo el capó de la sustitución del proceso, ya que hacemos stdout de todo el subshell y stdin de wc enlazado con la tubería . Por otro lado, la sustitución del proceso hace que un comando lea un descriptor de archivo temporal.

Entonces, si podemos agrupar con tuberías, ¿por qué necesitamos la sustitución de procesos? Porque a veces no podemos usar tuberías. Considere el siguiente ejemplo: comparar las salidas de dos comandos con diff(que necesita dos archivos, y en este caso le damos dos descriptores de archivo)

diff <(ls /bin) <(ls /usr/bin)
Sergiy Kolodyazhnyy
fuente
77
< <se usa cuando uno está recibiendo stdin de una sustitución de proceso . Tal orden podría ser: cmd1 < <(cmd2). Por ejemplo,wc < <(date)
John1024
44
Sí, la sustitución de procesos es compatible con bash, zsh y AT&T ksh {88,93} pero no con pdksh / mksh.
John1024
2
< < no es una cosa en sí misma, en el caso de la sustitución del proceso es solo <seguido de otra cosa con la que comienza<
immibis
1
@muru Hasta donde yo sé, <<<primero fue implementado por el puerto Unix de Plan 9 rc shell y luego adoptado por zsh, bash y ksh93. Entonces no lo llamaría bashism.
jlliagre
3
Otro ejemplo de donde no se puede utilizar tuberías: echo 'foo' | read; echo ${REPLY}será no regresar foo, porque readse inicia en una sub-shell - tuberías inicia un sub-shell. Sin embargo, read < <(echo 'foo'); echo ${REPLY}regresa correctamente foo, porque no hay sub-shell.
Paddy Landau
26

< < es un error de sintaxis:

$ cat < <
bash: syntax error near unexpected token `<'

< <()es la sustitución del proceso ( <()) combinada con la redirección (< ):

Un ejemplo artificial:

$ wc -l < <(grep ntfs /etc/fstab)
4
$ wc -l <(grep ntfs /etc/fstab)
4 /dev/fd/63

Con la sustitución del proceso, la ruta al descriptor de archivo se usa como un nombre de archivo. En caso de que no desee (o no pueda) usar un nombre de archivo directamente, combina la sustitución del proceso con la redirección.

Para ser claros, no hay < <operador.

muru
fuente
obtengo por su respuesta que, <<() más útil que <() ¿verdad?
solfish
1
@solfish <()le da un aspecto de nombre de archivo, por lo que es más útil en general: < <()está reemplazando el stdin donde podría no ser necesario. En wc, este último pasa a ser más útil. Podría ser menos útil en otros lugares
muru
12

< <es un error de sintaxis, probablemente quiere decir command1 < <( command2 )que es una simple redirección de entrada seguida de una sustitución de proceso y es muy similar pero no equivalente a:

command2 | command1

La diferencia asumiendo que está ejecutando bashse command1ejecuta en un subshell en el segundo caso mientras se ejecuta en el shell actual en el primero. Eso significa que las variables establecidas command1no se perderían con la variante de sustitución del proceso.

jlliagre
fuente
11

< <dará un error de sintaxis. El uso adecuado es el siguiente:

Explicando con la ayuda de ejemplos:

Ejemplo para < <():

while read line;do
   echo $line
done< <(ls)

En el ejemplo anterior, la entrada al bucle while vendrá del lscomando que se puede leer línea por línea y echoeditar en el bucle.

<()se usa para la sustitución de procesos. <()Puede encontrar más información y ejemplos en este enlace:

Proceso de sustitución y tubería

fisgonear
fuente