web-dev-qa-db-ja.com

トラップされた関数に対して間違った$ LINENOを取得する

私はスクリプトを学ぶために自分のためにBashスクリプトを書いています。ある時点で、スクリプトが強制終了された場合に不要なディレクトリとファイルをクリーンアップするためにトラップを追加する必要があります。ただし、なぜかわからないため、トラップはクリーニング関数を呼び出します-clean_a()-スクリプトが強制終了されたが、$LINENOがクリーニング関数自体の行を指しており、int関数ではない-archieve_it()-スクリプトが強制終了されたとき。

予想される動作:

  1. スクリプトを実行する
  2. 押す Ctrl+C
  3. トラップキャッシュ Ctrl+Cclean_a()関数を呼び出します
  4. clean_a()関数は行番号をエコーし​​ます。 Ctrl+C が押されました。 archieve_it()の10行目とします。

実際に起こること:

  1. スクリプトを実行する
  2. 押す Ctrl+C
  3. トラップキャッシュ Ctrl+Cclean_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:元のスクリプトは見ることができます here 。定義と変数名はトルコ語です。必要に応じて、何でも英語に翻訳できます。

EDIT:次のような@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でスクリプトを実行し、それをで終了すると Ctrl+C、以下のように正しい行番号を出力します。

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

ただし、clean_a()関数では、$LASTNOの値は1として出力されます。

 line: 462 1

@Arkadiusz Drabczykが示すバグと関係がありますか?

EDIT2:@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

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

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

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

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

#!/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を押したときに線を指す Ctrl+C、rsyncの実行中に。

別のテストでは、次のようなclear_a関数を書いてみました。

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

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

つまり、パラメーター置換を使用すると、スクリプトが終了したときに正しい行番号を取得できるということです。

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

これが私が望むように動作するスクリプトです:

#!/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

スクリプトが終了すると、trap$LASTNO-最初の引数-、$LINENO- 2番目の引数-、および$BASH_COMMAND- 3番目の引数-を評価し、それらの値をclear_a関数に渡します。最後に、スクリプトが終了する行番号として$1を指定して$ LASTNOを出力します。

7
numand

mikeservのソリューションは優れていますが、トラップの実行時にfntrap行の$LINENOに渡されると彼は間違っています。 trap ...の前に行を挿入すると、トラップが宣言された場所に関係なく、fnが実際には常に1に渡されることがわかります。

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"一重引用符で囲まれているため、$LINENOexpandedを取得します、EXITがトリガーされた場合にのみ、トリガーされたため、fn 5に展開する必要があります。では、なぜそれができないのでしょうか?実際、トラップがトリガーされたときに$ LINENOが1にリセットされるように意図的に変更されたbash-4.0まではそうでした。したがって、fn 1に展開されます。 [source] 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)でテスト済み

6
Niklas Holm