zsh에서 Git 브랜치를 삭제하는 데 도움이 되는 유틸리티 스크립트를 작성 중입니다. 현재는 다음과 같습니다.
git for-each-ref --format="%(refname:short)" refs/heads/ |
while read -r line; do
printf "remove %s? (y/n)" $line;
read ans </dev/tty;
case "$ans" in
y|Y) echo "$line";;
esac;
done
이것은 입력 프롬프트에 응답할 때 하나씩 작동하고 분기를 인쇄합니다.
그러나 스크립트가 아닌 명령줄에서 직접 동일한 명령 시퀀스를 실행하면 첫 번째 분기를 건너뜁니다.
git for-each-ref --format="%(refname:short)" refs/heads/ | while read -r line; do printf "remove %s? (y/n)" $line; read ans </dev/tty; case "$ans" in y|Y) echo "$line";; esac; done
이러한 동작 차이가 발생하는 이유와 해결 방법을 아시나요?
답변1
에서 zsh
와 마찬가지로 ksh
파이프라인의 마지막 구성 요소는 하위 셸이 아닌 현재 셸에서 실행됩니다.
대화형 셸 인스턴스의 포그라운드에서 작업을 실행하면 셸은 새 프로세스 그룹을 생성하고 해당 프로세스 그룹을 터미널의 포그라운드 프로세스 그룹으로 만듭니다. 그런 다음 해당 프로세스 그룹에 넣는 작업의 모든 프로세스를 생성합니다.
그러나 다음에서는
cmd | { some-builtin; some-builtin; }
을 실행하는 주요 쉘 프로세스입니다 some-builtin
. 그리고 해당 프로세스는 이미 프로세스 그룹의 리더이므로 포그라운드 프로세스 그룹에 포함될 수 없습니다. 이는 실행 중인 프로세스가 종료되지 않는 한 cmd
백그라운드에 남아 있음을 의미합니다.
포그라운드 프로세스 그룹에 속하지 않은 프로세스는 제어 터미널에서 데이터를 읽을 수 없습니다. 이렇게 하면 SIGTTIN
신호에 의해 일시 중지되고 이러한 신호를 무시하면(대화형 셸의 기본 프로세스처럼) read()
시스템 호출이 EIO 오류와 함께 실패합니다.
ksh93에서 동일한 내용이 표시되지 않는 유일한 이유는 내장된 기능이 read
읽기 전에 수행 select()
/ 작동하기 때문입니다 poll()
. 따라서 반환하기 전에 무언가를 입력하면 EIO만 표시됩니다 git
.
에는 zsh
다음과 같은 더 심각한 문제가 있습니다.
cmd1 | { cmd2; cmd3; }
cmd1
cmd2
동일한 프로세스 그룹과 포그라운드에 배치되지만 실행 시 zsh cmd3
는 다시 포그라운드 프로세스 그룹으로 돌아가서 둘 다 백그라운드에 있게 cmd1
됩니다 .cmd3
~$ (sleep 1; cat) | { ps -opid,pgid,tpgid,stat,args; ps -opid,pgid,tpgid,stat,args; sleep 2; echo done; }
PID PGID TPGID STAT COMMAND
571038 571038 653553 Ss zsh
653553 653553 653553 S+ zsh
653554 653553 653553 S+ sleep 1
653555 653553 653553 R+ ps -opid,pgid,tpgid,stat,args
PID PGID TPGID STAT COMMAND
571038 571038 571038 Ss+ zsh
653553 653553 571038 S zsh
653554 653553 571038 S sleep 1
653556 653553 571038 R ps -opid,pgid,tpgid,stat,args
zsh: suspended (tty input) sleep 2
$ ps -opid,pgid,tpgid,stat,args
PID PGID TPGID STAT COMMAND
571038 571038 654415 Ss zsh
653553 653553 654415 T cat
653557 653553 654415 T sleep 2
653558 653558 654415 T zsh
654415 654415 654415 R+ ps -opid,pgid,tpgid,stat,args
( +
포그라운드는 TPGID가 포그라운드 프로세스 그룹임을 의미합니다.)
여기에서는 전체 파이프라인이나 서브셸의 가장 오른쪽 요소를 실행하여 문제를 해결할 수 있습니다.
(git | while ...; done)
또는:
git | (while ...; done)
쉘이 비대화형일 때는 작업 제어를 수행하지 않기 때문에 스크립트에서는 이 문제가 발생하지 않습니다. 스크립트의 모든 프로세스는 동일한 프로세스 그룹에서 실행되며, 이를 시작한 셸이 호출된 방식에 따라 전경 또는 배경(터미널에서 실행된다고 가정)에 있게 됩니다.