web-dev-qa-db-ja.com

bashでの間接変数の割り当て

Bashで間接変数を設定する推奨される方法は、evalを使用することです。

var=x; val=foo
eval $var=$val
echo $x  # --> foo

問題はevalの通常の問題です:

var=x; val=1$'\n'pwd
eval $var=$val  # bad output here

(そしてそれは多くの場所で推奨されているので、このために脆弱なスクリプトがいくつあるのだろうか...)

いずれにしても、(エスケープされた)引用符を使用する明白な解決策は実際には機能しません。

var=x; val=1\"$'\n'pwd\"
eval $var=\"$val\"  # fail with the above

問題は、bashに間接変数参照が組み込まれていることです(${!foo})、しかし、間接割り当てを行うそのような方法は見当たりません-これを行うための健全な方法はありますか?

記録のために、私は解決策を見つけましたが、これは私が「正気」と考えるものではありません...:

eval "$var='"${val//\'/\'\"\'\"\'}"'"
34
Eli Barzilay

主なポイントは、これを行うための推奨される方法は次のとおりです。

eval "$var=\$val"

rHSも間接的に行われます。 evalは同じ環境で使用されるため、$valバインドされているので、延期は機能し、現在は単なる変数です。 $val変数には既知の名前があり、引用に問題はなく、次のように記述することもできます。

eval $var=\$val

しかし、常に引用符を追加する方が良いため、前者の方が優れています。

eval "$var=\"\$val\""

evalを完全に回避する(およびdeclareなどのように微妙ではない)全体について言及されたbashのより良い代替:

printf -v "$var" "%s" "$val"

これは私が最初に尋ねたものに対する直接的な答えではありませんが...

11
Eli Barzilay

evalを使用することで生じる可能性のあるセキュリティの影響を回避するための少し良い方法は、

declare "$var=$val"

declareは、typesetbashの同義語であることに注意してください。 typesetコマンドはより広くサポートされています(kshzshも使用しています):

typeset "$var=$val"

bashの最新バージョンでは、namerefを使用する必要があります。

declare -n var=x
x=$val

evalより安全ですが、まだ完全ではありません。

28
chepner

Bashには、結果を変数に保存するprintfの拡張機能があります。

printf -v "${VARNAME}" '%s' "${VALUE}"

これにより、発生する可能性のあるすべての問題が回避されます。

$VARNAMEに無効な識別子を使用すると、コマンドは失敗し、ステータスコード2が返されます。

$ printf -v ';;;' foobar; echo $?
bash: printf: `;;;': not a valid identifier
2
27
David Foerster
eval "$var=\$val"

evalの引数は、常に単一引用符または二重引用符で囲まれた単一の文字列でなければなりません。このパターンから逸脱するすべてのコードには、特殊文字を含むファイル名など、Edgeの場合に意図しない動作があります。

evalへの引数がシェルによって展開されると、$varは変数名に置き換えられ、\$は単純なドルに置き換えられます。したがって、評価される文字列は次のようになります。

varname=$value

これはまさにあなたが望むものです。

通常、$varname形式のすべての式は二重引用符で囲む必要があります。引用符を省略できる場所は、変数の割り当てとcaseの2つだけです。これは変数の割り当てなので、ここでは引用符は必要ありません。ただし、これらは害を与えないため、元のコードを次のように書くこともできます。

eval "$var=\"the value is $val\""
18
Roland Illig

Bashの新しいバージョンは、bash(1)の同じ名前のセクションに記載されている「パラメーター変換」と呼ばれるものをサポートしています。

"${value@Q}"は、シェル引用符付きバージョンの"${value}"入力として再利用できます。

これは、以下が安全なソリューションであることを意味します。

eval="${varname}=${value@Q}"
2
Darren Embry

完全を期すために、bash in readの可能な使用方法も提案したいと思います。 socowiのコメントに基づいて-d ''についても修正しました。

ただし、readを使用して入力が完全にサニタイズされるように注意する必要があります(-d ''はnull終了まで読み取り、printf "...\0"はnullで値を終了します)。その読み取り自体はサブシェルではなく、変数が必要なメインシェル(<<(...)構文)。

var=x; val=foo0shouldnotterminateearly
read -d'' -r "$var" < <(printf "$val\0")
echo $x  # --> foo0shouldnotterminateearly
echo ${!var} # -->  foo0shouldnotterminateearly

私はこれを\ n\t\rスペースと0などでテストしました。bashの私のバージョンでは期待通りに動作しました。

-rは\のエスケープを回避するため、値に文字「\」と「n」があり、実際の改行ではない場合、xには2つの文字「\」と「n」も含まれます。

このメソッドは、evalまたはprintfソリューションほど美しいものではなく、値がファイルまたは他の入力ファイル記述子から入力される場合により便利です。

read -d'' -r "$var" < <( cat $file )

そして、これは<<()構文の代替案です。

read -d'' -r "$var" <<< "$val"$'\0'
read -d'' -r "$var" < <(printf "$val") #Apparently I didn't even need the \0, the printf process ending was enough to trigger the read to finish.

read -d'' -r "$var" <<< $(printf "$val") 
read -d'' -r "$var" <<< "$val"
read -d'' -r "$var" < <(printf "$val")
1
forbidder