Jak przypisać wartości zawierające spację do zmiennych w bash za pomocą eval


19

Chcę dynamicznie przypisywać wartości do zmiennych za pomocą eval. Działa następujący przykładowy manekin:

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

Jednak gdy wartość zmiennej zawiera spacje, evalzwraca błąd, nawet jeśli $var_valuejest wstawiany między podwójnymi cudzysłowami:

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

Jest jakiś sposób na obejście tego?

Odpowiedzi:



11

Nie używaj evaldo tego; użyć declare.

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

Pamiętaj, że dzielenie słów nie jest problemem, ponieważ wszystko po nim =jest traktowane jako wartość declare, a nie tylko pierwsze słowo.

W bash4.3 nazwane odniesienia ułatwiają to.

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

Państwo może uczynić evalpracę, ale nadal nie należy :) Korzystając evaljest złym nawykiem, aby dostać się.

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

2
Używanie w eval ten sposób jest złe. Rozwijasz się $var_valueprzed przekazaniem go, evalco oznacza, że ​​będzie interpretowany jako kod powłoki! (spróbuj na przykład z var_value="';:(){ :|:&};:'")
Stéphane Chazelas,

1
Słuszna uwaga; istnieją pewne ciągi, których nie można bezpiecznie przypisać eval(co jest jednym z powodów, dla których powiedziałem, że nie powinieneś używać eval).
chepner

@chepner - Nie wierzę, że to prawda. może jest, ale przynajmniej nie ten. podstawienia parametrów pozwalają na rozszerzenie warunkowe, więc myślę, że w większości przypadków można rozwijać tylko bezpieczne wartości. nadal twoim głównym problemem $var_valuejest odwrócenie cudzysłowu - zakładając bezpieczną wartość $var_name (która może być tak samo niebezpiecznym założeniem, naprawdę) , wtedy powinieneś zawrzeć podwójne cudzysłowy w pojedynczych cudzysłowach - nie nawzajem.
mikeserv

Myślę, że naprawiłem format eval, using printfi jego bash-specyficzny %q. Nadal nie jest to zalecenie eval, ale myślę, że jest bezpieczniejsze niż wcześniej. Fakt, że musisz poświęcić tyle wysiłku, aby to zadziałało, jest dowodem na to, że powinieneś używać declarelub nazwać referencje.
chepner

Właściwie moim zdaniem nazwane referencje stanowią problem. Z mojego doświadczenia wynika, że ​​najlepszym sposobem jest użycie ... set -- a bunch of args; eval "process2 $(process1 "$@")"gdzie process1drukuje tylko cytowane liczby "${1}" "${8}" "${138}". To szalone proste - i tak łatwe jak '"${'$((i=$i+1))'}" 'w większości przypadków. indeksowane referencje sprawiają, że jest bezpieczny, solidny i szybki . Nadal - głosowałem.
mikeserv

4

Dobrym sposobem pracy evaljest zastąpienie go echotestem. echoi evaldziałają tak samo (jeśli odłożymy na bok \xrozszerzenie wykonane przez niektóre echoimplementacje, takie jak bashpod pewnymi warunkami).

Oba polecenia łączą argumenty z odstępem między nimi. Różnica polega na tym, że echo wyświetla wynik, podczas gdy eval ocenia / interpretuje jako kod powłoki wynik.

Tak więc, aby zobaczyć jaki kod powłoki

eval $(echo $var_name=$var_value)

oceni, możesz uruchomić:

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

Nie tego chcesz, czego chcesz:

fruit=$var_value

Również użycie $(echo ...)tutaj nie ma sensu.

Aby wyświetlić powyższe, uruchomiłbyś:

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

Aby to zinterpretować, to po prostu:

eval "$var_name=\$var_value"

Zauważ, że można go również użyć do ustawienia poszczególnych elementów tablicy:

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

Jak powiedzieli inni, jeśli nie zależy ci na tym bash, aby kod był konkretny, możesz użyć declarejako:

declare "$var_name=$var_value"

Należy jednak pamiętać, że ma pewne skutki uboczne.

Ogranicza zakres zmiennej do funkcji, w której jest uruchamiana. Nie można jej więc używać na przykład do:

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

Ponieważ zadeklarowałoby to foolokalną zmienną, setvarwięc byłoby to bezużyteczne.

bash-4.2dodaliśmy -gopcję declaredeklarowania zmiennej globalnej , ale nie tego też chcemy, ponieważ setvarustawilibyśmy zmienną globalną w przeciwieństwie do zmiennej wywołującej, gdyby funkcja wywołująca była jak w:

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"

co dałoby wynik:

1:
2: some value

Zauważ też, że chociaż declarejest wywoływany declare(faktycznie bashpożyczył koncepcję z typesetwbudowanej powłoki Korna ), jeśli zmienna jest już ustawiona, declarenie deklaruje nowej zmiennej, a sposób wykonania przypisania zależy od typu zmiennej.

Na przykład:

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

spowoduje inny wynik (i może mieć nieprzyjemne skutki uboczne), jeśli varnamezostał wcześniej zadeklarowany jako skalar , tablica lub tablica asocjacyjna .


2
Co jest złego w byciu specyficznym dla bash? OP postawił tag bash na pytaniu, więc używa bash. Podawanie alternatyw jest dobre, ale myślę, że mówienie komuś, by nie używał funkcji powłoki, ponieważ nie jest przenośna, jest głupie.
Patrick

@Patrick, widziałeś buźkę? Powiedziawszy to, użycie przenośnej składni oznacza mniejszy wysiłek, gdy trzeba przenieść swój kod do innego systemu, w którym bashnie jest on dostępny (lub gdy zdasz sobie sprawę, że potrzebujesz lepszej / szybszej powłoki). Te evalprace składniowe we wszystkich powłokach typu bourne i jest POSIX więc wszystkie systemy będą mieć shgdzie to działa. (oznacza to również, że moja odpowiedź dotyczy wszystkich pocisków, a prędzej czy później, jak to często się zdarza, zobaczysz zamknięte pytanie bez duplikatu jako duplikat tego)
Stéphane Chazelas

ale co jeśli $var_namezawiera tokeny? ... jak ;?
mikeserv

@mikeserv, to nie jest nazwa zmiennej. Jeżeli nie można ufać jego treść, to trzeba zdezynfekować ją zarówno evali declare(myśleć PATH, TMOUT, PS4, SECONDS...).
Stéphane Chazelas,

ale za pierwszym razem jest to zawsze rozszerzenie zmiennej, a nigdy nazwa zmiennej aż do drugiego. w mojej odpowiedzi dezynfekuję go rozszerzeniem parametrów, ale jeśli sugerujesz wykonanie sanityzacji w podpowłoce przy pierwszym przejściu, to równie dobrze można to zrobić przenośnie w / export. Nie podążam za nawiasami na końcu.
mikeserv

1

Jeśli zrobisz:

eval "$name=\$val"

... i $namezawiera jeden ;lub kilka innych tokenów, które powłoka może interpretować jako ograniczające proste polecenie - poprzedzone odpowiednią składnią powłoki, która zostanie wykonana.

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

WYNIK

hi
be careful with eval

Czasami jednak możliwe jest rozdzielenie oceny i wykonania takich oświadczeń. Na przykład aliasmożna użyć do wstępnej oceny polecenia. W poniższym przykładzie definicja zmiennej jest zapisywana w zmiennej, aliasktórą można pomyślnie zadeklarować tylko wtedy, gdy $nmzmienna, którą ocenia, nie zawiera bajtów, które nie pasują do znaków alfanumerycznych ASCII lub _.

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

evalsłuży tutaj do obsługi wywoływania nowego aliasz nazwy zmiennej. Ale wywoływana jest w ogóle tylko wtedy, gdy poprzednia aliasdefinicja jest udana i chociaż wiem, że wiele różnych implementacji zaakceptuje wiele różnych rodzajów aliasnazw, nie spotkałem się jeszcze z jedną, która zaakceptuje całkowicie pustą .

Definicja w tym aliasjest _$nmjednak, aby zapewnić, że żadne znaczące wartości środowiska nie zostaną zapisane. Nie znam żadnych wartych uwagi wartości środowiskowych rozpoczynających się od a, _i jest to zwykle bezpieczny zakład na deklarację półprywatną.

W każdym razie, jeśli aliasdefinicja się powiedzie, zadeklaruje wartość aliasnazwaną dla $nm. I evalwywoła to tylko aliaswtedy , gdy również nie zaczyna się od liczby - w przeciwnym razie evaldostaje tylko zerowy argument. Więc jeśli oba warunki są spełnione, evalwywołuje alias i tworzona jest definicja zmiennej zapisana w aliasie, po czym nowa aliasjest natychmiast usuwana z tabeli skrótów.


;nie jest dozwolone w nazwach zmiennych. Jeśli nie masz kontroli nad treścią $name, musisz ją zdezynfekować również dla export/ declare. Chociaż exportnie wykonuje kodu, ustawienie niektórych zmiennych PATH, PS4a wiele z nich info -f bash -n 'Bash Variables'ma równie niebezpieczne skutki uboczne.
Stéphane Chazelas

@ StéphaneChazelas - oczywiście nie jest dozwolone, ale, jak poprzednio, nie jest to nazwa zmiennej przy evalpierwszym przejściu - jest to rozszerzenie zmiennej. Jak powiedziałeś gdzie indziej, w tym kontekście jest to bardzo dozwolone. Mimo to argument $ PATH jest bardzo dobry - dokonałem niewielkiej edycji i dodam go później.
mikeserv

@ StéphaneChazelas - lepiej późno niż nigdy ...?
mikeserv

W praktyce zsh, pdksh, mksh, yashnie skarżą się na unset 'a;b'.
Stéphane Chazelas

Ty też będziesz chciał unset -v -- ....
Stéphane Chazelas
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.