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


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)

쉘은 대기한다 cmd1과 cmd1대기 전형적 것이다 cmd2파일 끝 치환 된 파이프까지 읽고 그 덕분에, 그리고 그 파일의 끝은 전형적 때 발생하는 cmd2다이. 그렇기 때문에 여러 쉘 (이 아닌 bash)이에서 기다리지 않는 이유 cmd2가 cmd2 | 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

즉 모두 제작 cmd1및 cmd2파이프 개방 자신의 FD (3)를 가지고있다. cat, 파일의 마지막에 다른 쪽 끝에서 기다리는 너무 전형적 종료 할 때 둘 것 cmd1와 cmd2죽은. 그리고 쉘은 그 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 열려있는 파이프에서 읽고 cmd1와 cmd2. 종료 상태 cmd1는에서 사용할 수 있도록 변수 할당을 사용하고 있습니다 $?.

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

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

이전에 언급했듯이 모든 sh구현 이 완료 된 cmd1후에 기다릴 수 는 없습니다 cmd2(반대보다 낫습니다). 그때, $?종료 상태가 cmd2; 하지만 bash및 zsh메이크업 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.