inverse-match를 grep하고“before”와“after”라인을 제외하는 방법


26

다음과 같은 항목이있는 텍스트 파일을 고려하십시오.

aaa
bbb
ccc
ddd
eee
fff
ggg
hhh
iii

패턴 (예 :)이 주어지면 fff위의 파일을 grep하여 출력을 얻습니다.

all_lines except (pattern_matching_lines  U (B lines_before) U (A lines_after))

예를 들어, if B = 2A = 1이면 패턴 =의 출력은 다음과 fff같아야합니다.

aaa
bbb
ccc
hhh
iii

grep 또는 다른 명령 줄 도구를 사용하여이 작업을 수행하려면 어떻게해야합니까?


내가 시도 할 때 참고하십시오 :

grep -v 'fff'  -A1 -B2 file.txt

내가 원하는 것을 얻지 못합니다. 대신에 나는 얻는다 :

aaa
bbb
ccc
ddd
eee
fff
--
--
fff
ggg
hhh
iii

답변:


9

돈의 더 나은 대부분의 경우에있을 수 있습니다,하지만 단지의 경우 파일은 정말 큰, 당신이 얻을 수없는 sed많은 것을 스크립트 파일 처리 (스크립트의 약 5000 라인에서 일어날 수있는)을 , 여기에 일반에이다 sed:

sed -ne:t -e"/\n.*$match/D" \
    -e'$!N;//D;/'"$match/{" \
            -e"s/\n/&/$A;t" \
            -e'$q;bt' -e\}  \
    -e's/\n/&/'"$B;tP"      \
    -e'$!bt' -e:P  -e'P;D'

이것은 입력시 슬라이딩 윈도우 라고 불리는 것의 예입니다 . 미리보기 버퍼를 구축하여 작동합니다.$B인쇄하기 전에 -count 라인 .

그리고 실제로, 나는 아마도 이전의 요점을 분명히해야합니다 :이 솔루션과하지 않는 것에 대한 주요 성능 제한 기는 간격과 직접 관련이 있습니다. 이 솔루션은 더 큰 간격 크기로 느리게 진행 되는 반면 더 큰 간격 주파수로 느리게 진행 . 다시 말해서, 입력 파일이 매우 큰 경우에도 실제 간격 발생이 여전히 매우 드물다면 그의 해결책이 나올 것입니다. 그러나 간격 크기를 상대적으로 관리 할 수 ​​있고 자주 발생할 수있는 경우이 방법을 선택해야합니다.

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

  • 경우 $match앞에는 패턴 공간에서 발견된다 \newline, sed재귀합니다 D모든 elete \newline 그 선행을.
    • 나는 정리하고 있었다 $match 전에 패턴 공간을 완전히 있었지만 겹치기를 쉽게 처리하면 랜드 마크를 남기는 것이 훨씬 잘 작동하는 것 같습니다.
    • 나는 또한 시도 s/.*\n.*\($match\)/\1/하나 가서 루프를 피할 그것을 얻기 위해 노력하지만, 때 $A/$B큰의 Delete 루프는 상당히 빠른 증명한다.
  • 그런 다음 ewline 구분 기호가 N앞에 오는 입력 의 ext 행 \nD가져 와서 /\n.*$match/가장 최근에 사용한 정규 표현식 w /을 참조하여 다시 한 번 다시 시도하십시오 //.
  • 패턴 공간이 일치 하면 라인의 헤드 $match에서만 가능합니다. $match모든 $Before 라인이 지워졌습니다.
    • 그래서 우리는 $After를 반복하기 시작 합니다.
    • 이 루프의 각 실행 우리는에 시도 할 수 있습니다 s///에 대한 ubstitute &자체 $A번째 \n패턴 공간에서 ewline 문자를하고, 성공하면, t우리의 전체와 - 동부 표준시 우리를 분기 것이다 $A완전히 정상에서 위로 스크립트를 시작하는 스크립트 밖으로 - 따고 버퍼 다음 입력 라인이있는 경우
    • 는 IF t추정이 성공하지 우리는거야 b받는 목장 다시 :t입력의 또 다른 라인에 대한 연산 레이블과 같이 Recurse - 가능하면 이상 루프 시작 $match수집하는 동안 발생 $A따고가.
  • 우리가 과거를 얻을 경우 $match기능 루프, 우리는하려고합니다 pRINT $경우이 그것을 경우 마지막 줄을, 그리고 !시도하지 s///에 대한 ubstitute &자체 $B번째 \n패턴 공간에서 ewline 문자.
    • 우리도 이것을 할 것이고 t, 성공하면 :Print 라벨로 분기 할 것 입니다.
    • 그렇지 않으면 :top로 다시 분기 하여 버퍼에 추가 된 다른 입력 라인을 얻습니다.
  • 우리가 만드는 경우는 다음의 제품에 :P우리가 있습니다 RINT P다음 RINT D첫까지 elete \n패턴 공간에서 ewline와 남아있는 것과 상단에서 스크립트를 다시 실행하십시오.

이번에는 우리가하고 있다면 A=2 B=2 match=5; seq 5 | sed...

:Print 에서 첫 번째 반복을위한 패턴 공간 은 다음과 같습니다.

^1\n2\n3$

그리고 그것이 efore 버퍼를 sed모으는 방법 $B입니다. 그리고 수집 된 입력 뒤에sed 출력 $B카운트 라인을 인쇄합니다 . 앞의 예를 주어,이 수단은 것 RINT 출력 한 다음 elete 그와 스크립트의 상단에 같이있는 모습 패턴 공간을 다시 보내 :sedP1D

^2\n3$

... 그리고 스크립트 상단에서 Next 입력 라인이 검색되므로 다음 반복은 다음과 같습니다.

^2\n3\n4$

5입력에서 처음 나타나는 것을 발견 하면 패턴 공간은 실제로 다음과 같습니다.

^3\n4\n5$

그런 다음 Delete 루프가 시작되고 통과하면 다음과 같습니다.

^5$

그리고 Next 입력 라인을 당기면 sedEOF를 누르고 종료합니다. 그때 P까지는 라인 1과 라인 2 만 헹 구었습니다.

다음은 예제 실행입니다.

A=8 B=7 match='[24689]0'
seq 100 |
sed -ne:t -e"/\n.*$match/D" \
    -e'$!N;//D;/'"$match/{" \
            -e"s/\n/&/$A;t" \
            -e'$q;bt' -e\}  \
    -e's/\n/&/'"$B;tP"      \
    -e'$!bt' -e:P  -e'P;D'

인쇄 :

1
2
3
4
5
6
7
8
9
10
11
12
29
30
31
32
49
50
51
52
69
70
71
72
99
100

실제로 큰 파일로 작업하고 있으며이 솔루션보다 응답 속도가 현저히 느립니다. 나는 처음에 내 대답을 바꾸는 것을 주저했지만 속도 차이는 상당히 눈에.니다.
Amelio Vazquez-Reina

4
@Amelio-이것은 모든 크기의 스트림에서 작동하며 파일을 읽지 않아도 작동합니다. 가장 큰 성능 요소는 $A및 / 또는 의 크기입니다 $B. 숫자가 클수록 속도가 느려지지만 합리적으로 크게 만들 수 있습니다.
mikeserv

1
@ AmelioVazquez-Reina-오래된 것을 사용하는 경우 더 좋습니다.
mikeserv

11

당신은 사용할 수 있습니다 gnu grep-A하고 -B제외하지만, 추가 할 파일의 정확히 부분 인쇄 -n도 할 줄 번호를 인쇄 한 후 출력 형식 및 명령 스크립트로 전달하는 스위치를 sed그 라인을 삭제를 :

grep -n -A1 -B2 PATTERN infile | \
sed -n 's/^\([0-9]\{1,\}\).*/\1d/p' | \
sed -f - infile

이것은 또한 다음을 grep통해 전달 된 패턴 파일에서도 작동합니다 -f.

grep -n -A1 -B2 -f patterns infile | \
sed -n 's/^\([0-9]\{1,\}\).*/\1d/p' | \
sed -f - infile

입력이 몇 개만 있으면 입력 할 가치가 없지만 2,6d대신 3 개 이상의 연속적인 줄 번호를 범위로 축소하면 약간 최적화 될 수 있다고 생각 2d;3d;4d;5d;6d합니다.


: 라인 순서를 보존하고 가장 가능성이 느린하지 않는 다른 방법
으로 comm:

comm -13 <(grep PATTERN -A1 -B2 <(nl -ba -nrz -s: infile) | sort) \
<(nl -ba -nrz -s: infile | sort) | cut -d: -f2-

comm정렬 된 입력이 필요합니다. 즉, 파일이 이미 정렬되지 않은 경우 최종 출력에서 ​​줄 순서가 유지되지 않으므로 정렬 nl하기 전에 줄 번호 comm -13매기고 두 번째 FILE에 고유 한 줄만 인쇄 한 다음 cut추가 된 부분 을 제거합니다 nl(즉, 첫 번째 필드와 구분 기호 :)
join다음 과 같습니다.

join -t: -j1 -v1 <(nl -ba -nrz -s:  infile | sort) \
<(grep PATTERN -A1 -B2 <(nl -ba -nrz -s:  infile) | sort) | cut -d: -f2-

고마워 돈! 빠른 질문, 당신은과 솔루션이 기대 comm와 원래보다 빠르게로 sed하고 grep?
Amelio Vazquez-Reina

1
@ AmelioVazquez-Reina- 파일을 한 번만 처리하는 Mike의 솔루션과 달리 입력 파일을 두 번 읽거나 정렬하는 것도 아닙니다.
don_crissti

9

마음에 들지 않으면 vim:

$ export PAT=fff A=1 B=2
$ vim -Nes "+g/${PAT}/.-${B},.+${A}d" '+w !tee' '+q!' foo
aaa
bbb
ccc
hhh
iii
  • -Nes호환되지 않는 자동 전 모드를 켭니다. 스크립팅에 유용합니다.
  • +{command}vim에게 {command}파일 에서 실행하도록 지시 하십시오.
  • g/${PAT}/-일치하는 모든 줄에 /fff/. 패턴에 그런 식으로 취급하지 않으려는 정규 표현식 특수 문자가 패턴에 포함되어 있으면 까다로워집니다.
  • .-${B} -이 위의 한 줄부터
  • .+${A}-이 줄 아래 2 줄 ( :he cmdline-ranges이 두 항목 참조 )
  • d -줄을 삭제하십시오.
  • +w !tee 그런 다음 표준 출력에 씁니다.
  • +q! 변경 사항을 저장하지 않고 종료합니다.

변수를 건너 뛰고 패턴과 숫자를 직접 사용할 수 있습니다. 나는 명확성을 목적으로 사용했습니다.


3

어떻습니까 (GNU grepbash) :

$ grep -vFf - file.txt < <(grep -B2 -A1 'fff' file.txt)
aaa
bbb
ccc
hhh
iii

여기서는 버릴 행을 찾은 grep -B2 -A1 'fff' file.txt다음이를 입력 파일로 사용하여이를 버리는 원하는 행을 찾습니다.


흠, 이것은 내 컴퓨터에서 아무것도 출력하지 않습니다 (OS X)
Amelio Vazquez-Reina

@ AmelioVazquez-Reina 죄송합니다. .i 전에 OS를 몰랐습니다.
어쨌든

2
이것은 kos입력 파일에 중복 행이 있고 일부는 범위를 벗어나고 다른 일부는 해당 범위 내에있는 것처럼 (지금 삭제 된) 솔루션과 동일한 문제 가 있습니다. 이는 모두 삭제됩니다. 또한 여러 번의 pattern 발생으로 --입력 파일에 범위 와 같은 줄이 있으면 하나 이상의 줄이 패턴과 일치 할 때 구분 기호 --grep출력에 표시 되므로 줄이 삭제됩니다 (후자는 가능성이 높지 않지만 가치가 있습니다) 나는 추측한다).
don_crissti

@don_crissti Thanks..you 내가 OP의 예를 복용하고 있습니다 right..although 것은 literally..i 오전거야 찾을 경우 누군가에두고 그 도움이 나중에 ..
heemayl

1

임시 파일을 사용하여 충분한 결과를 얻을 수 있습니다.

my_file=file.txt #or =$1 if in a script

#create a file with all the lines to discard, numbered
grep -n -B1 -A5 TBD "$my_file" |cut -d\  -f1|tr -d ':-'|sort > /tmp/___"$my_file"_unpair

#number all the lines
nl -nln "$my_file"|cut -d\  -f1|tr -d ':-'|sort >  /tmp/___"$my_file"_all

#join the two, creating a file with the numbers of all the lines to keep
#i.e. of those _not_ found in the "unpair" file
join -v2  /tmp/___"$my_file"_unpair /tmp/___"$my_file"_all|sort -n > /tmp/___"$my_file"_lines_to_keep

#eventually use these line numbers to extract lines from the original file
nl -nln $my_file|join - /tmp/___"$my_file"_lines_to_keep |cut -d\  -f2- > "$my_file"_clean

프로세스에서 들여 쓰기를 잃어 버릴 수 있기 때문에 결과는 충분 하지만 xml 또는 들여 쓰기에 영향을받지 않는 파일이면 문제가되지 않습니다. 이 스크립트는 램 드라이브를 사용하므로 임시 파일을 쓰고 읽는 것이 메모리에서 작업하는 것만 큼 빠릅니다.


1

또한 주어진 마커보다 일부 라인을 제외하려면 다음을 사용할 수 있습니다.

awk -v nlines=2 '/Exception/ {for (i=0; i<nlines; i++) {getline}; next} 1'

( https://stackoverflow.com/a/1492538의 Glenn Jackman )

일부 명령을 파이핑하면 이전 / 이후 동작을 얻을 수 있습니다.

awk -v nlines_after=5 '/EXCEPTION/ {for (i=0; i<nlines_after; i++) {getline};print "EXCEPTION" ;next} 1' filename.txt|\
tac|\
awk -v nlines_before=1 '/EXCEPTION/ {for (i=0; i<nlines_before; i++) {getline}; next} 1'|\
tac

1
훌륭하게, awk반전 된 파일을 사용 하여 라인에 영향을 미치고 결과를 되돌릴 것을 의미 할 때 다음 라인을 처리하십시오.
karmakaze

0

이를 달성하는 한 가지 방법은 아마도 가장 쉬운 방법은 변수를 만들고 다음을 수행하는 것입니다.

grep -v "$(grep "fff" -A1 -B2 file.txt)" file.txt

이렇게하면 여전히 구조가 있습니다. 그리고 하나의 라이너에서 제거하려는 것을 쉽게 볼 수 있습니다.

$ grep -v "$(grep "fff" -A1 -B2 file.txt)" file.txt
aaa
bbb
ccc
hhh
iii

heemayl과 동일한 솔루션 및 don_crissti에 의해 설명 된 것과 동일한 문제 : 이것은 입력 파일에 중복 행이 있고 일부는 범위를 벗어나고 다른 일부는 그 범위 내에있는 것처럼 kos의 (현재 삭제 된) 솔루션과 동일한 문제가 있습니다. 이렇게하면 모두 삭제됩니다. 또한 패턴이 여러 번 발생하는 경우 입력 파일 (범위 외부)에 다음과 같은 줄이 있으면 구분 기호가 있기 때문에 삭제합니다. 하나 이상의 줄이 패턴과 일치 할 때 grep의 출력에 나타납니다 (후자는 높음). 가능성은 없지만 언급 할 가치가 있습니다.)
Bodo Thiesen

0

일치하는 항목이 하나만있는 경우 :

A=1; B=2; n=$(grep -n 'fff' file.txt | cut -d: -f1)
head -n $((n-B-1)) file.txt ; tail -n +$((n+A+1)) file.txt

그렇지 않으면 (awk) :

# -vA=a -vB=b -vpattern=pat must be provided
BEGIN{

    # add file again. assume single file
    ARGV[ARGC]=ARGV[ARGC-1]
    ++ARGC
}

# the same as grep -An -Bn pattern
FNR==NR && $0 ~ pattern{
    for (i = 0; i <= B; ++i)
        a[NR-i]++
    for (i = 1; i <= A; ++i)
        a[NR+i]++
}

FNR!=NR && !(FNR in a)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.