다양한 애플리케이션에 동일한 파이프라인을 사용하고 싶습니다. 예를 들면 다음과 같습니다.
cat my_file | {
cmd1
cmd2
cmd3
}
Cmd1은 입력의 일부를 소비해야 합니다. Cmd2는 다른 부분을 소비해야 합니다.
그러나 각 명령은 더 많은 입력을 소비하므로 읽은 내용을 적절하게 버퍼링해야 합니다.
예를 들어:
yes | nl | {
head -n 10 > /dev/null
cat
} | head -n 10
11행 대신 912행에서 출력됩니다.
Tee는 각 명령이 표준 입력의 일부를 소비해야 하기 때문에 좋은 선택이 아닙니다.
작동하게 하는 쉬운 방법이 있나요?
답변1
tee
전체 스트림을 처리하기 위해 명령을 복사하는 데 사용할 수 있는 여러 명령이 있습니다.
( ( seq 1 10 | tee /dev/fd/5 | sed s/^/line..\ / >&4 ) 5>&1 | wc -l ) 4>&1
line.. 1
line.. 2
line.. 3
line.. 4
line.. 5
line.. 6
line.. 7
line.. 8
line.. 9
line.. 10
10
또는 bash를 사용하여 한 줄씩 분할합니다.
while read line ;do
echo cmd1 $line
read line && echo cmd2 $line
read line && echo cmd3 $line
done < <(seq 1 10)
cmd1 1
cmd2 2
cmd3 3
cmd1 4
cmd2 5
cmd3 6
cmd1 7
cmd2 8
cmd3 9
cmd1 10
마지막으로 을 실행하는 방법이 있으며 cmd1
단 cmd2
한 cmd3
번만 흐름의 1/3이표준 입력:
( ( ( seq 1 10 |
tee /dev/fd/5 /dev/fd/6 |
sed -ne '1{:a;p;N;N;N;s/^.*\n//;ta;}' |
cmd1 >&4
) 5>&1 |
sed -ne '2{:a;p;N;N;N;s/^.*\n//;ta;}' |
cmd2 >&4
) 6>&1 |
sed -ne '3{:a;p;N;N;N;s/^.*\n//;ta;}' |
cmd3 >&4
) 4>&1
command_1: 1
command_1: 4
command_1: 7
command_1: 10
Command-2: 2
Command-2: 5
Command-2: 8
command 3: 3
command 3: 6
command 3: 9
이를 시도하려면 다음을 사용할 수 있습니다.
alias cmd1='sed -e "s/^/command_1: /"' \
cmd2='sed -e "s/^/Command_2: /"' \
cmd3='sed -e "s/^/Command_3: /"'
동일한 스크립트에서 다른 프로세스에서 스트림을 사용하려는 경우 다음을 수행할 수 있습니다.
(
for ((i=(RANDOM&7);i--;));do
read line;
echo CMD1 $line
done
for ((i=RANDOM&7;i--;));do
read line
echo CMD2 $line
done
while read line ;do
echo CMD3 $line
done
)
CMD1 1
CMD1 2
CMD1 3
CMD2 4
CMD2 5
CMD2 6
CMD2 7
CMD2 8
CMD2 9
CMD3 10
이렇게 하려면 별도의 스크립트를 다음으로 변환해야 할 수도 있습니다.배쉬 기능모놀리식 스크립트를 작성하는 능력.
또 다른 접근 방식은 각 스크립트가 아무것도 출력하지 않도록 하는 것입니다.표준 출력,cat
체인그들을:
#!/bin/sh
for ((i=1;1<n;i++));do
read line
pRoCeSS the $line
echo >output_log
done
cat
최종 명령은 다음과 같습니다.
seq 1 10 | cmd1 | cmd2 | cmd2
답변2
한 문자를 더 읽지 않고 stdin의 파이프에서 10줄을 읽을 수 있으려면 head -n 10
마지막 줄 바꿈 이후에는 아무것도 읽지 않도록 한 번에 한 문자씩 읽어야 합니다. 그것은 비효율적입니다.
이는 read
stdin을 검색할 수 없을 때 쉘 내장 함수가 수행하는 작업입니다.
{
head -n 10 > /dev/null
cat
} < myfile
head
이는 많은 양의 데이터를 읽고 lseek
라인 10의 끝을 지나 되돌아가기 때문에 작동합니다 . 이것은 분명히 파이프로는 할 수 없습니다.
최신 GNU 또는 FreeBSD 시스템에서는 또는 를 사용하여 stdio
특정 응용 프로그램에 한 번에 한 문자씩 읽도록 지시할 수 있습니다.stdbuf -i1
stdbuf -i0
그러나 이는 GNU에서는 작동하지 않습니다 head
. 하지만 GNU에서는 작동하므로 sed
다음과 같이 할 수 있습니다.
seq 20 | {
stdbuf -i0 sed -n 10q
cat
}
또는 한 번에 최대 한 줄이 있도록 파이프에 진행되는 작업을 제어할 수 있습니다.
예를 들어 Linux에서는 다음을 수행할 수 있습니다.
one_line_at_a_time() {
perl -MTime::HiRes=usleep -pe '
BEGIN{$|=1;open F, "<", "/dev/fd/1"; $r=""; vec($r,fileno(F),1) = 1}
usleep(1000) while select($ro=$r,undef,undef,0)'
}
seq 20 | one_line_at_a_time | { head -n 10 > /dev/null; cat; }
스크립트 perl
는 읽기 모드에서 "/dev/fd/1"을 엽니다. 그러면 Linux에서는 fd 1(stdout)에 연결된 파이프의 다른 쪽 끝이 열립니다. 이런 식으로 를 사용하면 select
다음 줄을 보내기 전에 파이프에 뭔가가 있는지 확인할 수 있습니다(그리고 그것이 비워질 때까지 잠자기 상태).
물론 이 역시 매우 비효율적이다.