シェルスクリプトの一般的な規則は、特別な理由がない限り、常に変数を引用する必要があることです。詳細については、次のQ&Aをご覧ください。bash / POSIXシェルで変数を引用することを忘れてしまうセキュリティリスク。
ただし、次の機能を検討してください。
run_this(){
$@
}
そこに引用する必要がありますか$@
?私はしばらく遊んでいましたが、引用符の欠落によって問題が発生した状況を見つけることができませんでした。一方、引用符を使用すると、スペースを含むコマンドを引用符で囲まれた変数に渡すと問題が発生します。
#!/usr/bin/sh
set -x
run_this(){
$@
}
run_that(){
"$@"
}
comm="ls -l"
run_this "$comm"
run_that "$comm"
上記のスクリプトを実行すると、次のものが返されます。
$ a.sh
+ comm='ls -l'
+ run_this 'ls -l'
+ ls -l
total 8
-rw-r--r-- 1 terdon users 0 Dec 22 12:58 da
-rw-r--r-- 1 terdon users 45 Dec 22 13:33 file
-rw-r--r-- 1 terdon users 43 Dec 22 12:38 file~
+ run_that 'ls -l'
+ 'ls -l'
/home/terdon/scripts/a.sh: line 7: ls -l: command not found
run_that $comm
代わりに、この問題を解決することができますが、(参照されていない)関数は両方とも機能するため、run_that "$comm"
これrun_this
はより安全なオプションのようです。
それでは、$@
コマンドで実行される関数で使用される具体的なケースでは、引用符で囲む必要がありますか?引用しなければならない理由や引用すべきではない理由を説明し、引用を壊す可能性があるデータの例を示します。$@
$@
答え1
問題は、コマンドが関数に渡される方法です。
$ run_this ls -l Untitled\ Document.pdf
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_that ls -l Untitled\ Document.pdf
-rw------- 1 muru muru 33879 Dec 20 11:09 Untitled Document.pdf
"$@"
as inrun_that
は、関数の前に一般的に書かれたコマンド(上記のように)がプレフィックスで付けられる一般的な場合に使用する必要があります。
引用符のない文字を使用しようとすると、スペースを含むファイル名は渡され$@
ませrun_this
ん。次の試みのいずれも機能しません。
$ run_this 'ls -l Untitled\ Document.pdf'
ls: cannot access Untitled\: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_this 'ls -l "Untitled\ Document.pdf"'
ls: cannot access "Untitled\: No such file or directory
ls: cannot access Document.pdf": No such file or directory
$ run_this 'ls -l Untitled Document.pdf'
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_this 'ls -l' 'Untitled Document.pdf'
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory
うまくいかないのは、引用符のない拡張子が単語分割を通過してスペースに分割され、引用符などを解釈する方法を提供しないためです(これを使用する必要がありますeval
)。
たとえば、参照してください。
- バッシュFAQ 050(または「コマンドを変数に入れようとしましたが、複雑なケースは常に失敗しました!」)
- 変数に保存されたコマンドをどのように実行できますか?
答え2
それ:
interpret_this_shell_code() {
eval "$1"
}
または:
interpret_the_shell_code_resulting_from_the_concatenation_of_those_strings_with_spaces() {
eval "$@"
}
または:
execute_this_simple_command_with_these_arguments() {
"$@"
}
しかし:
execute_the_simple_command_with_the_arguments_resulting_from_split+glob_applied_to_these_strings() {
$@
}
あまり意味がありません。
ls -l
コマンドを実行するには(引数ls
でコマンドを実行せずに)、次のようにします。ls
-l
interpret_this_shell_code '"ls -l"'
execute_this_simple_command_with_these_arguments 'ls -l'
ただし、(可能性が高い)これが引数をls
含むコマンドであるls
場合は、-l
次のようにします。
interpret_this_shell_code 'ls -l'
execute_this_simple_command_with_these_arguments ls -l
これで、単純なコマンド以上の操作を実行し、変数の割り当て、リダイレクト、パイピングを実行するには、次の手順を実行しますinterpret_this_shell_code
。
interpret_this_shell_code 'ls -l 2> /dev/null'
もちろん、いつでもこれを行うことができます。
execute_this_simple_command_with_these_arguments eval '
ls -l 2> /dev/null'
答え3
bash / ksh / zshの観点から見ると、
$*
これらはすべて$@
通常の配列拡張の特殊なケースです。配列拡張は通常の変数拡張とは異なります。
$ a=("a b c" "d e" f)
$ printf ' -> %s\n' "${a[*]}"
-> a b c d e f
$ printf ' -> %s\n' "${a[@]}"
-> a b c
-> d e
-> f
$ printf ' -> %s\n' ${a[*]}
-> a
-> b
-> c
-> d
-> e
-> f
$ printf ' -> %s\n' ${a[@]}
-> a
-> b
-> c
-> d
-> e
-> f
$*
/拡張機能を使用すると、最初の値(デフォルトでは空白)を持つ${a[*]}
配列を1つの巨大な文字列に連結できます。IFS
引用しないと、通常の文字列のように分割されます。
$@
/拡張の場合/拡張が引用されているかどうか${a[@]}
によって動作が異なります。$@
${a[@]}
"$@"
(または)で引用すると、またはと"${a[@]}"
同等の結果が得られます。"$1" "$2" "$3" #...
"${a[1]}" "${a[2]}" "${a[3]}" # ...
$@
(または)で囲まないと、または${a[@]}
同等の結果が得られます。$1 $2 $3 #...
${a[1]} ${a[2]} ${a[3]} # ...
コマンドをラップするには必ず必要です。引用@拡張(1.)。
bash(およびbashのような)配列に関する追加情報:https://lukeshu.com/blog/bash-arrays.html
答え4
変数の実行はbash
失敗しやすい技術です。run_this
次のようなすべての極端なケースを正しく処理する関数を書くことは不可能です。
- パイプ(例
ls | grep filename
:) - 入力/出力リダイレクト(例
ls > /dev/null
:) if
while
次のシェルドア
コードの重複を避けたい場合は、関数を使用することをお勧めします。たとえば、次のようになります。
run_this(){
"$@"
}
command="ls -l"
...
run_this "$command"
あなたは書かなければなりません
command() {
ls -l
}
...
command
これらのコマンドを実行時にのみ使用できる場合は、eval
エラーを引き起こすすべての問題を処理するように特別に設計されたコマンドを使用する必要がありますrun_this
。
command="ls -l | grep filename > /dev/null"
...
eval "$command"
注意しeval
てくださいみんな知ってるセキュリティ上の理由から、信頼できないソースから変数が渡されると、任意のrun_this
コード実行にさらされる可能性があります。