当我将“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
运行它(使用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
执行时,陷阱处理程序就会被执行。
陷阱处理程序是不是Enter当我按after时执行,Ctrl+C因为Enter在select
提示符下按只会重新显示菜单。