$ ls -l /tmp/test/my\ dir/ total 0
上記のコマンドを実行する次の方法が失敗または成功するのはなぜですか?
$ abc="ls -l "/tmp/test/my dir"" $ $abc ls: cannot access ""/tmp/test/my": No such file or directory ls: cannot access "dir"": No such file or directory $ "$abc" bash: ls -l "/tmp/test/my dir": No such file or directory $ bash -c $abc "my dir" $ bash -c "$abc" total 0 $ eval $abc total 0 $ eval "$abc" total 0
コメント
- mywiki.wooledge.org/BashFAQ / 050
- bash / POSIXシェルで変数を引用するのを忘れた場合のセキュリティへの影響—しかし、もし…?
回答
これはunix.SEに関する多くの質問で議論されています、私はすべてを収集しようとしますここで思いつくことができる問題。最後の参照。
失敗する理由
これらの問題に直面する理由は、単語の分割と、変数から展開された引用符は引用符として機能せず、単なる通常の文字です。
質問で提示されたケース:
ここでの割り当てでは、単一の文字列からabc
:
$ abc="ls -l "/tmp/test/my dir""
以下、$abc
は空白で分割され、ls
は3つの引数-l
、"/tmp/test/my
およびdir"
(2番目の前に引用符があり、3番目の後ろに別の引用符があります)。このオプションは機能しますが、パスが正しく処理されません。
$ $abc ls: cannot access ""/tmp/test/my": No such file or directory ls: cannot access "dir"": No such file or directory
ここでは、展開が引用されているため、1つの単語として保持されます。シェルは試行します。文字通りls -l "/tmp/test/my dir"
と呼ばれるプログラムを見つけるには、スペースと引用符を含めます。
$ "$abc" bash: ls -l "/tmp/test/my dir": No such file or directory
ここでは、$abc
は分割され、最初の結果の単語のみが-c
の引数として使用されるため、Bashはls
。他の単語はbashの引数であり、$0
、$1
などを埋めるために使用されます。
$ bash -c $abc "my dir"
bash -c "$abc"
およびeval "$abc"
を使用すると、追加の引用符を機能させるシェル処理ステップですが、すべてのシェル展開が再度処理されるため、「ユーザーが指定したデータからのコマンド置換など、誤って実行するリスクがあります」。非常に注意してください引用については十分です。
より良い方法
コマンドを保存する2つのより良い方法は、a)代わりに関数を使用する、b)配列変数を使用する(または位置パラメータ)。
関数の使用:
単に宣言するコマンドを内部に持つ関数であり、コマンドであるかのように関数を実行します。関数内のコマンドの展開は、コマンドが定義されたときではなく、実行されたときにのみ処理されます。個々のコマンドを引用符で囲む必要はありません。
# define it myls() { ls -l "/tmp/test/my dir" } # run it myls
配列の使用:
配列を使用すると、個々の単語に白が含まれる複数単語の変数を作成できますスペース。ここでは、個々の単語が個別の配列要素として格納され、"${array[@]}"
展開により、各要素が個別のシェル単語として展開されます。
# define the array mycmd=(ls -l "/tmp/test/my dir") # run the command "${mycmd[@]}"
構文は少しひどいですが、配列を使用すると、コマンドラインを1つずつ作成することもできます。例:
mycmd=(ls) # initial command if [ "$want_detail" = 1 ]; then mycmd+=(-l) # optional flag fi mycmd+=("$targetdir") # the filename "${mycmd[@]}"
または、コマンドラインの一部を一定に保ち、オプションやファイル名など、その一部だけを埋める配列を使用します。
options=(-x -v) files=(file1 "file name with whitespace") target=/somedir transmutate "${options[@]}" "${files[@]}" "$target"
配列の欠点は、それらが標準機能ではないため、プレーンなPOSIXシェル(dash
など)がデフォルトであるということです。 Debian / Ubuntuの/bin/sh
はそれらをサポートしていません(ただし、以下を参照してください)。ただし、Bash、ksh、zshは機能するため、システムに配列をサポートするシェルがある可能性があります。
名前付き配列をサポートしていないシェルでも、位置パラメーター(疑似配列)コマンドの引数を保持します。
以下は、前のセクションのコードビットと同等の移植可能なスクリプトビットである必要があります。配列は
、位置パラメータのリスト。"$@"
の設定はset
と二重引用符で行います。 "$@"
の周りが重要です(これらにより、リストの要素が個別に引用されます)。
まず、引数付きのコマンドを"$@"
に保存し、実行します。
set -- ls -l "/tmp/test/my dir" "$@"
コマンドのコマンドラインオプションの一部を条件付きで設定する:
set -- ls if [ "$want_detail" = 1 ]; then set -- "$@" -l fi set -- "$@" "$targetdir" "$@"
オプションとオペランドに"$@"
のみを使用する:
set -- -x -v set -- "$@" file1 "file name with whitespace" set -- "$@" /somedir transmutate "$@"
(もちろん、"$@"
は通常、スクリプト自体への引数で埋められているので、あなたは ” "$@"
を転用する前に、それらをどこかに保存する必要があります。)
eval
に注意してください!
eval
では追加レベルの見積もりと展開処理が導入されるため、ユーザー入力に注意する必要があります。たとえば、これはユーザーがいる限り機能します。一重引用符を入力しません:
read -r filename cmd="ls -l "$filename"" eval "$cmd";
ただし、入力が"$(uname)".txt
の場合、スクリプトは問題なく入力されます。コマンド置換を実行します。
配列を含むバージョンはthの影響を受けません。単語は常に分離されているため、filename
の内容に対する引用やその他の処理はありません。
read -r filename cmd=(ls -ld -- "$filename") "${cmd[@]}"
参照
- 単語分割 in BashGuide
- BashFAQ / 050または"コマンドを入力しようとしています変数内にありますが、複雑なケースは常に失敗します!"
- 質問なぜですかシェルスクリプトが空白やその他の特殊文字でチョークしますか?。コマンドの保存など、引用や空白に関連する多くの問題について説明しています。
コメント
-
cmd="ls -l $(printf "%q" "$filename")"
を実行すると、評価の引用を回避できます。きれいではありませんが、ユーザーがeval
を使用することに夢中になっている場合は、役に立ちます。 'は、ssh foohost "ls -l $(printf "%q" "$filename")"
などの同様の方法で、または次の質問の内容でコマンドを送信する場合にも非常に役立ちます:ssh foohost "$cmd"
。 - 直接関係はありませんが、ディレクトリをハードコーディングしましたか?その場合は、エイリアスを確認することをお勧めします。次のようなもの:
$ alias abc='ls -l "/tmp/test/my dir"'
回答
最も安全な方法(重要な)コマンドの実行はeval
です。次に、コマンドラインで行うのと同じようにコマンドを記述でき、入力したときとまったく同じように実行されます。ただし、すべてを引用する必要があります。
単純なケース:
abc="ls -l "/tmp/test/my dir"" eval "$abc"
それほど単純ではないケース:
# command: awk "! a[$0]++ { print "foo: " $0; }" inputfile abc="awk "\""! a[$0]++ { print "foo: " $0; }"\"" inputfile" eval "$abc"
コメント
- こんにちは@HaukeLaging、\ ' '?
- @jumping_monkey
'
で引用された文字列内に'
を含めることはできません。したがって、(1)引用符を終了/中断し、(2)次の'
をエスケープして文字どおりに取得し、(3)文字列の引用符を入力し、(4)を入力する必要があります。中断の場合は、引用符で囲まれた文字列を再開します:(1)'(2)\(3)'(4)'
- こんにちは@HaukeLaging、興味深いです。
abc='awk \'! a[$0]++ { print "foo: " $0; }\' inputfile'
だけではありません。 ? - @jumping_monkeyそれが機能しない理由を説明したことは言うまでもありません。'コードを投稿する前にテストするのは理にかなっていますか?
回答
2番目の引用符はコマンドを中断します。
実行時:
abc="ls -l "/home/wattana/Desktop"" $abc
エラーが発生しました。
しかし、実行すると
abc="ls -l /home/wattana/Desktop" $abc
エラーはまったくありません
現時点ではこれを修正する方法はありませんが(私にとって)、ディレクトリ名にスペースを入れないことでエラーを回避できます。
この回答は、evalコマンドを使用してこれを修正できると述べていますが、私には機能しません:(
コメント
- ええ、'などの必要がない限り機能します。スペースが埋め込まれたファイル名(またはglob文字を含むファイル名)。
回答
これが<で機能しない場合div id = "ce52beb0e5">
の場合は、``
を使用する必要があります:
abc=`ls -l /tmp/test/my\ dir/`
より適切に更新する方法:
abc=$(ls -l /tmp/test/my\ dir/)
コメント
- これにより、コマンドの結果が変数に格納されます。 OPは(奇妙なことに)コマンド自体を変数に保存したいと考えています。ああ、バックティックの代わりに
$( command... )
を使い始める必要があります。 - 説明とヒントをありがとうございます!
回答
python3ワンライナーはどうですか?
bash-4.4# pwd /home bash-4.4# CUSTOM="ls -altr" bash-4.4# python3 -c "import subprocess; subprocess.call(\"$CUSTOM\", shell=True)" total 8 drwxr-xr-x 2 root root 4096 Mar 4 2019 . drwxr-xr-x 1 root root 4096 Nov 27 16:42 ..
コメント
- 質問はPythonではなく、BASHに関するものです。
回答
変数は次のとおりです。
$ history -s $abc
プッシュUpArrow
またはCtrl-p
」を使用してコマンドラインに表示します。 この方法の他の方法とは異なり、必要に応じて実行前に編集できます。
このコマンドは、変数の内容を新しいエントリとしてBash履歴に追加し、UpArrow
。