파일의 특정 섹션 필터링 또는 파이프


14

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

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

X, Y, Z 줄이 일부 명령 ( nl예 :)을 통해 필터링되도록이 파일에 변환을 적용하고 싶지만 나머지 줄은 변경되지 않고 통과합니다. 공지 있음 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

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

이 모든 것에 대해 잘못 가고 있습니까? 이것은 꽤 일반적인 문제처럼 보입니다. 나는 이것을 해결할 간단한 파이프 라인이 있어야한다고 생각합니다.


nlstate축적 할 필요가 없습니다 . 봐에서 nl -d귀하의 확인 man/ info에 대한 정보는 페이지 nl섹션 구분 기호를 .
mikeserv

nl은 예일뿐입니다. 제 경우에는 nl 대신 사용자 정의 스크립트를 실행하고 있습니다.
James Scriven

이 경우 스크립트가 수행중인 작업을 명확히하십시오.
terdon

나는 nl예제 필터 로만 사용 하고 있다는 질문에 명확히했다 . 필터가 정확히 무엇을하고 있는지에 대한 세부 사항을 glossing하여 문제를 단순화 할 것이라고 생각했지만 아마도 더 혼란 스러울 것입니다. 사실, 나는 집에서 만든 정적 블로그 생성기를 위해 코드 하이 라이터를 통해 하위 섹션을 필터링하고 있습니다. 지금은 gnu 사용 source-highlight하고 있지만 변경 될 수 있으며 포맷터와 같은 더 많은 필터를 추가 할 수 있습니다.
James Scriven

답변:


7

나는 당신에게 동의 할 것 입니다 -아마도 일반적인 문제 일 것 입니다 . 그러나 일부 공용 유틸리티에는 처리 기능이 있습니다.


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기본 -delimiter 에 따라 출력이 왜곡 될 수 있음을 \:깨달았을 때 더주의를 기울이고 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 -wcount + -separator len) * 공백을 삽입하여 들여 쓰기와 항상 일치합니다 . 이를 통해 번호가 매겨지지 않은 콘텐츠를 번호가 매겨진 콘텐츠와 비교하여 적은 노력으로 정확하게 재생할 수 있습니다. 당신이 고려해야 할 때 nl당신을 위해 논리적 인 부분으로 입력을 나누고, 그 것이다 당신은 임의 삽입 할 수있는 -s다음의 출력을 처리하기 위해 아주 쉽게 얻을 수, 각 라인이 번호의 머리에 trings를 :

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 sede일치하는 항목에 따라 임의의 셸 명령을 실행할 수 있습니다.

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

sed는 대체를 성공적으로 통과 하고 abel에 다시 목장을 T중지 하기에 충분할 때까지 패턴 공간에서 입력을 수집합니다 . 그렇게 할 때 , 나머지 모든 패턴 공간에 대한 여기 문서 로 표현 된 입력 값으로 xecutes 됩니다 .b:lenl<<

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

  1. /^@@.*start$/!b
    • 경우 ^전체 라인은 $않습니다 !하지 /일치 /위의 패턴을, 다음이되는 b스크립트 밖으로 ranched 및 autoprinted - 그래서이 시점에서 우리는 패턴으로 시작 라인의 일련의 작업에.
  2. s//nl <<\\@@/
    • s//필드 /는 마지막으로 sed시도한 주소를 나타냅니다. 따라서이 명령 @@.*startnl <<\\@@대신 전체 행을 대체합니다.
  3. :l;N
    • :명령은 분기 레이블을 정의합니다. 여기서 :label 이라는 이름을 설정했습니다 . NEXT 명령은 다음 패턴 공간 입력의 다음 줄에 추가 \newline 캐릭터. 이것은 패턴 공간 \n에서 ewline 을 얻는 몇 가지 방법 중 하나입니다 sed. \newline 문자는 sed잠시 동안 작업을 수행 한 사람에게 확실한 구분 기호 입니다.
  4. s/\(\n@@\)[^\n]*end$/\1/
    • s///ubstitution은 시작 후에 만 났으며 엔드 라인 이 처음 나오는 경우에만 성공할 수 있습니다 . 마지막 \newline 바로 뒤에 패턴 공간의 @@.*end$을 표시 하는 패턴 공간에서만 작동 합니다. 작동하면 일치하는 전체 문자열을 \1첫 번째 \(그룹 \)또는로 바꿉니다 \n@@.
  5. Tl
    • T레이블로 추정 명령 지점 (제공되는 경우) 성공적으로 대체가 입력 라인 패턴의 공간으로 끌어 된 마지막 시간 이후 발생하지 않은 경우 (나는 / 승처럼 N) . 즉 \n, 끝 구분 기호와 일치하지 않는 패턴 공간에 ewline이 추가 될 때마다 Test 명령이 실패하고 :label로 분기 sed되어 N내선 을 당겨 성공할 때까지 반복됩니다.
  6. e

    • 최종 경기를 위해 대체에 성공하고 스크립트가 실패에 대한 분기 다시 수행하지 않을 경우 T추정을 sede명령을 xecute 그 l같은 ooks :

      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와 일치 하면 블록이 즉시 실행됩니다. 그 밖에, 지문 을 하고 라고합니다.doprintf$linereadsed

sed것이다 p는 만날 때까지 모든 라인을 RINT 시작 이 때 - 마커를 q완전히 입력을 UITS. -unbuffered 스위치는 GNU에 대한 필요 sed가 탐욕 그렇지 않으면 오히려 버퍼 만 할 수 있기 때문에 - 사양에 따라 - 다른 POSIX sed의 특별한 고려하지 않고 작동합니다 - 너무 오래 같은 <infile일반 파일입니다.

첫 번째 sed q신호가 발생하면 쉘은 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에 대한,하지만 sed, 당신은 CLI 스위치 필요)read본질적으로 매우 느리기 때문에 항상 의지 할 수 있어야합니다 . 이것이 위의 루프가 입력 블록 당 한 번만 호출하는 이유입니다.


두 번째 sed예제를 테스트 했지만 작동하지만 실제로 구문을 파악하는 데 문제가 있습니다. (나의 sed는 꽤 약하고 보통 s / findthis / replacethis / g로 제한됩니다. 앉아서 sed를 실제로 이해하기 위해 노력해야합니다.)
James Scriven

@JamesScriven-방금 더 잘 설명하기 위해 편집했습니다. 도움이되지 않는 경우 알려주십시오. 또한 명령을 많이 변경했습니다. 이제 작고 합리적인 조각입니다.
mikeserv

4

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.


예, vim은 굉장합니다! 그러나이 경우 스크립트 가능한 솔루션을 찾고 있습니다.
James Scriven

@JamesScriven, vim을 말하는 사람은 불충분하게 결정되지 않습니다. 먼저 프로젝트 디렉토리를 만들고 해당 디렉토리에 vim의 모든 시작 파일을 홈 디렉토리에서 복사하십시오 (ln -s는 수정하려는 .vimrc와 노이즈로 가득 찬 .viminfo를 제외하고는 정상적으로 작동합니다). 작업을 수행 할 함수 정의를 새 .vimrc 파일에 추가 한 다음 vim을 호출하십시오 HOME=$(pwd) vim -c 'call Mf()' f. xargs를 사용하는 경우 전용 xserver에서 gvim을 사용하여 tty가 손상되지 않도록 할 수 있습니다 (vnc는 비디오 카드에 독립적이며 모니터링 할 수 있음).
hildred

@hildred Hmmm ... [XSendEvent] ( tronche.com/gui/x/xlib/event-handling/XSendEvent.html )를 사용하여 마우스 클릭을 vim으로 시뮬레이션 할 수 없었 습니까?
James Scriven

2

내가 생각할 수있는 가장 간단한 수정은 사용하지 않고 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

고마워요 나는 번호 매기기 행의 특정 예보다는 입력의 하위 섹션을 필터링하는 일반적인 솔루션을 찾고 있음을 분명히하기 위해 질문을 업데이트했습니다. 아마도 더 나은 예제 명령은 "tac"(역행) 일 것입니다
James Scriven

2

전체 코드 블록을 단일 프로세스 인스턴스로 전송하는 것이 목표 인 경우 코드 블록의 끝에 도달 할 때까지 라인을 누적하고 배관을 지연시킬 수 있습니다.

#!/bin/bash

acc=""

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    acc=""
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    # Act on entire block of code
    echo "${acc:1}" | nl  # Chops off first leading new-line character using ${VAR:1}
  elif [[ $active = true ]]
  then
    acc=$( printf "%s\n%s" "$acc" "$line" )
  else
    # output
    echo $line
  fi
done

테스트 파일을 세 번 반복하는 입력 파일에 대해 다음을 생성합니다.

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

코드 블록을 사용하여 다른 작업을 수행하려면 (예 : 역순으로 번호를 매기려면) 다른 것을 통해 파이프하십시오 echo -E "${acc:1}" | tac | nl. 결과:

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

또는 단어 수 echo -E "${acc:1}" | wc:

line A
line B
      3       6      21
line C
line D

2

편집 은 사용자 제공 필터를 정의하는 옵션을 추가했습니다

#!/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 "line Y"인 경우 파이프 버퍼 크기).

더 큰 것들이 필요한 경우 (10000 "line Y"가 더 필요합니까) :

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

(2) 다음과 같이 기능 피펫을 대체하십시오.

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

정말 멋지다. 트릭은 한 줄씩 (재정복 $/s플래그로) 처리하지 않고 e플래그를 사용 하여 외부 명령을 실제로 호출하는 것입니다. 나는 두 번째 (아스키 아트) 예제를 정말 좋아합니다!
James Scriven

내가 알아 차린 것은 이것이 하위 섹션에서 2 천 라인 이상으로 확장되지 않는 것입니다. 나는 이것이 하위 섹션을 하나의 큰 텍스트 블록으로 취급하는 것과 관련이 있다고 생각합니다.
James Scriven

감사. 예 :`/ e` = eval; /s= ( "."의미 (.|\n)); $/레지스터 구분 기호를 재정의합니다.
JJoao

@ JamesScriven, 당신이 맞습니다 (파이프가 막혔습니다). 무슨 일이 일어나고 있는지 테스트하겠습니다.
JJoao

@JamesScriven, 내 업데이트를 참조하십시오 ...
JJoao

1

그것은 awk의 직업입니다.

#!/usr/bin/awk -f
$0 == "@@inline-code-start" {pipe = 1; next}
$0 == "@@inline-code-end" {pipe = 0; close("nl"); next}
pipe {print | "nl"}
!pipe {print}

스크립트가 시작 마커를 볼 때로 파이프를 시작해야한다는 것을 알려줍니다 nl. 때 pipe변수 (제로)에 해당하는 출력이된다 파이프로 nl명령; 변수가 false (설정되지 않은 또는 0)이면 출력이 직접 인쇄됩니다. 파이프 명령은 파이프 명령이 각 명령 문자열에 대해 처음 발생할 때 분기됩니다. 동일한 스트링을 갖는 파이프 운영자의 후속 평가는 기존 파이프를 재사용합니다. 다른 문자열 값은 다른 파이프를 만듭니다. 이 close함수는 주어진 명령 문자열에 대한 파이프를 닫습니다.


이것은 기본적으로 명명 된 파이프를 사용하는 쉘 스크립트와 동일한 논리이지만 철자를 쉽게 작성하고 가까운 논리를 올바르게 수행 할 수 있습니다. nl명령을 끝내고 버퍼를 플러시 하려면 적절한 시간에 파이프를 닫아야합니다 . 스크립트는 실제로 파이프를 너무 일찍 닫습니다 echo $line >myfifo. 첫 번째 실행이 완료 되면 파이프가 닫힙니다 . 그러나이 nl명령은 다음에 스크립트가 실행되기 전에 타임 슬라이스를 얻는 경우에만 파일의 끝을 확인합니다 echo $line >myfifo. 많은 양의 데이터가 있거나에 sleep 1쓴 후 추가 myfifo하는 nl경우 첫 번째 줄 또는 첫 번째 빠른 묶음 만 처리 한다는 것을 알 수 있습니다 . 입력의 끝이 보이므로 종료됩니다.

구조물을 사용하면 더 이상 필요하지 않을 때까지 파이프를 열어 두어야합니다. 파이프로 단일 출력 리디렉션이 필요합니다.

nl <myfifo &
exec 3>&1
while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    exec >myfifo
  elif [[ $line == @@inline-code-end* ]]
  then
    exec >&3
  else
    printf '%s\n' "$line"
  fi
done

(또한 올바른 인용 부호를 추가 할 기회를 얻었습니다. 왜 쉘 스크립트가 공백이나 다른 특수 문자에서 질식합니까? )

그렇게하면 명명 된 파이프 대신 파이프 라인을 사용할 수도 있습니다.

while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    while IFS= read -r line && [[ $line != @@inline-code-end* ]] do
      printf '%s\n' "$line"
    done | nl
  else
    printf '%s\n' "$line"
  fi
done

당신의 awk 솔루션은 정말 좋습니다! 나는 그것이 가장 간결한 (아직 읽을 수있는) 해결책이라고 생각합니다. 파이프를 nl로 재사용하는 awk의 동작이 보장됩니까, 아니면 "이봐 요, 당신은 지금 충분히 파이프했습니다. "파이프 라인"솔루션도 정말 좋습니다. 약간 혼란 스러울 수 있다고 생각했기 때문에 임베디드 while 루프를 사용하여 접근 방식을 원래 할인했습니다. 그러나 당신이 가진 것이 훌륭하다고 생각합니다. 앞에 세미콜론이 없습니다 do. (작은 편집을 위해 여기에 담당자가 없습니다.)
James Scriven

1
... 명명 된 파이프 솔루션을 작동시킬 수 없었습니다. nl로 파이프 된 섹션이 때때로 완전히 손실되도록 경쟁 조건이있는 것 같습니다. 또한 두 번째 @@ inline-code-start / end 섹션이 있으면 항상 손실됩니다.
James Scriven

0

좋아, 먼저; 파일 섹션의 줄 번호를 매길 수있는 방법을 찾고 있지 않다는 것을 알고 있습니다. 필터 이외의 필터에 대한 실제 예를 제공하지 않았으므로 필터 nl

tr "[[:lower:]]" "[[:upper:]]"

즉, 텍스트를 모두 대문자로 변환하십시오. 그래서, 입력

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

당신은 출력을 원한다

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

다음은 솔루션의 첫 번째 근사치입니다.

#!/bin/sh
> file0
> file1
active=0
nl -ba "$@" | while IFS= read -r line
do
        case "$line" in
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-start")
                active=1
                ;;
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-end")
                active=0
                ;;
            (*)
                printf "%s\n" "$line" >> file$active
        esac
done
(cat file0; tr "[[:lower:]]" "[[:upper:]]" < file1) | sort | sed 's/^[ 0-9]\{6\}        //'

여기서 @@문자열 앞의 공백 과 마지막 행의 끝 부근은 탭입니다. 내가 사용하고 있습니다 nl 내 자신의 목적을 위해 . (물론 나는 당신의 문제 를 해결하기 위해 노력하고 있지만 라인 번호 출력을 제공하지는 않습니다.)

이것은 입력 라인에 번호를 매기므로 섹션 마커에서 분리하여 나중에 다시 결합하는 방법을 알 수 있습니다. 루프의 본문은 섹션 마커에 줄 번호가 있다는 사실을 고려하여 첫 번째 시도를 기반으로합니다. 그것은 두 개의 파일로 따로 입력을 나누기 : file0(비활성, 아닌 부분에) 및 file1(활성; 에서 섹션). 위의 입력에서 다음과 같이 나타납니다.

file0:
     1  line A
     2  line B
     8  line C
     9  line D

file1:
     4  line X
     5  line Y
     6  line Z

그런 다음 대문자 필터를 통해 ( 모든 섹션 라인 file1의 연결) 실행 합니다. 필터링되지 않은 섹션 외부 라인과 결합하십시오. 정렬하여 원래 순서대로 되돌립니다. 그런 다음 줄 번호를 제거하십시오. 이것은 내 답변의 상단 근처에 출력을 생성합니다.

이것은 필터가 줄 번호 만 남겨 둔다고 가정합니다. 그렇지 않으면 (예를 들어, 줄의 시작 부분에 문자를 삽입하거나 삭제하는 경우)이 일반적인 접근 방식은 여전히 ​​사용될 수 있지만 약간 까다로운 코딩이 필요하다고 생각합니다.


nl이미 대부분의 작업을 수행 -d합니다. 이것이 그것의 elimiter 옵션입니다.
mikeserv

0

sed를 사용하여 구분되지 않은 행의 청크를 출력하고 구분 된 행의 청크를 필터 프로그램에 공급하는 쉘 스크립트 :

#!/bin/bash

usage(){
    echo "  usage: $0 <input file>"
}

# Check input file
if [ ! -f "$1" ]; then
    usage
    exit 1
fi

# Program to use for filtering
# e.g. FILTER='tr X -'
FILTER='./filter.sh'

# Generate arrays with starting/ending line numbers of demarcators
startposs=($(grep -n '^@@inline-code-start$' "$1" | cut -d: -f1))
endposs=($(grep -n '^@@inline-code-end$' "$1" | cut -d: -f1))

nums=${#startposs[*]}
nume=${#endposs[*]}

# Verify both line number arrays have the same number of elements
if (($nums != $nume)); then
    echo "Tag mismatch"
    exit 2
fi

lastline=1
i=0
while ((i < nums)); do
    # Exclude lines with code demarcators
    sprev=$((${startposs[$i]} - 1))
    snext=$((${startposs[$i]} + 1))
    eprev=$((${endposs[$i]} - 1))

    # Don't run this bit if the first demarcator is on the first line
    if ((sprev > 1)); then
        # Output lines leading up to start demarcator
        sed -n "${lastline},${sprev} p" "$1"
    fi

    # Filter lines between demarcators
    sed -n "${snext},${eprev} p" "$1" | $FILTER

    lastline=$((${endposs[$i]} + 1))
    let i++
done

# Output lines (if any) following last demarcator
sed -n "${lastline},$ p" "$1"

이 스크립트를 detagger.sh라는 파일에 작성하고 다음과 같이 사용했습니다 ./detagger.sh infile.txt. 질문의 필터링 기능을 모방하기 위해 별도의 filter.sh 파일을 만들었습니다.

#!/bin/bash
awk '{ print "\t" NR " " $0}'

그러나 필터링 작업은 코드에서 변경 될 수 있습니다.

번호 매기기 라인과 같은 작업에 추가 / 내부 계산이 필요하지 않도록 일반 솔루션 의 아이디어를 따르려고했습니다 . 이 스크립트는 몇 가지 기본적인 검사를 수행하여 구분자 태그가 쌍으로되어 있고 중첩 된 태그를 전혀 처리하지 않는지 확인합니다.


-1

모든 훌륭한 아이디어에 감사드립니다. 임시 파일의 하위 섹션을 추적하고 한 번에 외부 명령으로 파이프하여 내 솔루션을 생각해 냈습니다. 이것은 Supr이 제안한 것과 매우 유사합니다 (그러나 temp 파일 대신 쉘 변수 사용). 또한, 나는 sed를 사용하는 아이디어를 정말로 좋아하지만,이 경우의 구문은 나에게 약간 위로 보입니다.

내 해결책 :

( nl예제 필터로 사용합니다)

#!/usr/bin/bash

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    tmpfile=$(mktemp)
    trap "rm -f $tmpfile" EXIT
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    <$tmpfile nl
    rm $tmpfile
  elif [[ $active = true ]]
  then
    echo $line >> $tmpfile
  else
    echo $line
  fi
done

나는 임시 파일 관리를 다루지 않고 싶지만 쉘 변수는 크기 제한이 낮을 수 있다는 것을 이해하고 임시 파일처럼 작동하는 bash 구성을 알지 못하지만 자동으로 사라집니다. 프로세스가 끝납니다.


나는 라인, 마이크의 테스트 데이터를 사용하여, 예를 들어, 그래서, 당신은 "줄에 걸쳐 축적 상태"를 수 있기를 원 생각 M, N그리고 O번호 것 4, 5하고 6. 그렇지 않습니다. 내 대답은 (현재의 화신에서 nl필터로 작동하지 않는다는 사실 외에도 ). 경우 대답은 당신이 원하는 출력을주고있다, 당신은 "라인에 걸쳐 축적 상태"에 의해 무엇을 의미 했습니까? 당신은 당신은 단지 상태를 유지하고 싶다고하셨습니까 을 통해 아니라, 각 부분 사이의 섹션 (에서)? (당신은 왜 당신의 질문에 다중 섹션 예제를 넣지 않았습니까?)
Scott

@Scott – 사용 nl -p합니다 M,N,O==4,5,6.
mikeserv

나는 다른 해석이 똑같이 재미 있다고 생각하지만 하위 섹션 내에서 상태를 유지하는 데에만 관심이 있음을 분명히하기 위해 질문을 업데이트했습니다.
James Scriven
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.