$ BASH_COMMANDを評価しても安全ですか?


11

私は変数から複雑なコマンドを構築するシェルスクリプトに取り組んでいます。たとえば、次のようにします(Bash FAQから学んだテクニックを使用)。

#!/bin/bash

SOME_ARG="abc"
ANOTHER_ARG="def"

some_complex_command \
  ${SOME_ARG:+--do-something "$SOME_ARG"} \
  ${ANOTHER_ARG:+--with "$ANOTHER_ARG"}

このスクリプトはパラメーター--do-something "$SOME_ARG"を動的に追加--with "$ANOTHER_ARG"some_complex_command、これらの変数が定義されているかどうかを確認します。これまでのところ、これは正常に動作しています。

しかし、コマンドを実行しているとき(たとえば、スクリプトがデバッグモードで実行されているとき)に、コマンドを印刷またはログに記録できるようにしたいと思っています。そのため、スクリプトを実行するときに、some_complex_command --do-something abc --with defこのコマンドを変数に含めて、syslogに記録できるようにしたいと考えています。

Bash FAQは、この目的でトラップと変数(たとえば、デバッグ目的)を使用DEBUG$BASH_COMMANDする手法を示してます。私は次のコードでそれを試しました:

#!/bin/bash

ARG="test string"

trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG"

echo "Command was: ${COMMAND}"

これは機能しますが、コマンドの変数は展開されません。

host ~ # ./test.sh
test string
Command was: echo "$ARG"

展開echo "$ARG"するにはevalを使用する必要があると思いますecho test string(少なくとも、evalまだ方法を見つけていません)。以下は機能します:

eval echo "Command was: ${COMMAND}"

次の出力が生成されます。

host ~ # ./test.sh
test string
Command was: echo "$ARG"
Command was: echo test string

しかしeval、このように安全に使用できるかどうかは本当にわかりません。私はいくつかのことを悪用することに失敗しました:

#!/bin/bash

ARG="test string; touch /x"
DANGER='$(touch /y; cat /etc/shadow)'

trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG" $DANGER

echo "Command was: ${COMMAND}"
eval echo "Command was: ${COMMAND}"

これはうまく処理しているようですが、他の誰かが私が見逃した問題を見つけたのかどうか知りたいです。


これは、sudoラッパーで長年使用してきたもので、問題に気づいたことはありません。
w00t 2014年

回答:


7

1つの可能性は、次のように、コマンドを出力して実行するラッパー関数を作成することです。

debug() {
    # This function prints (to stdout) its arguments and executes them
    local args=() idx=0 IFS=' ' c
    for c; do printf -v args[idx++] '%q' "$c"; done
    printf "%s\n" "${args[*]}"
    # Execute!
    "$@"
}

そのため、スクリプトで次のことができます。

debug echo "$ARG"

罠をいじる必要はありません。欠点はdebug、コード全体にいくつかのキーワードを追加することです(ただし、問題ないはずです。アサートなどのようなものを使用するのが一般的です)。

グローバル変数DEBUGを追加して、次のようにdebug関数を変更することもできます。

debug() {
    # This function prints (to stdout) its arguments if DEBUG is non-null
    # and executes them
    if [[ $DEBUG ]]; then
        local args=() idx=0 IFS=' ' c
        for c; do printf -v args[idx++] '%q' "$c"; done
        printf "%s\n" "${args[*]}"
    fi
    # Execute!
    "$@"
}

次に、次のようにスクリプトを呼び出すことができます。

$ DEBUG=yes ./myscript

または

$ DEBUG= ./myscript

あるいは単に

$ ./myscript

デバッグ情報が必要かどうかによって異なります。

DEBUG環境変数として扱う必要があるため、変数を大文字にしました。DEBUG簡単な一般的な名前なので、他のコマンドと競合する可能性があります。多分それを呼び出すGNIOURF_DEBUGMARTIN_VON_WITTICH_DEBUGUNICORN_DEBUGユニコーンが好きなら(そしておそらくポニーも好きかも)。

注意。ではdebug機能、私は慎重に各引数をフォーマットしprintf '%q'、出力が正しくエスケープし、直接コピーして再利用可能な逐語的になると貼り付けるように引用されるように。また、各スペース(スペースやその他の面白い記号の場合)を理解できるようになるため、シェルが見たものを正確に表示します。この関数は、不要なサブシェルを回避するために、-vスイッチの直接割り当ても使用しprintfます。


1
関数はうまく機能しますが、残念ながら私のコマンドにはリダイレクトと「バックグラウンドで実行」する制御演算子が含まれてい&ます。それを機能させるためには、それらを関数に移動する必要がありました。あまり良い方法ではありませんが、もっと良い方法はないと思います。しかしeval、これ以上はありません。それでうまくいきました。それは素晴らしいことです:)
Martin von Wittich

5

eval "$BASH_COMMAND" コマンドを実行します。

printf '%s\n' "$BASH_COMMAND" 指定されたとおりのコマンドと改行を出力します。

コマンドに変数が含まれている場合(つまり、のような場合cat "$foo")、コマンドを出力すると、変数のテキストが出力されます。コマンドを実行せずに変数の値を出力することは不可能です—などのコマンドを考えてくださいvariable=$(some_function) other_variable=$variable

シェルスクリプトの実行からトレースを取得する最も簡単な方法はxtrace、スクリプトを実行するbash -x /path/to/scriptset -x、シェル内で呼び出すことにより、シェルオプションを設定することです。トレースは標準エラーに出力されます。


1
についてxtraceは知っていますが、それではあまり制御できません。「set -x; ...; set + x」を試してみましたが、1)xtraceを無効にする「set + x」コマンドも出力されます2)たとえば、タイムスタンプを付けて出力の前に付けることができません3)できますsyslogに記録しないでください。
Martin von Wittich、2014年

2
「コマンドを実行せずに変数の値を出力することは不可能です」-良い例として、私はそのような場合を考慮していませんでした。ただし、bashの横にBASH_COMMAND、展開されたコマンドを含む追加の変数がある場合は、ある時点でコマンドを実行するときに変数の展開を行わなければならないので、すばらしいでしょう:)
Martin von Wittich
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.