Come assegnare valori contenenti spazio alle variabili in bash usando eval


19

Voglio assegnare dinamicamente i valori alle variabili usando eval. Il seguente esempio fittizio funziona:

var_name="fruit"
var_value="orange"
eval $(echo $var_name=$var_value)
echo $fruit
orange

Tuttavia, quando il valore della variabile contiene spazi, evalrestituisce un errore, anche se $var_valueviene inserito tra virgolette doppie:

var_name="fruit"
var_value="blue orange"
eval $(echo $var_name="$var_value")
bash: orange : command not found

Un modo per aggirare questo?

Risposte:


13

Non usare eval, usa declare

$ declare "$var_name=$var_value"
$ echo "fruit: >$fruit<"
fruit: >blue orange<

11

Non usare evalper questo; usare declare.

var_name="fruit"
var_value="blue orange"
declare "$var_name=$var_value"

Si noti che la suddivisione delle parole non è un problema, perché tutto ciò che segue =viene trattato come il valore da declare, non solo la prima parola.

In bash4.3, i riferimenti nominati lo rendono un po 'più semplice.

$ declare -n var_name=fruit
$ var_name="blue orange"
$ echo $fruit
blue orange

È possibile effettuare evalil lavoro, ma non è ancora dovrebbe :) Utilizzando evalè una cattiva abitudine per entrare.

$ eval "$(printf "%q=%q" "$var_name" "$var_value")"

2
Usare in eval quel modo è sbagliato. Ti stai espandendo $var_valueprima di passarlo al evalche significa che verrà interpretato come codice shell! (prova ad esempio con var_value="';:(){ :|:&};:'")
Stéphane Chazelas l'

1
Buon punto; ci sono alcune stringhe che non puoi assegnare in modo sicuro usando eval(che è una delle ragioni per cui ho detto che non dovresti usare eval).
Chepner,

@chepner - Non credo sia vero. forse lo è, ma non questo almeno. le sostituzioni di parametri consentono l'espansione condizionale e quindi è possibile espandere solo valori sicuri nella maggior parte dei casi, credo. tuttavia, il tuo problema principale $var_valueè l'inversione di virgolette - assumendo un valore sicuro per $var_name (che può essere un presupposto altrettanto pericoloso, in realtà) , quindi dovresti racchiudere le virgolette doppie del lato destro tra virgolette singole - non vice versa.
Mikeserv,

Penso di aver corretto il eval, usando printfe il suo formato bashspecifico %q. Questa non è ancora una raccomandazione da usare eval, ma penso che sia più sicura di prima. Il fatto che sia necessario fare tutto questo sforzo per farlo funzionare è la prova che invece dovresti usare declareriferimenti nominati.
Chepner,

Bene, in realtà, secondo me, i riferimenti nominati sono il problema. Il modo migliore per usarlo - nella mia esperienza - è come ... set -- a bunch of args; eval "process2 $(process1 "$@")"dove process1stampa solo numeri tra virgolette "${1}" "${8}" "${138}". È pazzesco semplice e facile come '"${'$((i=$i+1))'}" 'nella maggior parte dei casi. i riferimenti indicizzati lo rendono sicuro, robusto e veloce . Comunque - ho votato.
Mikeserv,

4

Un buon modo di lavorare evalè sostituirlo con echoper i test. echoe evalfunzionano allo stesso modo (se mettiamo da parte l' \xespansione effettuata da alcune echoimplementazioni come quelle bashin alcune condizioni).

Entrambi i comandi uniscono i loro argomenti con uno spazio in mezzo. La differenza è che echo visualizza il risultato mentre eval valuta / interpreta il risultato come codice shell.

Quindi, per vedere quale codice shell

eval $(echo $var_name=$var_value)

valutare, è possibile eseguire:

$ echo $(echo $var_name=$var_value)
fruit=blue orange

Non è quello che vuoi, quello che vuoi è:

fruit=$var_value

Inoltre, utilizzare $(echo ...)qui non ha senso.

Per generare quanto sopra, dovresti eseguire:

$ echo "$var_name=\$var_value"
fruit=$var_value

Quindi, per interpretarlo, è semplicemente:

eval "$var_name=\$var_value"

Si noti che può anche essere utilizzato per impostare singoli elementi dell'array:

var_name='myarray[23]'
var_value='something'
eval "$var_name=\$var_value"

Come altri hanno già detto, se non ti interessa che il tuo codice sia bashspecifico, puoi usare declarecome:

declare "$var_name=$var_value"

Tuttavia nota che ha alcuni effetti collaterali.

Limita l'ambito della variabile alla funzione in cui è eseguita. Quindi non puoi usarla per esempio in cose come:

setvar() {
  var_name=$1 var_value=$2
  declare "$var_name=$var_value"
}
setvar foo bar

Perché ciò dichiarerebbe a foo variabile locale setvarcosì sarebbe inutile.

bash-4.2aggiunta -gun'opzione per declaredichiarare una variabile globale , ma non è nemmeno quello che vogliamo, dato setvarche imposteremmo una var globale rispetto a quella del chiamante se il chiamante fosse una funzione, come in:

setvar() {
  var_name=$1 var_value=$2
  declare -g "$var_name=$var_value"
}
foo() {
  local myvar
  setvar myvar 'some value'
  echo "1: $myvar"
}
foo
echo "2: $myvar"

che produrrebbe:

1:
2: some value

Inoltre, nota che while declareviene chiamato declare(in realtà bashpreso in prestito il concetto dalla shell di Korntypeset ), se la variabile è già impostata, declarenon dichiara una nuova variabile e il modo in cui viene eseguita l'assegnazione dipende dal tipo di variabile.

Per esempio:

varname=foo
varvalue='([PATH=1000]=something)'
declare "$varname=$varvalue"

produrrà un risultato diverso (e potenzialmente avrà effetti collaterali negativi) se è varnamestato precedentemente dichiarato come array scalare , array o associativo .


2
Cosa c'è di sbagliato nell'essere specifici per bash? OP ha messo il tag bash sulla domanda, quindi sta usando bash. Fornire alternative è buono, ma penso che dire a qualcuno di non usare una funzionalità di una shell perché non è portatile sia sciocco.
Patrick,

@Patrick, hai visto lo smiley? Detto questo, l'uso della sintassi portatile significa meno sforzi quando è necessario eseguire il porting del codice su un altro sistema dove bashnon è disponibile (o quando ti rendi conto che hai bisogno di una shell migliore / più veloce). La evalsintassi funziona in tutte le shell tipo Bourne ed è POSIX, quindi tutti i sistemi avranno un punto in shcui funziona. (ciò significa anche che la mia risposta si applica a tutte le conchiglie, e prima o poi, come spesso accade qui, vedrai una domanda specifica non bash chiusa come duplicata di questa.
Stéphane Chazelas,

ma cosa succede se $var_namecontiene token? ... come ;?
Mikeserv,

@mikeserv, quindi non è un nome variabile. Se non ci si può fidare del suo contenuto, allora avete bisogno di disinfettare con entrambi evale declare(si pensi PATH, TMOUT, PS4, SECONDS...).
Stéphane Chazelas,

ma al primo passaggio è sempre un'espansione variabile e mai un nome variabile fino al secondo. nella mia risposta lo igienizzo con un'espansione dei parametri, ma se stai implicando di eseguire la sanificazione in una subshell al primo passaggio, ciò potrebbe anche essere fatto in modo portabile w / export. non seguo però la parte tra parentesi alla fine.
Mikeserv,

1

Se fate:

eval "$name=\$val"

... e $namecontiene uno ;- o uno qualsiasi dei tanti altri token che la shell potrebbe interpretare come delimitare un semplice comando - preceduto dalla corretta sintassi della shell, che verrà eseguita.

name='echo hi;varname' val='be careful with eval'
eval "$name=\$val" && echo "$varname"

PRODUZIONE

hi
be careful with eval

Tuttavia, a volte può essere possibile separare la valutazione e l'esecuzione di tali dichiarazioni. Ad esempio, aliaspuò essere utilizzato per pre-valutare un comando. Nel seguente esempio la definizione della variabile viene salvata in una aliasche può essere dichiarata correttamente solo se la $nmvariabile che sta valutando non contiene byte che non corrispondono a caratteri alfanumerici ASCII o _.

LC_OLD=$LC_ALL LC_ALL=C
alias "${nm##*[!_A-Z0-9a-z]*}=_$nm=\$val" &&
eval "${nm##[0-9]*}" && unalias "$nm"
LC_ALL=$LC_OLD

evalviene utilizzato qui per gestire l'invocazione del nuovo aliasda un varname. Ma viene chiamato solo se la aliasdefinizione precedente ha esito positivo, e mentre so che molte implementazioni diverse accetteranno molti tipi diversi di valori peralias nomi, non ne ho ancora incontrato uno che accetti uno completamente vuoto .

La definizione all'interno di aliasè _$nm, tuttavia, per garantire che non vengano sovrascritti valori ambientali significativi. Non conosco valori ambientali degni di nota che iniziano con a_ ed è generalmente una scommessa sicura per una dichiarazione semi-privata.

Comunque, se la aliasdefinizione ha esito positivo, dichiarerà un aliasnome per $nmil valore di. E evallo chiamerò solo aliasse anche non inizia con un numero - altrimenti evalottiene solo un argomento nullo. Pertanto, se vengono soddisfatte entrambe le condizioni, viene evalchiamato l'alias e viene creata la definizione della variabile salvata nell'alias, dopodiché la nuova aliasviene prontamente rimossa dalla tabella hash.


;non è consentito nei nomi delle variabili. Se non si ha il controllo sul contenuto di $name, è necessario disinfettarlo per export/ declarepure. Mentre exportnon esegue il codice, impostando alcune variabili come PATH, PS4e molti di quelli info -f bash -n 'Bash Variables'hanno effetti collaterali ugualmente pericolosi.
Stéphane Chazelas,

@ StéphaneChazelas - ovviamente non è permesso, ma, come prima, non è un nome variabile al evalprimo passaggio - è un'espansione variabile. Come hai detto altrove, in quel contesto è molto permesso. Comunque l'argomento $ PATH è molto valido - ho fatto una piccola modifica e ne aggiungerò alcune in seguito.
Mikeserv,

@ StéphaneChazelas - meglio tardi che mai ...?
Mikeserv,

In pratica, zsh, pdksh, mksh, yashnon si lamentano su unset 'a;b'.
Stéphane Chazelas,

Lo vorrai unset -v -- ...anche tu .
Stéphane Chazelas,
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.