回答:
この問題には、目に見える以上のものがあります。eval
「ダーティ」データを実行する可能性があるという明白なものから始めましょう。ダーティデータとは、その場での使用に適したXYZとして書き換えられていないデータのことです。私たちの場合、それは評価のために安全であるようにフォーマットされていない任意の文字列です。
データのサニタイズは、一見すると簡単に見えます。オプションのリストを使い回していると仮定すると、bashはすでに個々の要素をサニタイズする優れた方法と、配列全体を単一の文字列としてサニタイズする別の方法を提供しています。
function println
{
# Send each element as a separate argument, starting with the second element.
# Arguments to printf:
# 1 -> "$1\n"
# 2 -> "$2"
# 3 -> "$3"
# 4 -> "$4"
# etc.
printf "$1\n" "${@:2}"
}
function error
{
# Send the first element as one argument, and the rest of the elements as a combined argument.
# Arguments to println:
# 1 -> '\e[31mError (%d): %s\e[m'
# 2 -> "$1"
# 3 -> "${*:2}"
println '\e[31mError (%d): %s\e[m' "$1" "${*:2}"
exit "$1"
}
# This...
error 1234 Something went wrong.
# And this...
error 1234 'Something went wrong.'
# Result in the same output (as long as $IFS has not been modified).
次に、出力を引数としてprintlnにリダイレクトするオプションを追加するとします。もちろん、呼び出しごとにprintlnの出力をリダイレクトすることもできますが、例として、これを行うことはしません。eval
変数は出力のリダイレクトに使用できないため、を使用する必要があります。
function println
{
eval printf "$2\n" "${@:3}" $1
}
function error
{
println '>&2' '\e[31mError (%d): %s\e[m' "$1" "${*:2}"
exit $1
}
error 1234 Something went wrong.
よさそうですね?問題は、evalが(任意のシェルで)コマンド行を2回解析することです。解析の最初のパスでは、引用符の1つの層が削除されます。引用符を削除すると、いくつかの可変コンテンツが実行されます。
これを修正するには、変数の展開を内で実行しeval
ます。私たちがしなければならないのは、すべてを単一引用符で囲み、二重引用符はそのままにしておきます。1つの例外:の前にリダイレクトを拡張する必要があるためeval
、引用符の外側にとどまる必要があります。
function println
{
eval 'printf "$2\n" "${@:3}"' $1
}
function error
{
println '&2' '\e[31mError (%d): %s\e[m' "$1" "${*:2}"
exit $1
}
error 1234 Something went wrong.
これはうまくいくはずです。また、長いほど安全だ$1
中がprintln
汚いことはありません。
ちょっと待ってください。私は最初からsudo
いつも使用していたのと同じ、引用符で囲まれていない構文を使用します。なぜここでは機能しないのですか?なぜすべてを一重引用符で囲まなければならないのですか? sudo
もう少し現代的です:単純化しすぎですが、受け取った各引数を引用符で囲むことがわかっています。 eval
単にすべてを連結します。
残念ながら、シェル組み込みのeval
ようにsudo
、引数をそうのように処理するためのドロップイン置換はありませんeval
。これは、関数のように新しいスタックとスコープを作成するのではなく、実行時に周囲のコードの環境とスコープを引き受けるため、重要です。
多くの場合、特定のユースケースには、に代わる実行可能な選択肢がありeval
ます。こちらが便利なリストです。 command
通常送信するものを表しますeval
。好きなように置き換えてください。
単純なコロンはbashでは何もしません:
:
( command ) # Standard notation
外部コマンドに依存しないでください。常に戻り値を制御する必要があります。これらを独自の行に配置します。
$(command) # Preferred
`command` # Old: should be avoided, and often considered deprecated
# Nesting:
$(command1 "$(command2)")
`command "\`command\`"` # Careful: \ only escapes $ and \ with old style, and
# special case \` results in nesting.
コードを呼び出す際に、ターゲット&3
(またはそれ以上&2
)をターゲットにマップします。
exec 3<&0 # Redirect from stdin
exec 3>&1 # Redirect to stdout
exec 3>&2 # Redirect to stderr
exec 3> /dev/null # Don't save output anywhere
exec 3> file.txt # Redirect to file
exec 3> "$var" # Redirect to file stored in $var--only works for files!
exec 3<&0 4>&1 # Input and output!
1回限りの呼び出しであれば、シェル全体をリダイレクトする必要はありません。
func arg1 arg2 3>&2
呼び出される関数内で、次の場所にリダイレクトし&3
ます。
command <&3 # Redirect stdin
command >&3 # Redirect stdout
command 2>&3 # Redirect stderr
command &>&3 # Redirect stdout and stderr
command 2>&1 >&3 # idem, but for older bash versions
command >&3 2>&1 # Redirect stdout to &3, and stderr to stdout: order matters
command <&3 >&4 # Input and output!
シナリオ:
VAR='1 2 3'
REF=VAR
悪い:
eval "echo \"\$$REF\""
どうして?REFに二重引用符が含まれていると、コードが破損し、コードが悪用されます。REFをサニタイズすることは可能ですが、これを行うと時間の無駄になります。
echo "${!REF}"
そうです、bashにはバージョン2の時点で変数の間接参照が組み込まれeval
ています。より複雑なことをしたい場合よりも少し注意が必要です。
# Add to scenario:
VAR_2='4 5 6'
# We could use:
local ref="${REF}_2"
echo "${!ref}"
# Versus the bash < 2 method, which might be simpler to those accustomed to eval:
eval "echo \"\$${REF}_2\""
いずれにせよ、新しい方法の方が直感的ですが、慣れてeval
いるプログラミング経験者にとってはそうではないかもしれません。
連想配列は本質的にbash 4に実装されています。1つの注意点:を使用して作成する必要がありますdeclare
。
declare -A VAR # Local
declare -gA VAR # Global
# Use spaces between parentheses and contents; I've heard reports of subtle bugs
# on some versions when they are omitted having to do with spaces in keys.
declare -A VAR=( ['']='a' [0]='1' ['duck']='quack' )
VAR+=( ['alpha']='beta' [2]=3 ) # Combine arrays
VAR['cow']='moo' # Set a single element
unset VAR['cow'] # Unset a single element
unset VAR # Unset an entire array
unset VAR[@] # Unset an entire array
unset VAR[*] # Unset each element with a key corresponding to a file in the
# current directory; if * doesn't expand, unset the entire array
local KEYS=( "${!VAR[@]}" ) # Get all of the keys in VAR
古いバージョンのbashでは、変数の間接参照を使用できます。
VAR=( ) # This will store our keys.
# Store a value with a simple key.
# You will need to declare it in a global scope to make it global prior to bash 4.
# In bash 4, use the -g option.
declare "VAR_$key"="$value"
VAR+="$key"
# Or, if your version is lacking +=
VAR=( "$VAR[@]" "$key" )
# Recover a simple value.
local var_key="VAR_$key" # The name of the variable that holds the value
local var_value="${!var_key}" # The actual value--requires bash 2
# For < bash 2, eval is required for this method. Safe as long as $key is not dirty.
local var_value="`eval echo -n \"\$$var_value\""
# If you don't need to enumerate the indices quickly, and you're on bash 2+, this
# can be cut down to one line per operation:
declare "VAR_$key"="$value" # Store
echo "`var_key="VAR_$key" echo -n "${!var_key}"`" # Retrieve
# If you're using more complex values, you'll need to hash your keys:
function mkkey
{
local key="`mkpasswd -5R0 "$1" 00000000`"
echo -n "${key##*$}"
}
local var_key="VAR_`mkkey "$key"`"
# ...
export "$var"="$val"
おそらくあなたが望むものです。フォームを使用する可能性があるのは、if var='$var2'
であり、それを二重逆参照したい場合だけですが、bashでそのようなことをするべきではありません。本当に必要な場合は、を使用できますexport "${!var}"="$val"
。
x="echo hello world";
その後に含まれているものは何でも実行するためにx
私たちが使用することができ、eval $x
しかし、$($x)
それは、間違っていないのですか? はい:$($x)
不正解です。echo hello world
実行され、キャプチャされた出力を実行しようとします(少なくとも、私が使用していると思われるコンテキストで)hello
。
ref="${REF}_2" echo "${!ref}"
例は間違っています。コマンドが実行される前に bashが変数を置き換えるため、意図したとおりに機能しません。ref
以前に本当に変数が定義されていない場合、置換の結果はref="VAR_2" echo ""
となり、それが実行されます。
eval
安全にする方法eval
安全に使用できますが、その引数はすべて最初に引用する必要があります。方法は次のとおりです。
あなたのためにそれをするこの関数:
function token_quote {
local quoted=()
for token; do
quoted+=( "$(printf '%q' "$token")" )
done
printf '%s\n' "${quoted[*]}"
}
使用例:
信頼できないユーザー入力がある場合:
% input="Trying to hack you; date"
評価するコマンドを作成します。
% cmd=(echo "User gave:" "$input")
一見正しい引用でそれを評価します:
% eval "$(echo "${cmd[@]}")"
User gave: Trying to hack you
Thu Sep 27 20:41:31 +07 2018
ハッキングされたことに注意してください。date
文字通りに印刷されるのではなく、実行されました。
代わりにtoken_quote()
:
% eval "$(token_quote "${cmd[@]}")"
User gave: Trying to hack you; date
%
eval
悪ではありません-それは単に誤解されています:)
in words
一部for
はオプションです。
arg="$1"
ですか?forループは、関数に渡された引数をどのようにして知るのですか?
eval
あり、言語によってすでに提供されているより良いオプションがないことを確認するために綿密に検討する必要があります。
eval "export $var='$val'"
...(?)への言及がありません