「bash -x」がこのスクリプトをクラッシュさせるのはなぜですか?

「bash -x」がこのスクリプトをクラッシュさせるのはなぜですか?

特定のコマンドを実行するのにかかる時間を測定するスクリプトがあります。

time「実際の」コマンド、つまりバイナリが必要です/usr/bin/time(bash組み込みには対応するフラグがないためです-f)。

デバッグできる単純化されたスクリプトは次のとおりです。

#!/bin/bash

TIMESEC=$(echo blah | ( /usr/bin/time -f %e grep blah >/dev/null ) 2>&1 | awk -F. '{print $1}')

echo ABC--$TIMESEC--DEF

if [ "$TIMESEC" -eq 0 ] ; then
   echo "we are here!"
fi

「test.sh」として保存し、以下を実行します。

$ bash test.sh
ABC--0--DEF
we are here!

だから動作します。

それでは、bashコマンドラインに「-x」を追加してデバッグしてみましょう。

$ bash -x test.sh
++ echo blah
++ awk -F. '{print $1}'
+ TIMESEC='++ /usr/bin/time -f %e grep blah
0'
+ echo ABC--++ /usr/bin/time -f %e grep blah 0--DEF
ABC--++ /usr/bin/time -f %e grep blah 0--DEF
+ '[' '++ /usr/bin/time -f %e grep blah
0' -eq 0 ']'
test.sh: line 10: [: ++ /usr/bin/time -f %e grep blah
0: integer expression expected

「-x」を使用するとこのスクリプトが中断されますが、「-x」なしでうまく動作するのはなぜですか?

答え1

問題は次のようになります。

TIMESEC=$(echo blah | ( /usr/bin/time -f %e grep blah >/dev/null ) 2>&1 | awk -F. '{print $1}')

標準出力に一致するように標準エラーをリダイレクトしています。 bash は標準エラーのトレース・メッセージを作成し (例えば) bash プロセス内の組み込み関数やその他のechoシェル構成を使用します。

次のように変更すると

TIMESEC=$(echo blah | sh -c "( /usr/bin/time -f %e grep blah >/dev/null )" 2>&1 | awk -F. '{print $1}')

これはこの問題を解決し、追跡と作業の間の許容可能な妥協点になる可能性があります。

++ awk -F. '{print $1}'
++ sh -c '( /usr/bin/time -f %e grep blah >/dev/null )'
++ echo blah
+ TIMESEC=0                 
+ echo ABC--0--DEF
ABC--0--DEF
+ '[' 0 -eq 0 ']'
+ echo 'we are here!'
we are here!

答え2

サブシェルを直接削除することもできます。明らかに互いに怒っているのは、入れ子になった殻です。

TIMESEC=$(
    echo blah |
    /usr/bin/time -f %e grep blah 2>&1 >/dev/null |
    awk -F. '{print $1}'
)

これにより:


...| ( subshell ) 2>pipe | ...

...パイプラインの対応する部分を処理するためにサブシェルを実行します。ホスティング内部のサブシェルです。シェルはサブシェルのデバッグ出力をリダイレクトしないため(使用することができる他の種類の{複合コマンドでも機能します); } >redirect パイプセクションで流れを混ぜます。リダイレクトの順序に関連しています。

代わりに最初にリダイレクトするとただ測定しようとしているコマンドのエラー出力を持ち、ホストシェルの出力にそれをstderrに送信させると、同じ問題は発生しません。

だから...


... | command 2>pipe 1>/dev/null | ...

...ホストシェルは任意の場所でstderrに自由に書き込むことができ、呼び出すコマンドの出力のみをパイプにリダイレクトします。


bash -x time.sh
+++ echo blah
+++ /usr/bin/time -f %e grep blah
+++ awk -F. '{print $1}'
++ TIMESEC=0
++ echo ABC--0--DEF
ABC--0--DEF
++ '[' 0 -eq 0 ']'
++ echo 'we are here!'
we are here!

この質問について...


TIMESEC=$(
    echo blah |
    /usr/bin/time -f %e grep blah 2>&1 >/dev/null
)
printf %s\\n "ABC--${TIMESEC%%.*}--DEF"
if [ "${TIMESEC%%.*}" -eq 0 ] ; then
   echo "we are here!"
fi

関連情報