Forma limpia de escribir cadenas complejas de varias líneas en una variable

109

Necesito escribir algunos xml complejos en una variable dentro de un script bash. El xml debe ser legible dentro del script bash ya que aquí es donde vivirá el fragmento xml, no se está leyendo desde otro archivo o fuente.

Entonces, mi pregunta es esta: si tengo una cadena larga que quiero que sea legible para los humanos dentro de mi script bash, ¿cuál es la mejor manera de hacerlo?

Idealmente quiero:

  • no tener que escapar de ninguno de los personajes
  • hacer que se rompa en varias líneas haciéndolo legible
  • mantener su sangría

¿Se puede hacer esto con EOF o algo así, alguien podría darme un ejemplo?

p.ej

String = <<EOF
 <?xml version="1.0" encoding='UTF-8'?>
 <painting>
   <img src="madonna.jpg" alt='Foligno Madonna, by Raphael'/>
   <caption>This is Raphael's "Foligno" Madonna, painted in
   <date>1511</date>-<date>1512</date>.</caption>
 </painting>
EOF
ChrisInCambo
fuente
Estoy dispuesto a apostar que simplemente volcará esos datos en una transmisión nuevamente. ¿Por qué almacenarlo en una variable cuando podría hacer las cosas más complejas y usar transmisiones?
Zenexer

Respuestas:

140

Esto colocará su texto en su variable sin necesidad de escapar de las comillas. También manejará comillas desequilibradas (apóstrofes, es decir '). Poner comillas alrededor del centinela (EOF) evita que el texto sufra expansión de parámetros. Esto -d''hace que lea varias líneas (ignore las nuevas líneas). reades un Bash incorporado, por lo que no requiere llamar a un comando externo como cat.

IFS='' read -r -d '' String <<"EOF"
<?xml version="1.0" encoding='UTF-8'?>
 <painting>
   <img src="madonna.jpg" alt='Foligno Madonna, by Raphael'/>
   <caption>This is Raphael's "Foligno" Madonna, painted in
   <date>1511</date>-<date>1512</date>.</caption>
 </painting>
EOF
Dennis Williamson
fuente
17
+1 por evitar cat.
James Sneeringer el
44
catEs un comando externo. No usarlo ahorra haciendo eso. Además, algunos tienen la filosofía de que si está usando gato con menos de dos argumentos "Ur lo está haciendo mal" (que es distinto del "uso inútil de cat").
Dennis Williamson
99
y nunca sangría la segunda EOF ... (múltiples golpes de tabla a cabeza involucrados)
IljaBek
99
Traté de usar la declaración anterior mientras set -e. Parece que readsiempre devuelve un valor distinto de cero. Puede ! read -d .......
aumentar
11
Y si está utilizando esta Stringvariable de varias líneas para escribir en un archivo, coloque la variable alrededor de "COTIZACIONES" como echo "${String}" > /tmp/multiline_file.txto echo "${String}" | tee /tmp/multiline_file.txt. Me llevó más de una hora encontrar eso.
Aditya
28

Ya casi has estado allí. O usa cat para el ensamblaje de su cadena o cita la cadena completa (en cuyo caso, tendría que escapar de las comillas dentro de su cadena):

#!/bin/sh
VAR1=$(cat <<EOF
<?xml version="1.0" encoding='UTF-8'?>
<painting>
  <img src="madonna.jpg" alt='Foligno Madonna, by Raphael'/>
  <caption>This is Raphael's "Foligno" Madonna, painted in
  <date>1511</date>-<date>1512</date>.</caption>
</painting>
EOF
)

VAR2="<?xml version=\"1.0\" encoding='UTF-8'?>
<painting>
  <img src=\"madonna.jpg\" alt='Foligno Madonna, by Raphael'/>
  <caption>This is Raphael's \"Foligno\" Madonna, painted in
  <date>1511</date>-<date>1512</date>.</caption>
</painting>"

echo "${VAR1}"
echo "${VAR2}"
joschi
fuente
Desafortunadamente, el apóstrofe en "Raphael's" hace que el primero no funcione.
Dennis Williamson
Ambas tareas me funcionan eventualmente. La comilla simple en VAR1 no debería ser un problema (al menos no para bash). ¿Quizás te haya engañado el resaltado de sintaxis?
joschi
1
Funciona en un script, pero no en un indicador de Bash. Perdón por no ser más claro.
Dennis Williamson
1
Es mejor citar EOF como 'EOF'o "EOF", de lo contrario, se analizarán las variables de shell.
Stanislav German-Evtushenko
13
#!/bin/sh

VAR1=`cat <<EOF
<?xml version="1.0" encoding='UTF-8'?>
<painting>
  <img src="madonna.jpg" alt='Foligno Madonna, by Raphael'/>
  <caption>This is Raphael's "Foligno" Madonna, painted in
  <date>1511</date>-<date>1512</date>.</caption>
</painting>
EOF
`
echo "VAR1: ${VAR1}"

Esto debería funcionar bien en el entorno de shell Bourne

Schweiz
fuente
1
+1 esta solución permite la sustitución de variables como $ {foo}
Offirmo
Al revés: compatible con sh. Desventaja: los backticks están desaprobados / desaconsejados en bash. Ahora si tuviera que elegir entre sh y bash ...
Zenexer
2
¿Desde cuándo se desaconsejan / desalientan los backticks? curioso
Alexander Mills
6

Otra forma de hacer lo mismo ...

Me gusta usar variables y especiales <<-que eliminan la tabulación al comienzo de cada línea para permitir la sangría del script:

#!/bin/bash

mapfile Pattern <<-eof
        <?xml version="1.0" encoding='UTF-8'?>
        <painting>
          <img src="%s" alt='%s'/>
          <caption>%s, painted in
          <date>%s</date>-<date>%s</date>.</caption>
        </painting>
        eof

while IFS=";" read file alt caption start end ;do
    printf "${Pattern[*]}" "$file" "$alt" "$caption" "$start" "$end"
  done <<-eof
        madonna.jpg;Foligno Madonna, by Raphael;This is Raphael's "Foligno" Madonna;1511;1512
        eof

advertencia : no hay espacio en blanco antes, eofsino solo tabulación .

<?xml version="1.0" encoding='UTF-8'?>
 <painting>
   <img src="madonna.jpg" alt='Foligno Madonna, by Raphael'/>
   <caption>This is Raphael's "Foligno" Madonna, painted in
   <date>1511</date>-<date>1512</date>.</caption>
 </painting>
Algunas explicaciones:
  • mapfile lee todo el documento aquí en una matriz.
  • la sintaxis "${Pattern[*]}"convierte esta matriz en una cadena.
  • Lo uso IFS=";"porque no hay ;cadenas requeridas
  • La sintaxis while IFS=";" read file ...evita IFSque se modifique el resto del script. En esto, solo readuse el modificado IFS.
  • sin tenedor
F. Hauri
fuente
Tenga en cuenta que mapfilerequiere Bash 4 o superior. Y la sintaxis "${Pattern[*]}"convierte la matriz en una cadena cuando está entre comillas (como se muestra en el código de ejemplo).
Dennis Williamson
Sí, bash 4 era muy nuevo cuando se hizo esta pregunta.
F. Hauri
2

Hay demasiados casos de esquina en muchas de las otras respuestas.

Para estar absolutamente seguro de que no hay problemas con espacios, pestañas, IFS, etc., un mejor enfoque es usar el constructo "heredoc", pero codificar el contenido del heredoc usando uuencodecomo se explica aquí:

https://stackoverflow.com/questions/6896025/#11379627 .

Chris Johnson
fuente