将 stdout 与 stderr 分开缓冲

Jam*_*ude 4 pipe shell-script buffer

我正在运行一个实用程序,它会发出以下内容:

  • 标准错误的进展
  • 数据/产量/输出到其标准输出

我没有构建该实用程序,也无法轻松修改它。

我希望做到以下几点:

  • 将其标准错误直接发送到标准输出
  • 缓冲其Output,并在命令退出后将
    其刷新到标准输出 (此处可能存在少于 10KiB 的数据,因此 RAM 不成问题。)

这可以在 POSIX sh 中完成(并且仅调用 LinuxOpenBSD 通用的实用程序),而不会因命名管道或临时文件而产生不确定性/潜在的竞争条件/等吗?

Sté*_*las 5

您应该能够执行以下操作:

{
  cmd 2>&3 3>&- |
    awk '    {saved = saved $0 ORS}
         END {printf "%s", saved}' 3>&-
} 3>&1
Run Code Online (Sandbox Code Playgroud)

这里用于awk保存所有cmd的输出(在cmd将其 stderr 输出写入脚本的 stdout 之后)。

awk将读取直到管道的写入端关闭。通常,只有当cmd(以及它分叉的所有进程仍然持有管道的 fd )完成时才会发生这种情况。如果出于某种原因,cmd决定显式关闭其标准输出,然后在标准错误上写入更多进度,则额外的进度可能会在正常输出之后结束。cmd您可以通过替换为来解决这个问题(cmd; exit)awk然后 where 也会等待该子 shell(它的标准输出也向管道打开)完成,并且该子 shell 恰好等待完成cmd(并使用 报告其退出状态exit)。

但对于一个行为良好的人来说,这不应该是必要的cmd。这也无法解决分叉cmd(并且不等待)子进程及其 stdout 重定向的情况,该子进程可能会在该脚本完成后很久awk甚至该脚本完成后写入其 stderr (可能是比显式关闭的命令更可能的情况)它的标准输出)。

如果cmd的输出不是文本,请注意并非所有awk实现都可以处理字节 0 或超长行,并且如果输入中尚未存在换行符,则会在末尾添加换行符。

POSIX 工具箱没有任何命令可以在内存中存储任意数量的二进制数据并稍后显示。

如果perl可用,您可以将awk命令替换为perl -0777 -pe ''.

在这里,您可以将输出存储在临时文件中,而不是内存中,这将解决二进制输出问题,并且可能会更好地扩展到更大的输出。

不幸的是,可靠地创建临时文件的唯一 POSIX 方法是使用该m4实用程序,但如今在生产系统上并不总能找到该实用程序(即使是 POSIX 强制使用的实用程序)。您可能更有可能找到perlm4.

无论如何,这可能是:

die() {
  [ "$#" -eq 0 ] || printf >&2 '%s\n' "$@"
  exit 1
}

tmpdir=${TMPDIR:-/tmp}
tmpfile=$(
  echo 'mkstemp(TEMPLATE)' |
    m4 -D "TEMPLATE=${tmpdir%/}/XXXXXXX"
) && [ -n "$tmpfile" ] || die 'Cannot get a temp file'

{
  rm -f -- "$tmpfile" || die "Cannot remove $tmpfile"
  cmd 2>&1 >&3 3>&- 4<&-
  cat <&4
} 3> "$tmpfile" 4< "$tmpfile"
Run Code Online (Sandbox Code Playgroud)

这里,在打开临时文件后但在运行之前取消临时文件的链接,这cmd是处理清理的一种巧妙方法。

如果您仅针对 GNU(记住“Linux”不是操作系统,只是在各种操作系统上找到的内核,其中一些操作系统甚至没有)sh和 OpenBSD 系统,那么您应该能够使用mktemp创建m4临时文件。