다양한 쉘에서 coproc 명령을 어떻게 사용합니까? 예를 제공 할 수 있습니까

누군가 사용 방법에 대한 몇 가지 예를 제공 할 수 있습니까 coproc?



답변

공동 프로세스는 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, 또는 명명 된 파이프.

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

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


답변

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

답변

“coproc”은 무엇입니까?

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

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

coproc command

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

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


답변

다음은 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)
$