Forzare bash ad espandere le variabili in una stringa caricata da un file


89

Sto cercando di capire come fare in modo che bash (force?) Espandi le variabili in una stringa (che è stata caricata da un file).

Ho un file chiamato "something.txt" con il contenuto:

hello $FOO world

Allora corro

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

questo restituisce:

   hello $FOO world

Non ha espanso $ FOO anche se la variabile è stata impostata. Non riesco a valutare o generare il file - poiché proverà ad eseguirlo (non è eseguibile così com'è - voglio solo la stringa con le variabili interpolate).

Qualche idea?


3
Sii consapevole delle implicazioni pereval la sicurezza di .
Dennis Williamson

Intendevi che il commento fosse per la risposta qui sotto?
Michael Neale

Intendevo il commento per te. Più di una risposta propone l'uso di evaled è importante essere consapevoli delle implicazioni.
Dennis Williamson

@DennisWilliamson gotcha - Sono un po 'in ritardo a rispondere ma buon consiglio. E ovviamente negli anni successivi abbiamo avuto cose come "shock da conchiglia", quindi il tuo commento ha superato la prova del tempo! punte cappello
Michael Neale

Questo risponde alla tua domanda? Bash espande la variabile in una variabile
LoganMzz

Risposte:


115

Mi sono imbattuto in quella che penso sia LA risposta a questa domanda: il envsubstcomando.

envsubst < something.txt

Nel caso in cui non sia già disponibile nella tua distribuzione, è nel file

Pacchetto GNU gettext.

@Rockallite - Ho scritto un piccolo script wrapper per occuparmi del problema "\ $".

(A proposito, c'è una "funzionalità" di envsubst, spiegata su https://unix.stackexchange.com/a/294400/7088 per espandere solo alcune delle variabili nell'input, ma sono d'accordo sul fatto che sfuggire alle eccezioni sia molto di più conveniente.)

Ecco il mio 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  "$@"

5
+1. Questa è l'unica risposta garantita per non eseguire sostituzioni di comandi, rimozione di virgolette, elaborazione di argomenti, ecc.
Josh Kelley

6
Funziona solo per le shell vars completamente esportate (in bash). ad esempio S = '$ a $ b'; a = set; export b = esportato; echo $ S | envsubst; rendimenti: "esportato"
gaoithe

1
grazie - penso che dopo tutti questi anni, dovrei accettare questa risposta.
Michael Neale

1
Tuttavia, non gestisce l' $escape del segno di dollaro ( ), ad es. echo '\$USER' | envsubstProdurrà il nome utente corrente con una barra rovesciata come prefisso, no $USER.
Rockallite

3
sembra ottenerlo su un Mac con esigenze homebrewbrew link --force gettext
KeepCalmAndCarry il

25

Molte delle risposte utilizzando evaleecho tipo di lavoro, ma si interrompono su varie cose, come più righe, tentativi di sfuggire ai meta-caratteri della shell, fughe all'interno del modello non destinato ad essere espanso da bash, ecc.

Ho avuto lo stesso problema e ho scritto questa funzione di shell, che per quanto posso dire, gestisce tutto correttamente. Questo rimuoverà ancora solo le nuove righe finali dal modello, a causa delle regole di sostituzione dei comandi di bash, ma non ho mai scoperto che sia un problema finché tutto il resto rimane intatto.

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

Ad esempio, puoi usarlo in questo modo con uno parameters.cfgscript di shell che imposta solo le variabili e template.txtun modello che utilizza tali variabili:

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

In pratica, lo uso come una sorta di sistema di template leggero.


4
Questo è uno dei migliori trucchi che ho trovato finora :). In base alla tua risposta è così facile costruire una funzione che espanda una variabile con una stringa contenente riferimenti ad altre variabili: basta sostituire $ data con $ 1 nella tua funzione ed eccoci! Credo che questa sia la migliore risposta alla domanda originale, BTW.
galassia

Anche questo ha le solite evalinsidie. Prova ad aggiungere ; $(eval date)alla fine di qualsiasi riga all'interno del file di input e verrà eseguito il datecomando.
anubhava

1
@anubhava Non sono sicuro di cosa intendi; questo è effettivamente il comportamento di espansione desiderato in questo caso. In altre parole, sì, dovresti essere in grado di eseguire codice shell arbitrario con questo.
wjl

22

Puoi provare

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

9
Utilizzare echo -e "$(eval "echo -e \"`<something.txt`\"")"se è necessario mantenere nuove linee.
pevik

Funziona a
meraviglia

1
Ma solo se il tuo template.txtè un testo. Questa operazione è pericolosa perché esegue (valuta) comandi se lo sono.
kyb

2
ma questo ha rimosso le doppie virgolette
Thomas Decaux

Dai la colpa alla subshell.
ingyhere

8

Non vuoi stampare ogni riga, vuoi valutarla in modo che Bash possa eseguire sostituzioni di variabili.

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

Vedere help evalo il manuale di Bash per ulteriori informazioni.


2
evalè pericoloso ; non solo vieni $varnameespanso (come faresti con envsubst), ma $(rm -rf ~)anche.
Charles Duffy

4

Un altro approccio (che sembra icky, ma lo metto comunque qui):

Scrivi il contenuto di something.txt in un file temporaneo, con un'istruzione echo racchiusa attorno ad esso:

something=$(cat something.txt)

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

quindi restituiscilo a una variabile:

RESULT=$(source temp.out)

e $ RESULT avrà tutto espanso. Ma sembra così sbagliato!


1
icky ma è molto diretto, semplice e funziona.
kyb

3

Se vuoi solo espandere i riferimenti alle variabili (un obiettivo che avevo per me stesso) puoi fare quanto segue.

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

(Le virgolette con escape intorno a $ contenuti sono fondamentali qui)


Ho provato questo, tuttavia $ () rimuove le terminazioni di riga nel mio caso, interrompendo la stringa risultante
moz

Ho cambiato la riga di valutazione in eval "echo \"$$contents\""e ha conservato lo spazio bianco
moz

2
  1. Se qualcosa.txt ha solo una riga, un bashmetodo, (una versione più breve della risposta "icky" di Michael Neale ), usando la sostituzione di processo e comando :

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

    Produzione:

    hello 42 world
    

    Nota che exportnon è necessario.

  2. Se qualcosa.txt ha una o più righe, un metodo di valutazione GNU :sed e

    FOO=42 sed 's/"/\\\"/g;s/.*/echo "&"/e' something.txt
    

1
Ho trovato la sedvalutazione più adatta al mio scopo. Avevo bisogno di produrre script da modelli, che avrebbero valori riempiti da variabili esportate in un altro file di configurazione. Gestisce correttamente l'escaping per l'espansione del comando, ad esempio inserendo \$(date)il modello per ottenere $(date)l'output. Grazie!
Gadamiak

1

Soluzione seguente:

  • consente la sostituzione di variabili definite

  • lascia inalterati i segnaposto delle variabili non definiti. Ciò è particolarmente utile durante le distribuzioni automatiche.

  • supporta la sostituzione delle variabili nei seguenti formati:

    ${var_NAME}

    $var_NAME

  • segnala quali variabili non sono definite nell'ambiente e restituisce il codice di errore per tali casi



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

0
foo=45
file=something.txt       # in a file is written: Hello $foo world!
eval echo $(cat $file)

1
Grazie per questo snippet di codice, che potrebbe fornire un aiuto limitato e immediato. Una spiegazione adeguata migliorerebbe notevolmente il suo valore a lungo termine mostrando perché questa è una buona soluzione al problema e la renderebbe più utile ai futuri lettori con altre domande simili. Si prega di modificare la risposta di aggiungere qualche spiegazione, tra le ipotesi che hai fatto.
Capricorno

-1

envsubst è un'ottima soluzione (vedi la risposta di LenW) se il contenuto che stai sostituendo è di lunghezza "ragionevole".

Nel mio caso, dovevo sostituire il contenuto di un file per sostituire il nome della variabile. envsubstrichiede che il contenuto venga esportato come variabili di ambiente e bash ha un problema durante l'esportazione di variabili di ambiente che sono più di un megabyte o giù di lì.

soluzione awk

Usando la soluzione di cuonglm da una domanda diversa:

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

Questa soluzione funziona anche per file di grandi dimensioni.


-3

I seguenti lavori: bash -c "echo \"$(cat something.txt)"\"


Questo non è solo inefficiente, ma anche estremamente pericoloso; non esegue solo espansioni sicure come $foo, ma non sicure come $(rm -rf ~).
Charles Duffy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.