파일의 특정 부분 필터링 또는 파이프

파일의 특정 부분 필터링 또는 파이프

시작 태그와 끝 태그로 구분된 일부 섹션이 있는 입력 파일이 있습니다. 예를 들면 다음과 같습니다.

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

nlX,Y,Z 줄이 특정 명령(예:)으로 필터링되지만 나머지 줄은 변경되지 않도록 이 파일에 변환을 적용하고 싶습니다 . (Number Line) 은 nl여러 줄에 걸쳐 상태를 누적하므로 각 줄 X, Y, Z에 적용되는 정적 변환이 아닙니다. (편집하다nl: 누적 상태가 필요하지 않은 모드에서도 작업이 가능하다고 누군가 지적했는데 , nl문제를 단순화하기 위해 예시로 사용한 것 뿐입니다. 실제로 이 명령은 더 복잡한 사용자 정의 스크립트입니다.내가 정말로 찾고 있는 것은 입력 파일의 하위 섹션에 표준 필터를 적용하는 문제에 대한 일반적인 해결책입니다.)

출력은 다음과 같아야 합니다.

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D

파일에는 변환해야 하는 섹션이 여러 개 있을 수 있습니다.

업데이트 2처음에는 여러 부분이 있을 경우 어떤 일이 일어날지 지정하지 않았습니다. 예를 들면 다음과 같습니다.

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
 @@inline-code-start
line L
line M
line N
@@inline-code-end

내 기대는 다음과 같은 경우 주어진 섹션 내에서만 상태가 유지되어야 한다는 것입니다.

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D
     1 line L
     2 line M
     3 line N

그러나 나는 이 질문을 여러 부분에 걸쳐 상태를 보존해야 한다고 해석하는 것이 많은 경우에 타당하고 유용하다고 생각합니다.

업데이트 2 종료

나의 첫 번째 아이디어는 우리가 어느 섹션에 있는지 추적하기 위해 간단한 상태 머신을 구축하는 것이었습니다.

#!/usr/bin/bash
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
  echo $line | nl
  else
    # output
    echo $line
  fi
done

나는 그것을 다음과 같이 실행합니다 :

cat test-inline-codify | ./inline-codify

각 호출이 독립적이기 때문에 작동하지 않으므로 nl줄 번호가 증가하지 않습니다.

line A
line B
     1  line X
     1  line Y
     1  line Z
line C
line D

다음 시도는 fifo를 사용하는 것이었습니다.

#!/usr/bin/bash
mkfifo myfifo
nl < myfifo &
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
    echo $line > myfifo
  else
    # output
    echo $line
  fi
done
rm myfifo

이는 올바른 출력을 제공하지만 순서가 잘못되었습니다.

line A
line B
line C
line D
     1  line 1
     2  line 2
     3  line 3

일부 캐싱이 진행 중일 수 있습니다.

이 모든 것에 대해 내가 틀렸습니까? 이것은 매우 일반적인 문제인 것 같습니다. 이 문제를 해결하려면 간단한 파이프라인이 있어야 한다고 생각합니다.

답변1

나는 당신의 의견에 동의합니다 - 아마도일반적인 질문입니다. 그러나 일부 일반 유틸리티에는 이를 처리하기 위한 몇 가지 기능이 있습니다.


nl

nl예를 들어, 입력을 다음으로 분할합니다.논리 페이지-d두 문자 로 구분됨섹션 구분 기호. 한 줄에 세 번 발생하면 개별적으로 이벤트의 시작을 나타냅니다.제목, 둘그리고보행인. 입력에서 발견된 이들 중 하나를 출력의 빈 줄로 대체합니다. 이는 인쇄되는 유일한 빈 줄입니다.

다른 부분을 포함하도록 예제를 변경하고 ./infile.

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
@@start
line M
line N
line O
@@end

그런 다음 다음 명령을 실행했습니다.

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end$/@@/'  <infile |
nl -d@@ -ha -bn -w1

nl말할 수있다누적 현황논리적 페이지 전반에 걸쳐 있지만 기본적으로는 그렇지 않습니다. 대신에 다음과 같이 입력 줄의 번호를 매깁니다.스타일, 그리고부분. 즉, -ha숫자가 모두 의미합니다.머리글선과 -bn수단바디라인이 없다- 시작하기 때문에상태.

nl이것을 배우기 전에는 모든 입력 에 사용했지만 기본 리미터에 따라 출력이 왜곡 nl될 수 있다는 것을 깨달은 후에는 더 주의 깊게 사용하는 방법을 배웠고 테스트되지 않은 입력에 사용하기 시작했습니다. 하지만 그날 배운 또 다른 교훈은 위에서 했던 것처럼 입력을 조금만 수정하면 다른 측면(예: 이 항목)에 매우 유용하게 적용될 수 있다는 것입니다 .-d\:grep -nF ''nlsed

산출

  line A
  line B

1       line X
2       line Y
3       line Z

  line C
  line D

1       line M
2       line N
3       line O

자세한 내용은 여기를 참조하세요 nl. 번호가 매겨진 줄을 제외한 위의 모든 줄이 공백으로 시작되는 것을 보셨나요? 숫자 줄 의 경우 nl각 줄의 시작 부분에 특정 수의 문자를 삽입합니다. 이 줄의 경우 번호가 매겨지지 않습니다. 공백의 경우에도 항상 (idth count + eparator len) * 공백을 번호가 없는 줄의 머리에 삽입하여 -w들여쓰기와 일치합니다 . -s이를 통해 번호가 매겨진 콘텐츠와 비교하여 번호가 없는 콘텐츠를 많은 노력 없이 정확하게 재현할 수 있습니다. 이것이 nl입력을 논리적 부분으로 나누고 번호가 매겨진 각 줄의 시작 부분에 임의의 문자열을 삽입할 수 있다는 점을 고려하면 -s출력 처리가 매우 쉬워집니다.

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end/@@/; t
     s/^\(@@\)\{1,3\}$/& /' <infile |
nl -d@@ -ha -bn -s' do something with the next line!
'

위의 인쇄물...

                                        line A
                                        line B

 1 do something with the next line!
line X
 2 do something with the next line!
line Y
 3 do something with the next line!
line Z

                                        line C
                                        line D

 1 do something with the next line!
line M
 2 do something with the next line!
line N
 3 do something with the next line!
line O

암소 비슷한 일종의 영양sed

nl대상 응용 프로그램이 아닌 경우 GNU는 일치 항목에 따라 임의의 셸 명령을 실행할 sed수 있습니다 .e

sed '/^@@.*start$/!b
     s//nl <<\\@@/;:l;N
     s/\(\n@@\)[^\n]*end$/\1/
Tl;e'  <infile

위의 코드 는 est 를 바꾸고 abel 로 다시 목초지를 중지하여 sed성공하기에 충분한 입력이 있을 때까지 패턴 공간에서 입력을 수집합니다 . 실행되면 여기에 문서로 표시된 입력을 사용하여 나머지 패턴 공간을 모두 실행합니다.Tb:lenl<<

워크플로는 다음과 같습니다.

  1. /^@@.*start$/!b
    • ^전체 줄이 위 패턴과 일치하지 않으면 $스크립트 에서 !제거되고 자동으로 인쇄됩니다. 따라서 이제부터는 이 패턴으로 시작하는 일련의 줄만 처리합니다.//b
  2. s//nl <<\\@@/
    • s//필드는 /마지막으로 sed시도된 일치를 나타냅니다. 따라서 이 명령은 전체 @@.*start행을 대체합니다 nl <<\\@@.
  3. :l;N
    • :명령은 분기 레이블을 정의합니다. 여기서는 :label이라는 레이블을 설정합니다. ext 명령은 N패턴 공간에 다음 입력 행을 추가하고 그 뒤에 \newline 문자를 추가합니다. 이것은 \n패턴 공간에서 ewline을 얻는 유일한 방법 중 하나 입니다. ewline 문자는 sed한동안 이 작업을 수행해 온 der에 대한 \n명확한 구분 기호입니다 .sed
  4. s/\(\n@@\)[^\n]*end$/\1/
    • s///대체는 일정 시간이 경과한 후에만 성공합니다.시작마주하고 또 처음으로 마주한철사. 이는 패턴 공간의 끝을 \n표시하는 마지막 줄 바꿈 바로 뒤의 패턴 공간 에만 작용합니다 . 작동하면 일치하는 문자열 전체를 첫 번째 그룹 으로 바꾸 거나@@.*end$\1\(\)\n@@
  5. Tl
    • est 명령은 T레이블로 분기됩니다.(제공된 경우)입력 라인이 패턴 공간으로 마지막으로 당겨진 이후 성공적인 대체가 발생하지 않은 경우(내가 그랬던 것처럼 N). \n이는 닫는 구분 기호와 일치하지 않는 패턴 공간에 ewline이 추가 될 때마다 Test 명령이 실패하고 abel로 다시 분기되어 ext 라인이 :l추가 sed되어 N성공할 때까지 반복됨을 의미합니다.
  6. e

    • T일치를 종료하는 교체가 성공하고 스크립트가 실패한 est 로 다시 분기되지 않으면 sed다음과 같은 명령이 실행됩니다 e.l

      nl <<\\@@\nline X\nline Y\nline Z\n@@$
      

마지막 줄을 편집하여 다음과 같이 보이도록 직접 확인할 수 있습니다 Tl;l;e.

다음과 같이 인쇄됩니다.

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
     1  line M
     2  line N
     3  line O

while ... read

마지막 방법이자 아마도 가장 쉬운 방법은 을 사용하는 것이지만 while read그럴 만한 이유가 있습니다. 껍데기-(가장 특이한 점은 bash껍질입니다)- 대규모 또는 꾸준한 양의 입력을 처리하는 데 종종 매우 실패합니다. 이것도 의미가 있습니다. 셸의 작업은 입력 문자를 문자별로 처리하고 더 큰 항목을 처리할 수 있는 다른 명령을 호출하는 것입니다.

하지만 그 역할에서 중요한 점은 쉘이기필코 아니다 read입력이 너무 많습니다. 다음과 같이 지정됩니다.아니요너무 많이 소비하거나 제 시간에 충분히 전달되지 않아 호출하는 명령의 바이트가 부족해지는 지점까지 입력 또는 출력을 버퍼링합니다. 따라서 read우수한 입력을 제공할 수 있습니다.시험return- 남은 입력이 있는지 여부에 대한 정보를 읽으려면 다음 명령을 호출해야 하지만 일반적으로 최선의 방법은 아닙니다.

그러나 다음은 사용 방법의 예입니다.read 그리고입력을 동기적으로 처리하는 기타 명령:

while   IFS= read -r line        &&
case    $line in (@@*start) :;;  (*)
        printf %s\\n "$line"
        sed -un "/^@@.*start$/q;p";;
esac;do sed -un "/^@@.*end$/q;=;p" |
        paste -d: - -
done    <infile

반복할 때마다 가장 먼저 일어나는 일은 read선을 당기는 것입니다. 성공하면 루프가 EOF에 도달하지 않았음을 의미하므로 case일치하기 전에시작구분 기호 do블록은 즉시 실행됩니다. 그렇지 않은 경우 printf인쇄하여 $line전화 를 read받으십시오 sed.

sedp만날 때까지 각 줄을 인쇄 합니다 .시작q마크업 - 입력 내용에 정확히 맞는 경우 . nbuffered 스위치는 -uGNU에 필요합니다. sed그렇지 않으면 매우 탐욕스럽게 버퍼링할 수 있기 때문입니다. 그러나 사양에 따르면 다른 POSIX는 일반 파일 sed인 한 특별한 고려 사항 없이 작동해야 합니다 .<infile

첫 번째 sed quit가 실행되면 쉘은 do루프 블록을 실행합니다. 다른 루프 블록을 호출하여 sed만날 때까지 각 줄을 인쇄합니다.표시. paste해당 라인의 라인 번호를 인쇄할 때 출력을 파이프합니다 . 이와 같이:

1
line M
2
line N
3
line O

paste그런 다음 문자에 붙여 넣으면 :전체 출력은 다음과 같습니다.

line A
line B
1:line X
2:line Y
3:line Z
line C
line D
1:line M
2:line N
3:line O

이는 단지 예일 뿐입니다. 여기에서는 테스트나 do 블록에서 무엇이든 수행할 수 있지만 첫 번째 유틸리티는 너무 많은 입력을 소비해서는 안 됩니다.

관련된 모든 유틸리티는 동일한 입력을 순차적으로 읽고 결과를 인쇄합니다. 이런 종류의 작업은 이해하기 어려울 수 있습니다. 다른 유틸리티는 다른 유틸리티보다 버퍼링을 더 많이 하기 때문에 일반적으로 dd, head및 를 사용 sed하여 올바른 작업을 수행 할 수 있습니다.(단, GNU의 경우 sedcli 스위치가 필요합니다)항상 의지할 수 있어야 합니다 read. 본질적으로 그렇습니다.아주 느린. 이것이 바로 위의 루프가 입력 블록당 한 번만 호출하는 이유입니다.

답변2

한 가지 가능성은 vim 텍스트 편집기를 사용하여 이를 수행하는 것입니다. 쉘 명령을 통해 임의의 부품을 전송할 수 있습니다.

한 가지 방법은 라인 번호를 사용하는 것입니다 :4,6!nl. 이 ex 명령은 라인 4-6(포함)에서 nl을 실행하여 예제 입력에서 원하는 효과를 얻습니다.

또 다른 대화형 방법은 행 선택 모드(shift-V)와 화살표 키를 사용하거나 검색하여 적절한 행을 선택한 다음 를 사용하는 것입니다 :!nl. 예를 들어 입력의 전체 명령 순서는 다음과 같습니다.

/@@inline-code-start
jV/@@inline-code-end
k:!nl

이는 자동화에는 그다지 적합하지 않지만(예를 들어 sed를 사용하는 것이 더 좋습니다), 20줄의 쉘스크립트에 의존하지 않고도 일회성 편집에 매우 유용합니다.

vi(m)을 처음 사용하는 경우 최소한 이러한 변경 후에는 :wq.

답변3

내가 생각할 수 있는 가장 간단한 해결책은 사용하는 것이 아니라 nl직접 행 수를 계산하는 것입니다.

#!/usr/bin/env bash
while read line
do
    if [[ $line == @@inline-code-start* ]]
    then
        active=true
    elif [[ $line == @@inline-code-end* ]]
    then
        active=false
    elif [[ $active = true ]]
    then
        ## Count the line number
        let num++;
        printf "\t%s %s\n" "$num" "$line"
    else
        # output
        printf "%s\n" "$line"
    fi
done

그런 다음 해당 파일에서 실행합니다.

$ foo.sh < file
line A
line B
    1 line X
    2 line Y
    3 line Z
line C
line D

답변4

편집하다사용자 제공 필터를 정의하는 옵션이 추가되었습니다.

#!/usr/bin/perl -s
use IPC::Open2;
our $p;
$p = "nl" unless $p;    ## default filter

$/ = "\@\@inline-code-end\n";
while(<>) { 
   chomp;
   s/\@\@inline-code-start\n(.*)/pipeit($1,$p)/se;
   print;
}

sub pipeit{my($text,$pipe)=@_;
  open2(my $R, my $W,$pipe) || die("can open2");
  local $/ = undef;
  print $W $text;
  close $W;
  return <$R>;
}

기본 필터는 "nl"입니다. 필터를 변경하려면 "-p" 옵션과 일부 사용자 제공 명령을 사용하십시오.

codify -p="wc" file

또는

codify -p="sed -e 's@^@ ║ @; 1s@^@ ╓─\n@; \$s@\$@\n ╙─@'" file

마지막 필터는 다음을 출력합니다.

line A
line B
 ╓─
 ║ line X
 ║ line Y
 ║ line Z
 ╙─
line C
line D

업데이트 1 IPC::Open2 사용에는 크기 조정 문제가 있습니다. 버퍼 크기를 초과하면 차단될 수 있습니다. (내 컴퓨터에서 64K이면 파이프 버퍼 크기는 10_000 x "Y 행"에 해당합니다).

더 큰 것이 필요한 경우(10,000개의 "Y선"이 더 필요함):

(1) 설치 및 사용use Forks::Super 'open2';

(2) 또는 Pipelineit 함수를 다음으로 대체합니다.

sub pipeit{my($text,$pipe)=@_;
  open(F,">","/tmp/_$$");
  print F $text;
  close F;
  my $out = `$pipe < /tmp/_$$ `;
  unlink "/tmp/_$$";
  return $out;
}

관련 정보