如果输出重定向到标准输出,为什么 Bash 陷阱不起作用?

cer*_*cem 7 bash

鉴于以下测试脚本:

#!/bin/bash

# see "killing timeout": https://unix.stackexchange.com/a/57692/65781
declare -a timeout_pids
my_timeout(){
    local args tp
    args="$@"
    timeout $args &
    tp=$!
    #echo "pid of timeout: $tp"
    timeout_pids+=($tp)
    wait $tp
}

cleanup(){
    echo "-----------------------------------------"
    echo "Restoring previous routing table settings"
}

pre_cleanup(){
    echo "Executing pre-cleanup..."
    exit
}

trap pre_cleanup INT
trap cleanup EXIT

echo "ctrl+c now to execute cleanup"
#my_timeout 9s sleep 20 2> /dev/null >/dev/null # <- does not work as expected!
my_timeout 9s sleep 20 2> /dev/null # <- works as expected 
Run Code Online (Sandbox Code Playgroud)

如果启用了“不起作用”行并且脚本运行然后Ctrl+C按下;脚本立即结束,不执行陷阱。

如果删除“重定向输出到标准输出”部分(启用“按预期工作”行)然后Ctrl+C按下,则执行陷阱。

这是为什么?

Kam*_*ski 11

其他答案建议exec &> /dev/tty,这样的陷阱写入/dev/tty不管以前的重定向:

陷阱已运行,但重定向到的标准输出/dev/null仍在原位,因此不会打印输出。[...]exec &> /dev/tty通过重新建立从标准输出/错误到终端的连接来解决它。

有时这可能不是最好的解决方案。当您希望您的脚本(“固定”为exec &> /dev/tty)保持沉默时,请考虑一般情况。你调用

./the_script >/dev/null 2>/dev/null
Run Code Online (Sandbox Code Playgroud)

但随后陷阱被触发,/dev/tty无论如何它都会写入。

对我来说,更好的方法是以“备份”文件描述符的形式存储原始标准输出和标准错误:

# at the very beginning of the script
exec 21>&1
exec 22>&2
Run Code Online (Sandbox Code Playgroud)

然后,在陷阱函数中,您可以重定向任何单个命令:

echo "Some output" >&21
echo "Some error" >&22
Run Code Online (Sandbox Code Playgroud)

或恢复原始目的地并照常进行:

# at the beginning of the trap function
exec 1>&21
exec 2>&22
Run Code Online (Sandbox Code Playgroud)

这样,应用于中断命令的重定向就不会影响陷阱;应用于整个脚本的重定向仍然会。


l0b*_*0b0 8

陷阱运行,但重定向到 /dev/null 的标准输出仍然存在,因此不会打印输出。尝试替换trap内容touch "$FUNCNAME"以验证,或exec &> /dev/tty通过重新建立从标准输出/错误到终端的连接来解决它。至于为什么,这可能是一个更大的功能的一部分,以便在运行陷阱时保留很多原始环境以避免出现意外。