キャッチ関数$LINENOエラー

キャッチ関数$LINENOエラー

私はスクリプトを学ぶためにBashスクリプトを書いています。ある時点では、スクリプトの終了時に不要なディレクトリとファイルがクリーンアップされるようにトラップを追加する必要があります。しかし、何らかの理由でスクリプトがclean_a()終了すると、トラップはクリーンアップ関数を呼び出しますが、スクリプトが終了したときに$LINENOint関数ではなくクリーンアップ関数自体の行を指すことを理解していません。archieve_it()

予想される動作:

  1. スクリプトの実行
  2. Ctrl+を押すC
  3. トラップキャッシュCtrl+C呼び出しclean_a()機能
  4. clean_a()Ctrlこの関数は+を押した行番号をエコーし​​ますC。 10行目にしてくださいarchieve_it()

実際に起こったこと:

  1. スクリプトの実行
  2. Ctrl+を押すC
  3. トラップキャッシュCtrl+C呼び出しclean_a()機能
  4. clean_a()無関係な行番号をエコーし​​ます。たとえば、clean_a()関数の25行です。

以下は私のスクリプトの例です。

archieve_it () {
  trap 'clean_a $LINENO $BASH_COMMAND'\
                SIGHUP SIGINT SIGTERM SIGQUIT
  for src in ${sources}; do
   mkdir -p "${dest}${today}${src}"
   if [[ "$?" -ne 0 ]] ; then
    error "Something!" 
   fi
   rsync "${options}" \
         --stats -i \
         --log-file="${dest}${rsync_log}" \
         "${excludes}" "${src}" "${dest}${today}${src}"
  done
}
clean_a () {
  error "something!
  line: $LINENO
  command: $BASH_COMMAND
  removing ${dest}${today}..."
  cd "${dest}"
  rm -rdf "${today}"
  exit "$1"
}

PS:元のスクリプトが表示されます。ここ。定義と変数名はトルコ語です。必要に応じて何でも英語に翻訳できます。

編集する:@mikeservの説明に基づいてスクリプトをできるだけ変更しました。次のようになります。

#!/bin/bash
PS4='DEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'clean_a $LASTNO $LINENO $BASH_COMMAND'\
                SIGHUP SIGINT SIGTERM SIGQUIT
  ..
}
clean_a () {
  error " ...
  line: $LINENO $LASTNO
  ..."
}

スクリプトを実行してset -x+で終了すると、次のように正しい行番号が印刷されます。CtrlC

 DDEBUG: 1 : clean_a 1 336 rsync '"${options}"' ...

ただし、clean_a()関数では値が$LASTNO1 で印刷されます。

 line: 462 1

@Arkadiusz Drabczykが表示したエラーに関連していますか?

編集2: @mikesrv 推奨通りにスクリプトを変更しました。ただし、スクリプトが終了すると、$LASTNOは行値として1を返します(337でなければなりません)。

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
                SIGHUP SIGINT SIGTERM SIGQUIT
  ...
} 2>/dev/null
clean_a () {
  error " ...
  line: $LASTNO $LINENO
  ..."
} 2>&1

スクリプトを実行し、実行中に+を使用してrsyncを終了すると、次Ctrlの出力が表示されます。C

^^MDEBUG: 1 : clean_a '337 1 rsync "${options}" --delete-during ...
...
line: 1 465

ご覧のとおり、$LASTNOの値は1です。

問題が何であるかを調べようとしたときに、testingパラメータ置換形式を使用する別の関数を作成しました${parameter:-default}。したがって、スクリプトは次のようになります。

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'testing "$LASTNO $LINENO $BASH_COMMAND"'\
                 SIGHUP SIGINT SIGTERM SIGQUIT
  ...
} 2>/dev/null
testing() {
  echo -e "${1:-Unknown error!}"
  exit 1
} 2>&1

スクリプトを実行してCtrl+を押すと、C次の結果が表示されます。

^^MDEBUG: 1 : testing '337 1 rsync "${options}" --delete-during ...
337 1 rsync "${options}" --delete-during ... 

337は、rsyncの実行中にCtrl+を押した行を指します。C

clear_a別のテストのために、次の関数を書いてみました。

clear_a () {
  echo -e " $LASTNO $LINENO"
}

そして$ LASTNOはまだ1を返します。

それでは、パラメータ置換を使用すると、スクリプトが終了したときに正しい行番号を取得できることを意味しますか?

編集3EDIT2で@mikeservの説明を誤って適用したようです。私の間違いを修正しました。関数の位置パラメーターは"$1$ LASTNOで置き換える必要があります。clear_a

私が望む方法で動作するスクリプトは次のとおりです。

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
                SIGHUP SIGINT SIGTERM SIGQUIT
  ...
} 2>/dev/null
clean_a () {
  error " ...
  line: $1
  ..."
} 2>&1

スクリプトが終了すると、最初のパラメータ、2番目のパラメータ、および3番目のパラメータがtrap評価され、その値が関数に渡されます。最後に、スクリプトが終了する行番号として$ LASTNOを印刷します。$LASTNO$LINENO$BASH_COMMANDclear_a$1

答え1

mikeservの解決策は良いですが、トラップの実行中にその行がfn渡されるという彼の説明はtrap正しくありません。$LINENO前に行を挿入すると、トラップが宣言されている場所に関係なく、トラップが実際に常に転送されるtrap ...ことがわかります。fn1

PS4='DEBUG: $LINENO : ' \
bash -x <<\EOF
    echo Foo
    trap 'fn "$LINENO"' EXIT             
    fn() { printf %s\\n "$LINENO" "$1"; }
    echo "$LINENO"
    exit
EOF

出力

DEBUG: 1 : echo Foo
Foo
DEBUG: 2 : trap 'fn "$LINENO"' EXIT
DEBUG: 4 : echo 4
4
DEBUG: 5 : exit
DEBUG: 1 : fn 1
DEBUG: 3 : printf '%s\n' 3 1
3
1

トラップの最初のパラメータfn "$LINENO"が配置されるため一つ引用する、$LINENO得る拡大するそして、もしEXITをトリガするので、拡張する必要がありますfn 5。それでは、なぜできないのですか?実際、bash-4.0まではトラップがトリガされると$ LINENOが1にリセットされますfn 1[源泉]ただし、ERRトラップの元の動作は依然として保持されています。おそらく同様のものがあまりにも頻繁にtrap 'echo "Error at line $LINENO"' ERR使用されるからです。

#!/bin/bash

trap 'echo "exit at line $LINENO"' EXIT
trap 'echo "error at line $LINENO"' ERR
false
exit 0

出力

error at line 5
exit at line 1

GNU bashバージョン4.3.42(1)リリース(x86_64-pc-linux-gnu)を使用してテストされました。

答え2

私の考えの問題は、ほとんど機能するかもしれない"$LINENO"最後のコマンドの実行ラインを提供すると期待していることです。clean_a() 返品自分のものを持つには、$LINENO次のようにする必要があります。

error "something!
line: $1
...

ただし、設定した内容だけを印刷したいので、この方法も機能しない可能性がありますtrap

以下は小さなデモです。

PS4='DEBUG: $LINENO : ' \
bash -x <<\CMD          
    trap 'fn "$LINENO"' EXIT             
    fn() { printf %s\\n "$LINENO" "$1"; }
    echo "$LINENO"
CMD

出力

DEBUG: 1 : trap 'fn "$LINENO"' EXIT
DEBUG: 3 : echo 3
3
DEBUG: 1 : fn 1
DEBUG: 2 : printf '%s\n' 2 1
2
1

したがってtrap、設定を取得して定義してfn()実行echoします。シェルが入力の実行を終了すると、EXITトラップが実行され、fn呼び出されます。 1つの引数が渡されます。これはそのtrap行です$LINENOfnまず、独自の内容を印刷してから、$LINENO最初の引数を印刷します。

予想される動作を取得する方法を考えることができますが、これはシェルをめちゃくちゃにしますstderr

PS4='DEBUG: $((LASTNO=$LINENO)) : ' \
bash -x <<\CMD
    trap 'fn "$LINENO" "$LASTNO"' EXIT
    fn() { printf %s\\n "$LINENO" "$LASTNO" "$@"; }
    echo "$LINENO"
CMD

出力

DEBUG: 1 : trap 'fn "$LINENO" "$LASTNO"' EXIT
DEBUG: 3 : echo 3
3
DEBUG: 1 : fn 1 3
DEBUG: 2 : printf '%s\n' 2 1 1 3
2
1
1
3

これはシェルのデバッグプロンプトを使用して各実行行を$PS4定義します。$LASTNOこれは現在のシェル変数であり、スクリプトのどこからでもアクセスできます。つまり、現在アクセス中の行に関係なく、実行中のスクリプトの最新行を参照できます$LASTNO。もちろんご覧のとおり、デバッグ出力が付属しています。これを2>/dev/nullスクリプトの実行のほとんどにプッシュしてから、他の操作を2>&1実行できますclean_a()

1あなたが入った$LASTNO理由はい最後の値が設定されました。$LASTNOなぜなら、それが最後の$LINENO値だからです。以下の仕様で説明されているように、機能trapにはあなたの機能があるので、独自のarchieve_it()機能もあります。とにかく正しいことをしている$LINENOようには見えませんが、信号に応じてシェルを再実行する必要があるため、リセットされることがあります。この場合、私はそれについて少しあいまいです。明らかにそれはすべてです。bashtrapINT$LINENObash

$LASTNO私の考えでは、あなたが評価をしたくないと思いますclean_a()。より良いアプローチは、それを評価し、受け取ったtrap値をパラメータとしてに渡すことです。たぶん、次のようなものがあります。trap$LASTNOclean_a()

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
    trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
        SIGHUP SIGINT SIGTERM SIGQUIT
    while :; do sleep 1; done
} 2>/dev/null
clean_a () { : "$@" ; } 2>&1

一度試してみてください。好きなように動作しなければならないと思います。ああ - Ctrl + V Enterと同様に、文字通りの戻り値がPS4=^Mあることに注意してください。^M

~からPOSIXシェル仕様:

各コマンドが実行される前に、シェルでスクリプトまたは関数の現在の連続行番号を表す10進数に設定します(番号は1から始まります)。ユーザーがLINENO変数を設定解除またはリセットすると、シェルのライフサイクル中に特別な意味が失われる可能性があります。シェルが現在スクリプトまたは関数を実行していない場合、値はLINENO指定されません。 IEEE Std 1003.1-2001のこのボリュームは、ユーザー移植性ユーティリティオプションをサポートするシステムに対する変数の効果のみを指定します。

答え3

LINENO = 0スクリプトが終了したときに実際の行番号の代わりに取得するのはERR(代わりに)キャプチャすることでEXIT変更できます。

また、set -EこれらのERRトラップは、関数、コマンド置換、およびサブシェル環境によって継承されるように追加されました。たとえば、スクリプトエラーを引き起こした関数名とその行番号を印刷する方法は次のとおりです。

set -eE
trap 'echo "Error in function $FUNCNAME at line $LINENO"' ERR

感謝の言葉:https://citizen428.net/blog/bash-error-handling-with-trap/

答え4

だから、偶然bashで$ LINENO変数を正しく表示し(例えば、DEBUGやEXITトラップで)、予想される行番号を提供する方法を見つけました。他の公開された方法よりはるかに簡単で、PS4とSTDERRをハイジャックする必要はありません。よく分からないなぜこれはうまくいきますが、他の人が説明できる場合は、その理由を知りたいです。

デフォルトでは、実行したい項目をダミー関数(呼び出しなど_ff)でラップしてsource <(declare -f _ff)実行すると、_ffinのすべてのインスタンスに実際に行番号が与えられます。$LINENO_ff

はい

    _ff() (
    trap 'echo "LINENO=$LINENO"' DEBUG;
    echo a
    echo b
    echo c
    echo d
    echo e
    echo f
    echo g
    )
    source <(declare -f _ff)

今実行すると_ff出力されます。

LINENO=2
a
LINENO=3
b
LINENO=4
c
LINENO=5
d
LINENO=6
e
LINENO=7
f
LINENO=8
g

注:ランニングはsource <(declare -f _ff)重要です。実行しないと、LINENO=1すべての$LINENO文が取得されます。

注:この質問はかなり古いですが、基本的にはERRトラップの外側に正しい$ LINENOを表示するための機能的な解決策を提供するインターネット上で見つけた唯一の場所です。だからここに投稿するのが適切だと思います。

関連情報