web-dev-qa-db-ja.com

'select'ループの実行時にSIGINTトラップを無視するBash

'trap'をselectループと組み合わせて使用​​すると、つまり、オプションが表示されているときにCTRL + Cを押してブレークアウトしようとすると、ターミナルに^ Cが出力されます。スクリプトから「トラップ」を削除すると、通常は終了します。つまり、CTRL + Cを受け入れます。

これを2つの異なるバージョンのbash(1つはCentOSに付属、もう1つはFedoraに付属)でテストしましたが、Fedora(4.4.23(1)-リリース)のものに問題があります。 Bashバージョン4.2.46(2)-CentOSに同梱されているリリースは正常に動作しているようです。ローカル端末とリモート(ssh経由)でもこれをテストしました。そして問題は常にFedora側にあります。

私が話していることを確認するためにコードを投稿します

これは機能しません:

#!/bin/bash

trap exit SIGINT

select opt in One Two Three; do
        break
done

'trap exit SIGINT'行全体を削除した場合、問題なく動作し、CTRL + Cを受け入れます。

これを修正またはバイパスする方法はありますか?

2
Marko Todoric

これを修正またはバイパスする方法はありますか?

_--posix_オプションを使用するか、一時的に_set -o posix_を使用して、posixモードをオンにすることでバイパスできます。

_set -o posix
select opt in foo bar baz; do
    echo "opt=$opt"
done
set +o posix
_

この動作の説明については、 zread() 関数を参照してください。これはreadビルトイン(selectのbashによって内部的にも呼び出されます)によって使用されます。

_  while ((r = read (fd, buf, len)) < 0 && errno == EINTR)
    /* XXX - bash-5.0 */
    /* We check executing_builtin and run traps here for backwards compatibility */
    if (executing_builtin)
      check_signals_and_traps ();   /* XXX - should it be check_signals()? */
    else
      check_signals ();
_

特別な理由により、_executing_builtin_は、readビルトインが明示的に呼び出された場合にのみ設定され、selectによって呼び出された場合には設定されません。これは非常にバグのように見えますが、意図的なものではありません。

Posixモードで実行している場合、シグナルはreadビルトインをキャンセルします。その場合、zreadintr()が呼び出されます。これは、zread()とは異なり、トラップの実行後に中断されたread(2)システムコールを再呼び出ししません。 _builtins/read.def_ を参照してください:

_      if (unbuffered_read == 2)
        retval = posixly_correct ? zreadintr (fd, &c, 1) : zreadn (fd, &c, nchars - nr);
      else if (unbuffered_read)
        retval = posixly_correct ? zreadintr (fd, &c, 1) : zread (fd, &c, 1);
      else
        retval = posixly_correct ? zreadcintr (fd, &c) : zreadc (fd, &c);
_

Bashの「再起動」に関する詳細read組み込み ここ

4
mosvy

bashマニュアルの関連セクションは次のとおりです(私は信じています;少なくともこれはそれがどのように動作するかです):

bashがコマンドの完了を待機していて、トラップが設定されているシグナルを受信した場合、コマンドが完了するまでトラップは実行されません。

したがって、selectはコマンドの完了を待機しているため、トラップハンドラーはbashループの本体が実行されるまで呼び出されません。入力がselectによって受信されると、トラップハンドラーが実行されます。

次の変更されたスクリプトは、それをよりよく示しています。

#!/bin/bash

trap 'echo INT;exit' SIGINT

select opt in One Two Three; do
    printf 'Got %s (%s)\n' "$REPLY" "$opt"
done

それを実行し(bash 5.0.3で)、1を選択し、を押します Ctrl+C その後 Enter、次に3を選択します。

$ bash script.sh
1) One
2) Two
3) Three
#? 1
Got 1 (One)
#? ^C
1) One
2) Two
3) Three
#? 3
INT

トラップハンドラーは、現在の入力(3)が受け入れられ、selectループの本体が実行される前に実行されます。

トラップハンドラーはnotを押すと実行されます Enter 後 Ctrl+C 押すので Enterselectプロンプトで、メニューが再表示されます。

1
Kusalananda