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//\'/\'\"\'\"\'}"'"
主なポイントは、これを行うための推奨される方法は次のとおりです。
eval "$var=\$val"
rHSも間接的に行われます。 eval
は同じ環境で使用されるため、$val
バインドされているので、延期は機能し、現在は単なる変数です。 $val
変数には既知の名前があり、引用に問題はなく、次のように記述することもできます。
eval $var=\$val
しかし、常に引用符を追加する方が良いため、前者の方が優れています。
eval "$var=\"\$val\""
eval
を完全に回避する(およびdeclare
などのように微妙ではない)全体について言及されたbashのより良い代替:
printf -v "$var" "%s" "$val"
これは私が最初に尋ねたものに対する直接的な答えではありませんが...
eval
を使用することで生じる可能性のあるセキュリティの影響を回避するための少し良い方法は、
declare "$var=$val"
declare
は、typeset
のbash
の同義語であることに注意してください。 typeset
コマンドはより広くサポートされています(ksh
とzsh
も使用しています):
typeset "$var=$val"
bash
の最新バージョンでは、namerefを使用する必要があります。
declare -n var=x
x=$val
eval
より安全ですが、まだ完全ではありません。
Bashには、結果を変数に保存するprintf
の拡張機能があります。
printf -v "${VARNAME}" '%s' "${VALUE}"
これにより、発生する可能性のあるすべての問題が回避されます。
$VARNAME
に無効な識別子を使用すると、コマンドは失敗し、ステータスコード2が返されます。
$ printf -v ';;;' foobar; echo $?
bash: printf: `;;;': not a valid identifier
2
eval "$var=\$val"
eval
の引数は、常に単一引用符または二重引用符で囲まれた単一の文字列でなければなりません。このパターンから逸脱するすべてのコードには、特殊文字を含むファイル名など、Edgeの場合に意図しない動作があります。
eval
への引数がシェルによって展開されると、$var
は変数名に置き換えられ、\$
は単純なドルに置き換えられます。したがって、評価される文字列は次のようになります。
varname=$value
これはまさにあなたが望むものです。
通常、$varname
形式のすべての式は二重引用符で囲む必要があります。引用符を省略できる場所は、変数の割り当てとcase
の2つだけです。これは変数の割り当てなので、ここでは引用符は必要ありません。ただし、これらは害を与えないため、元のコードを次のように書くこともできます。
eval "$var=\"the value is $val\""
Bashの新しいバージョンは、bash(1)の同じ名前のセクションに記載されている「パラメーター変換」と呼ばれるものをサポートしています。
"${value@Q}"
は、シェル引用符付きバージョンの"${value}"
入力として再利用できます。
これは、以下が安全なソリューションであることを意味します。
eval="${varname}=${value@Q}"
完全を期すために、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")