Forcer bash à développer des variables dans une chaîne chargée à partir d'un fichier

88

J'essaie de trouver comment faire en sorte que bash (force?) Développe les variables dans une chaîne (qui a été chargée à partir d'un fichier).

J'ai un fichier appelé "something.txt" avec le contenu:

hello $FOO world

Je cours alors

export FOO=42
echo $(cat something.txt)

cela renvoie:

   hello $FOO world

Il n'a pas développé $ FOO même si la variable a été définie. Je ne peux pas évaluer ou générer le fichier - car il essaiera de l'exécuter (il n'est pas exécutable tel quel - je veux juste la chaîne avec les variables interpolées).

Des idées?

Michael Neale
la source
3
Soyez conscient des implications de sécurité deeval .
Suspendu jusqu'à nouvel ordre.
Voulez-vous dire que le commentaire doit être pour la réponse ci-dessous?
Michael Neale
Je voulais dire le commentaire pour vous. Plus d'une réponse propose l'utilisation de evalet il est important d'être conscient des implications.
Suspendu jusqu'à nouvel ordre.
@DennisWilliamson gotcha - Je suis un peu en retard pour répondre mais bon conseil. Et bien sûr, dans les années qui ont suivi, nous avons eu des choses comme "shell shock" donc votre commentaire a résisté à l'épreuve du temps! tips hat
Michael Neale
Est-ce que cela répond à votre question? Bash développer la variable dans une variable
LoganMzz

Réponses:

111

Je suis tombé sur ce que je pense être LA réponse à cette question: la envsubstcommande.

envsubst < something.txt

Si ce n'est pas déjà disponible dans votre distribution, c'est dans le

Paquet GNU gettext.

@Rockallite - J'ai écrit un petit script wrapper pour résoudre le problème '\ $'.

(BTW, il existe une "fonctionnalité" d'envsubst, expliquée à https://unix.stackexchange.com/a/294400/7088 pour développer uniquement certaines des variables de l'entrée, mais je conviens qu'échapper aux exceptions est beaucoup plus pratique.)

Voici mon script:

#! /bin/bash
      ## -*-Shell-Script-*-
CmdName=${0##*/}
Usage="usage: $CmdName runs envsubst, but allows '\$' to  keep variables from
    being expanded.
  With option   -sl   '\$' keeps the back-slash.
  Default is to replace  '\$' with '$'
"

if [[ $1 = -h ]]  ;then echo -e >&2  "$Usage" ; exit 1 ;fi
if [[ $1 = -sl ]] ;then  sl='\'  ; shift ;fi

sed 's/\\\$/\${EnVsUbDolR}/g' |  EnVsUbDolR=$sl\$  envsubst  "$@"
LenW
la source
5
+1. C'est la seule réponse qui garantit de ne pas effectuer de substitutions de commandes, de suppression de devis, de traitement d'arguments, etc.
Josh Kelley
6
Cela fonctionne juste pour les variables shell entièrement exportées (en bash). par exemple S = '$ a $ b'; a = ensemble; export b = exporté; echo $ S | envsubst; rendements: "exporté"
gaoithe
1
merci - je pense qu'après toutes ces années, je devrais accepter cette réponse.
Michael Neale
1
Cependant, il ne gère pas l' $échappement du signe dollar ( ), par exemple, echo '\$USER' | envsubstil affichera le nom d'utilisateur actuel avec une barre oblique inversée, non $USER.
Rockallite
3
semble l'obtenir sur un mac avec des besoins homebrewbrew link --force gettext
KeepCalmAndCarryOn
25

De nombreuses réponses utilisant evaletecho type de travail, mais interrompent sur diverses choses, telles que plusieurs lignes, la tentative d'échapper aux méta-caractères du shell, les échappements à l'intérieur du modèle qui ne sont pas destinés à être développés par bash, etc.

J'ai eu le même problème et j'ai écrit cette fonction shell qui, pour autant que je sache, gère tout correctement. Cela supprimera toujours uniquement les nouvelles lignes de fin du modèle, à cause des règles de substitution de commande de bash, mais je n'ai jamais trouvé que cela posait problème tant que tout le reste reste intact.

apply_shell_expansion() {
    declare file="$1"
    declare data=$(< "$file")
    declare delimiter="__apply_shell_expansion_delimiter__"
    declare command="cat <<$delimiter"$'\n'"$data"$'\n'"$delimiter"
    eval "$command"
}

Par exemple, vous pouvez l'utiliser comme ceci avec un parameters.cfgqui est en fait un script shell qui ne fait que définir des variables, et un template.txtqui est un modèle qui utilise ces variables:

. parameters.cfg
printf "%s\n" "$(apply_shell_expansion template.txt)" > result.txt

En pratique, je l'utilise comme une sorte de système de gabarit léger.

wjl
la source
4
C'est l'une des meilleures astuces que j'ai trouvées jusqu'à présent :). En fonction de votre réponse, il est si facile de créer une fonction qui développe une variable avec une chaîne contenant des références à d'autres variables - remplacez simplement $ data par $ 1 dans votre fonction et c'est parti! Je pense que c'est la meilleure réponse à la question initiale, BTW.
galaxy le
Même cela a des evalpièges habituels . Essayez d'ajouter ; $(eval date)à la fin de n'importe quelle ligne dans le fichier d'entrée et il exécutera la datecommande.
anubhava
1
@anubhava Je ne suis pas sûr de ce que vous voulez dire; c'est en fait le comportement d'expansion souhaité dans ce cas. En d'autres termes, oui, vous êtes censé pouvoir exécuter du code shell arbitraire avec cela.
wjl
22

tu peux essayer

echo $(eval echo $(cat something.txt))
Pizza
la source
9
À utiliser echo -e "$(eval "echo -e \"`<something.txt`\"")"si vous avez besoin de conserver de nouvelles lignes.
pevik
Fonctionne comme un charme
kyb
1
Mais seulement si votre template.txttexte est. Cette opération est dangereuse car elle exécute (évalue) les commandes si elles le sont.
kyb
2
mais cette double-quote supprimée
Thomas Decaux
Blâmez-le sur le sous-shell.
ingyhere
8

Vous ne voulez pas imprimer chaque ligne, vous voulez l' évaluer pour que Bash puisse effectuer des substitutions de variables.

FOO=42
while read; do
    eval echo "$REPLY"
done < something.txt

Voir help evalou le manuel Bash pour plus d'informations.

Todd A. Jacobs
la source
2
evalest dangereux ; vous obtenez non seulement $varnameélargi (comme vous le feriez avec envsubst), mais $(rm -rf ~)aussi.
Charles Duffy
4

Une autre approche (qui semble dégoûtante, mais je la mets quand même ici):

Écrivez le contenu de quelque chose.txt dans un fichier temporaire, avec une instruction echo entourée:

something=$(cat something.txt)

echo "echo \"" > temp.out
echo "$something" >> temp.out
echo "\"" >> temp.out

puis retournez-le dans une variable:

RESULT=$(source temp.out)

et le $ RESULT aura tout développé. Mais cela semble tellement faux!

Michael Neale
la source
1
icky mais il est plus simple, simple et fonctionne.
kyb
3

Si vous voulez seulement que les références de variables soient développées (un objectif que j'avais pour moi), vous pouvez faire ce qui suit.

contents="$(cat something.txt)"
echo $(eval echo \"$contents\")

(Les guillemets échappés autour de $ contents sont la clé ici)

SS781
la source
J'ai essayé celui-ci, mais $ () supprime les fins de ligne dans mon cas, ce qui casse la chaîne résultante
moz
J'ai changé la ligne eval eval "echo \"$$contents\""et elle a conservé l'espace blanc
moz
1

Solution suivante:

  • permet de remplacer les variables qui sont définies

  • laisse les espaces réservés de variables inchangés qui ne sont pas définis. Ceci est particulièrement utile lors des déploiements automatisés.

  • prend en charge le remplacement des variables dans les formats suivants:

    ${var_NAME}

    $var_NAME

  • signale quelles variables ne sont pas définies dans l'environnement et renvoie le code d'erreur pour de tels cas



    TARGET_FILE=someFile.txt;
    ERR_CNT=0;

    for VARNAME in $(grep -P -o -e '\$[\{]?(\w+)*[\}]?' ${TARGET_FILE} | sort -u); do     
      VAR_VALUE=${!VARNAME};
      VARNAME2=$(echo $VARNAME| sed -e 's|^\${||g' -e 's|}$||g' -e 's|^\$||g' );
      VAR_VALUE2=${!VARNAME2};

      if [ "xxx" = "xxx$VAR_VALUE2" ]; then
         echo "$VARNAME is undefined ";
         ERR_CNT=$((ERR_CNT+1));
      else
         echo "replacing $VARNAME with $VAR_VALUE2" ;
         sed -i "s|$VARNAME|$VAR_VALUE2|g" ${TARGET_FILE}; 
      fi      
    done

    if [ ${ERR_CNT} -gt 0 ]; then
        echo "Found $ERR_CNT undefined environment variables";
        exit 1 
    fi
aprodan
la source
1
  1. Si something.txt a seulement une ligne, une bashméthode, (une version plus courte de Michael Neale réponse « dégueu » ), en utilisant processus et substitution de commande :

    FOO=42 . <(echo -e echo $(<something.txt))
    

    Production:

    hello 42 world
    

    Notez que ce exportn'est pas nécessaire.

  2. Si quelque chose.txt a une ou plusieurs lignes, une méthode de valorisation GNU :sed e

    FOO=42 sed 's/"/\\\"/g;s/.*/echo "&"/e' something.txt
    
agc
la source
1
J'ai trouvé que l' sedévaluation correspondait le mieux à mon objectif. J'avais besoin de produire des scripts à partir de modèles, qui auraient des valeurs remplies à partir de variables exportées dans un autre fichier de configuration. Il gère correctement les échappements pour l'expansion de la commande, par exemple, placez \$(date)le modèle pour obtenir $(date)la sortie. Merci!
gadamiak le
0
$ eval echo $(cat something.txt)
hello 42 world
$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
Copyright (C) 2007 Free Software Foundation, Inc.
EffacerCrescendo
la source
0
foo=45
file=something.txt       # in a file is written: Hello $foo world!
eval echo $(cat $file)
Božský Boch
la source
1
Merci pour cet extrait de code, qui pourrait fournir une aide limitée et immédiate. Une explication appropriée améliorerait considérablement sa valeur à long terme en montrant pourquoi c'est une bonne solution au problème et la rendrait plus utile aux futurs lecteurs avec d'autres questions similaires. Veuillez modifier votre réponse pour ajouter des explications, y compris les hypothèses que vous avez formulées.
Capricorne du
-1

envsubst est une excellente solution (voir la réponse de LenW) si le contenu que vous remplacez est de longueur "raisonnable".

Dans mon cas, j'avais besoin de remplacer le contenu d'un fichier pour remplacer le nom de la variable. envsubstexige que le contenu soit exporté en tant que variables d'environnement et bash a un problème lors de l'exportation de variables d'environnement de plus d'un mégaoctet.

solution awk

Utilisation de la solution de cuonglm à partir d'une question différente:

needle="doc1_base64" # The "variable name" in the file. (A $ is not needed.)
needle_file="doc1_base64.txt" # Will be substituted for the needle 
haystack=$requestfile1 # File containing the needle
out=$requestfile2
awk "BEGIN{getline l < \"${needle_file}\"}/${needle}/{gsub(\"${needle}\",l)}1" $haystack > $out

Cette solution fonctionne même pour les fichiers volumineux.

Larry K
la source
-3

Les travaux suivants: bash -c "echo \"$(cat something.txt)"\"

Tourbillon
la source
Ce n'est pas seulement inefficace, mais aussi extrêmement dangereux; il n'exécute pas seulement des extensions sûres comme $foo, mais des extensions non sûres comme $(rm -rf ~).
Charles Duffy