명령 대체 시 coproc 및 명명된 파이프 동작

명령 대체 시 coproc 및 명명된 파이프 동작

동일한 명령 대체에 대한 후속 호출과 상태를 전달하는 명령 대체를 통해 호출되는 zsh 쉘 스크립트에서 함수를 생성해야 합니다.

C 함수의 정적 변수와 유사합니다(대략적으로).

이를 위해 저는 보조 프로세서를 사용하는 방법과 명명된 파이프를 사용하는 방법 등 두 가지 접근 방식을 시도했습니다. 명명된 파이프 방법을 작동시킬 수 없습니다. 보조 프로세서와 관련된 유일한 문제를 해결할 것이라고 생각했기 때문에 실망스럽습니다. 즉, 터미널에서 새 zsh 셸을 입력하면 상위 zsh 세션의 coproc을 볼 수 있을 것 같습니다.

아래 문제를 설명하기 위해 단순화된 스크립트를 만들었습니다. 제가 무엇을 하려는지 궁금하시다면, 교체된 스크립트로 build_prompt() 함수를 호출하는 탄환 열차 zsh 테마에 새로운 상태 저장 구성 요소를 추가하는 것입니다. 명령은 여기 있습니다: https://github.com/caiogondim/bullet-train.zsh/blob/d60f62c34b3d9253292eb8be81fb46fa65d8f048/bullet-train.zsh-theme#L692

스크립트 1 - 보조 프로세서

#!/usr/bin/env zsh

coproc cat
disown
print 'Hello World!' >&p

call_me_from_cmd_subst() {
    read get_contents <&p
    print "Retrieved: $get_contents"
    print 'Hello Response!' >&p
    print 'Response Sent!'
}

# Run this first
call_me_from_cmd_subst

# Then comment out the above call
# And run this instead
#print "$(call_me_from_cmd_subst)"

# Hello Response!
read finally <&p
echo $finally

스크립트 2 - 명명된 파이프

#!/usr/bin/env zsh

rm -rf /tmp/foo.bar
mkfifo /tmp/foo.bar
print 'Hello World!' > /tmp/foo.bar &

call_me_from_cmd_subst() {
    get_contents=$(cat /tmp/foo.bar)
    print "Retrieved: $get_contents"
    print 'Hello Response!' > /tmp/foo.bar &!
    print 'Response Sent!'
}

# Run this first
call_me_from_cmd_subst

# Then comment out the above call
# And run this instead
#print "$(call_me_from_cmd_subst)"

# Hello Response!
cat /tmp/foo.bar

초기 형식에서는 둘 다 정확히 동일한 출력을 생성합니다.

$ ./named-pipe.zsh
Retrieved: Hello World!
Response Sent!
Hello Response!

$ ./coproc.zsh
Retrieved: Hello World!
Response Sent!
Hello Response!

이제 명령 대체를 사용하여 호출되도록 coproc 스크립트를 전환하면 아무 것도 변경되지 않습니다.

# Run this first
#call_me_from_cmd_subst

# Then comment out the above call
# And run this instead
print "$(call_me_from_cmd_subst)"

즉, 명령 대체에 의해 생성된 하위 프로세스에서 coprocess를 읽고 쓰는 것은 문제를 일으키지 않습니다. 저는 이 사실에 조금 놀랐습니다. 하지만 좋은 소식입니다!

그러나 명명된 파이프 예제에서 동일한 변경을 수행하면 스크립트가 차단되고 출력이 없습니다. 내가 왜 그것을 로 실행했는지 알아내려면 zsh -x다음과 같이 하십시오.

+named-pipe.zsh:3> rm -rf /tmp/foo.bar
+named-pipe.zsh:4> mkfifo /tmp/foo.bar
+named-pipe.zsh:15> call_me_from_cmd_subst
+call_me_from_cmd_subst:1> get_contents=+call_me_from_cmd_subst:1> cat /tmp/foo.bar
+named-pipe.zsh:5> print 'Hello World!'
+call_me_from_cmd_subst:1> get_contents='Hello World!'
+call_me_from_cmd_subst:2> print 'Retrieved: Hello World!'
+call_me_from_cmd_subst:4> print 'Response Sent!'

다음 줄이 아직 종료되지 않은 동안 명령 대체에 의해 생성된 하위 프로세스가 종료되지 않는 것 같습니다( 저는 using 을 사용했으며 결과 &에는 변화가 없습니다).&!disown

print 'Hello Response!' > /tmp/foo.bar &!

이를 입증하기 위해 고양이가 응답을 읽도록 수동으로 트리거할 수 있습니다.

$ cat /tmp/foo.bar
Hello Response!

이제 파이프에 읽을 내용이 없기 때문에 스크립트는 최종 cat 명령을 기다립니다.


내 질문은 다음과 같습니다

  1. 명령 대체가 있을 때 코프로세스와 똑같이 동작하도록 명명된 파이프를 구성하는 것이 가능합니까?
  2. coprocess가 분명히 하위 프로세스에서 읽고 쓸 수 있지만 zsh콘솔에서 (입력을 통해) 수동으로 하위 쉘을 생성하면 하위 프로세스와 독립적으로 더 이상 액세스할 수 없는 이유를 설명할 수 있습니까? 해당 부모와 종료, 계속해서 부모를 사용합니다! ).
  3. 1이 가능하다면 명명된 파이프가 특정 쉘 프로세스에 연결되지 않기 때문에 명명된 파이프가 2만큼 복잡하지 않을 것이라고 가정합니까?

2와 3의 의미를 설명하세요.

$ coproc cat
[1] 24516
$ print -p test
$ read -ep
test
$ print -p test_parent
$ zsh
$ print -p test_child
print: -p: no coprocess
$ coproc cat
[1] 28424
$ disown
$ print -p test_child
$ read -ep
test_child
$ exit
$ read -ep
test_parent

하위 zsh 내부에서는 코프로세스를 볼 수 없지만 명령 대체를 사용하여 하위 프로세스 내부에서는 볼 수 있습니까?

마지막으로 Ubuntu 18.04를 사용했습니다.

$ zsh --version
zsh 5.4.2 (x86_64-ubuntu-linux-gnu)

답변1

파이프 기반 스크립트가 작동하지 않는 이유는 zsh의 일부 기능이 아닙니다. 이는 셸 명령 대체, 셸 리디렉션 및 파이프가 작동하는 방식 때문입니다. 중복되는 부분이 없는 스크립트입니다.

mkfifo /tmp/foo.bar
echo 'Hello World!' > /tmp/foo.bar &

call_me_from_cmd_subst() {
    echo 'Hello Response!' > /tmp/foo.bar &
    echo 'Response Sent!'
}

echo "$(call_me_from_cmd_subst)"
cat /tmp/foo.bar

명령 대체는 $(call_me_from_cmd_subst)함수를 실행하는 하위 쉘의 출력을 원래 쉘 프로세스에 연결하는 익명 파이프를 생성합니다. 원래 프로세스는 이 파이프에서 데이터를 읽습니다. 자식 프로세스는 실행할 손자 프로세스를 생성합니다 echo 'Hello Response!' > /tmp/foo.bar. 두 프로세스 모두 익명 파이프를 포함하여 동일한 열린 파일로 시작됩니다. Sun Tzu는 리디렉션을 수행합니다 > /tmp/foo.bar. 명명된 파이프에서 아무것도 읽지 않기 때문에 차단됩니다 /tmp/foo.bar.

리디렉션은 파일을 열 때 해당 파일 설명자를 선택할 수 없기 때문에 2단계 프로세스입니다(실제로는 3단계이지만 여기서는 세 번째 단계가 중요하지 않습니다). 운영자는 >표준 출력을 리디렉션하려고 합니다. 즉, 특정 파일을 파일 설명자 1에 연결하려고 합니다. 이를 위해서는 세 가지 시스템 호출이 필요합니다.

  1. fd = open("/tmp/foo.bar", O_RDWR)파일을 열기 위해 호출됩니다 . 파일은 fd현재 프로세스에서 사용하지 않는 일부 파일 설명자에서 열립니다. 이것은 명명된 파이프에서 무언가를 읽기 시작할 때까지 차단하는 단계입니다 /tmp/foo.bar. 아무도 듣고 있지 않으면 명명된 파이프를 여는 것이 차단됩니다.
  2. dup2(fd, 1)커널이 선택한 파일 설명자 외에 필수 파일 설명자에서 파일을 열기 위해 호출됩니다. 새 설명자(1)(명령 대체를 위한 익명 파이프)에 열려 있는 항목이 있으면 이때 닫힙니다.
  3. close(fd)필수 파일 설명자에만 리디렉션 대상을 유지하기 위해 호출됩니다 .

동시에 하위 프로세스가 인쇄 Reponse Sent!되고 종료됩니다. 원래 셸 프로세스는 여전히 파이프에서 데이터를 읽고 있습니다. 손자에 쓰기 위해 파이프가 여전히 열려 있으므로 원래 쉘 프로세스는 계속 대기합니다.

이 교착 상태를 해결하려면 손자가 필요한 것보다 오랫동안 파이프를 열어두지 않도록 해야 합니다. 예를 들어:

call_me_from_cmd_subst() {
    { exec >&-; /bin/echo 'Hello Response!' > /tmp/foo.bar; } &
    echo 'Response Sent!'
}

또는

call_me_from_cmd_subst() {
    { echo 'Hello Response!' > /tmp/foo.bar; } >/dev/null &
    echo 'Response Sent!'
}

또는 해당 주제에 대한 다양한 변형이 있습니다.

코프로세스에는 명명된 파이프가 포함되지 않으므로 교착 상태 절반이 차단되지 않습니다. >/tmp/foo.bar명명된 파이프를 열 때 차단되지만 >&p이미 열려 있는 파일을 리디렉션하기 때문에 차단되지 않습니다. 설명자.

답변2

코드를 약간 수정하면 예상대로 작동합니다.

#!/usr/bin/env zsh

rm -rf /tmp/foo.bar
mkfifo /tmp/foo.bar
print 'Hello World!' > /tmp/foo.bar &

call_me_from_cmd_subst() {
    get_contents=$(cat /tmp/foo.bar)
    print "Retrieved: $get_contents"
    (print 'Hello Response!' > /tmp/foo.bar &!) >/dev/null
    print 'Response Sent!'
}

# Run this first
#call_me_from_cmd_subst

# Then comment out the above call
# And run this instead
print "$(call_me_from_cmd_subst)"

# Hello Response!
cat /tmp/foo.bar

이 명령을 실행하면 예상된 결과가 생성됩니다.

Retrieved: Hello World!
Response Sent!
Hello Response!

왜 그런지 잘 모르겠습니다. zsh가 fifo로 인쇄하면 stdout에서 일종의 출력이 생성될 수 있다고 생각하는 것 같습니다.

관련 정보