変数に格納されているコマンドを実行するにはどうすればよいですか?

$ 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 

コメント

回答

これは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[@]}" 

参照

コメント

  • 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

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です