¿Cómo cat << EOF >> un archivo que contiene código?

101

Quiero imprimir código en un archivo usando cat <<EOF >>:

cat <<EOF >> brightup.sh
!/bin/bash
curr=`cat /sys/class/backlight/intel_backlight/actual_brightness`
if [ $curr -lt 4477 ]; then
   curr=$((curr+406));
   echo $curr  > /sys/class/backlight/intel_backlight/brightness;
fi
EOF

pero cuando verifico la salida del archivo, obtengo esto:

!/bin/bash
curr=1634
if [  -lt 4477 ]; then
   curr=406;
   echo   > /sys/class/backlight/intel_backlight/brightness;
fi

Intenté poner comillas simples, pero la salida también lleva las comillas simples. ¿Cómo puedo evitar este problema?

UberNate
fuente
2
También deberías arreglar el shebang. La primera línea debe ser literalmente #!/bin/bashy nada más: #!es lo que la convierte en una línea de shebang válida, y lo que viene después es el camino hacia el intérprete.
tripleee
1
ver man bashy buscar Here Documents. Todos los detalles ahí.
Hong
1
Como comentario tardío, la sintaxis moderna para la sustitución de procesos es en $(command)lugar de `command`. Para obtener el contenido de un archivo, Bash tiene$(<file)
tripleee

Respuestas:

159

Solo necesita un cambio mínimo; entre comillas simples el delimitador aquí-documento después <<.

cat <<'EOF' >> brightup.sh

o equivalentemente barra invertida-escape:

cat <<\EOF >>brightup.sh

Sin citar, el documento aquí se someterá a una sustitución de variables, se evaluarán las comillas inversas, etc., como descubrió.

Si necesita expandir algunos valores, pero no todos, debe escapar individualmente de los que desea evitar.

cat <<EOF >>brightup.sh
#!/bin/sh
# Created on $(date # : <<-- this will be evaluated before cat;)
echo "\$HOME will not be evaluated because it is backslash-escaped"
EOF

Producirá

#!/bin/sh
# Created on Fri Feb 16 11:00:18 UTC 2018
echo "$HOME will not be evaluated because it is backslash-escaped"

Como sugirió @fedorqui , aquí está la sección relevante de man bash:

Aquí Documentos

Este tipo de redireccionamiento le indica al shell que lea la entrada de la fuente actual hasta que se vea una línea que contiene solo un delimitador (sin espacios en blanco al final). Todas las líneas leídas hasta ese punto se utilizan como entrada estándar para un comando.

El formato de aquí-documentos es:

      <<[-]word
              here-document
      delimiter

No se realiza expansión de parámetro, sustitución de comando, expansión aritmética o expansión de nombre de ruta en la palabra. Si se citan caracteres en word, el delimitador es el resultado de la eliminación de comillas en word 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 el último caso, la secuencia de caracteres \ se ignora y debe usarse \ para citar los caracteres \, $ y `.

triples
fuente
1
Veo que marcó ¿Cómo evitar que heredoc expanda variables? como un duplicado de este. Sin objeciones, solo que incluiría la referencia de documentos de Bash, como hice en la mía (que con solo 13k visitas obtuvo casi 100 repeticiones, por lo que parece ser bastante útil).
fedorqui 'SO deja de dañar'
@fedorqui ¿Quizás quieras portar tu respuesta a esta pregunta? O podemos cambiar la marca duplicada para que sea al revés; Solo miré el puntaje de la pregunta, no mucho el puntaje de la respuesta.
tripleee
Mmm, ¿qué hay de fusionarlos , teniendo esta como la pregunta objetivo? La pregunta duplicada tiene un buen título, solo que es demasiado larga. Sin embargo, de alguna manera llamó bastante la atención últimamente (estoy recibiendo algunos votos positivos por mes ).
fedorqui 'SO deja de dañar'
@fedorqui Me gusta el concepto de fusión pero nunca lo había visto suceder en la práctica; si entiendo la situación correctamente, los mods simplemente no pueden manejar fusiones no triviales porque las molestias y complejidades superan con creces los beneficios. Lo que he hecho una o dos veces es eliminar una respuesta útil y votada a favor y volver a publicarla con una pregunta diferente, aunque no puedo recomendar esta solución.
tripleee
Es difícil saber cuándo vale la pena hacerlo. Lo hice en un sitio que modifiqué cuando se publicó una buena pregunta sin darme cuenta de que había otra buena de antes, ambas con buenas respuestas. Eliminar mi respuesta de puntuación de más de 90 no parece un buen plan, ya que dejaría esa pregunta huérfana.
fedorqui 'SO deja de dañar'
20

O, utilizando sus marcadores EOF, debe citar el marcador inicial para que no se realice la expansión:

#-----v---v------
cat <<'EOF' >> brightup.sh
#!/bin/bash
curr=`cat /sys/class/backlight/intel_backlight/actual_brightness`
if [ $curr -lt 4477 ]; then
   curr=$((curr+406));
   echo $curr  > /sys/class/backlight/intel_backlight/brightness;
fi
EOF

IHTH

shellter
fuente
1
Editaría tu publicación, pero solo para estar seguro, ¿no debería ser #! / Bin / bash y no! / Bin / bash?
Matthew Hoggan
@MatthewHoggan: ¡Sí, tienes razón! Gracias por captar eso. Lo estoy arreglando ahora.
shellter
16

Esto debería funcionar, solo lo probé y funcionó como se esperaba: no hubo expansión, sustitución o lo que sea.

cat <<< '
#!/bin/bash
curr=`cat /sys/class/backlight/intel_backlight/actual_brightness`
if [ $curr -lt 4477 ]; then
  curr=$((curr+406));
  echo $curr  > /sys/class/backlight/intel_backlight/brightness;
fi' > file # use overwrite mode so that you don't keep on appending the same script to that file over and over again, unless that's what you want. 

Usar lo siguiente también funciona.

cat <<< ' > file
 ... code ...'

Además, vale la pena señalar que cuando se utilizan heredocs, tales como << EOF, se lleva a cabo la sustitución y expansión de variables y similares. Entonces, haciendo algo como esto:

cat << EOF > file
cd "$HOME"
echo "$PWD" # echo the current path
EOF

siempre resultará en la expansión de las variables $HOMEy $PWD. Entonces, si su directorio de inicio es /home/foobary la ruta actual es /home/foobar/bin, filese verá así:

cd "/home/foobar"
echo "/home/foobar/bin"

en lugar de lo esperado:

cd "$HOME"
echo "$PWD"
Alexej Magura
fuente
2
Una cadena aquí entre comillas simples obviamente no puede contener comillas simples, lo que puede ser un problema prohibitivo. Aquí los documentos son la única solución sana si tiene un script que debe contener comillas simples y dobles, aunque, por supuesto, ese no es el caso con el ejemplo simple del OP. Además, tangencialmente, las cadenas de <<<aquí solo están disponibles a partir de Bash 3, y no son portátiles a otros shells.
tripleee
<<<también está disponible en Zsh
Alexej Magura
3
hoy aprendí que puede hacer que el nombre del archivo siga inmediatamente a la apertura del heredoc. ¡Gracias @AlexejMagura!
chaseadamsio