web-dev-qa-db-fra.com

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

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

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

hello $FOO world

Je cours ensuite

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

cela retourne: 

   hello $FOO world

Il n'a pas développé $ FOO même si la variable a été définie. Je ne peux ni évaluer ni source 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? 

59
Michael Neale

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

envsubst < something.txt

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

Paquet GNU gettext.

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

(BTW, il existe une "fonctionnalité" de envsubst, expliquée à l'adresse https://unix.stackexchange.com/a/294400/7088 Pour développer uniquement certaines des variables de l'entrée mais je conviens que fuir les 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  "$@"
73
LenW

La plupart des réponses utilisent le type de travail eval et echo, mais ne tiennent pas compte de plusieurs choses, telles que plusieurs lignes, la tentative d'échapper aux méta-caractères de Shell, les échappements à l'intérieur du modèle ne devant pas être développés par bash, etc.

J'avais le même problème et j'ai écrit cette fonction Shell qui, autant que je sache, gère tout correctement. Cela ne supprimera toujours que les nouvelles lignes de sortie du modèle, en raison des règles de substitution de commande de bash, mais je n'ai jamais constaté que ce soit un 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.cfg qui est en réalité un script Shell qui ne définit que des variables, et un template.txt qui 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.

25
wjl

tu peux essayer 

echo $(eval echo $(cat something.txt))
16
pizza

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

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

Voir help eval ou le manuel de Bash pour plus d'informations.

10
Todd A. Jacobs

Une autre approche (qui semble dégueulasse, mais je le mets ici quand même): 

Ecrivez le contenu de quelquechose.txt dans un fichier temporaire, avec une instruction echo intégrée: 

something=$(cat something.txt)

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

puis source dans une variable: 

RESULT=$(source temp.out)

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

4
Michael Neale

Si vous souhaitez uniquement que les références de variable soient étendues (un objectif que j'ai moi-même), vous pouvez procéder comme suit.

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

(Les citations échappées autour de $ content est la clé ici)

2
SS781
  1. Méthode bash, (variante à une ligne de la réponse de Michael Neale ), en utilisant processus & substitution de commande:

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

    Sortie:

    hello 42 world
    

    Notez que export n'est pas nécessaire.

  2. GNUsede _ ​​valuate méthode, si quelque chose.txt a plusieurs lignes:

    FOO=42 sed 's/"/\\\"/g;s/.*/echo "&"/e' something.txt
    
2
agc
$ 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.
1
ClearCrescendo
foo=45
file=something.txt       # in a file is written: Hello $foo world!
eval echo $(cat $file)
1
Božský Boch

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

Dans mon cas, je devais remplacer le nom de la variable par le contenu d'un fichier. envsubst nécessite 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 ou plus.

solution awk

Utilisation de la solution 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 gros fichiers.

0
Larry K

Solution suivante: 

  • permet de remplacer des variables définies 

  • laisse des variables inchangées, des espaces réservés non définis. Ceci est particulièrement utile lors de déploiements automatisés. 

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

    ${var_NAME}

    $var_NAME

  • indique quelles variables ne sont pas définies dans l'environnement et renvoie un 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
0
aprodan