実行時に変数を引用する必要がありますか?


18

シェルスクリプトの一般的なルールは、やむを得ない理由がない限り、変数を常に引用することです。あなたがおそらく知りたいと思うより多くの詳細については、この素晴らしいQ&Aを見てください:bash / POSIX shellsで変数を引用するのを忘れることのセキュリティの意味

ただし、次のような関数を検討してください。

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(引用されていない)関数は両方で機能するため、より安全な賭けのようです。

したがって、コマンドとして$@実行するジョブを持つ関数で使用する特定のケースでは、引用する$@必要$@がありますか?引用すべき/すべきでない理由を説明し、それを破る可能性のあるデータの例を挙げてください。


6
run_thatの動作は間違いなく私が期待するものです(コマンドへのパスにスペースがある場合はどうなりますか?)。他の動作が必要な場合は、コールサイトでデータの内容を知っている場所で引用符を外してください。この関数をとして呼び出すことを期待しますがrun_that ls -l、どちらのバージョンでも同じように機能します。違った期待をした事例はありますか?
マイケルホーマー

@MichaelHomer私のここでの編集はこれを促したと思います:unix.stackexchange.com/a/250985/70524
muru

何らかの理由で@MichaelHomer(おそらく2番目のコーヒーを飲んでいないため)コマンドの引数またはパスにスペースを考慮せず、コマンド自体(オプション)にのみスペースを考慮しました。よくあることですが、これは振り返ってみると非常に明らかです。
テルドン

単純にコマンドを配列に詰めて実行するのではなく、シェルがまだ関数をサポートしているのには理由があります${mycmd[@]}
chepner

回答:


20

問題は、コマンドが関数に渡される方法にあります。

$ 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

"$@"run_this関数が通常書かれたコマンドの前に付けられる一般的な場合に使用されるべきです。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

にファイル名をスペースで渡す方法がわかりませんrun_this


1
実際にこれを促したのはあなたの編集でした。何らかの理由で、スペースを含むファイル名でテストすることは私にはまったく起こりませんでした。なぜそうなのか全く分かりませんが、そこに行きます。もちろん、あなたはまったく正しいです、私はrun_thisどちらかでこれを正しく行う方法がわかりません。
テルドン

@terdonのクォートは習慣になりすぎて、誤ってクォートされないままにしておいたと思います$@。例を残すべきでした。:D
ムル

2
いや、それは実際に非常に多くの習慣であり、(間違って)テストし、「これは引用符を必要としないかもしれない」と結論付けました。一般的にブレインファートとして知られる手順。
テルドン

1
スペースを含むファイル名をに渡すことはできませんrun_this。これは基本的に、Bash FAQ 050で説明されているように、複雑なコマンドを文字列に詰め込むときに発生する問題と同じです。
エタンReisner

9

次のいずれかです。

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コマンドを(引数としてlslsおよび-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'

5

バッシュ/ 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[*]}拡張あなたが最初の値と結合配列を取得IFSスペースは、デフォルト-に1つの巨大な文字列で-whichです。引用符を付けないと、通常の文字列のように分割されます。

$@/ ${a[@]}拡張、動作はするかどうかに依存して$@/の${a[@]}拡張が引用されたりされていません。

  1. 引用符で囲まれている場合("$@"または"${a[@]}")、同等の "$1" "$2" "$3" #... または"${a[1]}" "${a[2]}" "${a[3]}" # ...
  2. 引用符で囲まれていない場合($@または${a[@]})、同等の $1 $2 $3 #... または${a[1]} ${a[2]} ${a[3]} # ...

ラッピングコマンドの場合は、引用符で囲まれた@展開(1.)が必要です。


bash(およびbashのような)配列に関するより良い情報:https : //lukeshu.com/blog/bash-arrays.html


1
Vaderマスクを着用しているときに、Lukeで始まるリンクを参照していることに気付きました。この投稿では力が強い。
PSkocik

4

二重引用符を使用しない場合、関数に指定$@たリンクにすべての問題を残しているためです。

というコマンドをどのように実行できます*か?あなたはそれをすることはできませんrun_this

$ ls
1 2
$ run_this '*'
dash: 2: 1: not found
$ run_that '*'
dash: 3: *: not found

また、エラーが発生しrun_thatた場合でも、より意味のあるメッセージが表示されます。

$@個々の単語に展開する唯一の方法は、二重引用符で囲むことです。コマンドとして実行する場合は、コマンドとパラメーターを別々の単語として渡す必要があります。関数内ではなく、呼び出し側で行ったこと。

$ cmd=ls
$ param1=-l
$ run_that "$cmd" "$param1"
total 0
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 1
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 2

より良い選択です。または、シェルが配列をサポートしている場合:

$ cmd=(ls -l)
$ run_that "${cmd[@]}"
total 0
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 1
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 2

シェルが配列をまったくサポートしていない場合でも、を使用して配列で遊ぶ"$@"ことができます。


3

変数の実行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、あなただけのようにも任意のコード実行に直面するだろう。


1

選択はあなた次第です。$@値を引用しない場合、追加の展開と解釈が行われます。引用すると、関数に渡されたすべての引数がそのまま展開されます。&>|とにかく自分自身で引数を解析することなく、などのようなシェル構文トークンを確実に処理することは決してできません-したがって、次のいずれかの関数を渡すより合理的な選択肢が残ります:

  1. を使用した単一の単純なコマンドの実行で使用される正確な単語"$@"

...または...

  1. 引数をさらに展開および解釈したバージョン。これは、と一緒に単純なコマンドとして一緒に適用されるだけ$@です。

意図的であり、選択したものの効果が十分に理解されている場合、どちらの方法も間違っていません。どちらの方法にも利点がありますが、2番目の方法の利点が特に役立つことはほとんどありません。それでも...

(run_this(){ $@; }; IFS=@ run_this 'ls@-dl@/tmp')

drwxrwxrwt 22 root root 660 Dec 28 19:58 /tmp

...そうではありません役に立たない、ただまれそうなくらいであるように使用。また、bashシェルでは、特別なビルトインのコマンドラインまたは関数の前に定義が追加されてbashいても、デフォルトでは変数定義を環境に固定しないため、グローバル値$IFSは影響を受けず、その宣言はローカルですrun_this()呼び出しのみ。

同様に:

(run_this(){ $@; }; set -f; run_this ls -l \*)

ls: cannot access *: No such file or directory

...グロビングも構成可能です。引用符は目的に役立ちます-それらは無料です。それらがなければ、シェル拡張は追加の解釈を受けます - 設定可能な解釈。以前は-非常に古いシェルで- 展開だけでなく、すべての入力$IFSグローバルに適用されていました。実際、シェルrun_this()はの値ですべての入力語を壊したという点で非常によく似た動作をします$IFS。したがって、探しているのが非常に古いシェルの動作である場合は、を使用する必要がありますrun_this()

私はそれを探しているわけではありません。そして、現時点で有用な例を見つけるのにかなり苦労しています。一般的に、シェルで実行するコマンドは、入力するコマンドを好む。ですから、選択肢があれば、私はほとんどいつもそうしrun_that()ます。それ以外で...

(run_that(){ "$@"; }; IFS=l run_that 'ls' '-ld' '/tmp')

drwxrwxrwt 22 root root 660 Dec 28 19:58 /tmp

ほぼ何でも引用できます。コマンドは引用符付きで実行されます。コマンドが実際に実行されるまでに、すべての入力語はすでに引用除去を行っているため、これが機能します。これはシェルの入力解釈プロセスの最後の段階です。違いはそう'ls'ls問題だけシェルが解釈されることができますが-と引用理由ですlsどんなことを保証し、エイリアスの名前がls私の引用されたために置換されていないlsコマンドワードを。それ以外に、引用符が影響するのは、単語の区切り(変数/入力空白の引用が機能する方法と理由)と、メタ文字と予約語の解釈だけです。

そう:

'for' f in ...
 do   :
 done

bash: for: command not found
bash:  do: unexpected token 'do'
bash:  do: unexpected token 'done'

run_this()またはのいずれかでそれを行うことはできませんrun_that()

しかし、関数名、または$PATH「dコマンド、または組み込みコマンドがうまく引用したり引用符で囲まれていない実行され、それは正確にどのようにだrun_this()run_that()最初の場所での作業。$<>|&(){}これらのいずれでも有用なことはできません。の略evalです。

(run_that(){ "$@"; }; run_that eval printf '"%s\n"' '"$@"')

eval
printf
"%s\n"
"$@"

しかし、それがないと、使用する引用符によって単純なコマンドの制限に制限されます$@コマンドがメタ文字を解析するときにプロセスの開始時に引用符のように振る舞わないため)。同じ制約は、コマンドラインの割り当てとリダイレクトにも当てはまりますが、これらは関数のコマンドラインに限定されます。しかし、それは大したことではありません:

(run_that(){ "$@";}; echo hey | run_that cat)

hey

パイプを開いたときと同じくらい簡単に<入力または>出力をリダイレクトできます。

とにかく、回り道で、ここには正しい方法も間違った方法もありません-それぞれの方法には用途があります。使用するつもりで書いて、あなたが何をするつもりなのかを知っておくべきです。引用符を省略することには目的があります -そうでなければ、引用符はまったくありません-しかし、目的に関係のない理由引用符を省略すると、単に悪いコードを書いていることになります。あなたが意味することをしてください。とにかくやってみます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.