将 stdout 与 stderr 分开缓冲

将 stdout 与 stderr 分开缓冲

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

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

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

我希望做到以下几点:

  • 发送其标准错误直接地标准输出
  • 缓冲其输出,并将其刷新到标准输出一旦命令退出
    (这里可能有不到 10KiB 的数据,所以 RAM 不是问题。)

这可以在 POSIX sh 中完成吗(并且仅调用 Linux 通用的实用程序)OpenBSD),没有命名管道或临时文件引起的不确定性/潜在的竞争条件/等?

答案1

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

{
  cmd 2>&3 3>&- |
    awk '    {saved = saved $0 ORS}
         END {printf "%s", saved}' 3>&-
} 3>&1

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

awk将读取直到管道的写入端关闭。通常,只有当cmd(以及它分叉的所有进程仍然持有管道的 fd )完成时才会发生这种情况。如果出于某种原因,cmd决定显式关闭其 stdout,然后在 stderr 上写入更多进度,则额外的进度可能最终会出现正常输出。您可以通过替换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"

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

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

答案2

#!/bin/bash

mycmd() {
  echo progress >&2
  echo out
  sleep 1
  echo progress >&2
  echo out
  sleep 1
  echo progress >&2
  echo out
  sleep 1
}

# Make sure the tempfile is made in a secure way that avoids indeterminism / potential race conditions / etc.
tmpfile="$(tempfile)"
# Write output to the file
mycmd 2>&1 >"$tmpfile"; cat "$tmpfile"; rm "$tmpfile"

# or if output is small: Write it to an environment variable.
# These are below the limit for "small" for different shells
# (Determined by making "mycmd" output that amount of data)
# ash  30 GB
# dash 30 GB
# bash 3 GB
# zsh  3 GB
# ksh  1 GB
out=`mycmd` 2>&1
echo "$out"

这两种解决方案都对 root 可见(在文件系统和 /proc/*/environ 中),并且系统上的其他用户不可见。

如果您的系统有sponge并且 shell 支持 /dev/stdout:

{
  cmd 2>&3 3>&- |
    sponge /dev/stdout 3>&-;
} 3>&1

(Ksh版本:

$ ksh --version
  version         sh (AT&T Research) 93u+ 2012-08-01

相关内容