我的函数的管道错误导致命令在 1 次失败后被忽略

我的函数的管道错误导致命令在 1 次失败后被忽略

上下文:我有一个复制文件的 Bash 脚本

function log () {
    read IN
    if [ "$IN" == "" ]; then
        :
    else

        echo "$datetime"$'\t'"$IN" | tee -a logfile
    fi
}

function copy () {
    command cp -L --parents $@
}

...
copy -R /etc . 2>&1 | log
...

问题:当cp -L --parents -R /etc 2>&1手动运行时,我遇到了大约 10 次失败(符号链接损坏,这是预料之中的),并且整个 /etc 已被复制。

但是当脚本运行时,只报告 1 次失败,并且 /etc 只复制到 1 次失败发生的位置。

在尝试排除故障时,我所做的就是2>&1从脚本中删除,然后复制按预期进行。

问题:是我的log函数造成了麻烦,还是脚本编写方式存在语法问题(尽管不是破坏脚本)?

答案1

你的log功能是罪魁祸首。它的作用是:读取一行,如果该行不为空,则打印时间戳,然后打印该行内容。这就是它所做的一切:一旦处理完一行,它就会返回。

cp发出第一条错误消息时,该log函数会读取它并处理它。由于该log函数随后返回,因此管道右侧的进程退出,这导致管道的读取端关闭。当cp发出第二条错误消息时,它会尝试写入关闭的管道,这会导致它因SIGPIPE信号。标准错误是行缓冲的(默认情况下,并且cp不会尝试更改它),因此缓冲不起作用。

要处理所有输入行,您需要read循环。

log () {
    while IFS= read -r IN; do
        echo "$datetime"$'\t'"$IN"
    done | tee -a logfile >&2
}

我还修复了read呼叫IFS= read -r实际读取一行。我删除了对空行的特殊处理,这是毫无意义的(输入中不会有空行)。我认为您将其放入是为了处理输入为空(零行输入)时的情况,但处理该问题的正确方法是检查命令的返回状态read。我还修复了log打印其标准错误,因为它用于处理错误消息。

在命令的每一行输出前添加时间戳了解其他方法来执行此操作。

请注意,将命令放在管道的左侧有一个主要缺点:它导致退出状态被忽略。因此,如果cp失败,您的脚本将愉快地继续。错误将记录在某处,但后续命令将正常执行,并且不会提醒您应该去阅读日志。在 bash、ksh 或 zsh 中,您可以设置pipefail选项除此之外,set -e一旦任何命令失败,即使在管道的左侧,您的脚本也会以错误状态退出。

set -o errexit -o pipefail
copy … |& log

或者,使用流程替代而不是通过另一个进程通过管道传输错误输出的管道。进程替代的注意事项与管道略有不同;中的错误log会被有效地忽略,并且命令可能会在log完成之前返回(在 bash 中,log在后台运行)。

set -e
copy … 2> >(log)

几乎,您不需要IFS= read -r IN阅读更多内容,也不可能破坏该行。

相关内容