답변:
공동 프로세스는 ksh
기능입니다 (이미 있음 ksh88
). zsh
그것은 단지에만 추가 된 반면, 시작 (90 년대 초)의 기능을했다 bash
년 4.0
(2009 년).
그러나 동작과 인터페이스는 3 개의 셸에서 크게 다릅니다.
그러나 아이디어는 동일합니다. 백그라운드에서 작업을 시작하고 명명 된 파이프에 의존하지 않고도 입력을 보내고 출력을 읽을 수 있습니다.
일부 시스템에서는 최신 버전의 ksh93이있는 대부분의 쉘과 소켓 쌍이있는 명명되지 않은 파이프로 수행됩니다.
에서는 a | cmd | b
, a
데이터를 공급 cmd
하고 b
출력을 판독한다. cmd
코 프로세스로 실행 하면 쉘이 a
및 로 둘 수 있습니다 b
.
에서 다음 ksh
과 같이 공동 프로세스를 시작합니다.
cmd |&
다음 cmd
과 같은 작업을 수행 하여 데이터를 공급합니다 .
echo test >&p
또는
print -p test
그리고 다음 cmd
과 같은 결과를 읽습니다 .
read var <&p
또는
read -p var
cmd
백그라운드 작업으로 시작된 경우 fg
,을 (를 bg
) 사용 하거나를 통해 kill
참조 할 수 있습니다 .%job-number
$!
파이프의 쓰기 끝을 cmd
읽으려면 다음을 수행하십시오.
exec 3>&p 3>&-
그리고 다른 파이프의 읽기 끝을 닫으려면 (하나 cmd
는 쓰고 있습니다) :
exec 3<&p 3<&-
파이프 파일 설명자를 다른 fd에 먼저 저장하지 않으면 두 번째 공동 프로세스를 시작할 수 없습니다. 예를 들어 :
tr a b |&
exec 3>&p 4<&p
tr b c |&
echo aaa >&3
echo bbb >&p
의 zsh
공동 프로세스는의 프로세스와 거의 동일합니다 ksh
. 유일한 차이점은 키워드로 zsh
공동 프로세스가 시작 된다는 것 coproc
입니다.
coproc cmd
echo test >&p
read var <&p
print -p test
read -p var
하기:
exec 3>&p
이것은 이동하지 않습니다 참고 coproc
가 fd하는 파일 기술자를 3
(처럼 ksh
)하지만, 중복을. 그래서, 공급 또는 읽기 파이프를 닫습니다 명시 적 방법이 없다, 다른 하나는 시작하는 또 다른 coproc
.
예를 들어, 급 지단을 닫으려면 :
coproc tr a b
echo aaaa >&p # send some data
exec 4<&p # preserve the reading end on fd 4
coproc : # start a new short-lived coproc (runs the null command)
cat <&4 # read the output of the first coproc
파이프 기반의 공동 프로세스 외에도 zsh
(2000 년에 출시 된 3.1.6-dev19 이후) pseudo-tty 기반 구성이 expect
있습니다. 대부분의 프로그램과 상호 작용하기 위해 ksh 스타일의 공동 프로세스는 출력이 파이프 일 때 프로그램이 버퍼링을 시작하기 때문에 작동하지 않습니다.
여기 몇 가지 예가 있어요.
공동 프로세스를 시작하십시오 x
.
zmodload zsh/zpty
zpty x cmd
(여기 cmd
에는 간단한 명령이 있지만 eval
기능이나 기능을 사용 하여 더 멋진 작업을 수행 할 수 있습니다 .)
공동 프로세스 데이터를 공급하십시오.
zpty -w x some data
공동 프로세스 데이터를 읽습니다 (가장 간단한 경우).
zpty -r x var
마찬가지로 expect
, 주어진 패턴과 일치하는 공동 프로세스의 일부 출력을 기다릴 수 있습니다.
bash 구문은 훨씬 새롭고 최근 ksh93, bash 및 zsh에 추가 된 새로운 기능을 기반으로합니다. 동적으로 할당 된 파일 디스크립터를 10 이상으로 처리 할 수있는 구문을 제공합니다.
bash
기본 coproc
구문과 확장 구문을 제공합니다 .
공동 프로세스를 시작하기위한 기본 구문은 다음과 같습니다 zsh
.
coproc cmd
에서는 ksh
하거나 zsh
, 및 상기 공동 과정에서 파이프에 액세스 >&p
하고 <&p
.
그러나에 bash
, 공동 과정에서 파이프와 공동 였는지를하고 다른 파이프의 파일 기술자는 반환되는 $COPROC
각각 배열 ( ${COPROC[0]}
및 ${COPROC[1]}
그래서. ...
공동 프로세스에 데이터를 공급하십시오.
echo xxx >&"${COPROC[1]}"
공동 프로세스에서 데이터를 읽습니다.
read var <&"${COPROC[0]}"
기본 구문을 사용하면 한 번에 하나의 공동 프로세스 만 시작할 수 있습니다.
확장 구문에서는 공동 프로세스의 이름 을 zsh
zpty 공동 프로세스 와 같이 지정할 수 있습니다 .
coproc mycoproc { cmd; }
명령 은 복합 명령이어야합니다. (위의 예를 어떻게 생각 나게 하는지를 주목하십시오 function f { ...; }
.)
이번에는 파일 디스크립터가 ${mycoproc[0]}
및에 ${mycoproc[1]}
있습니다.
당신은 하나 개 이상의 공동 프로세스를 시작할 수있는 시간 -하지만 당신은 할 일이 여전히 (심지어 비 대화식 모드에서) 실행되는 동안 공동 프로세스를 시작할 때 경고를 얻을.
확장 구문을 사용할 때 파일 디스크립터를 닫을 수 있습니다.
coproc tr { tr a b; }
echo aaa >&"${tr[1]}"
exec {tr[1]}>&-
cat <&"${tr[0]}"
대신 4.3을 작성해야하는 bash 버전에서는이 방식으로 닫을 수 없습니다.
fd=${tr[1]}
exec {fd}>&-
에서 ksh
와 같이 zsh
파이프 파일 설명자는 close-on-exec로 표시됩니다.
그러나에서 bash
실행 명령에 사람들을 전달하는 유일한 방법은 FDS로 복제하는 것입니다 0
, 1
또는 2
. 이는 단일 명령에 대해 상호 작용할 수있는 공동 프로세스의 수를 제한합니다. (예는 아래를 참조하십시오.)
yash
공동 프로세스 기능 자체는 없지만 파이프 라인 및 프로세스 리디렉션 기능으로 동일한 개념을 구현할 수 있습니다 . 시스템 호출에 yash
대한 인터페이스를 가지고 pipe()
있으므로 이런 종류의 일은 비교적 쉽게 수작업으로 수행 할 수 있습니다.
다음과 함께 공동 프로세스를 시작합니다.
exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-
먼저 pipe(4,5)
(5는 쓰기 끝, 4는 읽기 끝)을 만들고 fd 3을 파이프의 다른 쪽 끝에서 stdin으로 실행되는 프로세스로 리디렉션하고 stdout은 이전에 만든 파이프로 이동합니다. 그런 다음 필요하지 않은 부모에서 해당 파이프의 쓰기 끝을 닫습니다. 이제 쉘에서 fd 3은 cmd의 stdin에 연결되고 fd 4는 파이프를 사용하여 cmd의 stdout에 연결되었습니다.
close-on-exec 플래그는 해당 파일 설명자에 설정되어 있지 않습니다.
데이터를 공급하려면
echo data >&3 4<&-
데이터를 읽으려면 :
read var <&4 3>&-
평소와 같이 fd를 닫을 수 있습니다.
exec 3>&- 4<&-
공동 프로세스는 표준 명명 된 파이프로 쉽게 구현할 수 있습니다. 정확히 명명 된 파이프가 언제 소개되었는지는 모르겠지만 ksh
80 년대 중반 ksh88이 88 년에 "해제"되었지만 ksh
몇 년 전에 AT & T에서 내부적으로 사용 된 것으로 보입니다. 그 이유)를 설명합니다.
cmd |&
echo data >&p
read var <&p
다음과 같이 쓸 수 있습니다 :
mkfifo in out
cmd <in >out &
exec 3> in 4< out
echo data >&3
read var <&4
이들과 상호 작용하는 것이 더 간단합니다. 특히 두 개 이상의 공동 프로세스를 실행해야하는 경우 더욱 그렇습니다. (아래 예 참조)
사용의 유일한 이점은 사용 coproc
후 명명 된 파이프를 정리할 필요가 없다는 것입니다.
쉘은 몇 가지 구성에서 파이프를 사용합니다.
cmd1 | cmd2
,$(cmd)
,<(cmd)
, >(cmd)
.이들에서 데이터 는 서로 다른 프로세스간에 한 방향으로 만 흐릅니다 .
그러나 공동 프로세스와 명명 된 파이프를 사용하면 교착 상태에 빠지기 쉽습니다. 어떤 명령이 어떤 파일 디스크립터를 열 었는지 추적해야합니다. 교착 상태는 비 결정적으로 발생할 수 있으므로 조사하기 까다로울 수 있습니다. 예를 들어, 한 파이프를 채우는 데 필요한만큼의 데이터가 전송되는 경우에만 해당됩니다.
expect
설계된 것보다 더 나쁘게 작동 합니다.공동 프로세스의 주요 목적은 셸에 명령과 상호 작용할 수있는 방법을 제공하는 것입니다. 그러나 잘 작동하지 않습니다.
위에서 언급 한 가장 간단한 교착 상태는 다음과 같습니다.
tr a b |&
echo a >&p
read var<&p
출력은 터미널로 이동하지 않으므로 tr
출력을 버퍼링합니다. 따라서 파일 끝이 파일을 stdin
보거나 출력 할 버퍼가 가득 찰 때까지 아무것도 출력하지 않습니다 . 따라서 위의 셸에서 출력이 a\n
2 바이트 만 된 후에는 셸이 더 많은 데이터를 보내기를 기다리 read
므로 무기한 차단됩니다 tr
.
즉, 파이프는 명령과 상호 작용하기에 좋지 않습니다. 코 프로세스는 출력을 버퍼링하지 않는 명령 또는 출력 을 버퍼링하지 말라고 명령 할 수있는 명령 과 상호 작용하는 데만 사용할 수 있습니다. 예를 들어, stdbuf
최신 GNU 또는 FreeBSD 시스템에서 일부 명령을 사용하여 .
이것이 의사 터미널을 대신 expect
하거나 zpty
사용하는 이유 입니다. expect
명령과의 상호 작용을 위해 설계된 도구이며 잘 작동합니다.
공동 프로세스를 사용하여 간단한 쉘 파이프보다 더 복잡한 배관 작업을 수행 할 수 있습니다.
다른 Unix.SE 답변 에는 coproc 사용법의 예가 있습니다.
다음은 간단한 예 입니다. 명령의 출력 사본을 3 개의 다른 명령에 공급 한 다음 해당 3 개의 명령의 출력을 연결하는 함수를 원한다고 가정하십시오.
모두 파이프를 사용합니다.
예를 들어 :의 출력 공급 printf '%s\n' foo bar
에를 tr a b
, sed 's/./&&/g'
그리고 cut -b2-
뭔가를 얻을 :
foo
bbr
ffoooo
bbaarr
oo
ar
첫째, 반드시 분명하지는 않지만 교착 상태가 발생할 가능성이 있으며 몇 킬로바이트의 데이터 만 발생하기 시작합니다.
그런 다음 쉘에 따라 다르게 해결해야하는 여러 가지 다른 문제가 발생합니다.
예 zsh
를 들어을 사용하면 다음과 같이 할 수 있습니다.
f() (
coproc tr a b
exec {o1}<&p {i1}>&p
coproc sed 's/./&&/g' {i1}>&- {o1}<&-
exec {o2}<&p {i2}>&p
coproc cut -c2- {i1}>&- {o1}<&- {i2}>&- {o2}<&-
tee /dev/fd/$i1 /dev/fd/$i2 >&p {o1}<&- {o2}<&- &
exec cat /dev/fd/$o1 /dev/fd/$o2 - <&p {i1}>&- {i2}>&-
)
printf '%s\n' foo bar | f
위의 공동 프로세스 fd에는 close-on-exec 플래그가 설정되어 있지만 중복 플래그 는 없습니다 (에서와 같이 {o1}<&p
). 따라서 교착 상태를 피하려면 필요없는 프로세스에서 교착 상태를 닫아야합니다.
마찬가지로 exec cat
파이프를 열린 상태로 유지하는 것과 관련된 쉘 프로세스가 없도록 서브 쉘을 사용 하고 마지막에 사용해야 합니다.
ksh
(여기 ksh93
)를 사용하면 다음과 같아야 합니다.
f() (
tr a b |&
exec {o1}<&p {i1}>&p
sed 's/./&&/g' |&
exec {o2}<&p {i2}>&p
cut -c2- |&
exec {o3}<&p {i3}>&p
eval 'tee "/dev/fd/$i1" "/dev/fd/$i2"' >&"$i3" {i1}>&"$i1" {i2}>&"$i2" &
eval 'exec cat "/dev/fd/$o1" "/dev/fd/$o2" -' <&"$o3" {o1}<&"$o1" {o2}<&"$o2"
)
printf '%s\n' foo bar | f
( 참고 : 그 어디 시스템에서 작동하지 않습니다 ksh
사용하는 socketpairs
대신에 pipes
, 어디에서 /dev/fd/n
리눅스처럼 작동합니다.)
에서 ksh
, FDS 위 2
가 명령 줄에 명시 적으로 전달하지 않는 한, close-on-exec 플래그로 표시됩니다. 우리와 같은 사용하지 않는 파일 디스크립터를 닫을 필요가 없습니다 이유입니다 zsh
-하지만 우리가해야 할 이유도의 {i1}>&$i1
사용이 eval
그 새의 값 $i1
으로 전달되는, tee
그리고 cat
...
에서 bash
당신이 close-on-exec 플래그를 피할 수 없기 때문에이, 할 수 없습니다.
우리는 간단한 외부 명령 만 사용하기 때문에 비교적 간단합니다. 대신 쉘 구조를 사용하려고 할 때 더 복잡해지고 쉘 버그가 발생하기 시작합니다.
명명 된 파이프를 사용하여 위와 동일한 것을 비교하십시오.
f() {
mkfifo p{i,o}{1,2,3}
tr a b < pi1 > po1 &
sed 's/./&&/g' < pi2 > po2 &
cut -c2- < pi3 > po3 &
tee pi{1,2} > pi3 &
cat po{1,2,3}
rm -f p{i,o}{1,2,3}
}
printf '%s\n' foo bar | f
당신은 명령, 사용과 상호 작용하려는 경우 expect
, 또는 zsh
년대 zpty
, 또는 명명 된 파이프.
파이프로 멋진 배관 작업을하려면 명명 된 파이프를 사용하십시오.
공동 프로세스는 위의 작업 중 일부를 수행 할 수 있지만 사소하지 않은 작업에 대해서는 심각한 헤드 스크래치를 수행 할 수 있도록 준비해야합니다.
exec {tr[1]}>&-
실제로 최신 버전에서 작동하는 것으로 보이며 CWRU / changelog 항목에서 참조됩니다 ( {array [ind]}와 같은 단어를 올바른 리디렉션으로 허용 ... 2012-09-01). exec {tr[1]}<&-
(또는 두 가지를 모두 >&-
호출 close()
하기 때문에 아무런 차이가 없지만 더 정확한 내용 은) coproc의 stdin을 닫지 않고 해당 coproc에 대한 파이프의 쓰기 끝을 닫습니다.
yash
.
mkfifo
은 파이프 조건에 대한 경쟁 조건 및 보안에 대해 걱정할 필요가 없다는 것입니다. 여전히 fifo로 교착 상태에 대해 걱정해야합니다.
stdbuf
명령은 이들 중 일부를 방지하는 데 도움이 될 수 있습니다. 나는 리눅스와 bash에서 그것을 사용했다. 어쨌든 @ StéphaneChazelas가 결론에 옳다고 생각합니다. "머리 긁기"단계는 명명 된 파이프로 다시 전환했을 때만 끝났습니다.
공동 프로세스는 먼저 ksh88
셸 (1988) 과 함께 셸 스크립팅 언어로 도입되었으며 나중에 zsh
1993 년 전 어느 시점에 도입되었습니다 .
ksh에서 공동 프로세스를 시작하는 구문은 command |&
입니다. 거기서부터로 command
표준 입력에 쓰고 print -p
표준 출력을로 읽을 수 있습니다 read -p
.
수십 년 후,이 기능이 부족한 bash는 마침내 4.0 릴리스에서 소개되었습니다. 불행하게도, 호환되지 않고 더 복잡한 구문이 선택되었습니다.
bash 4.0 이상에서는 다음과 같은 coproc
명령 으로 공동 프로세스를 시작할 수 있습니다 .
$ coproc awk '{print $2;fflush();}'
그런 다음 stdin 명령에 무언가를 전달할 수 있습니다.
$ echo one two three >&${COPROC[1]}
다음을 사용하여 awk 출력을 읽습니다.
$ read -ru ${COPROC[0]} foo
$ echo $foo
two
ksh에서는 다음과 같습니다.
$ awk '{print $2;fflush();}' |&
$ print -p "one two three"
$ read -p foo
$ echo $foo
two
"coproc"은 무엇입니까?
쉘과 협력하는 두 번째 프로세스를 의미하는 "공-프로세스"의 줄임말입니다. 명령 끝에서 "&"로 시작한 백그라운드 작업과 매우 유사합니다. 단, 상위 I / O와 동일한 표준 입력 및 출력을 공유하는 대신 표준 I / O가 특수한 방법으로 상위 쉘에 연결됩니다. FIFO라고하는 파이프의 종류. 참고를 위해 여기를 클릭하십시오
하나는 다음과 같이 zsh에서 coproc을 시작합니다.
coproc command
이 명령은 stdin에서 읽거나 stdout에 쓸 준비가되어 있어야합니다. 그렇지 않으면 coproc로 많이 사용되지 않습니다.
이 기사를 읽고 여기 가 간부와 coproc 사이에 사례 연구를 제공합니다
|
. (즉, 대부분의 쉘에서 파이프를 사용하고 ksh93에서 소켓 쌍을 사용하십시오). 파이프와 소켓 쌍은 선입 선출이며 모두 선입 선출 (FIFO)입니다. mkfifo
명명 된 파이프를 만들고, 공동 프로세스는 명명 된 파이프를 사용하지 않습니다.
다음은 BASH로 작성된 간단한 서버입니다. netcat
클래식은 작동하지 않는 OpenBSD가 필요 합니다. 물론 유닉스 소켓 대신 inet 소켓을 사용할 수 있습니다.
server.sh
:
#!/usr/bin/env bash
SOCKET=server.sock
PIDFILE=server.pid
(
exec </dev/null
exec >/dev/null
exec 2>/dev/null
coproc SERVER {
exec nc -l -k -U $SOCKET
}
echo $SERVER_PID > $PIDFILE
{
while read ; do
echo "pong $REPLY"
done
} <&${SERVER[0]} >&${SERVER[1]}
rm -f $PIDFILE
rm -f $SOCKET
) &
disown $!
client.sh
:
#!/usr/bin/env bash
SOCKET=server.sock
coproc CLIENT {
exec nc -U $SOCKET
}
{
echo "$@"
read
} <&${CLIENT[0]} >&${CLIENT[1]}
echo $REPLY
용법:
$ ./server.sh
$ ./client.sh ping
pong ping
$ ./client.sh 12345
pong 12345
$ kill $(cat server.pid)
$
bash 4.3.11
, 당신이 할 수있는 지금 닫습니다 coproc 파일 기술자 직접 억스 필요없이. 변하기 쉬운; 귀하의 답변에있는 예제와 관련하여exec {tr[1]}<&-
이제는 작동합니다 (coproc의 stdin을 닫으십시오. 코드는 간접적으로{tr[1]}
사용하여 닫으려고 시도>&-
하지만{tr[1]}
coproc의 stdin 이며으로 닫아야합니다<&-
). 이 문제는4.2.25
여전히 문제 가있는 사이에 있고 그렇지 않은 곳에 있어야합니다4.3.11
.