为什么DEBUG陷阱执行的次数比预期的多?

时间:2018-07-27 11:43:19

标签: bash

以下代码触发调试陷阱3次。但是,我只希望执行2次-一次执行foo,第二次执行echo hello

foo() {
    echo hello
}

set -T
trap 'echo oops' DEBUG
foo

输出:

oops
oops
oops
hello

预期输出:

oops
oops
hello

已测试

Bash个版本:

GNU bash, version 4.3.30(1)-release (x86_64-unknown-linux-gnu)
GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu)
GNU bash, version 5.0.0(1)-alpha (x86_64-pc-linux-gnu)

我敢肯定,我只是误解了手册,在这里我遗漏了一些非常简单/显而易见的内容。

2 个答案:

答案 0 :(得分:2)

我假设您了解,DEBUG陷阱允许在每个其他Shell命令之前运行其定义(trap 'action' signal)中定义的命令,并且set -T允许对所有子Shell和功能的陷阱也。因此,一旦您设置了trap,就将执行三个处决。

  1. 调用函数foo,在此之前一旦触发trap,就会导致打印字符串的第一个实例。
  2. 然后在echo oops定义中有一个trap命令,其中echo只是另一个命令,因此在运行{{1} }被解雇。
  3. 并且trap中的实际echo命令产生了第三个实例
  4. 然后trap紧随hello执行完毕之后。

查看从调试模式运行时的顺序

trap

添加更多解释来解释该序列,假设在运行任何shell函数/命令之前 触发了$ bash -x script.sh + set -T + trap 'echo oops' DEBUG ++ echo oops # <--- triggered by call to 'foo' oops + foo ++ echo oops # <--- triggered by call to 'echo oops' inside trap definition oops ++ echo oops # <--- result of the actual command to be run oops + echo hello # <--- result of the function call 'foo' hello 陷阱。让我们细分为以下内容。理解这是两个指令/命令以及这些命令的两个结果动作

  1. 调用DEBUG是第一条指令,因此将触发陷阱并立即运行操作foo。请记住,echo oops是触发器,foo action ,我们第一次看到它。
  2. 现在,第二条指令是echo oops,它会触发另一个动作,因此由于echo oopstrap被触发,然后实际的指令被触发了。完成,而恰好也echo oops

答案 1 :(得分:2)

tl; dr:Bash在调用函数时多运行一次调试陷阱,以允许中断函数本身而不是第一个命令。这来自用于调用bash函数的源代码execute_function

  /* Run the debug trap here so we can trap at the start of a function's
     execution rather than the execution of the body's first command. */

源潜水时间

Here is the code执行调试陷阱:

int run_debug_trap () {
(...)
    trap_exit_value = _run_trap_internal (DEBUG_TRAP, "debug trap");

我在今天trap.c:1081的那一行上设置了一个断点,并查看了回溯轨迹:

初审

Breakpoint 1, run_debug_trap () at trap.c:1081
1081          trap_exit_value = _run_trap_internal (DEBUG_TRAP, "debug trap");
(gdb) where
#0  run_debug_trap () at trap.c:1081
#1  0x000055555559fd3d in execute_simple_command (simple_command=0x5555558aacc8, pipe_in=-1, pipe_out=-1, async=0, fds_to_close=0x555555899148) at execute_cmd.c:4056
#2  0x0000555555599fd7 in execute_command_internal (command=0x5555558aae08, asynchronous=0, pipe_in=-1, pipe_out=-1, fds_to_close=0x555555899148) at execute_cmd.c:807
#3  0x00005555555995c1 in execute_command (command=0x5555558aae08) at execute_cmd.c:405
#4  0x0000555555583c9e in reader_loop () at eval.c:180
#5  0x0000555555581794 in main (argc=2, argv=0x7fffffffe4d8, env=0x7fffffffe4f0) at shell.c:792
(gdb) up
#1  0x000055555559fd3d in execute_simple_command (simple_command=0x5555558aacc8, pipe_in=-1, pipe_out=-1, async=0, fds_to_close=0x555555899148) at execute_cmd.c:4056
4056      result = run_debug_trap ();

(gdb) print *simple_command->words->word
$3 = {word = 0x5555558a5268 "foo", flags = 0}

换句话说,这是简单的命令foo。到目前为止一切顺利。

第二个实例

Breakpoint 1, run_debug_trap () at trap.c:1081
1081          trap_exit_value = _run_trap_internal (DEBUG_TRAP, "debug trap");
(gdb) where
#0  run_debug_trap () at trap.c:1081
#1  0x00005555555a170a in execute_function (var=0x5555558ab648, words=0x5555558aac28, flags=0, fds_to_close=0x555555899148, async=0, subshell=0) at execute_cmd.c:4787
#2  0x00005555555a1c68 in execute_builtin_or_function (words=0x5555558aac28, builtin=0x0, var=0x5555558ab648, redirects=0x0, fds_to_close=0x555555899148, flags=0) at execute_cmd.c:5030
#3  0x00005555555a0660 in execute_simple_command (simple_command=0x5555558aacc8, pipe_in=-1, pipe_out=-1, async=0, fds_to_close=0x555555899148) at execute_cmd.c:4330
#4  0x0000555555599fd7 in execute_command_internal (command=0x5555558aae08, asynchronous=0, pipe_in=-1, pipe_out=-1, fds_to_close=0x555555899148) at execute_cmd.c:807
#5  0x00005555555995c1 in execute_command (command=0x5555558aae08) at execute_cmd.c:405
#6  0x0000555555583c9e in reader_loop () at eval.c:180
#7  0x0000555555581794 in main (argc=2, argv=0x7fffffffe4d8, env=0x7fffffffe4f0) at shell.c:792

这更有趣。我们仍然从以前开始执行简单的命令foo,但是它再次被触发!为什么呢让我们看一下呼叫站点execute_cmd.c:4787

  /* Run the debug trap here so we can trap at the start of a function's
     execution rather than the execution of the body's first command. */
  showing_function_line = 1;
  save_current = currently_executing_command;
  result = run_debug_trap ();

换句话说,Bash似乎有意再运行一次调试陷阱,以中断某个功能而不是第一个命令。

第三实例

毫不奇怪,该函数的echo命令,因此我将不包含它。