Méthode propre pour écrire une chaîne complexe de plusieurs lignes dans une variable

110

J'ai besoin d'écrire du XML complexe à une variable dans un script bash. Le xml doit être lisible dans le script bash car c'est là que le fragment xml vivra, il n'est pas lu à partir d'un autre fichier ou d'une autre source.

Ma question est donc la suivante: quelle est la meilleure façon de s'y prendre si j'ai une longue chaîne que je veux être lisible par l'homme dans mon script bash?

Idéalement je veux:

  • ne pas avoir à échapper à aucun des personnages
  • le faire traverser plusieurs lignes le rendant lisible par l'homme
  • garder c'est l'indentation

Est-ce que ceci peut être fait avec EOF ou quelque chose, quelqu'un pourrait-il me donner un exemple?

par exemple

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
la source
Je suis prêt à parier que vous allez simplement vider ces données dans un flux. Pourquoi le stocker dans une variable quand vous pouvez rendre les choses plus complexes et utiliser des flux?
Zenexer

Réponses:

140

Cela mettra votre texte dans votre variable sans avoir besoin d'échapper aux guillemets. Il traitera également les citations non équilibrées (apostrophes, c. '-à-d.). Mettre des guillemets autour de la sentinelle (EOF) empêche le texte de se développer. Les -d''causes de ce à lire plusieurs lignes (ignorer les sauts de ligne). readest un Bash intégré, il n’est donc pas nécessaire d’appeler une commande externe telle que 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
la source
17
+1 pour éviter cat.
James Sneeringer
4
catest une commande externe. Ne pas l'utiliser enregistre cela. De plus, certains ont la philosophie que si vous utilisez un chat avec moins de deux arguments "Ur doin 'it false" (ce qui est distinct de "l'utilisation inutile de cat").
Dennis Williamson
9
et jamais jamais indenter deuxième EOF .... (table multiple à la tête bangs impliqués)
IljaBek
9
J'ai essayé d'utiliser la déclaration ci-dessus tout en set -e. Il semble readtoujours revenir non nul. Vous pouvez ! read -d .......
épaissir
11
Et si vous utilisez cette Stringvariable multiligne pour écrire dans un fichier, mettez la variable autour de "QUOTES", comme echo "${String}" > /tmp/multiline_file.txtou echo "${String}" | tee /tmp/multiline_file.txt. Cela m'a pris plus d'une heure pour le trouver.
Aditya
28

Vous avez été presque là. Soit vous utilisez cat pour l'assemblage de votre chaîne, soit vous citez la chaîne entière (dans ce cas, vous devez échapper aux guillemets à l'intérieur de votre chaîne):

#!/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
la source
Malheureusement, l'apostrophe dans "Raphael's" empêche la première de fonctionner.
Dennis Williamson
Les deux missions fonctionnent pour moi par la suite. La citation simple dans VAR1 ne devrait pas poser de problème (du moins pas pour bash). Peut-être avez-vous été induit en erreur par la coloration syntaxique?
joschi
1
Cela fonctionne dans un script, mais pas à l'invite de Bash. Désolé de ne pas être plus clair.
Dennis Williamson
1
Il est préférable de citer EOF comme 'EOF'ou "EOF", sinon les variables du shell seront analysées.
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}"

Cela devrait fonctionner correctement dans l'environnement Bourne Shell

schweiz
la source
1
+1 cette solution autorise la substitution de variables comme $ {foo}
Offirmo le
Upside: sh-compatible. Inconvénient: les backticks sont déconseillés / déconseillés en bash. Maintenant, si je devais choisir entre sh et bash ...
Zenexer
2
depuis quand les backticks sont-ils déconseillés / déconseillés? juste curieux
Alexander Mills
6

Encore une autre façon de faire la même chose ...

J'aime utiliser des variables et des caractères spéciaux <<-qui suppriment la tabulation au début de chaque ligne pour permettre l'indentation du 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

attention : il n’ya pas d’ espace vide avant eofmais seulement de la tabulation .

<?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>
Quelques explications:
  • mapfile lit l'intégralité du document ici dans un tableau.
  • la syntaxe "${Pattern[*]}"transforme ce tableau en chaîne.
  • J'utilise IFS=";"parce qu'il n'y a pas ;de chaînes obligatoires
  • La syntaxe while IFS=";" read file ...empêche IFSd'être modifié pour le reste du script. En cela, readutilisez uniquement le modifié IFS.
  • pas de fourchette.
F. Hauri
la source
Notez que mapfilenécessite Bash 4 ou supérieur. Et la syntaxe "${Pattern[*]}"convertit le tableau en chaîne entre guillemets (comme indiqué dans l'exemple de code).
Commentaires
Oui, bash 4 était très nouveau quand cette question a été posée.
F. Hauri
2

Il y a trop de cas dans de nombreuses autres réponses.

Pour être absolument sûr qu'il n'y a pas de problèmes d'espaces, de tabulations, d'IFS, etc., une meilleure approche consiste à utiliser la construction "heredoc", mais à coder le contenu de l'hérédoc en utilisant uuencodecomme expliqué ici:

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

Chris Johnson
la source