동일한 명령 대체에 대한 후속 호출과 상태를 전달하는 명령 대체를 통해 호출되는 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 명령을 기다립니다.
내 질문은 다음과 같습니다
- 명령 대체가 있을 때 코프로세스와 똑같이 동작하도록 명명된 파이프를 구성하는 것이 가능합니까?
- coprocess가 분명히 하위 프로세스에서 읽고 쓸 수 있지만
zsh
콘솔에서 (입력을 통해) 수동으로 하위 쉘을 생성하면 하위 프로세스와 독립적으로 더 이상 액세스할 수 없는 이유를 설명할 수 있습니까? 해당 부모와 종료, 계속해서 부모를 사용합니다! ). - 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에 연결하려고 합니다. 이를 위해서는 세 가지 시스템 호출이 필요합니다.
fd = open("/tmp/foo.bar", O_RDWR)
파일을 열기 위해 호출됩니다 . 파일은fd
현재 프로세스에서 사용하지 않는 일부 파일 설명자에서 열립니다. 이것은 명명된 파이프에서 무언가를 읽기 시작할 때까지 차단하는 단계입니다/tmp/foo.bar
. 아무도 듣고 있지 않으면 명명된 파이프를 여는 것이 차단됩니다.dup2(fd, 1)
커널이 선택한 파일 설명자 외에 필수 파일 설명자에서 파일을 열기 위해 호출됩니다. 새 설명자(1)(명령 대체를 위한 익명 파이프)에 열려 있는 항목이 있으면 이때 닫힙니다.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에서 일종의 출력이 생성될 수 있다고 생각하는 것 같습니다.