当“select”循环运行时,Bash 忽略 SIGINT 陷阱

当“select”循环运行时,Bash 忽略 SIGINT 陷阱

当我将“trap”与选择循环结合使用时,即当我尝试在显示选项时按 CTRL+C 来突破时,它只会在终端中打印 ^C 。如果我从脚本中删除“trap”,它通常会退出,也就是说它将接受 CTRL+C。

我已经在两种不同版本的 bash 上对此进行了测试(一种是 CentOS 附带的,一种是 Fedora 附带的),而我对 Fedora 版本(4.4.23(1)-release)的版本有问题。 CentOS 附带的 Bash 版本 4.2.46(2)-release 似乎运行良好。我还在本地终端和远程(通过 ssh)进行了测试。问题总是出在 Fedora 方面。

我将发布代码来看看我在说什么

这个不起作用:

#!/bin/bash

trap exit SIGINT

select opt in One Two Three; do
        break
done

如果我要删除整个“trap exit SIGINT”行,它将正常工作并接受 CTRL+C 而不会出现问题。

有什么想法可以解决或绕过这个问题吗?

答案1

有什么想法可以解决或绕过这个问题吗?

您可以通过打开 posix 模式来绕过它,可以使用选项 --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仅在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内置命令的更多详细信息这里

答案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当我按after时执行,Ctrl+C因为Enterselect提示符下按只会重新显示菜单。

相关内容