/dev/stderr을 파이프로 사용할 수 없는 이유는 무엇입니까?

/dev/stderr을 파이프로 사용할 수 없는 이유는 무엇입니까?

보통 paste인쇄인접한 열에 있는 명명된(또는 이에 상응하는) 파일은 다음과 같습니다.

paste <(printf '%s\n' a b) <(seq 2)

산출:

a   1
b   2

하지만 두 파일 이 /dev/stdin및 ./dev/stderr

우리가 가지고 있다고 가정하자부족상자두 줄을 출력하는 프로그램표준 출력그리고 두 줄표준 에러. 설명을 위해 다음 함수를 사용하여 이를 시뮬레이션할 수 있습니다.

bb() { seq 2 | tee >(sed 's/^/e/' > /dev/stderr) ; }

지금 달려라annotate-output, (내부에개발 스크립트캡슐화됨데비안/우분투/등.) 작동하는지 보여주기 위해:

annotate-output bash -c 'bb() { seq 2 | tee >(sed 's/^/e/' > /dev/stderr) ; }; bb'
22:06:17 I: Started bash -c bb() { seq 2 | tee >(sed s/^/e/ > /dev/stderr) ; }; bb
22:06:17 O: 1
22:06:17 E: e1
22:06:17 O: 2
22:06:17 E: e2
22:06:17 I: Finished with exitcode 0

그래서 작동합니다. 피드 :bbpaste

bb | paste /dev/stdin /dev/stderr

산출:

1   e1
e2
^C

정지됨 - ^C눌려졌다는 의미Control-C그만두다.

|다음 중 하나로 변경해 ;도 작동하지 않습니다.

bb ; paste /dev/stdin /dev/stderr

산출:

1
2
e1
e2
^C

또한 일시 중지됨 - ^C눌려졌음을 의미합니다.Control-C그만두다.

원하는 출력:

1    e1
2    e2

을 사용하여 수행할 수 있습니까 paste? 그렇지 않다면 왜 안 됩니까?

답변1

/dev/stderr을 파이프로 사용할 수 없는 이유는 무엇입니까?

문제는 그렇지 paste도 않고 그렇지도 않습니다 /dev/stdin. 그것은 와 함께 간다 /dev/stderr.

모든 명령은 하나의 개방형 입력 설명자(0: 표준 입력)와 두 개의 출력(1: 표준 출력 및 2: 표준 오류)으로 생성됩니다. 일반적 으로 각각 /dev/stdin및 이름을 사용하여 액세스할 수 있지만 다음을 참조하세요./dev/stdout/dev/stderr/dev/stdin, /dev/stdout 및 /dev/stderr의 이식성은 얼마나 됩니까?. (포함 paste) 많은 명령 도 파일 이름을 -STDIN으로 해석합니다.

개별적으로 실행 하면 bbSTDOUT과 STDERR은 모두 콘솔이며 일반적으로 명령 출력이 나타납니다. 라인은 (귀하의 설명과 같이) 다른 설명자를 통과 annotate-output하지만 동일한 위치에 끝납니다.

하나와 두 번째 명령을 추가하면 |파이프가 생성됩니다.

bb | paste /dev/stdin /dev/stderr

|의 출력을 bb의 입력에 연결하도록 쉘에 지시합니다 paste. paste먼저 를 읽으십시오 /dev/stdin. 이것은 (일부 심볼릭 링크를 통해) 자신의 표준 입력 설명자(쉘이 방금 연결한)로 해석되어 행이 1통과하도록 합니다.

그러나 쉘/파이프라인은 STDERR에 영향을 미치지 않습니다. bb여전히 e1 e2콘솔로 전송 중입니다. 동시에 paste동일한 콘솔에서 데이터를 읽으려고 하면 콘솔이 정지됩니다(무언가를 입력할 때까지).

당신의 링크텍스트 편집기를 사용하여 /dev/stdout을 읽을 수 없는 이유는 무엇입니까?./dev/stderr

두 번째 파이프를 만드는 방법

표준 출력과 표준 오류를 생성하는 명령이 있고 paste이 두 줄을 서로 옆에 두기를 원합니다. 이는 각 열에 하나씩 두 개의 동시 파이프라인을 의미합니다. 쉘 파이프는 ... | ...이들 중 하나를 제공합니다. 두 번째 파이프를 직접 생성하고 .redirect를 사용하여 STDERR을 해당 파이프로 리디렉션해야 합니다 2>filename.

mkfifo RHS
bb 2>RHS | paste /dev/stdin RHS

이것이 스크립트 내에서 사용되는 경우 FIFO를 임시 디렉토리에 배치하고 사용 후 삭제하는 것이 좋습니다.

답변2

annotate-outputpaste이 작업을 수행할 수 있는 이유는 특별한 작업(예: 명령의 표준 오류를 fifo로 리디렉션)을 수행하고 있기 때문입니다.전적으로절대 안돼 - 단지 paste왜냐하면아니요입력을 받는 명령을 실행하며 입력이나 출력을 리디렉션할 수 없습니다.

그러나 annotate-output에서 사용하는 것과 똑같은 트릭을 사용하여 래퍼를 작성할 수 있습니다.

pasteout(){
  f=$(mktemp -u) || return
  mkfifo -m 600 -- "$f" || return
  "$@" 2>"$f" | paste -- - "$f"
  rm -f -- "$f"
}
pasteout bb

하지만 교착상태에 빠지기 쉬우니 주의하세요. 예를 들어, bb파이프가 보유할 수 있는 것보다 더 많은 stdout을 생성하고 처음에 읽은 추가 양을 더한 값을 paste생성하지만 오류 출력을 생성하지 않는 경우 pastefifo에 대한 입력을 기다리며 차단되고 해당 stdout 출력이 삭제되지 않습니다 bb. bb파이프의 write()도 중단 됩니다 .

답변3

전체 생산 라인에는 분석해야 할 몇 가지 문제가 있습니다. 즉:

seq 2 | tee >(sed 's/^/e/' > /dev/stderr) | paste /dev/stdin /dev/stderr

표준 에러

첫 번째는 마지막 명령입니다. 오직표준 출력파이프 연결 가능:

$ seq2 | paste -
1
2

$ seq2 | paste - -
1 2

읽을 내용 없음 stderr:

$ seq 2 | paste - /dev/stderr 
1   ^C

^C읽을 내용을 차단하고 남기지 않기 때문에 필요합니다 stderr.
일부 출력을 생성하더라도 stderr파이핑되지 않습니다.

$ { seq 2; seq 3 4 >/dev/stderr; } | paste - /dev/stderr
1   3
4

이전과 마찬가지로 1이 인쇄되고 paste블록은 기다립니다 stderr.
다른 2개의 숫자는 콘솔로 직접 이동하여 (독립적으로) 인쇄됩니다.

stderr파이프라인의 마지막 명령에 일부 입력을 제공할 수 있습니다.

$ { seq 2; seq 3 4 >/dev/stderr; } | paste - /dev/stderr 2</dev/null
1
2
3
4

2>/dev/null그건 그렇고, 이는 차단 명령에 사용된 두 번째 파일 설명자를 피하는 것과 정확히 같습니다 paste. 그러나 인쇄된 값은 seq 3 4에서가 아니라 리디렉션에서 콘솔로 직접 가져옵니다 paste.

$ { seq 2; seq 3 4 >/dev/tty; } | paste - /dev/stderr 2</dev/null
1   
2   
3
4

이는 다음을 방지하지 않습니다.

$ seq 2 | tee >(sed 's/^/e/' > /dev/stderr) | 
  paste /dev/stdin /dev/stderr 2</dev/null
1   
2   
e1
e2

주문하다

둘째, 의 출력이 tee"순서대로" 이루어질 필요는 없습니다.`tee` 및 `bash` 프로세스 교체 순서

그리고 실제로 프로세스 대체의 출력은 "순서대로"일 필요는 없습니다. 프로세스 교체 출력이 고장났습니다.

$ echo one; echo two > >(cat); echo three;
one
three
two

실제로 일부 예에서는 여러 번 시도하면 다른 주문을 받을 수도 있습니다.프로세스 교체를 통해 동시에 실행 중인 독립 프로세스의 비결정적 출력

$ printf '%s\n' {0..1000} | tee >(head -n2) >(sort -grk1,1 | head -n3) >/dev/null
1000
999
998
0
1

따라서 아니요. 절차적 교체 및 붙여넣기를 통해 수행할 수 없습니다.
몇 가지 명령을 실행해야 합니다.

$ seq 2 | { while read a; do printf "%s %s\n" "$a" "e$a" ; done; }
1 e1
2 e2

BB

따라서 bb 함수에는 기본적으로 다음이 포함됩니다.

| tee >(sed 's/^/e/')

이는 다음을 사용하여 테스트할 수 있습니다.

$ printf '%s\n' {0..1000} | tee >(sort -grk1,1 | head -n3 >&2) | head -n 2
0
1
291
290
289

0, 1, 1000, 999, 998을 이 순서대로 인쇄해야 하지만 그렇지 않은 경우가 많습니다.
즉: 그렇습니다본질적으로불안정한.

안정적인 실제 솔루션.

bb에 대한 유일한 안전한 솔루션은 프로세스 교체를 피하는 것입니다.
그리고 {…}stdout 및 stderr 캡처를 활용하세요. 예를 들면 다음과 같습니다.

$ bash -c '{ echo test-str >/dev/stderr; }' 2>/dev/null

출력이 없습니다. 2개를 제거하여 확인하세요.

이것은 bb에서 작동합니다.

$ bb() { seq 5 | tee /dev/stderr | sed 's/^/e/'; }

그리고 fifo를 사용하여 다음을 붙여넣습니다.

$ mkfifo out2
$ bb 2>out2  | paste out2 -
1   e1
2   e2
3   e3
4   e4
5   e5

fifo 파일을 삭제하려면 트랩을 설정하고 fifo 파일을 생성하기 전에 해당 파일이 존재하는지 테스트해야 합니다.

내가 테스트한 모든 쉘에서 이식 가능한 것 같습니다(Almquist 구문과 호환 가능). 아직 완전히 테스트되지 않았습니다. 다른 사용자에게 확인해 주세요. 알려지지 않은 놀라움이 있을 수 있습니다.

관련 정보