¿Cómo funciona "cat << EOF" en bash?

632

Necesitaba escribir un script para ingresar entradas de varias líneas a un programa ( psql).

Después de buscar un poco en Google, encontré que la siguiente sintaxis funciona:

cat << EOF | psql ---params
BEGIN;

`pg_dump ----something`

update table .... statement ...;

END;
EOF

Esto construye correctamente la cadena de varias líneas (desde BEGIN;hasta END;, inclusive) y la canaliza como entrada para psql.

Pero no tengo idea de cómo / por qué funciona, ¿alguien puede explicarlo?

Me refiero principalmente a cat << EOF, sé >salidas a un archivo, se >>agrega a un archivo, <lee la entrada del archivo.

¿Qué hace <<exactamente?

¿Y hay una página de manual para ello?

Hasen
fuente
26
Probablemente sea un uso inútil de cat. Intenta psql ... << EOF ... ver también "cadenas aquí". mywiki.wooledge.org/BashGuide/InputAndOutput?#Here_Strings
hasta nuevo aviso.
1
Me sorprende que funcione con gato pero no con eco. cat debería esperar un nombre de archivo como stdin, no una cadena de caracteres. psql << EOF suena lógico, pero no de otra manera. Funciona con gato pero no con eco. Comportamiento extraño. ¿Alguna pista sobre eso?
Alex
Respondiéndome a mí mismo: cat sin parámetros se ejecuta y se replica en la salida, lo que se envía a través de input (stdin), por lo tanto, usa su salida para llenar el archivo a través de> De hecho, un nombre de archivo leído como parámetro no es una secuencia estándar.
Alex
@Alex echo solo imprime sus argumentos de la línea de comandos mientras catlee stding (cuando se canaliza a él) o lee un archivo que corresponde a sus argumentos de la línea de comandos
The-null-Pointer-

Respuestas:

519

Esto se llama formato heredoc para proporcionar una cadena en stdin. Ver https://en.wikipedia.org/wiki/Here_document#Unix_shells para más detalles.


De man bash:

Aquí documentos

Este tipo de redirección le indica al shell que lea la entrada de la fuente actual hasta que se vea una línea que contiene solo una palabra (sin espacios en blanco).

Todas las líneas leídas hasta ese punto se utilizan como entrada estándar para un comando.

El formato de los documentos aquí es:

          <<[-]word
                  here-document
          delimiter

No se realiza expansión de parámetros, sustitución de comandos, expansión aritmética o expansión de nombre de ruta en Word . Si se citan caracteres en palabras , el delimitador es el resultado de la eliminación de comillas en palabras , y las líneas en el documento aquí no se expanden. Si la palabra no está entre comillas, todas las líneas del documento aquí están sujetas a expansión de parámetros, sustitución de comandos y expansión aritmética. En este último caso, la secuencia de caracteres \<newline>es ignorado, y \se debe utilizar para proteger los caracteres \, $y `.

Si el operador de redirección es <<-, entonces todos los caracteres de tabulación iniciales se eliminan de las líneas de entrada y la línea que contiene el delimitador . Esto permite que los documentos aquí dentro de los scripts de shell sean sangrados de forma natural.

kennytm
fuente
12
Estaba teniendo dificultades para deshabilitar la expansión de variables / parámetros. ¡Todo lo que necesitaba hacer era usar "comillas dobles" y eso lo solucionó! Gracias por la info!
Xeoncross
11
Con respecto a esto, <<-tenga en cuenta que solo se eliminan los caracteres de tabulación iniciales, no los caracteres de tabulación suave. Este es uno de esos casos raros cuando realmente necesita el carácter de tabulación. Si el resto de su documento usa pestañas suaves, asegúrese de mostrar caracteres invisibles y (por ejemplo) copie y pegue un carácter de pestaña. Si lo hace bien, su resaltado de sintaxis debería captar correctamente el delimitador final.
trkoch
1
No veo cómo esta respuesta es más útil que las siguientes. Simplemente regurgita información que se puede encontrar en otros lugares (que probablemente ya se haya verificado)
BrDaHa
@BrDaHa, tal vez no lo sea. ¿Porque la pregunta? por los votos a favor? que era el único que desde hace varios años. se ve al comparar fechas.
Alexei Martianov
500

La cat <<EOFsintaxis es muy útil cuando se trabaja con texto de varias líneas en Bash, por ejemplo. al asignar cadenas de varias líneas a una variable de shell, archivo o tubería.

Ejemplos de cat <<EOFuso de sintaxis en Bash:

1. Asigne una cadena de varias líneas a una variable de shell

$ sql=$(cat <<EOF
SELECT foo, bar FROM db
WHERE foo='baz'
EOF
)

La $sqlvariable ahora también contiene los caracteres de nueva línea. Puedes verificar con echo -e "$sql".

2. Pase una cadena de varias líneas a un archivo en Bash

$ cat <<EOF > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
EOF

El print.sharchivo ahora contiene:

#!/bin/bash
echo $PWD
echo /home/user

3. Pase una cadena de varias líneas a una tubería en Bash

$ cat <<EOF | grep 'b' | tee b.txt
foo
bar
baz
EOF

El b.txtarchivo contiene bary bazlíneas. Se imprime la misma salida stdout.

Vojtech Vitek
fuente
1. 1 y 3 se pueden hacer sin gato; 2. El ejemplo 1 se puede hacer con una cadena simple de varias líneas
Daniel Alder
270

En su caso, "EOF" se conoce como "Etiqueta aquí". Básicamente <<Herele dice al shell que vas a ingresar una cadena multilínea hasta la "etiqueta" Here. Puede nombrar esta etiqueta como desee, a menudo EOFo STOP.

Algunas reglas sobre las etiquetas Aquí:

  1. La etiqueta puede ser cualquier cadena, mayúscula o minúscula, aunque la mayoría de las personas usan mayúsculas por convención.
  2. La etiqueta no se considerará una etiqueta Aquí si hay otras palabras en esa línea. En este caso, simplemente se considerará parte de la cadena. La etiqueta debe estar sola en una línea separada, para ser considerada una etiqueta.
  3. La etiqueta no debe tener espacios iniciales o finales en esa línea para ser considerada una etiqueta. De lo contrario, se considerará como parte de la cadena.

ejemplo:

$ cat >> test <<HERE
> Hello world HERE <-- Not by itself on a separate line -> not considered end of string
> This is a test
>  HERE <-- Leading space, so not considered end of string
> and a new line
> HERE <-- Now we have the end of the string
Edelans
fuente
31
esta es la mejor respuesta real ... definir ambos y establecer claramente el propósito principal del uso en lugar de teoría relacionada ... lo cual es importante pero no es necesario ... gracias - super útil
oemb1905
55
@edelans debe agregar que cuando <<-se usa la pestaña inicial no evitará que se reconozca la etiqueta
The-null-Pointer-
1
tu respuesta me hizo clic en "vas a ingresar una cadena multilínea"
Cálculo
79

POSIX 7

kennytm citado man bash, pero la mayor parte de eso también es POSIX 7: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04 :

Los operadores de redirección "<<" y "<< -" permiten la redirección de líneas contenidas en un archivo de entrada de shell, conocido como "documento aquí", a la entrada de un comando.

El documento aquí se tratará como una sola palabra que comienza después de la siguiente y continúa hasta que haya una línea que contenga solo el delimitador y a, sin caracteres intermedios. Entonces comienza el siguiente documento aquí, si hay uno. El formato es el siguiente:

[n]<<word
    here-document
delimiter

donde el n opcional representa el número de descriptor de archivo. Si se omite el número, el documento aquí se refiere a la entrada estándar (descriptor de archivo 0).

Si se cita cualquier carácter en la palabra, el delimitador se formará mediante la eliminación de la cita en la palabra, y las líneas del documento aquí no se expandirán. De lo contrario, el delimitador será la palabra misma.

Si no se citan caracteres en palabras, todas las líneas del documento aquí se expandirán para la expansión de parámetros, sustitución de comandos y expansión aritmética. En este caso, la entrada se comporta como comillas dobles internas (consulte Comillas dobles). Sin embargo, el carácter de comillas dobles ('"') no se tratará especialmente dentro de un documento aquí, excepto cuando la comilla doble aparezca dentro de" $ () "," `` "o" $ {} ".

Si el símbolo de redireccionamiento es "<< -", todos los <tab>caracteres iniciales se eliminarán de las líneas de entrada y de la línea que contiene el delimitador final. Si se especifica más de un operador "<<" o "<< -" en una línea, el documento adjunto asociado con el primer operador será proporcionado primero por la aplicación y el shell deberá leerlo primero.

Cuando un documento aquí se lee desde un dispositivo terminal y el shell es interactivo, debe escribir los contenidos de la variable PS2, procesados ​​como se describe en Variables de Shell, a error estándar antes de leer cada línea de entrada hasta que se haya reconocido el delimitador.

Ejemplos

Algunos ejemplos aún no se dan.

Las cotizaciones evitan la expansión de parámetros

Sin comillas:

a=0
cat <<EOF
$a
EOF

Salida:

0

Con comillas:

a=0
cat <<'EOF'
$a
EOF

o (feo pero válido):

a=0
cat <<E"O"F
$a
EOF

Salidas:

$a

El guión elimina las pestañas iniciales

Sin guión:

cat <<EOF
<tab>a
EOF

donde <tab>es una pestaña literal y se puede insertar conCtrl + V <tab>

Salida:

<tab>a

Con guión:

cat <<-EOF
<tab>a
<tab>EOF

Salida:

a

Esto existe, por supuesto, para que pueda sangrar catel código que lo rodea, que es más fácil de leer y mantener. P.ej:

if true; then
    cat <<-EOF
    a
    EOF
fi

Desafortunadamente, esto no funciona para los caracteres de espacio: POSIX favoreció la tabsangría aquí. Yikes

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
En su último ejemplo discutiendo <<-y <tab>a, debe tenerse en cuenta que el propósito era permitir la sangría normal del código dentro del script al tiempo que permite que el texto heredoc presentado al proceso de recepción comience en la columna 0. Es una característica poco común y un poco más contexto puede evitar una gran cantidad de rascarse la cabeza ...
David C. Rankin
1
¿Cómo debo escapar de la experiencia si parte del contenido entre mis etiquetas EOF necesita expandirse y otras no?
Jeanmichel Cote
2
... solo use la barra invertida frente al$
Jeanmichel Cote el
@JeanmichelCote No veo una mejor opción :-) Con cadenas regulares también puede considerar mezclar citas como "$a"'$b'"$c", pero no hay un análogo aquí AFAIK.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
25

Usando tee en lugar de gato

No exactamente como respuesta a la pregunta original, pero quería compartir esto de todos modos: tuve la necesidad de crear un archivo de configuración en un directorio que requería derechos de root.

Lo siguiente no funciona para ese caso:

$ sudo cat <<EOF >/etc/somedir/foo.conf
# my config file
foo=bar
EOF

porque la redirección se maneja fuera del contexto sudo.

Terminé usando esto en su lugar:

$ sudo tee <<EOF /etc/somedir/foo.conf >/dev/null
# my config file
foo=bar
EOF
Andreas Maier
fuente
en su caso use sudo bash -c 'cat << EOF> /etc/somedir/foo.conf # my config file foo = bar EOF'
likewhoa
5

Una pequeña extensión a las respuestas anteriores. El seguimiento >dirige la entrada al archivo, sobrescribiendo el contenido existente. Sin embargo, un uso particularmente conveniente es la flecha doble >>que se agrega, agregando su nuevo contenido al final del archivo, como en:

cat <<EOF >> /etc/fstab
data_server:/var/sharedServer/authority/cert /var/sharedFolder/sometin/authority/cert nfs
data_server:/var/sharedServer/cert   /var/sharedFolder/sometin/vsdc/cert nfs
EOF

Esto extiende su fstabsin tener que preocuparse por modificar accidentalmente ninguno de sus contenidos.

Zurdo G Balogh
fuente
1

Esto no es necesariamente una respuesta a la pregunta original, sino un intercambio de algunos resultados de mis propias pruebas. Esta:

<<test > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
test

producirá el mismo archivo que:

cat <<test > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
test

Entonces, no veo el punto de usar el comando cat.


fuente
2
cual caparazón? Probé con bash 4.4 en Ubuntu 18.04 y bash 3.2 en OSX. Ambos crearon un archivo vacío cuando solo lo usaban <<testsin cat <<test.
wisbucky
Esto funcionó para mí en LInux Mint 19 Tara en zsh
Geoff Langenderfer
0

Vale la pena señalar que aquí los documentos también funcionan en bash loops. Este ejemplo muestra cómo obtener la lista de columnas de la tabla:

export postgres_db_name='my_db'
export table_name='my_table_name'

# start copy 
while read -r c; do test -z "$c" || echo $table_name.$c , ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
SELECT column_name
FROM information_schema.columns
WHERE 1=1
AND table_schema = 'public'
AND table_name   =:'table_name'  ;
EOF
)
# stop copy , now paste straight into the bash shell ...

output: 
my_table_name.guid ,
my_table_name.id ,
my_table_name.level ,
my_table_name.seq ,

o incluso sin la nueva línea

while read -r c; do test -z "$c" || echo $table_name.$c , | perl -ne 
's/\n//gm;print' ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
 SELECT column_name
 FROM information_schema.columns
 WHERE 1=1
 AND table_schema = 'public'
 AND table_name   =:'table_name'  ;
 EOF
 )

 # output: daily_issues.guid ,daily_issues.id ,daily_issues.level ,daily_issues.seq ,daily_issues.prio ,daily_issues.weight ,daily_issues.status ,daily_issues.category ,daily_issues.name ,daily_issues.description ,daily_issues.type ,daily_issues.owner
Yordan Georgiev
fuente