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

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

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}"

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


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

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 .

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.