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( cmdstderr 출력을 스크립트의 stdout에 쓴 후).

awk파이프의 쓰기 끝이 닫힐 때까지 읽습니다. 일반적으로 cmd이는 파이프의 fd를 여전히 보유하고 있는 분기된 모든 프로세스가 완료된 경우에만 발생합니다. 어떤 이유로 cmdstdout을 명시적으로 닫고 stderr에 추가 진행 상황을 기록하기로 결정한 경우 추가 진행 상황이 표시될 수 있습니다.뒤쪽에정상 출력. 해당 하위 쉘(파이프에 대한 표준 출력도 열려 있음)이 완료될 때까지 기다리는 위치 와 해당 하위 쉘이 완료될 때까지 정확히 기다리는 위치(및 종료 상태를 로 보고하는 위치)를 대체하여 cmd이 문제를 해결할 수 있습니다 .(cmd; exit)awkcmdexit

하지만 행동이 좋은 사람에게는 이것이 필요하지 않습니다 cmd. 이는 또한 스크립트가 완료된 후 오랫동안 또는 심지어 스크립트가 완료된 후에도(아마도 다음보다 오래된 명령을 사용하여) stderr에 쓸 cmd수 있는 하위 프로세스 및 해당 stdout 리디렉션을 포크(기다리지 않고)하는 경우를 해결하지 못합니다. awk명시적인 종료) 가능성이 높음) 표준 출력).

cmd출력이 텍스트가 아닌 경우 모든 awk구현이 바이트 0 또는 매우 긴 줄을 처리할 수 있는 것은 아니며, 입력에 아직 없는 경우 줄 바꿈이 끝에 추가됩니다.

POSIX 도구 상자에는 메모리에 임의의 양의 이진 데이터를 저장하고 나중에 표시하는 명령이 없습니다.

사용 가능한 경우 명령을 로 바꿀 perl수 있습니다 .awkperl -0777 -pe ''

여기서 출력을 메모리 대신 임시 파일에 저장할 수 있습니다. 그러면 바이너리 출력 문제가 해결되고 더 큰 출력으로 더 잘 확장될 수 있습니다.

불행하게도 임시 파일을 안정적으로 생성하는 유일한 POSIX 방법은 이 m4유틸리티를 사용하는 것입니다. 이 유틸리티는 오늘날 프로덕션 시스템(POSIX 필수 시스템에서도)에서 항상 발견되지는 않습니다. perl보다 찾을 가능성이 더 높을 수 있습니다 m4.

어쨌든 이는 다음과 같을 수 있습니다.

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을 사용하여 .mktempm4

답변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"

두 솔루션 모두 루트(파일 시스템 및 /proc/*/environ)에 표시되며 시스템의 다른 사용자에게는 표시되지 않습니다.

시스템에 sponge/dev/stdout이 있고 쉘이 이를 지원하는 경우:

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

(Ksh 버전:

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

)

관련 정보