¿Cómo puedo escribir un heredoc en un archivo en Bash script?

Respuestas:

1073

Lea la Guía avanzada de secuencias de comandos Bash Capítulo 19. Aquí Documentos .

Aquí hay un ejemplo que escribirá el contenido en un archivo en /tmp/yourfilehere

cat << EOF > /tmp/yourfilehere
These contents will be written to the file.
        This line is indented.
EOF

Tenga en cuenta que el 'EOF' final (The LimitString) no debe tener ningún espacio en blanco delante de la palabra, porque significa que LimitStringno se reconocerá.

En un script de shell, es posible que desee utilizar la sangría para que el código sea legible, sin embargo, esto puede tener el efecto indeseable de sangrar el texto dentro de su documento aquí. En este caso, use <<-(seguido de un guión) para deshabilitar las pestañas iniciales ( tenga en cuenta que para probar esto necesitará reemplazar el espacio en blanco inicial con un carácter de tabulación , ya que no puedo imprimir los caracteres de tabulación reales aquí).

#!/usr/bin/env bash

if true ; then
    cat <<- EOF > /tmp/yourfilehere
    The leading tab is ignored.
    EOF
fi

Si no desea interpretar variables en el texto, utilice comillas simples:

cat << 'EOF' > /tmp/yourfilehere
The variable $FOO will not be interpreted.
EOF

Para canalizar el heredoc a través de una canalización de comandos:

cat <<'EOF' |  sed 's/a/b/'
foo
bar
baz
EOF

Salida:

foo
bbr
bbz

... o escribir el heredoc en un archivo usando sudo:

cat <<'EOF' |  sed 's/a/b/' | sudo tee /etc/config_file.conf
foo
bar
baz
EOF
Stefan Lasiewski
fuente
11
Ni siquiera necesita Bash, esta característica también se encuentra en los shells Bourne / Korn / POSIX.
Janus Troelsen
55
¿qué pasa <<<, cómo se llaman?
Jürgen Paul
18
@PineappleUndertheSea <<<se llaman 'Here Strings'. Código similar tr a-z A-Z <<< 'one two three'dará como resultado la cadena ONE TWO THREE. Más información en es.wikipedia.org/wiki/Here_document#Here_strings
Stefan Lasiewski
8
El EOF final tampoco debe tener ningún espacio en blanco después . Al menos en bash, esto hace que no sea reconocido como delimitador
carpii
10
Dado que este heredoc en particular está destinado a ser contenido literal, en lugar de contener sustituciones, debería serlo en <<'EOF'lugar de <<EOF.
Charles Duffy
153

En lugar de usar caty redireccionar E / S, podría ser útil usar teeen su lugar:

tee newfile <<EOF
line 1
line 2
line 3
EOF

Es más conciso, además, a diferencia del operador de redireccionamiento, puede combinarse sudosi necesita escribir en archivos con permisos de root.

Livven
fuente
19
Sugeriría agregar > /dev/nullal final de la primera línea para evitar que el contenido del archivo here se muestre en stdout cuando se crea.
Joe Carroll
11
Es cierto, pero su solución me atrajo por su compatibilidad sudo, en lugar de por su brevedad :-)
Joe Carroll
2
¿Cómo usaría este método para agregar a un archivo existente?
MountainX
55
@MountainX Echa un vistazo man tee. Use la -abandera para agregar en lugar de sobrescribir.
Livven
3
Para usar en un script de configuración que a veces necesito supervisar, me gusta más porque imprime el contenido.
Alois Mahdal
59

Nota:

La pregunta (¿cómo escribir un documento aquí (también conocido como heredoc ) en un archivo en un script bash?) Tiene (al menos) 3 dimensiones independientes principales o preguntas secundarias:

  1. ¿Desea sobrescribir un archivo existente, agregarlo a un archivo existente o escribir en un archivo nuevo?
  2. ¿Su usuario u otro usuario (p. Ej. root) Posee el archivo?
  3. ¿Desea escribir el contenido de su heredoc literalmente, o hacer que bash interprete referencias variables dentro de su heredoc?

(Hay otras dimensiones / preguntas secundarias que no considero importantes. ¡Considere editar esta respuesta para agregarlas!) Estas son algunas de las combinaciones más importantes de las dimensiones de la pregunta enumerada anteriormente, con varios identificadores de delimitación diferentes: no hay nada sagrado acerca de EOF, solo asegúrese de que la cadena que utiliza como su identificador de delimitación no se encuentre dentro de su heredoc:

  1. Para sobrescribir un archivo existente (o escribir en un archivo nuevo) de su propiedad, sustituyendo las referencias de variables dentro del heredoc:

    cat << EOF > /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, with the variable contents substituted.
    EOF
    
  2. Para anexar un archivo existente (o escribir en un archivo nuevo) de su propiedad, sustituyendo referencias variables dentro del heredoc:

    cat << FOE >> /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, with the variable contents substituted.
    FOE
    
  3. Para sobrescribir un archivo existente (o escribir en un archivo nuevo) que posea, con el contenido literal del heredoc:

    cat << 'END_OF_FILE' > /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, without the variable contents substituted.
    END_OF_FILE
    
  4. Para agregar un archivo existente (o escribir en un archivo nuevo) que posea, con el contenido literal del heredoc:

    cat << 'eof' >> /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, without the variable contents substituted.
    eof
    
  5. Para sobrescribir un archivo existente (o escribir en un archivo nuevo) propiedad de root, sustituyendo referencias variables dentro del heredoc:

    cat << until_it_ends | sudo tee /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, with the variable contents substituted.
    until_it_ends
    
  6. Para agregar un archivo existente (o escribir en un archivo nuevo) propiedad de user = foo, con el contenido literal del heredoc:

    cat << 'Screw_you_Foo' | sudo -u foo tee -a /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, without the variable contents substituted.
    Screw_you_Foo
    
TomRoche
fuente
# 6 es el mejor. Pero, ¿cómo sobrescribe el contenido del archivo existente con el n. ° 6?
Aleksandr Makov
2
@Aleksandr Makov: ¿cómo se sobrescribe el contenido del archivo existente con el n. ° 6? Omita el -a== --append; es decir, tee -a-> tee. Mira info tee(lo citaría aquí, pero el marcado de comentarios es demasiado limitado.
TomRoche
1
¿Hay algún beneficio para el n. ° 6 que usa gato y tuberías para el tee en lugar de sudo tee /path/to/your/file << 'Screw_you_Foo'?
codemonkee
¿Por qué en FOElugar de EOFen el ejemplo adjunto?
Becko
2
@becko: solo para ilustrar que la etiqueta es solo una etiqueta. Tenga en cuenta que usé una etiqueta diferente en cada ejemplo.
TomRoche
41

Para construir sobre la respuesta de @ Livven , aquí hay algunas combinaciones útiles.

  1. sustitución de variables, pestaña inicial retenida, sobrescribir archivo, eco a stdout

    tee /path/to/file <<EOF
    ${variable}
    EOF
    
  2. sin sustitución de variables , pestaña inicial retenida, sobrescribir archivo, eco a stdout

    tee /path/to/file <<'EOF'
    ${variable}
    EOF
    
  3. sustitución de variables, pestaña inicial eliminada , sobrescribir archivo, eco a stdout

    tee /path/to/file <<-EOF
        ${variable}
    EOF
    
  4. sustitución de variables, pestaña inicial retenida, agregar al archivo , eco a stdout

    tee -a /path/to/file <<EOF
    ${variable}
    EOF
    
  5. sustitución de variables, pestaña inicial retenida, sobrescribir archivo, sin eco a stdout

    tee /path/to/file <<EOF >/dev/null
    ${variable}
    EOF
    
  6. lo anterior se puede combinar con sudo, así

    sudo -u USER tee /path/to/file <<EOF
    ${variable}
    EOF
    
go2null
fuente
16

Cuando se requieren permisos de root

Cuando se requieren permisos de root para el archivo de destino, use en |sudo teelugar de >:

cat << 'EOF' |sudo tee /tmp/yourprotectedfilehere
The variable $FOO will *not* be interpreted.
EOF
Serge Stroobandt
fuente
¿Es posible pasar variables a los documentos aquí? ¿Cómo podría obtenerlo para que se interpretara $ FOO?
user1527227
@ user1527227 Como dice el archivo de ejemplo aquí: La variable $ FOO no se interpretará.
Serge Stroobandt
A continuación , he intentado combinar y organizar esta respuesta con la de Stefan Lasiewski .
TomRoche
3
@ user1527227 Simplemente no incluya EOF entre comillas simples. Entonces se interpretará $ FOO.
imnd_neel 01 de
11

Para las personas futuras que puedan tener este problema, el siguiente formato funcionó:

(cat <<- _EOF_
        LogFile /var/log/clamd.log
        LogTime yes
        DatabaseDirectory /var/lib/clamav
        LocalSocket /tmp/clamd.socket
        TCPAddr 127.0.0.1
        SelfCheck 1020
        ScanPDF yes
        _EOF_
) > /etc/clamd.conf
Joshua Enfield
fuente
66
No necesito los paréntesis: cat << END > afileseguido del heredoc funciona perfectamente bien.
Glenn Jackman
Gracias, esto realmente resolvió otro problema que encontré. Después de unos pocos documentos aquí hubo algunos problemas. Creo que tenía que ver con los padres, ya que con el consejo anterior lo solucionó.
Joshua Enfield
2
Esto no funcionara. La redirección de salida debe estar al final de la línea que comienza catcomo se muestra en la respuesta aceptada.
Pausado hasta nuevo aviso.
2
@DennisWilliamson Funciona, para eso están los padres. Todo se catejecuta dentro de una subshell, y toda la salida de la subshell se redirige al archivo
Izkata
2
@Izkata: Si miras el historial de edición de esta respuesta, los paréntesis se eliminaron antes de hacer mi comentario y luego se agregaron. se aplica el comentario de Glenn Jackman (y mi).
Pausado hasta nuevo aviso.
3

Como ejemplo, puedes usarlo:

Primero (haciendo conexión ssh):

while read pass port user ip files directs; do
    sshpass -p$pass scp -o 'StrictHostKeyChecking no' -P $port $files $user@$ip:$directs
done <<____HERE
    PASS    PORT    USER    IP    FILES    DIRECTS
      .      .       .       .      .         .
      .      .       .       .      .         .
      .      .       .       .      .         .
    PASS    PORT    USER    IP    FILES    DIRECTS
____HERE

Segundo (ejecutar comandos):

while read pass port user ip; do
    sshpass -p$pass ssh -p $port $user@$ip <<ENDSSH1
    COMMAND 1
    .
    .
    .
    COMMAND n
ENDSSH1
done <<____HERE
    PASS    PORT    USER    IP
      .      .       .       .
      .      .       .       .
      .      .       .       .
    PASS    PORT    USER    IP    
____HERE

Tercero (ejecutar comandos):

Script=$'
#Your commands
'

while read pass port user ip; do
    sshpass -p$pass ssh -o 'StrictHostKeyChecking no' -p $port $user@$ip "$Script"

done <<___HERE
PASS    PORT    USER    IP
  .      .       .       .
  .      .       .       .
  .      .       .       .
PASS    PORT    USER    IP  
___HERE

Adelante (usando variables):

while read pass port user ip fileoutput; do
    sshpass -p$pass ssh -o 'StrictHostKeyChecking no' -p $port $user@$ip fileinput=$fileinput 'bash -s'<<ENDSSH1
    #Your command > $fileinput
    #Your command > $fileinput
ENDSSH1
done <<____HERE
    PASS    PORT    USER    IP      FILE-OUTPUT
      .      .       .       .          .
      .      .       .       .          .
      .      .       .       .          .
    PASS    PORT    USER    IP      FILE-OUTPUT
____HERE
MLSC
fuente
-1

Me gusta este método de concisión, legibilidad y presentación en un guión sangrado:

<<-End_of_file >file
       foo bar
End_of_file

¿Dónde →       está un tabpersonaje?

dan
fuente