Comment attribuer une valeur hérédoc à une variable dans Bash?

374

J'ai cette chaîne multi-lignes (guillemets inclus):

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

Comment pourrais-je l'affecter à une variable en utilisant un hérédoc dans Bash?

Je dois préserver les nouvelles lignes.

Je ne veux pas échapper aux caractères de la chaîne, ce serait ennuyeux ...

Neil
la source
@JohnM - Je viens d'essayer une affectation heredoc avec des guillemets simples 'EOF', avec des sauts de ligne échappés avec la ` in the content: if the second line has commande cd`, je reviens: " .sh: ligne X: cd: commande introuvable "; mais si je cite deux fois "EOF"; alors les variables bash ${A}ne sont pas conservées sous forme de chaînes (elles sont développées); mais ensuite, les sauts de ligne sont préservés - et, je n'ai aucun problème à exécuter une commande avec cden deuxième ligne ( et les deux 'EOF' et "EOF" semblent bien fonctionner également avec eval, pour exécuter un ensemble de commandes stockées dans une variable chaîne ). À votre santé!
sdaau
1
... et pour ajouter à mon commentaire précédent: les commentaires bash "#" dans la "EOF"variable double-qouted , s'ils sont appelés via eval $VAR, feront commenter tout le reste du script, car ici $ VAR sera vu comme une seule ligne ; pour pouvoir utiliser les #commentaires bash dans un script multiligne, double-quote aussi variable dans l' eval call: eval "$ VAR" `.
sdaau
@sdaau: J'ai eu des problèmes avec evalcette méthode, mais je ne l'ai pas retrouvée car elle faisait partie d'un paquet evalcontenant des variables définies dans son fichier de configuration. Message d'erreur était: /usr/lib/network/network: eval: line 153: syntax error: unexpected end of file. Je viens de passer à une autre solution.
Golar Ramblar

Réponses:

515

Vous pouvez éviter une utilisation inutile catet mieux gérer les citations incompatibles avec ceci:

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

Si vous ne citez pas la variable lorsque vous l'échoz, les sauts de ligne sont perdus. Le citer les conserve:

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

Si vous souhaitez utiliser l'indentation pour la lisibilité dans le code source, utilisez un tiret après les moins. L'indentation doit être effectuée en utilisant uniquement des tabulations (pas d'espaces).

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

Si, à la place, vous souhaitez conserver les onglets dans le contenu de la variable résultante, vous devez supprimer l'onglet de IFS. Le marqueur terminal pour ici doc ( EOF) ne doit pas être mis en retrait.

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

Des onglets peuvent être insérés sur la ligne de commande en appuyant sur Ctrl- V Tab. Si vous utilisez un éditeur, selon celui-ci, cela peut également fonctionner ou vous devrez peut-être désactiver la fonctionnalité qui convertit automatiquement les tabulations en espaces.

En pause jusqu'à nouvel ordre.
la source
117
Je pense qu'il vaut la peine de mentionner que si vous avez set -o errexit(aka set -e) dans votre script et que vous l'utilisez, il terminera votre script car readretourne un code retour non nul lorsqu'il atteindra EOF.
Mark Byers
14
@MarkByers: C'est l'une des raisons pour lesquelles je n'utilise jamais set -eet déconseille toujours son utilisation. Il est préférable d'utiliser à la place une gestion des erreurs appropriée.trapest votre ami. Autres amis: elseet ||entre autres.
pause jusqu'à nouvel ordre.
7
L'évitement de la catpeine en vaut-il vraiment la peine dans un tel cas? Assigner un hérédoc à une variable avec catest un idiome bien connu. En quelque sorte en utilisantread obscurcit les choses pour de petits avantages à mon humble avis.
Gregory Pakosz
6
@ulidtko C'est parce que vous n'avez pas d'espace entre det la chaîne vide; bashs'effondre -rd''simplement -rdavant de readvoir ses arguments, il VARest donc traité comme l'argument de -d.
chepner
6
Dans ce format, readreviendra avec un code de sortie différent de zéro. Cela rend cette méthode loin d'être idéale dans un script avec vérification des erreurs activée (par exemple set -e).
Swiss
246

Utilisez $ () pour affecter la sortie de catà votre variable comme ceci:

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

Assurez-vous de délimiter le début END_HEREDOC avec des guillemets simples.

Notez que la fin du délimiteur hérédoc END_HEREDOC doit être seul sur la ligne (par conséquent, la parenthèse de fin se trouve sur la ligne suivante).

Grâce à @ephemient pour la réponse.

Neil
la source
1
En fait, celui-ci laisse passer des citations dans certaines circonstances. J'ai réussi à faire ça en Perl facilement ...
Neil
32
+1. C'est la solution la plus lisible, du moins pour mes yeux. Il laisse le nom de la variable à l'extrême gauche de la page, au lieu de l'intégrer dans la commande de lecture.
Clayton Stanley
12
PSA: rappelez-vous que la variable doit être citée pour conserver les retours à la ligne. echo "$VAR"au lieu de echo $VAR.
sevko
7
C'est bien avec ashet OpenWRT où readne prend pas en charge -d.
David Ehrmann
3
Pour des raisons que je ne peux pas comprendre, cela échoue avec une erreur "EOF inattendue" si vous avez un backtick non apparié dans l'hérédoc.
Radon Rosborough
79

il s'agit d'une variante de la méthode Dennis, plus élégante dans les scripts.

définition de la fonction:

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

usage:

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

echo "$VAR"

prendre plaisir

ps a fait une version en «boucle de lecture» pour les shells qui ne sont pas compatibles read -d. devrait travailler avec set -euet contre - apostrophes non appariés , mais pas testé très bien:

define(){ o=; while IFS="\n" read -r a; do o="$o$a"'
'; done; eval "$1=\$o"; }
ttt
la source
1
Cela ne semble fonctionner que superficiellement. La fonction define retournera un état de 1, et je ne sais pas trop ce qui doit être corrigé.
fny
1
Ceci est également supérieur à la réponse acceptée, car il peut être modifié pour prendre en charge POSIX sh en plus de bash (un read boucle dans la fonction, pour éviter le -d ''bashisme nécessaire pour préserver les retours à la ligne).
ELLIOTTCABLE
Contrairement à l'option cat-in-a-subshell, cela fonctionne avec des backticks non appariés dans l'hérédoc. Je vous remercie!
Radon Rosborough
Cette solution fonctionne avec set -eset, contrairement à la réponse sélectionnée. Il semble que ce soit à cause dehttp://unix.stackexchange.com/a/265151/20650
ffledgling
2
Le statut de retour de @fny ps est depuis longtemps corrigé
ttt
36
VAR=<<END
abc
END

ne fonctionne pas parce que vous redirigez stdin vers quelque chose qui ne vous intéresse pas, à savoir l'affectation

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

fonctionne, mais il y a un back-tic là-dedans qui peut vous empêcher d'utiliser cela. De plus, vous devriez vraiment éviter d'utiliser des backticks, il est préférable d'utiliser la notation de substitution de commande $(..).

export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END
) ; echo $A
l0st3d
la source
J'ai mis à jour ma question pour inclure $ (exécutable). De plus, comment conservez-vous les nouvelles lignes?
Neil
2
@ l0st3d: Si proche ... Utilisez $(cat <<'END'plutôt. @Neil: La toute dernière nouvelle ligne ne fera pas partie de la variable, mais le reste sera conservé.
éphémère
1
Il ne semble pas que les nouvelles lignes soient conservées. En exécutant l'exemple ci-dessus, je vois: "sdfsdf sdfsdf sdfsfds" ... ah! Mais en écrivant echo "$A"(c'est-à-dire en mettant $ A entre guillemets) et vous voyez les nouvelles lignes!
Darren Cook
1
@Darren: aha! J'avais remarqué le problème des sauts de ligne et l'utilisation des guillemets autour de la variable de sortie résout le problème. THX!
javadba
1
Il est intéressant, en raison de la bizarrerie du premier exemple, dans un pincement , vous pouvez l' utiliser pour des blocs de commentaires de fortune comme ceci: REM=<< 'REM' ... comment block goes here ... REM. Ou plus compacte, : << 'REM' .... Où "REM" pourrait être quelque chose comme "NOTES" ou "SCRATCHPAD", etc.
Beejor
34

Il n'y a toujours pas de solution qui préserve les nouvelles lignes.

Ce n'est pas vrai - vous êtes probablement juste trompé par le comportement de l'écho:

echo $VAR # strips newlines

echo "$VAR" # preserves newlines

patspam
la source
5
C'est vraiment le comportement du fonctionnement d'une citation d'une variable. Sans guillemets, il les insérera en tant que paramètres différents, sans espace, tandis qu'avec des guillemets, le contenu entier des variables sera traité comme un argument
Czipperz
11

En dérivant la réponse de Neil , vous n'avez souvent pas besoin de var du tout, vous pouvez utiliser une fonction de la même manière qu'une variable et c'est beaucoup plus facile à lire que les readsolutions en ligne ou basées.

$ 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
la source
9

Un tableau est une variable, donc dans ce cas, le mapfile fonctionnera

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

Ensuite, vous pouvez imprimer comme ceci

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

affecter une valeur hérédoc à une variable

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

utilisé comme argument d'une commande

echo "$(cat <<'SQLEOF'
xxx''xxx'xxx'xx  123123    123123
abc'asdf"
$(dont-execute-this)
foo"bar"''
SQLEOF
)"
homme de bronze
la source
Lorsque j'ai essayé la première méthode, il ne semble pas y avoir de terminaison de ligne entre les lignes. Doit être une sorte de configuration sur ma machine Linux?
Kemin Zhou
Cela signifie probablement que lorsque vous faisiez écho à votre variable, vous n'avez pas mis de guillemets autour d'elle ... Essayez-le comme ceci:echo "$VAR"
Brad Parks
1

Je me suis retrouvé à devoir lire une chaîne contenant NULL, alors voici une solution qui lira tout ce vous lui lancerez. Bien que si vous avez réellement affaire à NULL, vous devrez vous en occuper au niveau hexadécimal.

$ 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 )
}

Preuve:

$ . 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"
$ 

Exemple HEREDOC (avec ^ 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
la source
0

Merci à la réponse de dimo414 , cela montre comment fonctionne sa grande solution et montre que vous pouvez également avoir facilement des guillemets et des variables dans le texte:

exemple de sortie

$ ./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
la source
Il serait intéressant de voir une citation inégalée dans le texte pour voir comment il gère cela. Peut-être `Ne paniquez pas, il y a des fichiers" $ COUNT "` donc l'apostrophe / guillemet simple peut rendre les choses intéressantes.
dragon788
-10
$TEST="ok"
read MYTEXT <<EOT
this bash trick
should preserve
newlines $TEST
long live perl
EOT
echo -e $MYTEXT
Guy Kastenbaum
la source