bash – echo コマンドの実行トレースを抑制するか?

bash bash-scripting command-line jenkins shell

私はJenkinsからシェルスクリプトを実行していますが、シェルスクリプトはshebangオプション#!/bin/sh -exでキックオフします

Bash Shebang for dummies? によると、-x, “シェルに実行トレースを表示させる”、これはほとんどの目的のために素晴らしいです – エコーを除いて

echo "Message"

出力を生成します

+ echo "Message"
Message

が冗長でちょっと変な感じがします。-xを有効にしたまま、出力のみにする方法はないのでしょうか

Message

の代わりに、例えば echo コマンドの前に特殊なコマンド文字を付けたり、出力をリダイレクトしたりすることで、上の 2 行の代わりに?

  30  Christian  2014-09-03


ベストアンサー

ワニに首までつっこんでいると、沼の水抜きが目的だったことを忘れがちになる。-流行語

この質問は echo についてのものですが、これまでの回答の大部分は set +x コマンドをこっそり入れる方法に焦点を当てています。もっとシンプルで直接的な解決策があります

{ echo "Message"; } 2> /dev/null

(以前の回答で{ …; } 2> /dev/nullを見ていなければ思いつかなかったかもしれないことは認めます)

多少面倒ですが、連続したechoコマンドのブロックがあれば、個別にやる必要はありません

{
echo "The quick brown fox"
echo "jumps over the lazy dog."
} 2> /dev/null

改行がある場合はセミコロンは不要なので注意してください

非標準のファイル記述子(例えば3)の上で/dev/nullを永続的に開き、2> /dev/nullの代わりに2>&3とずっと言っておくというkenorbのアイデアを使えば、タイピングの負担を減らすことができます


この記事を書いている時点での最初の4つの答えは、echoを実行するたびに特別なことをする必要があります(そして、ほとんどの場合、面倒です)。もし本当にすべての echo コマンドで実行トレースを抑制したいのであれば (なぜしたくないのでしょうか?)、多くのコードをつぶさずにグローバルに実行することができます。最初に、エイリアスがトレースされていないことに気づきました

$ myfunc()
> {
>     date
> }
$ alias myalias="date"
$ set -x
$ date
+ date
Mon, Oct 31, 2016  0:00:00 AM           # Happy Halloween!
$ myfunc
+ myfunc                                # Note that function call is traced.
+ date
Mon, Oct 31, 2016  0:00:01 AM
$ myalias
+ date                                  # Note that it doesn’t say  + myalias
Mon, Oct 31, 2016  0:00:02 AM

(以下のスクリプトスニペットは、shebangが#!/bin/shであれば、/bin/shがbashへのリンクであっても動作することに注意してください。しかし、shebang が #!/bin/bash の場合、スクリプトでエイリアスを動作させるには shopt -s expand_aliases コマンドを追加する必要があります)

というわけで、最初のトリックのために

alias echo='{ set +x; } 2> /dev/null; builtin echo'

さて、echo "Message"と言ったとき、私たちはエイリアスを呼び出していますが、これはトレースされません。このエイリアスはトレースオプションをオフにし、user5071535 の回答で最初に紹介した技術を使って、setコマンドからのトレースメッセージを抑制し、実際の echo コマンドを実行します。これにより、echoコマンドごとにコードを編集することなく、user5071535の回答と同様の効果を得ることができます。しかし、これはトレースモードをオフにしたままにしておきます。エイリアスにset -xを入れることはできません (少なくとも簡単にはできません)。 なぜなら、エイリアスは文字列を単語に置き換えることしかできないからです。そのため、例えば、スクリプトに以下のような記述があったとします

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
date

となると

+ date
Mon, Oct 31, 2016  0:00:03 AM
The quick brown fox
jumps over the lazy dog.
Mon, Oct 31, 2016  0:00:04 AM           # Note that it doesn’t say  + date

そのため、メッセージを表示した後もトレースオプションをオンにする必要があります – しかし、連続した echo コマンドのブロックごとに一度だけです

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
set -x
date

set -xechoの後に自動的にset -xにすることができればいいでしょう。しかし、それを提示する前に、次のことを考えてみてください。このOPは#!/bin/sh -exを使うスクリプトから始まっています。暗黙のうちに、ユーザはシェバングからxを削除して、実行トレースなしで正常に動作するスクリプトを持つことができます。このプロパティを保持するソリューションを開発できればいいと思います。ここでの最初のいくつかの回答は、echo文の後に無条件にトレースを “元に戻す “をオンにしているため、このプロパティに失敗しています。この回答は、echo出力をトレース出力に置き換えているため、この問題を認識していないことが目立ちます。ここで、条件付きでecho文の後にトレースをオンに戻すソリューションを紹介します。これを無条件にトレースを “戻す “ソリューションに格下げすることは些細なことであり、練習として残しておきます

alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'
echo_and_restore() {
builtin echo "$*"
case "$save_flags" in
(*x*)  set -x
esac
}

$- はオプションリストで、設定されているすべてのオプションに対応する文字を連結したものです。例えば、ex オプションが設定されている場合、$-ex を含む文字のごちゃまぜになります。私の新しいエイリアス(上)は、トレースをオフにする前に$-の値を保存します。そして、トレースをオフにした状態で、シェル関数に制御を渡します。この関数は実際にechoを実行し、エイリアスが起動されたときにxオプションがオンになっていたかどうかを確認します。オプションがオンになっていれば、この関数は再びオンにし、オフになっていればオフのままにします

上記の7行(shoptを含む場合は8行)をスクリプトの先頭に挿入して、残りはそのままにしておくことができます

これはあなたを許すでしょう

  1. #! /bin/sh -ex #! /bin/sh -e #! /bin/sh -x

    または単にプレーン

    #! /bin/sh

    のいずれかの行を使用してください

  2. のようなコードを持つように
    (shebang) command1 command2 command2 command3 set - のようなコードを持つようにします。x command4 command5 command6 set +x command7 command8 command9

    and

    • コマンド 4, 5, 6 はトレースされます – そのうちの一つが echo でない限り、その場合は実行されますがトレースされません。(ただし、コマンド 5 が echo であっても、コマンド 6 はトレースされます)
    • コマンド 7, 8, 9 はトレースされません。コマンド8がechoであっても、コマンド9はトレースされません
    • コマンド1,2,3は、 shebangにxが含まれているかどうかによって、(4,5,6のように)トレースされるかどうか(7,8,9のように)トレースされないかが決まります

追伸:私のシステムでは、真ん中の答えのbuiltinキーワード(echoのエイリアスに過ぎないもの)を省略できることを発見しました。これは驚くべきことではありません。 bash(1) によれば、エイリアス展開中に

… 拡張されたエイリアスと同一の単語は、二度目の拡張は行われません。これは、例えば lsls -F にエイリアスしても、bash は置換されたテキストを再帰的に展開しようとしないことを意味します

あまり驚くことではありませんが、最後の答え(echo_and_restoreがあるもの)は、builtinキーワードを省略すると失敗します1しかし、不思議なことに、builtinを削除して順番を入れ替えるとうまくいきます

echo_and_restore() {
echo "$*"
case "$save_flags" in
(*x*)  set -x
esac
}
alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'

__________ 1 定義されていない動作が発生するようです。私は見てきました

  • 無限ループ(おそらく非拘束再帰のため)が発生します
  • /dev/null: Bad address のエラーメッセージが表示され、そして
  • コアダンプ

28  G-Man Says ‘Reinstate Monica’  2016-11-01


私はInformITで部分的な解決策を見つけました

#!/bin/bash -ex
set +x;
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

outputs

set +x;
shell tracing is disabled here
+ echo "but is enabled here"
but is enabled here

残念ながら、それだとまだset +xが響いてしまうのですが、少なくともその後は静かになっているので、少なくとも部分的な解決にはなります

でも、もしかしたらもっと良い方法があるのかな?)

13  Christian  2014-09-03


この方法は、set +x出力を取り除くことで、あなた自身の解決策を改善します

#!/bin/bash -ex
{ set +x; } 2>/dev/null
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

4  user5071535  2015-12-11


set +xをカッコの中に入れて、ローカルスコープにのみ適用されるようにしてください

For example:

#!/bin/bash -x
exec 3<> /dev/null
(echo foo1 $(set +x)) 2>&3
($(set +x) echo foo2) 2>&3
( set +x; echo foo3 ) 2>&3
true

would output:

$ ./foo.sh
+ exec
foo1
foo2
foo3
+ true

2  kenorb  2015-12-11


私は、包括的でよく説明された g-man による回答 が大好きで、これまでに提供された中では最高のものだと思っています。スクリプトのコンテキストを気にしていて、必要のないときに設定を強制することはありません。ですから、もしあなたが最初にこの回答を読んでいるのであれば、先に行ってその回答をチェックしてみてください、すべてのメリットはそこにあります

しかし、その答えには重要な部分が欠けています:提案された方法は、典型的なユースケースでは機能しません、すなわち、エラーを報告する

COMMAND || echo "Command failed!"

エイリアスの構築方法の関係で、これが拡張されます

COMMAND || { save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore "Command failed!"

で、ご想像の通り、echo_and_restoreは常に無条件に実行されます。set +x の部分が実行されなかったことを考えると、その関数の内容も出力されることになります

最後の ;&& に変更してもうまくいかないでしょう。なぜなら Bash では ||&&左連想 であるからです

今回の使用例で効果のある改造を見つけました

echo_and_restore() {
echo "$(cat -)"
case "$save_flags" in
(*x*) set -x
esac
}
alias echo='({ save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore) <<<'

すべてのコマンドをグループ化するためにサブシェル((...)部分)を使用し、入力された文字列を Here String (<<<の部分)として標準入力に渡し、それをcat -で出力します。-はオプションですが、”明示的な方が暗黙よりも良い “ということはご存知の通りです

エコーを使わずに直接 cat - を使うこともできますが、この方法は他の文字列を出力に加えることができるので気に入っています。例えば、私はこのように使うことが多いです

BASENAME="$(basename "$0")"  # Complete file name
...
echo "[${BASENAME}] $(cat -)"

そして今では見事に機能しています

false || echo "Command failed"
> [test.sh] Command failed

1  j1elo  2018-07-13


実行トレースはstderrに行くので、このようにフィルタリングします

./script.sh 2> >(grep -v "^+ echo " >&2)

いくつかの説明、一歩一歩

  • stderrがリダイレクトされる… – 2>
  • …をコマンドにする。- – >(…)
  • grepはコマンド
  • …行頭を必要とする… – ^
  • …の後に+ echo….が続きます
  • …では、grepがマッチを反転させる… – -v
  • …そして、それはあなたが必要としないすべての行を破棄します
  • 結果は通常 stdout に送られますが、私たちはそれを stderr にリダイレクトします。- – >&2

問題は(私が思うに)この解決策はストリームを非同期化する可能性があるということです。フィルタリングのため、stderrstdoutに関連して少し遅れているかもしれません (デフォルトではechoの出力が属しています)。これを修正するには、stdoutに両方のストリームが入っていても構わないのであれば、最初にストリームを結合することができます

./script.sh > >(grep -v "^+ echo ") 2>&1

スクリプト自体にそのようなフィルタリングを組み込むことができますが、このアプローチは確かに非同期化になりやすいです (すなわち、私のテストで発生しました: コマンドの実行トレースは、直後に続く echo の出力の後に表示されるかもしれません)。

コードは次のようになります:

#!/bin/bash -x { # original script here # … } 2> >(grep -v "^+ echo " >&2)

トリックなしで実行してください:

./script.sh

また、> >(grep -v "^+ echo ") 2>&1を使用して、ストリームを結合することを犠牲にして同期を維持してください:


別のアプローチ。端末が stdoutstderr を混在させているため、「少し冗長」で奇妙な出力が得られます。これら二つのストリームは理由があって別の動物です。stderr を分析することだけがニーズに合っているかどうかを確認してください; stdout を破棄してください:

./script.sh > /dev/null

スクリプトの中に echo デバッグ/エラー メッセージを stderr に出力する機能がある場合、上で説明した方法で冗長性を取り除くことができます。完全なコマンド:

./script.sh > /dev/null 2> >(grep -v "^+ echo " >&2)

今回は stderr のみで作業しているので、非同期化はもはや心配ありません。残念ながら、この方法ではトレースも echo の出力も stdout に出力されるものを見ることはできません (もしあれば)。リダイレクト (>&2) を検出するためにフィルタを再構築することもできますが、echo foobar >&2echo >&2 foobarecho "foobar >&2" を見ると、おそらく物事が複雑になることに同意するでしょう。複雑なフィルタを実装する前によく考えてください。重要な情報を誤って見逃してしまうよりも、少し冗長性がある方が良いでしょう。


echoの実行トレースを破棄する代わりに、その出力を破棄することができます。実行トレースのみを分析するには、次のようにしてください:

./script.sh > /dev/null 2> >(grep "^+ " >&2)

馬鹿げたことはありませんか?スクリプトに echo "+ rm -rf --no-preserve-root /" >&2 があったらどうなるか考えてみてください。


スポンサーリンク

そして最後に…

幸いにも BASH_XTRACEFD 環境変数があります。man bashから:

BASH_XTRACEFD 有効なファイル記述子に対応する整数に設定すると、set -xが有効になったときに生成されたトレース出力をそのファイル記述子に書き出します。

この方法では、ファイル記述子はその後も有効なままではなく、現在のシェルに割り当てられた変数も有効なままになりません。

もちろん、別の方法を使用することもできますが、特にトレースと一緒に stdout および/または stderr を分析する必要がある場合には、別の方法を使用することができます。ただ、ある種の制限と落とし穴があることを覚えておく必要があります。

0  Kamil Maciorowski  2016-09-21


Makefileでは、@シンボルを使うことができます

使用例。@echo 'message'

this GNU docから

行が ‘@’ で始まる場合、その行のエコーは抑制されます。行がシェルに渡される前に ‘@’ は破棄されます。一般的には、 makefile での進行状況を示す echo コマンドのように、何かを印刷するだけのコマンドに使います

0  derek  2017-05-30


オリジナルは何が起こっているかを理解するのに十分な情報が含まれていなかったことをモデレーターの提案ごとに2016年10月29日に編集されました

この “トリック “は、xtraceがアクティブなときに、ターミナルで1行だけのメッセージ出力を生成します

元の質問は、-xを有効にしたままで、2行ではなくMessageだけを出力する方法はないのでしょうか?これは質問からの正確な引用です

私が理解しているのは、「set -xを有効にしたままで、メッセージのための一行を生成する」にはどうすればいいのか、という質問です

  • グローバルな意味では、この質問は基本的に美学に関するものです — 質問者はxtraceがアクティブな間に生成される2つの事実上重複した行の代わりに1つの行を生成したいと考えています

要約すると、OPには必要なんですね

  1. xを有効に設定していること
  2. 人間が読めるメッセージをプロデュースする
  3. メッセージ出力を1行のみ出力します

OPではエコーコマンドの使用を要求していません。彼らは、ラテン語の exempli gratia、あるいは “for example” の略語である e.g. を使って、メッセージ制作の一例として引用しています

ボックスの外」を考え、メッセージを生成するためにエコーを使用することを放棄して、私は課題文がすべての要件を満たすことができることに注意してください

(メッセージを含む)テキスト文字列を非アクティブ変数に代入します

スクリプトにこの行を追加しても、何もロジックを変更することはありませんが、トレースがアクティブになったときには一行になります: (変数 $echo を使用している場合は、使用されていない別の変数に名前を変更するだけです)

echo="====================== Divider line ================="

端末に表示されるのは1行のみです

++ echo='====================== Divider line ================='

2ではなく、OPが嫌っているように

+ echo '====================== Divider line ================='
====================== Divider line =================

以下にサンプルスクリプトを示します。メッセージ(最後から4行目の$HOMEディレクトリ名)に変数を代入しても動作するので、この方法で変数をトレースすることができます

#!/bin/bash -exu
#
#  Example Script showing how, with trace active, messages can be produced
#  without producing two virtually duplicate line on the terminal as echo does.
#
dummy="====================== Entering Test Script ================="
if [[ $PWD == $HOME ]];  then
dummy="*** "
dummy="*** Working in home directory!"
dummy="*** "
ls -la *.c || :
else
dummy="---- C Files in current directory"
ls -la *.c || :
dummy="----. C Files in Home directory "$HOME
ls -la  $HOME/*.c || :
fi

ルート、ホームディレクトリで実行したときの出力は以下の通りです

$ cd /&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ / == /g/GNU-GCC/home/user ]]
+ dummy='---- C Files in current directory'
+ ls -la '*.c'
ls: *.c: No such file or directory
+ :
+ dummy='----. C Files in Home directory /g/GNU-GCC/home/user'
+ ls -la /g/GNU-GCC/home/user/HelloWorld.c /g/GNU-GCC/home/user/hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/hw.c
+ dummy=---------------------------------

$ cd ~&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ /g/GNU-GCC/home/user == /g/GNU-GCC/home/user ]]
+ dummy='*** '
+ dummy='*** Working in home directory!'
+ dummy='*** '
+ ls -la HelloWorld.c hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 hw.c
+ dummy=---------------------------------

-1  HiTechHiTouch  2016-09-20


タイトルとURLをコピーしました