[解決済み] Bashでevalを避けるべき理由と、代わりに何を使うべきか?
質問
Stack Overflow で何度も Bash の回答を見かけます。
eval
を使用した Bash の回答を見かけますが、そのような邪悪な構造を使用した回答は、意図的にシャレで、バッシングされます。なぜ
eval
が邪悪なのか?
もし
eval
が安全に使用できない場合、代わりに何を使用すればよいでしょうか?
どのように解決するのですか?
この問題には、見た目以上のものがあります。 まずは明白なことから始めましょう。
eval
は、quot;dirty" データを実行する可能性を持っています。 ダーティ データとは、状況に応じて安全に使用できる 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回解析することです。最初のパースでは、一重の引用符が取り除かれます。引用符が削除されると、いくつかの変数の内容が実行されます。
この問題は、変数の展開を
eval
. 二重引用符はそのままにして、すべてをシングルクオートで囲めばいいのです。 ひとつだけ例外があります。
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の代用品
特定のユースケースには、しばしば
eval
. ここに便利なリストがあります。
command
は、通常送信するものを表します。
eval
に送るものを表します。
ノーオープン
bashでは、単純なコロンはno-opです。
:
サブシェルを作成する
( 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!
一回きりの呼び出しなら、シェル全体をリダイレクトする必要はないでしょう。
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
.
連想配列
連想配列はbash4で本質的に実装されています。 一つ注意点があります。
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"`"
# ...
関連
-
deepin20ターミナルでサーバーをリモート管理する方法
-
[解決済み] usr/bin/env bash」と「#!/usr/bin/bash」の違いは何ですか?
-
[解決済み] time(1) の出力における 'real', 'user' および 'sys' はどのような意味ですか?
-
[解決済み] Bashで標準出力と標準エラーの両方をファイルにリダイレクトして追記する方法
-
[解決済み] なぜテキストファイルは改行で終わらなければならないのですか?
-
[解決済み] 好ましいBashのシェバングとは?
-
[解決済み] bashスクリプトでset -eはどういう意味ですか?
-
[解決済み] 最初の引数を除くすべての引数を処理する(bashスクリプトの場合)
-
[解決済み】Bashでファイル名と拡張子を抽出する。
-
[解決済み】bashで実行するために、実行ファイルやスクリプトの名前の前に./(ドットスラッシュ)が必要なのはなぜですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Linux ddコマンド詳細説明:データのバックアップ、およびバックアッププロセスでのフォーマット変換のケース
-
Linux sarコマンドによるシステム性能の詳細解析事例
-
Manjaro linuxでマウスの速度を設定する方法は?Manjaroのマウスのカーソル速度を設定する方法に関するヒント
-
ロックされたdeepinファイルを削除する方法は?deepinのロック付きファイルを削除する方法のヒント
-
Linux 5.10.10 正式版リリース:NULLポインタの不具合修正など。
-
[解決済み] 出力をファイルや標準出力にリダイレクトする方法
-
[解決済み] Bashでコマンドの前に環境変数を設定すると、パイプ内の2番目のコマンドで動作しない
-
[解決済み] Bashのevalコマンドとその典型的な使用法
-
[解決済み] シェルスクリプトでコマンドを変数に格納するにはどうすればよいですか?
-
[解決済み] シェルスクリプトからウェブブラウザを起動する方法は?