다양한 명령에 파이프라인 데이터 재사용

다양한 명령에 파이프라인 데이터 재사용

다양한 애플리케이션에 동일한 파이프라인을 사용하고 싶습니다. 예를 들면 다음과 같습니다.

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

마지막으로 을 실행하는 방법이 있으며 cmd1cmd2cmd3번만 흐름의 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마지막 줄 바꿈 이후에는 아무것도 읽지 않도록 한 번에 한 문자씩 읽어야 합니다. 그것은 비효율적입니다.

이는 readstdin을 검색할 수 없을 때 쉘 내장 함수가 수행하는 작업입니다.

{
  head -n 10 > /dev/null
  cat
} < myfile

head이는 많은 양의 데이터를 읽고 lseek라인 10의 끝을 지나 되돌아가기 때문에 작동합니다 . 이것은 분명히 파이프로는 할 수 없습니다.

최신 GNU 또는 FreeBSD 시스템에서는 또는 를 사용하여 stdio특정 응용 프로그램에 한 번에 한 문자씩 읽도록 지시할 수 있습니다.stdbuf -i1stdbuf -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다음 줄을 보내기 전에 파이프에 뭔가가 있는지 확인할 수 있습니다(그리고 그것이 비워질 때까지 잠자기 상태).

물론 이 역시 매우 비효율적이다.

관련 정보