¿Cómo asignar un valor heredoc a una variable en Bash?

374

Tengo esta cadena de varias líneas (citas incluidas):

abc'asdf"
$(dont-execute-this)
foo"bar"''

¿Cómo lo asignaría a una variable usando un heredoc en Bash?

Necesito preservar nuevas líneas.

No quiero escapar de los caracteres en la cadena, eso sería molesto ...

Neil
fuente
@JohnM - Acabo de intentar una asignación heredoc con comillas simples 'EOF', con ` in the content: if the second line has saltos de línea escapados con el comando cd`, vuelvo: " .sh: línea X: cd: comando no encontrado "; pero si hago comillas dobles "EOF"; entonces las variables bash ${A}no se conservan como cadenas (se expanden); pero luego, los saltos de línea se conservan, y no tengo ningún problema al ejecutar un comando cden la segunda línea ( y tanto 'EOF' como "EOF" parecen funcionar bien también con eval, para ejecutar un conjunto de comandos almacenados en una variable de cadena ). ¡Salud!
sdaau
1
... y para agregar a mi comentario anterior: bash comenta "#" en la "EOF"variable de doble qouted , si se llama vía eval $VAR, hará que se comente todo el resto del script, ya que aquí $ VAR se verá como una sola línea ; para poder usar #comentarios bash en un script multilínea, la comilla doble también es variable en el eval call: eval "$ VAR" `.
sdaau
@sdaau: Tuve problemas con evalestos métodos, pero no lo rastreé porque era parte de un paquete que evalcontiene algunas variables definidas en su archivo de configuración. Mensaje de error fue: /usr/lib/network/network: eval: line 153: syntax error: unexpected end of file. Acabo de cambiar a otra solución.
Golar Ramblar

Respuestas:

515

Puede evitar un uso inútil de cat y manejar mejor las citas no coincidentes con esto:

$ read -r -d '' VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

Si no cita la variable cuando la repite, se pierden las nuevas líneas. Citarlo los preserva:

$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

Si desea utilizar la sangría para facilitar la lectura en el código fuente, use un guión después de la menor thans. La sangría debe hacerse usando solo pestañas (sin espacios).

$ read -r -d '' VAR <<-'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
    EOF
$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

Si, en cambio, desea conservar las pestañas en el contenido de la variable resultante, debe eliminar la pestaña de IFS. El marcador de terminal para el documento aquí (EOF ) no debe tener sangría.

$ IFS='' read -r -d '' VAR <<'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
EOF
$ echo "$VAR"
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''

Las pestañas se pueden insertar en la línea de comando presionando Ctrl- V Tab. Si está utilizando un editor, dependiendo de cuál, eso también puede funcionar o puede que tenga que desactivar la función que convierte automáticamente las pestañas en espacios.

Pausado hasta nuevo aviso.
fuente
117
Creo que vale la pena mencionar que si tiene set -o errexit(aka set -e) en su script y lo usa, entonces terminará su script porque readdevuelve un código de retorno distinto de cero cuando alcanza EOF.
Mark Byers
14
@ MarkByers: Esa es una de las razones por las que nunca uso set -ey siempre recomiendo su uso. En su lugar, es mejor usar un manejo de errores adecuado. trapes tu amigo. Otros amigos: elsey ||entre otros.
Pausado hasta nuevo aviso.
77
¿ catRealmente vale la pena evitarlo en tal caso? Asignar un heredoc a una variable con cates un idioma muy conocido. De alguna manera, el uso readofusca cosas por pequeños beneficios en mi humilde opinión.
Gregory Pakosz
66
@ulidtko Eso es porque no tienes un espacio entre dy la cadena vacía; bashse derrumba -rd''a simplemente -rdantes de readver sus argumentos, por lo que VARse trata como el argumento para -d.
chepner 01 de
66
En este formato, readregresará con un código de salida distinto de cero. Esto hace que este método sea menos que ideal en un script con comprobación de errores habilitada (p set -e. Ej .).
Suizo
246

Use $ () para asignar la salida de cata su variable de esta manera:

VAR=$(cat <<'END_HEREDOC'
abc'asdf"
$(dont-execute-this)
foo"bar"''
END_HEREDOC
)

# this will echo variable with new lines intact
echo "$VAR"
# this will echo variable without new lines (changed to space character)
echo $VAR

Asegurarse de delimitar el inicio de END_HEREDOC con comillas simples.

Tenga en cuenta que terminando aquí delimitador END_HEREDOC debe estar solo en la línea (por lo tanto, el paréntesis final está en la línea siguiente).

Gracias a @ephemientpor la respuesta.

Neil
fuente
1
En realidad, este deja pasar citas en algunas circunstancias. Me las arreglé para hacer esto en Perl fácilmente ...
Neil
32
+1. Esta es la solución más legible, al menos para mis ojos. Deja el nombre de la variable en el extremo izquierdo de la página, en lugar de incrustarlo en el comando de lectura.
Clayton Stanley
12
PSA: recuerde que la variable debe ser citada para preservar las nuevas líneas. echo "$VAR"en lugar de echo $VAR.
sevko
77
Esto es bueno con ashy OpenWRT donde readno es compatible -d.
David Ehrmann el
3
Por razones que no puedo entender, esto falla con un error "EOF inesperado" si tiene un backtick no emparejado en el heredoc.
Radon Rosborough
79

Esta es una variación del método Dennis, se ve más elegante en los guiones.

definición de función:

define(){ IFS='\n' read -r -d '' ${1} || true; }

uso:

define VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

echo "$VAR"

disfrutar

ps hizo un versión de 'lectura de bucle' para shells que no son compatibles read -d. debe trabajar con set -euy acentos abiertos no apareados , pero no han sido evaluados muy bien:

define(){ o=; while IFS="\n" read -r a; do o="$o$a"'
'; done; eval "$1=\$o"; }
ttt
fuente
1
Esto parece funcionar solo superficialmente. La función de definición devolverá un estado de 1, y no estoy muy seguro de lo que debe corregirse.
fny
1
Esto también es superior a la respuesta aceptada, ya que se puede modificar para admitir POSIX sh además de bash (un readbucle en la función, para evitar el -d ''bashism necesario para preservar nuevas líneas).
ELLIOTTCABLE
A diferencia de la opción cat-in-a-subshell, esto funciona con backticks no emparejados en el heredoc. ¡Gracias!
Radon Rosborough
Esta solución funciona con set -eset, mientras que la respuesta seleccionada no. Parece ser debido ahttp://unix.stackexchange.com/a/265151/20650
ffledgling
2
@fny ps el estado de retorno ha sido corregido desde hace mucho tiempo
ttt
36
VAR=<<END
abc
END

no funciona porque está redirigiendo stdin a algo que no le importa, es decir, la asignación

export A=`cat <<END
sdfsdf
sdfsdf
sdfsfds
END
` ; echo $A

funciona, pero hay un tic de respaldo que puede evitar que lo uses. Además, realmente debe evitar el uso de backticks, es mejor usar la notación de sustitución de comandos $(..).

export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END
) ; echo $A
l0st3d
fuente
He actualizado mi pregunta para incluir $ (ejecutable). Además, ¿cómo preservar las nuevas líneas?
Neil
2
@ l0st3d: Tan cerca ... Úselo en su $(cat <<'END'lugar. @Neil: La última línea nueva no será parte de la variable, pero el resto se conservará.
ephemient
1
No parece que se hayan conservado nuevas líneas. Al ejecutar el ejemplo anterior, veo: "sdfsdf sdfsdf sdfsfds" ... ¡ah! ¡Pero al escribir echo "$A"(es decir, poner $ A entre comillas dobles) y usted ve las nuevas líneas!
Darren Cook,
1
@Darren: ¡ajá! Había notado el problema de las nuevas líneas, y el uso de las comillas alrededor de la variable de salida soluciona el problema. ¡gracias!
javadba
1
Curiosamente, debido a la peculiaridad del primer ejemplo, en caso de necesidad se puede usar para bloques de comentarios improvisados como este: REM=<< 'REM' ... comment block goes here ... REM. O de forma más compacta, : << 'REM' .... Donde "REM" podría ser algo así como "NOTAS" o "SCRATCHPAD", etc.
Beejor
34

Todavía no hay una solución que conserve nuevas líneas.

Esto no es cierto: probablemente te esté engañando el comportamiento del eco:

echo $VAR # strips newlines

echo "$VAR" # preserves newlines

patspam
fuente
55
Realmente este es el comportamiento de cómo funciona la cita de una variable. Sin comillas, se insertará como diferentes parámetros, el espacio deliminated, mientras que con citas todo el contenido de variables serán tratados como un argumento
Czipperz
11

Ramificando la respuesta de Neil , a menudo no necesita una var en absoluto, puede usar una función de la misma manera que una variable y es mucho más fácil de leer que las readsoluciones en línea o basadas.

$ complex_message() {
  cat <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF
}

$ echo "This is a $(complex_message)"
This is a abc'asdf"
$(dont-execute-this)
foo"bar"''
dimo414
fuente
9

Una matriz es una variable, por lo que en ese caso funcionará mapfile

mapfile y <<z
abc'asdf"
$(dont-execute-this)
foo"bar"''
z

Entonces puedes imprimir así

printf %s "${y[@]}"
Steven Penny
fuente
3

asignar un valor heredoc a una variable

VAR="$(cat <<'VAREOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
VAREOF
)"

usado como argumento de un comando

echo "$(cat <<'SQLEOF'
xxx''xxx'xxx'xx  123123    123123
abc'asdf"
$(dont-execute-this)
foo"bar"''
SQLEOF
)"
hombre de bronce
fuente
Cuando probé el primer método, parece que no hay terminadores de línea entre las líneas. ¿Debe haber algún tipo de configuración en mi máquina Linux?
Kemin Zhou
Esto probablemente significa que cuando estaba haciendo eco de su variable, no puso comillas a su alrededor ... Pruébelo así:echo "$VAR"
Brad Parks
1

Me encontré teniendo que leer una cadena con NULL, así que aquí hay una solución que leerá todo lo que le arrojes. Aunque si en realidad estás lidiando con NULL, tendrás que lidiar con eso en el nivel hexadecimal.

$ cat> read.dd.sh

read.dd() {
     buf= 
     while read; do
        buf+=$REPLY
     done < <( dd bs=1 2>/dev/null | xxd -p )

     printf -v REPLY '%b' $( sed 's/../ \\\x&/g' <<< $buf )
}

Prueba:

$ . read.dd.sh
$ read.dd < read.dd.sh
$ echo -n "$REPLY" > read.dd.sh.copy
$ diff read.dd.sh read.dd.sh.copy || echo "File are different"
$ 

Ejemplo de HEREDOC (con ^ J, ^ M, ^ I):

$ read.dd <<'HEREDOC'
>       (TAB)
>       (SPACES)
(^J)^M(^M)
> DONE
>
> HEREDOC

$ declare -p REPLY
declare -- REPLY="  (TAB)
      (SPACES)
(^M)
DONE

"

$ declare -p REPLY | xxd
0000000: 6465 636c 6172 6520 2d2d 2052 4550 4c59  declare -- REPLY
0000010: 3d22 0928 5441 4229 0a20 2020 2020 2028  =".(TAB).      (
0000020: 5350 4143 4553 290a 285e 4a29 0d28 5e4d  SPACES).(^J).(^M
0000030: 290a 444f 4e45 0a0a 220a                 ).DONE
Orwellophile
fuente
0

Gracias a la respuesta de dimo414 , esto muestra cómo funciona su gran solución, y muestra que también puede tener comillas y variables en el texto fácilmente:

salida de ejemplo

$ ./test.sh

The text from the example function is:
  Welcome dev: Would you "like" to know how many 'files' there are in /tmp?

  There are "      38" files in /tmp, according to the "wc" command

test.sh

#!/bin/bash

function text1()
{
  COUNT=$(\ls /tmp | wc -l)
cat <<EOF

  $1 Would you "like" to know how many 'files' there are in /tmp?

  There are "$COUNT" files in /tmp, according to the "wc" command

EOF
}

function main()
{
  OUT=$(text1 "Welcome dev:")
  echo "The text from the example function is: $OUT"
}

main
Brad Parks
fuente
Sería interesante ver una cita inigualable en el texto para ver cómo maneja eso. Tal vez `No te asustes, hay archivos" $ COUNT ", por lo que el apóstrofe / comilla simple puede hacer que las cosas sean interesantes.
dragon788
-10
$TEST="ok"
read MYTEXT <<EOT
this bash trick
should preserve
newlines $TEST
long live perl
EOT
echo -e $MYTEXT
Guy Kastenbaum
fuente