将 stderr 和 stdout 分别重定向到 bash 脚本中的函数

Cli*_*ong 6 bash logs io-redirection shell-script systemd-journald

我正在编写一个包装脚本来将 cronjob 输出记录到 journald。

我有几个目标:

  • 必须将 stderr 和 stdout 以不同的优先级和前缀标签记录到日志中
  • 还必须将包装命令的 stderr 输出到包装脚本的 stderr(由 cron 捕获并通过电子邮件发送警报)
  • 必须始终保持行序

到目前为止,我似乎有两个问题:

  1. 我不确定如何将 stderr 和 stdout 分别重定向到我的日志记录功能。到目前为止,我尝试过的所有内容都无法重定向除 NULL 以外的任何内容。在我看来,重定向不是我的强项之一。(已解决,见评论)
  2. 我目前的方法{ $_EXEC $_ARGS 2>&1 1>&7 7>&- | journaldlog error; } 7>&1 1>&2 | journaldlog info似乎正好运行两次日志记录功能,一次用于 stderr,一次用于 stdout。这不会保留行顺序。我宁愿它在每个输出行运行一次该函数。

我应该放弃在 bash 中执行此操作并尝试使用 Perl 吗?我确信我的 Perl 经验会回到我身上......最终。

我尝试了很多不同的方法。其中大部分是在网上找到的,尤其是在堆栈交换上。老实说,我不记得我还尝试过什么……此时只是模糊。bash 文档也没有太大帮助。

Mic*_*mer 8

您可以直接在重定向中使用进程替换

gen > >(one) 2> >(two)
Run Code Online (Sandbox Code Playgroud)

这将使标准输出gen作为标准输入one和标准错误gen的输入two。这些可能是可执行的命令或函数;这是我使用的功能:

one() {
    while read line
    do
        echo $'\e[31m'"$line"$'\e[22m'
    done
}

two() {
    while read line
    do
        echo $'\e[32m'"$line"$'\e[22m'
        echo "$line" >&2
    done
}
Run Code Online (Sandbox Code Playgroud)

它们分别以红色和绿色输出其输入行,并two写入普通标准错误。你可以在循环中做任何你想做的事情,或者将输入发送到另一个程序或任何你需要的东西。


请注意,一旦您处于这一点就没有真正的“保留行顺序”之类的东西- 它们是两个单独的流和单独的进程,并且调度程序很可能在给另一个进程之前运行一个进程一段时间一次,将数据保存在内核管道缓冲区中,直到读取为止。我一直在使用一个函数将奇数输出到 stdout 甚至 stderr 以进行测试,并且从每个函数中连续运行一打或更多是很常见的。

有可能获得更接近终端中出现的排序的内容,但可能不会出现在 Bash 中。AC 程序使用pipe并且select可以重建排序(尽管仍然不能保证与显示的相同,出于同样的原因:您的流程可能不会被安排一段时间,一旦出现积压,就无法知道什么是最先出现的1)。如果排序非常重要,您将需要另一种方法,并且可能是基本可执行文件的合作。如果这是一个硬性要求,您可能需要重新考虑。

1可能还有特定于平台的方法来控制管道缓冲,但它们也不太可能对您有足够的帮助。在 Linux 上,您可以将缓冲区大小缩小到系统页面大小,但不能更小。我不知道有一种可以让您立即强制写入(或阻塞直到被读取)。在任何情况下,它们很可能以 stdio 流的方式在源端缓冲,然后您将永远看不到排序。


roa*_*ima 2

这是将stdoutstderr捕获到不同命令管道的一种方法。

#!/bin/bash
#
exec 3>&1 4>&2

loggerForStdout() {
    # FD 3 is the original stdout
    local x
    while IFS= read -r x; do printf "STDOUT\t%s\n" "$x"; done >&3
}
loggerForStderr() {
    # FD 4 is the original stderr
    local x
    while IFS= read -r x; do printf "STDERR\t%s\n" "$x"; done >&4
}


( (
    # This is where your command would be put
    # e.g. stdbuf -oL -eL rsync -a /from /to
    #
    echo this is stdout; echo this is stderr >&2

) | loggerForStdout ) 2>&1 | loggerForStderr
Run Code Online (Sandbox Code Playgroud)

我不完全确定即使使用stdbuf -oL -eL.

如果您有替代记录器,您可以省略前三行。它们的存在只是为了使代码成为完整且独立的示例。