파이프라인 중간에 있는 행 수를 계산하는 방법

파이프라인 중간에 있는 행 수를 계산하는 방법

파이프라인의 행 수를 계산한 다음 결과에 따라 파이프라인을 계속하고 싶습니다.

나는 노력했다

x=$(printf 'faa\nbor\nbaz\n' \
  | tee /dev/stderr | wc -l) 2>&1 \
  | if [[ $x -ge 2 ]]; then
      grep a
    else
      grep b
    fi

하지만 전혀 필터링하지 않습니다("a"도 "b"도 아님). 최소한 다음은 예상대로 작동하기 때문에 이것은 매우 예상치 못한 일입니다.

printf 'faa\nbor\nbaz\n' | if true; then grep a; else grep b; fi
printf 'faa\nbor\nbaz\n' | if false; then grep a; else grep b; fi

(bash에서는) 작동하지 않기 때문에 내부 명령 대체에서 stderr을 리디렉션할 수 없는 것 같습니다. 세 줄을 모두 인쇄합니다.

x=$(printf 'faa\nbor\nbaz\n' | tee /dev/stderr | wc -l) 2>&1 | grep a

zsh에서는 두 줄만 인쇄합니다.

그러나 두 셸 모두에서 변수 x는 파이프 뒤에 설정되지 않으며 심지어 파이프의 후반부에도 설정되지 않습니다.

파이프라인의 행 수를 계산한 다음 해당 수에 따라 조치를 취하려면 어떻게 해야 합니까? 임시 파일을 피하고 싶습니다.

답변1

이 댓글그건 진실이야:

파이프라인의 각 부분은 동일한 파이프라인의 다른 부분과 독립적으로 시작됩니다. 즉 $x, 다른 단계 중 하나에 설정하면 파이프라인 중간에 사용할 수 없습니다.

이것은 당신이 아무것도 할 수 없다는 것을 의미하지 않습니다. 파이프는 기본 데이터 채널로 간주될 수 있으며 프로세스는 파일, 명명된 fifo 또는 기타 모든 측면 채널을 사용하여 여전히 통신할 수 있습니다(때로는 차단하지 않도록 각별히 주의해야 하지만).

나중에 행 수를 계산하고 전체 데이터 스트림을 조건부로 처리하려고 합니다. 이는 전체 스트림을 전달하기 전에 스트림의 끝에 도달해야 함을 의미합니다. 따라서 어떻게든 전체 스트림을 저장해야 합니다. 임시 파일은 합리적인 접근 방식처럼 보입니다. 파이프를 최소한 두 부분으로 분할해야 합니다. 첫 번째 부분은 데이터를 파일에 저장해야 합니다. 그런 다음 행 수를 계산해야 합니다(이 작업은 첫 번째 부분에 속할 수 있다고 생각합니다). 그런 다음 마지막 부분은 숫자를 가져와서 파일을 읽어야 합니다. 시작하고 그에 따라 행동하십시오.


임시 파일을 피하려면 파이프라인의 일부가 와 같아야 합니다 sponge. 우회를 방지하려면 줄 번호를 출력의 첫 번째 줄로 전달해야 하며 나머지 파이프라인은 이 프로토콜을 이해해야 합니다.

다음 명령을 고려하십시오.

sed '$ {=; H; g; p;}; H; d'

예약된 공간에 라인을 축적합니다. 행이 하나 이상 있는 경우 마지막 행을 수신한 후 행 번호가 인쇄되고 sed그 뒤에 빈 행과 실제 입력이 표시됩니다.

빈 줄은 불필요하지만 이 간단한 코드에서 "자연스럽게" 나옵니다. 나는 그것을 피하려고 하지 않고 sed나중에 파이프라인에서 처리할 것입니다(예 sed '2 d':

사용 예:

#!/bin/sh

sed '$ {=; H; g; p;}; H; d' | sed '2 d' | {
   if ! IFS= read -r nlines; then
      echo "0 lines. Nothing to do." >&2
   else
      echo "$nlines lines. Processing accordingly." >&2
      if [ "$nlines" -ge 2 ]; then
         grep a
      else
         grep b
      fi
   fi
}

노트:

  • IFS= read -r첫 번째 행은 잘 정의되어 있고 고유한 숫자가 포함되어 있거나 존재하지 않기 때문에 과잉입니다.
  • 나는 그것을 사용했다 /bin/sh. 이 코드는 Bash에서도 실행됩니다.
  • sed어떤 양의 데이터도 저장할 수 있다고 가정할 수는 없습니다 .POSIX 사양설명하다:

    패턴 공간과 홀드 공간 모두 최소 8192바이트를 수용할 수 있어야 합니다.

    따라서 제한은 8192바이트만 될 수 있습니다. 반면에 임시 파일에는 1TB의 데이터를 쉽게 저장할 수 있다고 상상할 수 있습니다. 어떤 대가를 치르더라도 임시 파일을 피하지 마세요.


제목에는 "행 수 계산"이라고 되어 있지만 예제에서는 해당 숫자가 2 이상(보통 N 이상)인지 확인하려고 합니다. 이러한 질문은 동등하지 않습니다. 두 번째(N) 줄을 입력하면 후자의 질문에 대한 답을 알 수 있으며, 줄까지 무한정 나타납니다. 위의 코드는 정의되지 않은 입력을 처리할 수 없습니다. 어느 정도 고치도록 하겠습니다.

sed '
7~1 {p; d}
6 {H; g; i \
6+
p; d}
$ {=; H; g; p}
6! {H; d}
'

이 명령은 6행에 도달할 때 행 번호를 가정(인쇄)한다는 점을 제외하면 이전 솔루션과 동일하게 작동합니다 6+. 그런 다음 본 줄이 인쇄되고 다음 줄(있는 경우)이 나타나는 즉시 인쇄됩니다( cat비슷한 동작).

사용 예:

#!/bin/sh

threshold=6

sed "
$((threshold+1))~1 {p; d}
$threshold {H; g; i \
$threshold+
p; d}
$ {=; H; g; p}
${threshold}! {H; d}
" | sed '2 d' | {
   if ! IFS= read -r nlines; then
      echo "0 lines. Nothing to do." >&2
   else
      echo "$nlines lines. Processing accordingly." >&2
      if [ "$nlines" = "$threshold+" ]; then
         grep a
      else
         grep b
      fi
   fi
}

노트:

  • sed(귀하의 경우 제한 사항이 무엇이든) 제한 사항이 여전히 적용되므로 "어느 정도" 수정되었습니다 . 그러나 이제 처리할 수 있는 sed최대 $threshold행 수는 $threshold충분합니다.
  • 예제 코드는 테스트용이지만 $threshold+프로토콜을 사용하면 0, 1, 2, ..., 임계값 빼기 1, 임계값 이상의 행을 구별할 수 있습니다.

나는 그것을 잘 하지 못한다 sed. 내 sed코드를 단순화할 수 있다면 댓글에 한 줄씩 남겨주세요.

답변2

토론과 Kamil의 sed 코드를 바탕으로 다음과 같은 awk 솔루션을 찾았습니다.

awk -v th="$threshold" '
  function print_lines() { for (i in lines) print lines[i] }
  NR < th { lines[NR] = $0 }
  NR > th { print }
  NR == th { print th; print_lines(); print }
  END { if (NR < th) { print NR; print_lines(); } }' \
| if read nlines; then
    if [ "$nlines" -eq "$threshold" ]; then
      grep a
    else
      grep b
    fi
  fi

관련 정보