当被 AWK 调用并被 ^C 中断时,使 bash exit != 0

Tob*_*bia 7 bash signals awk gawk

我在使用以下 (G)AWK 脚本时遇到问题:

do {
    ...
} while (system("sleep 10"))
Run Code Online (Sandbox Code Playgroud)

我的目的是在用户在睡眠期间按 ^C 时打破循环,但它不起作用。

我相信问题在于当被 ^C 中断时,Bash 以 0 退出,至少当它被 AWK 执行时system()

$ awk 'BEGIN { print "\n" system("sleep 2") }'
(let the sleep complete)
0
Run Code Online (Sandbox Code Playgroud)

 

$ awk 'BEGIN { print "\n" system("sleep 2") }'
^C
0
Run Code Online (Sandbox Code Playgroud)

为什么会这样?

这是 Bash 或 (G)AWK 中的错误吗?

是否有一个不涉及复杂的 Bash 特定语法的简单解决方案,例如trap

我能想到的最好的方法是:

do {
    ...
} while (42 == system("sleep 10 && exit 42"))
Run Code Online (Sandbox Code Playgroud)

对我来说,这仍然是一个杂七杂八的东西。

Sté*_*las 6

awksystem()应该返回的内容没有详细说明

awk实现中似乎常见的是,在正常退出时,它返回退出代码(传递给exit(3)模 256的数字),但是当 shell 进程被信号杀死时,会有很多不同的行为。

另请注意,虽然 C 函数system(3)旨在忽略父级中的 SIGINT(和 SIGQUIT),但该要求也适用于awk's并不是很清楚(至少对我而言)system()。某些awk实现(如mawk)将在该 SIGINT 上消失(这也是我希望看到的行为,因为我不喜欢我的 CTRL-C 被忽略只是因为awk碰巧正在运行该system()函数),一些(如gawk或传统实现)惯于。

另请注意,某些 shell 可以拦截其中一些信号并最终调用exit()这会影响行为(例如,请参阅有关 Bourne shell 的评论中的讨论),这就是我exec在下面的示例中使用从循环中删除 shell 的原因。

对于SIGINT返回的值system()(如果您考虑close()1 ,则有更多变化),我们看到:

$ nawk 'BEGIN {print system("exec kill -s INT $$")}'
0.0078125
$ bwk-awk 'BEGIN {print system("exec kill -s INT $$")}'
0.0078125
$ mawk 'BEGIN {print system("exec kill -s INT $$")}'
130
$ gawk 'BEGIN {print system("exec kill -s INT $$")}'
0
Run Code Online (Sandbox Code Playgroud)

0.00781252 / 256(对SEGV11,你会得到0.542969((128 + 11)/ 256),如果一个核心转储,0.0429688(256分之11)其他),nawk正在nawk在Solaris 10或11,或它的Linux端口在发现传家宝工具箱中,bwk-awkawk由Brian Kernighan的自己保持(在Kawk)的基础awk上的一些BSD系统中(在Debian GNU / Linux的这里测试)。/usr/xpg4/bin/awk在 Solaris 11 上的行为类似于gawk.

因此,基于该值s通过返回system(3)(整数,其中位0至6是信号编号,第7位的核心位,并且位8到15中的退出代码),awksystem()上述返回任一:

  • s / 256(传统awk实现),
  • int(s/256)( gawk),
  • 或在 中mawk,与 Bourne 或 C-shell 之类的 shell 所做的转换相同((s&127)+128如果被杀死,s>>8否则),除了如果核心被转储,您将得到(s&127)+256而不是(s&127)+128(值为(s&255)+128)。

所以,在这里,你可以这样做:

awk 'BEGIN{print system("trap exit\\ 1 INT; sleep 10")}'
Run Code Online (Sandbox Code Playgroud)

但是它仍然会导致awk被某些awk实现杀死,例如mawk. 如果您shbashyash,您可以执行以下操作:

awk 'BEGIN{print system("set -m; sleep 10; exit")}'
Run Code Online (Sandbox Code Playgroud)

Sosleep在它自己的进程组中运行(并且只有它获得 SIGINT)。

另一种选择是在调用之前忽略 SIGINT awk。但是,如果信号在启动时已被忽略,则大多数 shell(这是 POSIX 要求)无法更改信号处理程序。所以像:

(
  trap '' INT
  awk 'BEGIN{print system("trap exit\\ 1 INT; sleep 10; exit")}'
)
Run Code Online (Sandbox Code Playgroud)

不会工作。zsh但是没有那个(自己造成的)限制,所以如果你知道zsh可用,你可以这样做:

(
  trap '' INT
  awk 'BEGIN{print system("exec zsh -c \"TRAPINT() exit 1; sleep 10\"")}'
)
Run Code Online (Sandbox Code Playgroud)

无论awk是 is还是其他,哪个都可以工作mawkgawk并且可以避免不得不搞乱作业控制。但此时,值得考虑使用perl/ python/ ruby... 而不是awk您可以根据需要调整信号处理的地方。

笔记

1close()管道上,如:

awk 'BEGIN {cmd = "kill -s INT $$"; cmd | getline; print close(cmd)}'
Run Code Online (Sandbox Code Playgroud)

首先,这次在我尝试过的所有实现中都会^C中断awk(没有像 for 那样需要忽略 SIGINT/SIGQUIT for popen(3)/ pclose(3)(实现那个的自然方式getlinesystem(3))。

但是当谈到退出状态时(上面spclose(3)/ waitpid(2)like返回的值在哪里system()),我们看到:

  • Solaris nawk:不起作用,您不能close()在 Solaris 中那样调用nawk
  • /usr/xpg4/bin/awk在 Solaris 上。始终返回 0,即使exit(1)进程已完成。显然是一致性错误。
  • gawkbwk-awk: 给出sexit 1给出 256,被 SIGINT 杀死给出 2,被 11 的 SIGSEGV 杀死,核心给出 139)。
  • mawk: 与 for 一样system(),看起来mawk是唯一对此有任何想法的实现。