Bashは 'select'ループが実行されたときにSIGINTトラップを無視します。

Bashは 'select'ループが実行されたときにSIGINTトラップを無視します。

選択ループで「トラップ」を使用すると、つまりオプションが表示されている間にCTRL + Cを押して中断しようとすると、端末に^ Cのみが印刷されます。スクリプトから「トラップ」を削除すると正常に終了します。つまり、Ctrl + Cを使用できます。

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

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

これはうまくいきません。

#!/bin/bash

trap exit SIGINT

select opt in One Two Three; do
        break
done

完全な「トラップ終了SIGINT」行を削除すると、問題なくCTRL + Cを使用でき、正しく機能します。

この問題を解決またはバイパスする方法についてのアイデアはありますか?

答え1

この問題を解決またはバイパスする方法についてのアイデアはありますか?

--posixoptionsを使用するか、一時的にposixモードをオンにすると、それをバイパスできます set -o posix

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

この動作の説明を表示するには確認できます。zread()組み込み関数で使用される関数read(bashから内部的に呼び出されることもありますselect):

  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。これはバグのように見え、意図的なものではありません。readselect

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の「restart」組み込みコマンドのread詳細ここ

答え2

マニュアルの関連部分は次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

bash5.0.3で実行し、を選択し、を押して1Ctrl+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

トラップハンドラはいいえEnterプロンプトでボタンを押すとメニューが再び表示されるCtrl+Cため、ボタンを押すと実行されます。Enterselect

関連情報