私は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 -x
をecho
の後に自動的に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
}
$-
はオプションリストで、設定されているすべてのオプションに対応する文字を連結したものです。例えば、e
と x
オプションが設定されている場合、$-
は e
と x
を含む文字のごちゃまぜになります。私の新しいエイリアス(上)は、トレースをオフにする前に$-
の値を保存します。そして、トレースをオフにした状態で、シェル関数に制御を渡します。この関数は実際にecho
を実行し、エイリアスが起動されたときにx
オプションがオンになっていたかどうかを確認します。オプションがオンになっていれば、この関数は再びオンにし、オフになっていればオフのままにします
上記の7行(shopt
を含む場合は8行)をスクリプトの先頭に挿入して、残りはそのままにしておくことができます
これはあなたを許すでしょう
#! /bin/sh -ex #! /bin/sh -e #! /bin/sh -x
または単にプレーン
#! /bin/sh
のいずれかの行を使用してください
- のようなコードを持つように
(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のように)トレースされないかが決まります
- コマンド 4, 5, 6 はトレースされます – そのうちの一つが
追伸:私のシステムでは、真ん中の答えのbuiltin
キーワード(echo
のエイリアスに過ぎないもの)を省略できることを発見しました。これは驚くべきことではありません。 bash(1) によれば、エイリアス展開中に
… 拡張されたエイリアスと同一の単語は、二度目の拡張は行われません。これは、例えば
ls
をls -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
問題は(私が思うに)この解決策はストリームを非同期化する可能性があるということです。フィルタリングのため、stderr
はstdout
に関連して少し遅れているかもしれません (デフォルトでは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
を使用して、ストリームを結合することを犠牲にして同期を維持してください:
別のアプローチ。端末が stdout
と stderr
を混在させているため、「少し冗長」で奇妙な出力が得られます。これら二つのストリームは理由があって別の動物です。stderr
を分析することだけがニーズに合っているかどうかを確認してください; stdout
を破棄してください:
./script.sh > /dev/null
スクリプトの中に echo
デバッグ/エラー メッセージを stderr
に出力する機能がある場合、上で説明した方法で冗長性を取り除くことができます。完全なコマンド:
./script.sh > /dev/null 2> >(grep -v "^+ echo " >&2)
今回は stderr
のみで作業しているので、非同期化はもはや心配ありません。残念ながら、この方法ではトレースも echo
の出力も stdout
に出力されるものを見ることはできません (もしあれば)。リダイレクト (>&2
) を検出するためにフィルタを再構築することもできますが、echo foobar >&2
、echo >&2 foobar
、echo "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には必要なんですね
- xを有効に設定していること
- 人間が読めるメッセージをプロデュースする
- メッセージ出力を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