シェルスクリプトの変数にコマンドを格納する方法は?


113

後で使用するコマンドを変数に保存したい(コマンドの出力ではなく、コマンド自体)

次のような簡単なスクリプトがあります。

command="ls";
echo "Command: $command"; #Output is: Command: ls

b=`$command`;
echo $b; #Output is: public_html REV test... (command worked successfully)

しかし、もう少し複雑なことをしようとすると失敗します。たとえば、私が作る場合

command="ls | grep -c '^'";

出力は次のとおりです。

Command: ls | grep -c '^'
ls: cannot access |: No such file or directory
ls: cannot access grep: No such file or directory
ls: cannot access '^': No such file or directory

後で使用するために、このようなコマンド(パイプ/複数のコマンドを含む)を変数に格納する方法を教えてください。


10
関数を使用してください!
gniourf_gniourf 2015年

回答:


146

evalを使用:

x="ls | wc"
eval "$x"
y=$(eval "$x")
echo "$y"

27
$(...)がの代わりに推奨されるようになりましたbackticks。y = $(eval $ x) mywiki.wooledge.org/BashFAQ/082
James Broadhead

14
eval変数の内容を信頼できる場合にのみ、許容される方法です。たとえばx="ls $name | wc"(またはx="ls '$name' | wc")を実行している場合、このコードは、インジェクションまたは特権エスカレーションの脆弱性への迅速な対応策です。(/tmpたとえば、のすべてのサブディレクトリを反復処理しますか?システム上のすべてのユーザーを信頼して、を呼び出さないようにした方がよいでしょう$'/tmp/evil-$(rm -rf $HOME)\'$(rm -rf $HOME)\'/')。
Charles Duffy

9
eval巨大なバグマグネットであり、予期しない解析動作のリスクに関する警告なしでは推奨されません(@CharlesDuffyの例のように、悪意のある文字列がなくても)。たとえば、してみてくださいx='echo $(( 6 * 7 ))'そしてそしてeval $x。「42」を出力することを期待しているかもしれませんが、おそらく出力しません。それが機能しない理由を説明できますか?私が「たぶん」と言った理由を説明できますか?これらの質問に対する答えが明確でない場合は、決して触れないでくださいeval
Gordon Davisson

1
@Student、set -x事前に実行してコマンドの実行をログに記録してください。これにより、何が起こっているのかを簡単に確認できます。
Charles Duffy

1
@学生私はまた、よくある間違い(およびあなたが拾ってはいけない悪い習慣)を指摘するためにshellcheck.netをお勧めします。
Gordon Davisson

41

使用しないでくださいeval!これには、任意のコード実行が導入される大きなリスクがあります。

BashFAQ-50-コマンドを変数に入れようとしていますが、複雑なケースは常に失敗します。

それを配列に入れ、単語の分割によって単語が分割さ"${arr[@]}"ないように、すべての単語を二重引用符で展開IFSます

cmdArgs=()
cmdArgs=('date' '+%H:%M:%S')

内部の配列の内容を確認してください。をdeclare -p使用すると、内部の配列の内容を、各コマンドパラメータを個別のインデックスで確認できます。そのような引数の1つにスペースが含まれている場合、配列に追加するときに内部で引用すると、ワード分割により分割されなくなります。

declare -p cmdArgs
declare -a cmdArgs='([0]="date" [1]="+%H:%M:%S")'

コマンドを次のように実行します

"${cmdArgs[@]}"
23:15:18

(または)bash関数を使用してコマンドを実行します。

cmd() {
   date '+%H:%M:%S'
}

と同じように関数を呼び出します

cmd

POSIXにshは配列がないため、最も近いのは、位置パラメーターの要素のリストを作成することです。shメールプログラムを実行するPOSIXの方法を次に示します

# POSIX sh
# Usage: sendto subject address [address ...]
sendto() {
    subject=$1
    shift
    first=1
    for addr; do
        if [ "$first" = 1 ]; then set --; first=0; fi
        set -- "$@" --recipient="$addr"
    done
    if [ "$first" = 1 ]; then
        echo "usage: sendto subject address [address ...]"
        return 1
    fi
    MailTool --subject="$subject" "$@"
}

このアプローチは、リダイレクトのない単純なコマンドのみを処理できることに注意してください。リダイレクト、パイプライン、for / whileループ、ifステートメントなどを処理できません

別の一般的な使用例は、curl複数のヘッダーフィールドとペイロードを使用して実行する場合です。以下のように常に引数を定義curlして、展開された配列コンテンツを呼び出すことができます

curlArgs=('-H' "keyheader: value" '-H' "2ndkeyheader: 2ndvalue")
curl "${curlArgs[@]}"

もう一つの例、

payload='{}'
hostURL='http://google.com'
authToken='someToken'
authHeader='Authorization:Bearer "'"$authToken"'"'

変数が定義されたので、配列を使用してコマンド引数を格納します

curlCMD=(-X POST "$hostURL" --data "$payload" -H "Content-Type:application/json" -H "$authHeader")

そして今、適切に引用された展開を行います

curl "${curlCMD[@]}"

これは私が試してみました、私のために動作しませんCommand=('echo aaa | grep a')し、"${Command[@]}"それは文字通りのコマンドを実行します期待して、echo aaa | grep a。そうではありません。を置き換える安全な方法があるかどうか疑問に思いますが、危険である可能性evalがあるのと同じ力を持つ各解決策があるようですeval。だよね?
学生

つまり、元の文字列にパイプ '|'が含まれている場合、これはどのように機能しますか?
学生

@Student、元の文字列にパイプが含まれている場合、その文字列をコードとして実行するには、bashパーサーの安全でない部分を通過する必要があります。その場合は文字列を使用しないでください。代わりに関数を使用しCommand() { echo aaa | grep a; }てください。-その後Command、またはresult=$(Command)、などを実行できます。
Charles Duffy

1
@学生、そうです。しかし、意図的に失敗します。なぜなら、あなたが求めていることは本質的に安全ではないからです。
Charles Duffy

1
@学生:最後に、特定の条件下では機能しないことに言及するためにメモを追加しました
Inian

25
var=$(echo "asdf")
echo $var
# => asdf

この方法を使用すると、コマンドはすぐに評価され、その戻り値が保存されます。

stored_date=$(date)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015

バックティックと同じ

stored_date=`date`
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015

でevalを使用しても、$(...)後で評価されません

stored_date=$(eval "date")
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015

evalを使用して、evalが使用されたときに評価されます

stored_date="date" # < storing the command itself
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:05 EST 2015
# (wait a few seconds)
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:16 EST 2015
#                     ^^ Time changed

上記の例で、引数を指定してコマンドを実行する必要がある場合は、保存する文字列にそれらを配置します

stored_date="date -u"
# ...

bashスクリプトの場合、これが関連することはめったにありませんが、最後に1つ注意します。に注意してくださいeval。自分が制御する文字列のみを評価し、信頼できないユーザーからの文字列や信頼できないユーザー入力から作成された文字列は決して評価しないでください。

  • コマンドを引用するよう通知してくれた@CharlesDuffyに感謝します!

これは、コマンドにパイプ '|'が含まれるという元の問題を解決しません。
学生

@Nate、のみが含まれているeval $stored_date場合でも十分な場合がありstored_dateますがdateeval "$stored_date"はるかに信頼性が高いことに注意してください。例のstr=$'printf \' * %s\\n\' *'; eval "$str"ファイナル"$str"を引用符付きで引用符なしで実行します。:)
Charles Duffy

@CharlesDuffyありがとう、引用のことを忘れていました。私がそれを実行するのに面倒であったなら、私のリンターは文句を言ったであろうに違いない。
ネイト

0

bashの場合、コマンドを次のように保存します。

command="ls | grep -c '^'"

次のようにコマンドを実行します。

echo $command | bash

1
確かではありませんが、おそらくこのコマンドの実行方法には、「eval」を使用する場合と同じリスクがあります。
Derek Hazell

0

私はさまざまな方法を試しました:

printexec() {
  printf -- "\033[1;37m$\033[0m"
  printf -- " %q" "$@"
  printf -- "\n"
  eval -- "$@"
  eval -- "$*"
  "$@"
  "$*"
}

出力:

$ printexec echo  -e "foo\n" bar
$ echo -e foo\\n bar
foon bar
foon bar
foo
 bar
bash: echo -e foo\n bar: command not found

ご覧のとおり、3つ目のみ"$@"が正しい結果を出しています。


0

以下を使用して注文を慎重に登録してください。 X=$(Command)

これは呼び出される前でも実行されます。これをチェックして確認するには、次の操作を実行できます。

echo test;
X=$(for ((c=0; c<=5; c++)); do
sleep 2;
done);
echo note the 5 seconds elapsed

-1
#!/bin/bash
#Note: this script works only when u use Bash. So, don't remove the first line.

TUNECOUNT=$(ifconfig |grep -c -o tune0) #Some command with "Grep".
echo $TUNECOUNT                         #This will return 0 
                                    #if you don't have tune0 interface.
                                    #Or count of installed tune0 interfaces.

-8

後で使用する必要がある場合でも、コマンドを変数に格納する必要はありません。通常どおりに実行してください。変数に格納する場合は、なんらかのevalステートメントが必要になるか、不要なシェルプロセスを呼び出して「変数を実行」する必要があります。


1
保存するコマンドは、送信するオプションに依存するため、プログラムの大部分に大量の条件ステートメントを含める代わりに、後で使用するために必要なコマンドを保存する方がはるかに簡単です。
ベンジャミン

1
@Benjaminの場合、少なくともコマンドではなくオプションを変数として保存します。例var='*.txt'; find . -name "$var"
くるみ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.