프로세스 대체 출력이 순서를 벗어났습니다


16

그만큼

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

명령은 예기치 않은 출력을 제공합니다.

나는 이것을 읽었습니다 : bash에서 프로세스 대체가 어떻게 구현됩니까? 인터넷에서의 프로세스 대체에 관한 많은 기사가 있지만 왜 이런 식으로 작동하는지 이해하지 못합니다.

예상 출력 :

one
two
three

실제 출력 :

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

또한이 두 명령은 내 관점과 동일해야하지만 다음과 같은 것은 아닙니다.

##### first command - the pipe is used.
prompt$ seq 1 5 | cat
1
2
3
4
5
##### second command - the process substitution and redirection are used.
prompt$ seq 1 5 > >(cat)
prompt$ 1
2
3
4
5

왜 내가 같아야합니까? 두 가지 모두 익명 파이프 -Wikipedia, Process replacement를 통해 seq출력을 cat입력에 연결하기 때문 입니다.

질문 : 왜 이런 식으로 동작합니까? 내 오류는 어디에 있습니까? 포괄적 인 답변이 필요합니다 ( bash후드에서 어떻게 수행하는지에 대한 설명과 함께 ).



2
실제로, 다른 질문이이 질문에 더 가깝기 때문에이 질문에 대한 중복으로 표시되는 것이 좋습니다. 그래서 나는 거기에 내 대답을 복사했습니다.
Stéphane Chazelas

답변:


21

예 (기능의 출처) 에서 bash와 같이 ksh프로세스 대체 내부 프로세스는 대기하지 않습니다 (스크립트에서 다음 명령을 실행하기 전에).

A의 <(...)같이 미세 보통의 하나 :

cmd1 <(cmd2)

쉘은 대기한다 cmd1cmd1대기 전형적 것이다 cmd2파일 끝 치환 된 파이프까지 읽고 그 덕분에, 그리고 그 파일의 끝은 전형적 때 발생하는 cmd2다이. 그렇기 때문에 여러 쉘 (이 아닌 bash)이에서 기다리지 않는 이유 cmd2cmd2 | cmd1있습니다.

들어 cmd1 >(cmd2)는 더으로, 그러나, 즉, 일반적으로 그렇지 않다 cmd2기다리는 일반적 cmd1것이다 일반적으로 종료 후 너무가.

즉 고정 것 zsh동안 그 대기 cmd2가 (하지만 당신은 그것을 쓰기가 아닌 경우 cmd1 > >(cmd2)cmd1내장되지 않은 사용 {cmd1} > >(cmd2)으로 대신 문서화 ).

ksh기본적으로 기다리지 않지만 wait내장 된 상태로 기다릴 수 있습니다 (pid를 사용할 수있게 만들지 $!만 도움이되지는 않습니다 cmd1 >(cmd2) >(cmd3))

rc를 사용하여 모든 백그라운드 프로세스의 pid를 얻을 수 있다는 점을 제외하고 는 cmd1 >{cmd2}구문 ksh과 함께 $apids.

es(와 함께 cmd1 >{cmd2})는 cmd2in처럼 zsh대기 cmd2하고 <{cmd2}프로세스 리디렉션 에서도 대기합니다 .

bash에서 pid를 cmd2(또는 cmd2마지막 명령이더라도 해당 서브 쉘의 하위 프로세스에서 실행될 때보다 정확하게 서브 쉘을 $!만들지 만 ) 기다릴 수는 없습니다.

를 사용해야 bash하는 경우 두 명령을 모두 기다리는 명령을 사용하여 문제를 해결할 수 있습니다.

{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1

즉 모두 제작 cmd1cmd2파이프 개방 자신의 FD (3)를 가지고있다. cat, 파일의 마지막에 다른 쪽 끝에서 기다리는 너무 전형적 종료 할 때 둘 것 cmd1cmd2죽은. 그리고 쉘은 그 cat명령 을 기다립니다 . 모든 백그라운드 프로세스의 종료를 포착하는 그물로 볼 수 있습니다 ( &, coprocs 또는 백그라운드에서 시작된 다른 것들에 대해 사용할 수 있습니다. ).

위에서 언급 한 낭비 된 서브 쉘 프로세스 덕분에 cmd2fd 3을 닫아도 작동합니다 (명령은 일반적으로 수행하지 않지만 일부는 비슷 sudo하거나 ssh수행합니다). 이후 버전의 버전은 bash다른 쉘과 마찬가지로 최적화를 수행 할 수 있습니다. 그렇다면 다음과 같은 것이 필요합니다.

{ { cmd1 >(sudo cmd2; exit); } 3>&1 >&4 4>&- | cat; } 4>&1

해당 sudo명령을 기다리는 fd 3 열린 상태로 추가 쉘 프로세스가 여전히 있는지 확인하십시오 .

cat(프로세스가 자신의 FD 3에 기록하지 않기 때문에) 아무것도 읽지 않습니다. 동기화를위한 것입니다. read()마지막에 아무것도 반환 하지 않는 하나의 시스템 호출 만 수행 합니다.

cat파이프 대체를 수행하기 위해 대체 명령을 사용하여 실행 을 피할 수 있습니다 .

{ unused=$( { cmd1 >(cmd2); } 3>&1 >&4 4>&-); } 4>&1

이 시간, 그것은 쉘 대신의 cat그 다른 쪽의 전략 중 3 열려있는 파이프에서 읽고 cmd1cmd2. 종료 상태 cmd1는에서 사용할 수 있도록 변수 할당을 사용하고 있습니다 $?.

또는 수동으로 프로세스 대체를 수행 한 다음 sh표준 쉘 구문이되는 시스템을 사용할 수도 있습니다 .

{ cmd1 /dev/fd/3 3>&1 >&4 4>&- | cmd2 4>&-; } 4>&1

이전에 언급했듯이 모든 sh구현 이 완료 된 cmd1후에 기다릴 수 는 없습니다 cmd2(반대보다 낫습니다). 그때, $?종료 상태가 cmd2; 하지만 bashzsh메이크업 cmd1에서 사용할 수의 종료 상태 ${PIPESTATUS[0]}$pipestatus[1]각각 (또한 참조pipefail 있도록 몇 가지 껍질에 옵션을 $?마지막 이외의 파이프 구성 요소의 실패를보고 할 수 있습니다)

참고 yash자신의 프로세스와 유사한 문제가 리디렉션 기능을. 거기에 cmd1 >(cmd2)쓰여질 cmd1 /dev/fd/3 3>(cmd2)것입니다. 그러나 기다리지 cmd2않고 wait기다릴 수 없으며 $!변수 에서 pid를 사용할 수 없습니다 . 에서와 동일한 해결 방법을 사용합니다 bash.


먼저을 시도한 echo one; { { echo two > >(cat); } 3>&1 >&4 4>&- | cat; } 4>&1; echo three;다음로 단순화 echo one; echo two > >(cat) | cat; echo three;하고 올바른 순서로 값을 출력합니다. 이 설명자 조작 3>&1 >&4 4>&-이 모두 필요합니까? 또한, 나는 이것을 얻지 못합니다 >&4 4>&-우리는 stdout네 번째 fd 로 리디렉션 한 다음 네 번째 fd를 닫은 다음 다시 사용 4>&1합니다. 왜 필요하고 어떻게 작동합니까? 이 주제에 대해 새 질문을 작성해야합니까?
MiniMax

1
@MiniMax, 그러나 당신은 cmd1and 의 stdout에 영향을 미치고 cmd2있습니다. 파일 기술자와 약간의 춤을 추는 요점은 원래의 것을 복원 하고 명령 출력을 채널링하는 대신 대기 를 위해 여분의 파이프 만 사용하는 것입니다.
Stéphane Chazelas

@MiniMax 이해하는 데 시간이 걸렸습니다. 파이프를 이렇게 낮은 레벨로 얻지 못했습니다. 가장 오른쪽 4>&1은 외부 괄호 명령리스트에 대한 파일 디스크립터 (fd) 4를 작성하고 외부 괄호의 stdout과 동일하게 만듭니다. 내부 버팀대에는 stdin / stdout / stderr가 자동으로 외부 버팀대에 연결되도록 설정되어 있습니다. 그러나 3>&1fd 3을 외부 괄호의 stdin에 연결합니다. >&4내부 괄호의 stdout을 외부 괄호 fd 4 (이전에 만든 것)에 연결합니다. 4>&-내부 브레이스에서 fd 4를 닫습니다 (내부 브레이스의 표준 출력은 이미 괄호의 fd 4에 연결되어 있으므로).
Nicholas Pipitone

@MiniMax 혼란스러운 부분은 오른쪽에서 왼쪽으로, 4>&1다른 경로 재 지정 전에 먼저 실행되므로 "다시 사용 4>&1" 하지 않습니다 . 전반적으로 내부 괄호는 stdout에 데이터를 보내고 있으며, 이는 주어진 fd 4로 덮어 쓰여졌습니다. 내부 브레이스가 제공된 fd 4는 외부 브레이스의 fd 4이며 외부 브레이스의 원래 표준 출력과 같습니다.
Nicholas Pipitone

Bash는 4>5"4가 5로 이동"을 의미 하는 것처럼 느끼지만 실제로 "fd 4는 fd 5로 덮어 씁니다". 그리고 실행하기 전에 fd 0/1/2는 자동으로 연결되며 (외부 쉘의 모든 fd와 함께) 원하는대로 덮어 쓸 수 있습니다. 그것은 적어도 bash 문서에 대한 나의 해석입니다. 이것 에서 다른 것을 이해했다면 lmk.
Nicholas Pipitone

4

두 번째 명령을 다른 명령으로 파이프하면 cat입력 파이프가 닫힐 때까지 기다립니다. 전의:

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

짧고 간단합니다.

==========

보이는 것처럼 간단하지만 많은 것들이 뒤에서 진행되고 있습니다. 이것이 어떻게 작동하는지에 관심이 없다면 나머지 답변을 무시할 수 있습니다.

당신이있을 때 echo two > >(cat); echo three, >(cat)대화 형 쉘에 의해 떨어져 포크, 독립적으로 실행됩니다 echo two. 따라서 echo two완료된 다음 완료 echo three되기 전에 실행 >(cat)됩니다. 때 bash데이터 가져 >(cat)가 (밀리 초 몇 이상)을 기대하지 않았을 때를, 당신을 제공하는 프롬프트처럼 (다른 사용자가있는 경우와 같은 터미널에 다시 얻을 당신이 히트 줄 바꿈에이 곳의 상황 mesg'을 에드).

그러나 주어진 echo two > >(cat) | cat; echo three두 개의 서브 쉘이 생성됩니다 ( |기호 의 문서에 따라 ).

A라는 서브 쉘 echo two > >(cat)하나는 B이고 서브 쉘 하나는 B입니다 cat. A는 자동으로 B에 연결됩니다 (A의 표준 출력은 B의 표준 입력입니다). 그런 다음, echo two>(cat)실행을 시작합니다. >(cat)의 표준 출력은 A의 표준 출력으로 설정되며 이는 B의 표준 입력과 같습니다. echo two완료 후 A가 종료되고 stdout이 닫힙니다. 그러나 >(cat)여전히 B의 표준 입력에 대한 참조를 유지하고 있습니다. 두 번째 catstdin은 B의 stdin을 잡고 cat있으며 EOF를 볼 때까지 종료되지 않습니다. EOF는 더 이상 파일을 쓰기 모드로 연 사람이 없을 때만 제공되므로 >(cat)stdout은 두 번째를 차단합니다 cat. B는 그 초를 기다리고 있습니다 cat. 이후로는echo two 종료, >(cat)결국 때문에, EOF를 가져옵니다>(cat)버퍼를 플러시하고 종료합니다. 아무도 cat더 이상 B / 초의 두 번째 종료되고 B가 대기 중이 어서 B가 종료됩니다 .catEOF를 읽습니다 (B는 stdin을 전혀 읽지 않으며 신경 쓰지 않습니다). 이 EOF는 두 번째 cat가 버퍼를 플러시하고 stdout을 닫은 다음 종료하고 B가 종료됩니다.catcat

이것의 경고는 배쉬가 서브 쉘을 생성한다는 것입니다 >(cat)! 이 때문에, 당신은 그것을 볼 수 있습니다

echo two > >(sleep 5) | cat; echo three

B의 표준 입력을 보유 echo three하고 sleep 5있지 않더라도 실행하기 전에 여전히 5 초 동안 대기 합니다. 이것은 생성 된 숨겨진 서브 쉘 C >(sleep 5)가 대기 중이고 sleepC가 B의 표준 입력을 보유하고 있기 때문 입니다. 당신은 방법을 볼 수 있습니다

echo two > >(exec sleep 5) | cat; echo three

그러나 sleepB의 stdin을 보유하지 않기 때문에 기다리지 않으며 B의 stdin을 보유하고있는 유령 서브 셸 C가 없습니다 (exec는 포크를하고 C를 기다리게하는 것과는 달리 C를 대체하도록 절전 모드를 강제합니다 sleep). 이 경고에 관계없이

echo two > >(exec cat) | cat; echo three

앞서 설명한대로 여전히 순서대로 기능을 올바르게 실행합니다.


내 답변에 대한 의견에서 @MiniMax 로의 변환에서 언급했듯이 명령의 stdout에 영향을 미치는 단점이 있으며 출력을 추가로 읽고 쓸 필요가 있음을 의미합니다.
Stéphane Chazelas

설명이 정확하지 않습니다. A에서 cat생성 된 것을 기다리지 않습니다 >(cat). 내 대답에서 언급했듯이 5 초 후에 echo two > >(sleep 5 &>/dev/null) | cat; echo three출력 되는 이유 three는 현재 버전의 bash낭비가 추가 쉘 프로세스 >(sleep 5)를 기다리고 대기 sleep중이며 pipe두 번째 프로세스가 cat종료 되는 것을 막기 위해 stdout으로 진행하기 때문 입니다 . echo two > >(exec sleep 5 &>/dev/null) | cat; echo three추가 프로세스를 제거하기 위해 교체하면 곧바로 돌아옵니다.
Stéphane Chazelas

중첩 된 서브 쉘을 작성합니까? 나는 bash 구현을 조사하여 그것을 알아 내려고 노력했지만, echo two > >(sleep 5 &>/dev/null)최소한 자체 서브 쉘을 얻습니다. sleep 5자체 서브 쉘을 가져 오는 문서화되지 않은 구현 세부 사항 입니까? 문서화 된 경우 더 적은 문자로 처리 할 수있는 합법적 인 방법 일 것입니다 (긴밀한 루프가 없다면 서브 셸 또는 고양이의 성능 문제를 알지 못할 것이라고 생각하지 않습니다). 문서화되어 있지 않으면 찢어지고 멋진 해킹은 향후 버전에서 작동하지 않습니다.
Nicholas Pipitone

$(...), <(...)실제로 서브 쉘을 포함하지만, 경우 ksh93 또는 zsh을은 동일한 프로세스에서 그 서브 쉘의 마지막 명령을 실행하지 않을 않는 bash열려있는 동안은 파이프를 잡고 다른 프로세스 거기에 이유있는 sleep오픈 파이프를 유지하지 실행됩니다. 향후 버전은 bash유사한 최적화를 구현할 수 있습니다.
Stéphane Chazelas

1
@ StéphaneChazelas 내 대답을 업데이트했으며 더 짧은 버전의 현재 설명이 정확하다고 생각하지만 쉘의 구현 세부 사항을 알고있는 것으로 확인 할 수 있습니다. 그럼에도 불구 하고이 솔루션은 파일 설명자 춤과 반대로 사용해야한다고 생각합니다 exec.
Nicholas Pipitone
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.