당신은 GNU stdbuf의 및 이들의 조합으로 사용할 수 pee
에서 moreutils를 :
echo "Hello world!" | stdbuf -o 1M pee cmd1 cmd2 cmd3 > output
popen(3)
이 3 개의 셸 명령 행을 pee로 fread
입력 한 다음 입력 fwrite
을 모두 3 개 까지 입력 하면 최대 1M까지 버퍼링됩니다.
아이디어는 최소한 입력만큼 큰 버퍼를 갖는 것입니다. 이렇게하면 세 명령이 동시에 시작 되더라도 세 명령이 pee
pclose
순차적으로 입력 될 때만 입력이 표시 됩니다.
각각 pclose
에 pee
대해 버퍼를 명령으로 플러시하고 종료를 기다립니다. 따라서 해당 cmdx
명령이 입력을 받기 전에 출력을 시작하지 않는 한 (부모가 반환 된 후에도 계속 출력 할 수있는 프로세스를 포크하지 않는 한) 세 명령의 출력은 인터리브.
실제로, 그것은 3 개의 명령이 동시에 시작된다는 결점을 가지고, 메모리에서 임시 파일을 사용하는 것과 조금 비슷합니다.
명령을 동시에 시작하지 않으려면 pee
쉘 함수로 작성할 수 있습니다.
pee() (
input=$(cat; echo .)
for i do
printf %s "${input%.}" | eval "$i"
done
)
echo "Hello world!" | pee cmd1 cmd2 cmd3 > out
그러나 zsh
NUL 문자를 사용한 이진 입력에 실패하지 않는 다른 쉘을주의하십시오 .
임시 파일을 사용하지 않지만 전체 입력이 메모리에 저장됩니다.
어쨌든 입력을 메모리 또는 임시 파일의 어딘가에 저장해야합니다.
실제로 여러 가지 간단한 도구가 단일 작업에 협력한다는 Unix 아이디어의 한계를 보여 주므로 상당히 흥미로운 질문입니다.
여기서는 몇 가지 도구를 사용하여 작업을 수행하려고합니다.
- 소스 명령 (여기
echo
)
- 디스패처 명령 (
tee
)
- 몇몇 필터 명령 (
cmd1
, cmd2
, cmd3
)
- 및 집계 명령 (
cat
).
모두 동시에 실행할 수 있고 가능한 빨리 처리해야 할 데이터에 대해 열심히 노력하면 좋을 것입니다.
하나의 필터 명령의 경우 쉽습니다.
src | tee | cmd1 | cat
모든 명령이 동시에 실행 되고 사용 가능한 즉시 cmd1
데이터를 뭉치기 시작 src
합니다.
이제 세 가지 필터 명령을 사용하여 동일한 작업을 수행 할 수 있습니다. 동시에 시작하고 파이프와 연결합니다.
┏━━━┓▁▁▁▁▁▁▁▁▁▁┏━━━━┓▁▁▁▁▁▁▁▁▁▁┏━━━┓
┃ ┃░░░░2░░░░░┃cmd1┃░░░░░5░░░░┃ ┃
┃ ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃ ┃
┏━━━┓▁▁▁▁▁▁▁▁▁▁┃ ┃▁▁▁▁▁▁▁▁▁▁┏━━━━┓▁▁▁▁▁▁▁▁▁▁┃ ┃▁▁▁▁▁▁▁▁▁┏━━━┓
┃src┃░░░░1░░░░░┃tee┃░░░░3░░░░░┃cmd2┃░░░░░6░░░░┃cat┃░░░░░░░░░┃out┃
┗━━━┛▔▔▔▔▔▔▔▔▔▔┃ ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃ ┃▔▔▔▔▔▔▔▔▔┗━━━┛
┃ ┃▁▁▁▁▁▁▁▁▁▁┏━━━━┓▁▁▁▁▁▁▁▁▁▁┃ ┃
┃ ┃░░░░4░░░░░┃cmd3┃░░░░░7░░░░┃ ┃
┗━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━┛
명명 된 파이프로 비교적 쉽게 할 수있는 것 :
pee() (
mkfifo tee-cmd1 tee-cmd2 tee-cmd3 cmd1-cat cmd2-cat cmd3-cat
{ tee tee-cmd1 tee-cmd2 tee-cmd3 > /dev/null <&3 3<&- & } 3<&0
eval "$1 < tee-cmd1 1<> cmd1-cat &"
eval "$2 < tee-cmd2 1<> cmd2-cat &"
eval "$3 < tee-cmd3 1<> cmd3-cat &"
exec cat cmd1-cat cmd2-cat cmd3-cat
)
echo abc | pee 'tr a A' 'tr b B' 'tr c C'
(위에서 } 3<&0
사실 해결하려면 &
리디렉션 stdin
로부터는 /dev/null
, 우리가 사용하는 <>
타단까지 블록 파이프의 개구 (피할 cat
아니라 열었다))를
또는 명명 된 파이프를 피하기 위해 zsh
coproc을 사용 하면 조금 더 고통 스럽습니다.
pee() (
n=0 ci= co= is=() os=()
for cmd do
eval "coproc $cmd $ci $co"
exec {i}<&p {o}>&p
is+=($i) os+=($o)
eval i$n=$i o$n=$o
ci+=" {i$n}<&-" co+=" {o$n}>&-"
((n++))
done
coproc :
read -p
eval tee /dev/fd/$^os $ci "> /dev/null &" exec cat /dev/fd/$^is $co
)
echo abc | pee 'tr a A' 'tr b B' 'tr c C'
이제 문제는 : 모든 프로그램이 시작되고 연결되면 데이터가 흐를 것인가?
우리는 두 가지 금기 사항이 있습니다.
tee
모든 출력을 동일한 속도로 공급하므로 가장 느린 출력 파이프의 속도로만 데이터를 디스패치 할 수 있습니다.
cat
첫 번째 데이터 (5)에서 모든 데이터를 읽었을 때 두 번째 파이프 (위의 그림에서 파이프 6)부터 읽기 시작합니다.
이는 데이터 cmd1
가 완료 될 때까지 파이프 6에서 데이터가 흐르지 않음을 의미 합니다. 그리고 tr b B
위 의 경우와 같이 데이터가 파이프 3으로 흐르지 않음을 의미 할 수 있습니다. 즉, 데이터가 tee
모두 3의 가장 느린 속도로 공급 되므로 파이프 2, 3 또는 4 중 어느 것도 흐르지 않습니다 .
실제로 이러한 파이프는 널이 아닌 크기를 가지므로 일부 데이터는 통과 할 수 있으며 시스템에서 최소한 다음과 같이 작동하도록 할 수 있습니다.
yes abc | head -c $((2 * 65536 + 8192)) | pee 'tr a A' 'tr b B' 'tr c C' | uniq -c -c
그 외에도
yes abc | head -c $((2 * 65536 + 8192 + 1)) | pee 'tr a A' 'tr b B' 'tr c C' | uniq -c
우리는이 상황에있는 교착 상태가 있습니다.
┏━━━┓▁▁▁▁2▁▁▁▁▁┏━━━━┓▁▁▁▁▁5▁▁▁▁┏━━━┓
┃ ┃░░░░░░░░░░┃cmd1┃░░░░░░░░░░┃ ┃
┃ ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃ ┃
┏━━━┓▁▁▁▁1▁▁▁▁▁┃ ┃▁▁▁▁3▁▁▁▁▁┏━━━━┓▁▁▁▁▁6▁▁▁▁┃ ┃▁▁▁▁▁▁▁▁▁┏━━━┓
┃src┃██████████┃tee┃██████████┃cmd2┃██████████┃cat┃░░░░░░░░░┃out┃
┗━━━┛▔▔▔▔▔▔▔▔▔▔┃ ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃ ┃▔▔▔▔▔▔▔▔▔┗━━━┛
┃ ┃▁▁▁▁4▁▁▁▁▁┏━━━━┓▁▁▁▁▁7▁▁▁▁┃ ┃
┃ ┃██████████┃cmd3┃██████████┃ ┃
┗━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━┛
파이프 3과 6을 채웠습니다 (각각 64kiB). tee
그 여분의 바이트를 읽었고에 그것을 공급 cmd1
했지만
- 파이프 3
cmd2
을 비우기를 기다리는 동안 쓰기가 차단 되었습니다.
cmd2
파이프 6의 쓰기가 차단 cat
되어 비우기를 기다리는 중이므로 비울 수 없습니다.
cat
파이프 5에 더 이상 입력이 없을 때까지 대기 중이므로 비울 수 없습니다.
cmd1
cat
에서 더 많은 입력을 기다리고 있기 때문에 더 이상 입력이 없다고 말할 수 없습니다 tee
.
- 하고
tee
말할 수 없다 cmd1
거기가 막혀 있기 때문에 더 이상 입력은 ... 등등.
의존성 루프와 교착 상태가 있습니다.
자, 해결책은 무엇입니까? 더 큰 파이프 3과 4 (모든 src
출력 을 포함하기에 충분히 큰 )가 그렇게합니다. 우리는 삽입하여 예를 들어 그렇게 할 수있는 pv -qB 1G
사이 tee
와 cmd2/3
위치를 pv
기다리고 데이터의 1G까지 저장할 수 cmd2
및 cmd3
읽을 수 있습니다. 그것은 두 가지 의미가 있습니다.
- 잠재적으로 많은 양의 메모리를 사용하고 있습니다.
cmd2
실제로는 cmd1이 완료되었을 때만 데이터 처리를 시작하기 때문에 3 개의 명령이 모두 협력하지 못했습니다 .
두 번째 문제에 대한 해결책은 파이프 6과 7을 더 크게 만드는 것입니다. 그 가정 cmd2
및 cmd3
더 많은 메모리를 소비하지 않을 그들이 소비하는만큼 출력으로 생산합니다.
첫 번째 문제에서 데이터 복제를 피하는 유일한 방법은 디스패처 자체에서 데이터 보존을 구현하는 것입니다. tee
즉, 가장 빠른 출력 속도로 데이터를 공급할 수 있는 변형을 구현하는 것입니다 . 그들 자신의 속도로 느린 것). 실제로 사소한 것은 아닙니다.
따라서 결국 프로그래밍없이 합리적으로 얻을 수있는 최선의 방법은 아마도 (Zsh 구문)과 같습니다.
max_hold=1G
pee() (
n=0 ci= co= is=() os=()
for cmd do
if ((n)); then
eval "coproc pv -qB $max_hold $ci $co | $cmd $ci $co | pv -qB $max_hold $ci $co"
else
eval "coproc $cmd $ci $co"
fi
exec {i}<&p {o}>&p
is+=($i) os+=($o)
eval i$n=$i o$n=$o
ci+=" {i$n}<&-" co+=" {o$n}>&-"
((n++))
done
coproc :
read -p
eval tee /dev/fd/$^os $ci "> /dev/null &" exec cat /dev/fd/$^is $co
)
yes abc | head -n 1000000 | pee 'tr a A' 'tr b B' 'tr c C' | uniq -c