다양한 쉘에서 coproc 명령을 어떻게 사용합니까?


답변:


118

공동 프로세스는 ksh기능입니다 (이미 있음 ksh88). zsh그것은 단지에만 추가 된 반면, 시작 (90 년대 초)의 기능을했다 bash4.0(2009 년).

그러나 동작과 인터페이스는 3 개의 셸에서 크게 다릅니다.

그러나 아이디어는 동일합니다. 백그라운드에서 작업을 시작하고 명명 된 파이프에 의존하지 않고도 입력을 보내고 출력을 읽을 수 있습니다.

일부 시스템에서는 최신 버전의 ksh93이있는 대부분의 쉘과 소켓 쌍이있는 명명되지 않은 파이프로 수행됩니다.

에서는 a | cmd | b, a데이터를 공급 cmd하고 b출력을 판독한다. cmd코 프로세스로 실행 하면 쉘이 a및 로 둘 수 있습니다 b.

ksh 공동 프로세스

에서 다음 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 공동 프로세스

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 공동 프로세스

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]}"

기본 구문을 사용하면 한 번에 하나의 공동 프로세스 만 시작할 수 있습니다.

확장 된 구문

확장 구문에서는 공동 프로세스의 이름zshzpty 공동 프로세스 와 같이 지정할 수 있습니다 .

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공동 프로세스 기능 자체는 없지만 파이프 라인프로세스 리디렉션 기능으로 동일한 개념을 구현할 수 있습니다 . 시스템 호출에 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<&-

자, 왜 그렇게 인기가 없습니까?

명명 된 파이프를 사용하는 것보다 이점이 거의 없습니다

공동 프로세스는 표준 명명 된 파이프로 쉽게 구현할 수 있습니다. 정확히 명명 된 파이프가 언제 소개되었는지는 모르겠지만 ksh80 년대 중반 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\n2 바이트 만 된 후에는 셸이 더 많은 데이터를 보내기를 기다리 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, 또는 명명 된 파이프.

파이프로 멋진 배관 작업을하려면 명명 된 파이프를 사용하십시오.

공동 프로세스는 위의 작업 중 일부를 수행 할 수 있지만 사소하지 않은 작업에 대해서는 심각한 헤드 스크래치를 수행 할 수 있도록 준비해야합니다.


실제로 큰 대답입니다. 나는 그것이 고정 할 때 구체적으로 알고 있지만,하지의 적어도 bash 4.3.11, 당신이 할 수있는 지금 닫습니다 coproc 파일 기술자 직접 억스 필요없이. 변하기 쉬운; 귀하의 답변에있는 예제와 관련하여 exec {tr[1]}<&- 이제는 작동합니다 (coproc의 stdin을 닫으십시오. 코드는 간접적으로 {tr[1]}사용하여 닫으려고 시도 >&-하지만 {tr[1]}coproc의 stdin 이며으로 닫아야합니다 <&-). 이 문제는 4.2.25여전히 문제 가있는 사이에 있고 그렇지 않은 곳에 있어야합니다 4.3.11.
mklement0

1
@ mklement0 감사합니다. exec {tr[1]}>&-실제로 최신 버전에서 작동하는 것으로 보이며 CWRU / changelog 항목에서 참조됩니다 ( {array [ind]}와 같은 단어를 올바른 리디렉션으로 허용 ... 2012-09-01). exec {tr[1]}<&-(또는 두 가지를 모두 >&-호출 close()하기 때문에 아무런 차이가 없지만 더 정확한 내용 은) coproc의 stdin을 닫지 않고 해당 coproc에 대한 파이프의 쓰기 끝을 닫습니다.
Stéphane Chazelas 2016 년

1
@ mklement0, 좋은 지적, 나는 그것을 업데이트하고 추가했다 yash.
Stéphane Chazelas

1
한 가지 장점 mkfifo은 파이프 조건에 대한 경쟁 조건 및 보안에 대해 걱정할 필요가 없다는 것입니다. 여전히 fifo로 교착 상태에 대해 걱정해야합니다.
Otheus

1
교착 상태 정보 : stdbuf명령은 이들 중 일부를 방지하는 데 도움이 될 수 있습니다. 나는 리눅스와 bash에서 그것을 사용했다. 어쨌든 @ StéphaneChazelas가 결론에 옳다고 생각합니다. "머리 긁기"단계는 명명 된 파이프로 다시 전환했을 때만 끝났습니다.
shub

7

공동 프로세스는 먼저 ksh88셸 (1988) 과 함께 셸 스크립팅 언어로 도입되었으며 나중에 zsh1993 년 전 어느 시점에 도입되었습니다 .

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

-1

"coproc"은 무엇입니까?

쉘과 협력하는 두 번째 프로세스를 의미하는 "공-프로세스"의 줄임말입니다. 명령 끝에서 "&"로 시작한 백그라운드 작업과 매우 유사합니다. 단, 상위 I / O와 동일한 표준 입력 및 출력을 공유하는 대신 표준 I / O가 특수한 방법으로 상위 쉘에 연결됩니다. FIFO라고하는 파이프의 종류. 참고를 위해 여기를 클릭하십시오

하나는 다음과 같이 zsh에서 coproc을 시작합니다.

coproc command

이 명령은 stdin에서 읽거나 stdout에 쓸 준비가되어 있어야합니다. 그렇지 않으면 coproc로 많이 사용되지 않습니다.

이 기사를 읽고 여기 가 간부와 coproc 사이에 사례 연구를 제공합니다


답변에 기사를 추가 할 수 있습니까? 나는이 주제가 아래에 표시된 것처럼 보이기 때문에 U & L에서 다루려고했습니다. 답변 주셔서 감사합니다! 또한 태그를 zsh가 아닌 Bash로 설정했습니다.
slm

@slm 이미 Bash 해커를 가리 켰습니다. 나는 충분한 예를 보았다. 당신의 긴장이 다음주의에서이 질문을 가지고 있었다면 네 당신은 성공 :>
발렌틴 Bajrami에게

그들은 특별한 종류의 파이프가 아니며와 함께 사용되는 것과 같은 파이프입니다 |. (즉, 대부분의 쉘에서 파이프를 사용하고 ksh93에서 소켓 쌍을 사용하십시오). 파이프와 소켓 쌍은 선입 선출이며 모두 선입 선출 (FIFO)입니다. mkfifo명명 된 파이프를 만들고, 공동 프로세스는 명명 된 파이프를 사용하지 않습니다.
Stéphane Chazelas

@ slm 죄송합니다 zsh ... 실제로 zsh에서 작동합니다. 나는 때때로 흐름과 함께하는 경향이 있습니다. 그것은 ... 너무 배쉬에서 잘 작동
Munai 다스 Udasin에게

@ 스테판 Chazelas가 나는 내가 그것을 I / O가 FIFO라고 파이프의 특별한 종류와 연결되어 있다고 어딘가에 읽어 확신 ...
Munai 다스 Udasin

-1

다음은 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)
$
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.