grep-inverse-matchと「前」と「後」の行を除外する方法


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))

たとえば、B = 2およびの場合、A = 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'

これは、入力時のスライディングウィンドウと呼ばれるものの例です。これは、何かを印刷しようとする前に、-count行の先読みバッファーを$B作成することで機能します。

実際、おそらく、以前のポイントを明確にする必要があります。このソリューションの主なパフォーマンスリミッターは、間隔に直接関連します。このソリューションは、間隔サイズが大きいと遅くなります、間隔周波数が大きいとドンは遅くなります。言い換えれば、入力ファイルが非常に大きい場合でも、実際の間隔の発生が依然として非常にまれである場合、彼の解決策がおそらく道です。ただし、間隔サイズが比較的管理しやすく、頻繁に発生する可能性がある場合は、これが選択すべきソリューションです。

ワークフローは次のとおりです。

  • 場合$matchによって先行パターンスペースで発見され\newline、sed再帰的になりD、すべてのelete \newlineその先行して。
    • $match以前はパターンのスペースを完全に消去していましたが、オーバーラップを簡単に処理するために、ランドマークを残しておくとはるかにうまくいくようです。
    • またs/.*\n.*\($match\)/\1/、一度に取得してループをかわそうとしました$A/$Bが、大きい場合は、Dエレテループの方がかなり高速です。
  • 次にN\newlineデリミタが前にある入力のext行をD取得し、/\n.*$match/最後に使用した正規表現w /を参照してもう一度エレテを選択しようとします//
  • パターンスペースが一致する場合、行の先頭で$matchのみ一致することができます。以前の行は$matchすべて$Bクリアされています。
    • そこで、$Afterをループし始めます。
    • このループの各実行は、我々がしようとしますs///ためubstitute &自体$A番目の\nパターンスペースでewline文字を、そして、成功した場合、t当社全体および- ESTたちを分岐する$A完全に上からかけてスクリプトを起動するためのスクリプトの実行- FTERバッファを次の入力行がある場合。
    • testが成功しなかった場合、opラベルにb戻って:t別の入力行を再帰します。fterの$match収集中にループオーバーが発生する可能性があり$Aます。
  • 私たちが乗り越える場合$match、機能ループ、そして我々はしようとするでしょうpRINT $場合、これはそれをされた場合、最後の行をし、!しようとしないs///ためubstitute &自体$B番目の\nパターンスペースでewline文字を。
    • 我々はよt、それが成功した場合、EST、この、あまりにも、私たちはに分岐するでしょう:PRINTラベル。
    • そうでない場合は、:topに戻って、バッファーに追加された別の入力行を取得します。
  • 我々はそれがために作る場合:PRINT我々はよPその後、RINT D最初までelete \nパターンスペースにewlineとどのような遺跡でトップからスクリプトを再実行します。

それで、今回は A=2 B=2 match=5; seq 5 | sed...

:Print での最初の反復のパターンスペースは次のようになります。

^1\n2\n3$

そして、それsedはその$B前のバッファを収集する方法です。そして、収集した入力の後ろsed出力$B-count行に出力します。この前の例与えられた、ということでしょうRINT を出力し、[ eleteこととのように見えるスクリプトパターンスペースの上部に送り返すには:sedP1D

^2\n3$

...そして、スクリプトの上部でNext入力行が取得されるため、次の反復は次のようになります。

^2\n3\n4$

したがって5、入力で最初の出現を見つけると、パターン空間は実際には次のようになります。

^3\n4\n5$

次に、Dエレテループが開始され、終了すると次のようになります。

^5$

そして、Next入力行がプルされるsedと、EOFにヒットして終了します。その時までに、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

私は実際に巨大なファイルで作業していますが、答えはこのソリューションよりも著しく遅かったです。最初は、受け入れられた答えを変更することにwasしていましたが、速度の違いは非常にはっきりしています。
アメリオバスケスレイナ

4
@Amelio-これは任意のサイズのストリームで機能し、機能するためにファイルを読み取る必要はありません。最大のパフォーマンス要因は、$Aおよび/またはのサイズです$B。これらの数値を大きくすると、遅くなりますが、かなり大きくすることができます。
mikeserv

1
@ AmelioVazquez-Reina-古いものを使用している場合、これは良いと思います。
mikeserv

11

gnu grepwith -Aおよびwith を使用して、-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

3つ以上の連続する行番号を、たとえば...の2,6d代わりに持つように範囲に折りたたむと、これはわずかに最適化できると思います2d;3d;4d;5d;6d


行の順序を保持せず、最も遅い可能性が高い他の方法:
with 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と、元のものよりも速くするsedgrep
アメリオバスケスレイナ

1
@ AmelioVazquez-Reina- ファイルを1回だけ処理するMikeのソリューションとは対照的に、入力ファイルを2回読み取ります(さらに、並べ替えも行います)。
-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互換性のないサイレントexモードをオンにします。スクリプトの作成に役立ちます。
  • +{command}{command}ファイルで実行するようにvimに指示します。
  • g/${PAT}/-すべての行の一致/fff/。パターンに正規表現の特殊文字が含まれていて、その方法で処理するつもりがなかった場合、これは扱いにくくなります。
  • .-${B} -この行の上の1行から
  • .+${A}-この行の2行下(:he cmdline-rangesこれら2行を参照)
  • d -行を削除します。
  • +w !tee その後、標準出力に書き込みます。
  • +q! 変更を保存せずに終了します。

変数をスキップして、パターンと数字を直接使用できます。目的を明確にするためだけに使用しました。


3

方法は(GNU grepとを使用してbash):

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

ここでは、によって破棄される行を見つけ、grep -B2 -A1 'fff' file.txtこれを入力ファイルとして使用して、これらを破棄する目的の行を見つけます。


うーん、これは私のマシンでは何も出力しません(OS X)
アメリオバスケスレイナ

@ AmelioVazquez-レイナ私はUbuntuの上でこれをテストしてbefore..anyway that..iは、お使いのOSを知らなかったについては申し訳ありません...
heemayl

2
これはkos、入力ファイルに重複行があり、それらの一部が範囲外にあり、その他がその範囲内にあるため、すべての(現在削除されている)ソリューションと同じ問題があります。また、patternが複数出現する場合--、入力ファイル(範囲外)のような行がある場合、複数行がpatternに一致するときに区切り文字--grep出力に表示されるため、それらが削除されます(後者はほとんどありませんが、価値があります)私が推測する言及)。
-don_crissti

@don_crisstiありがとう。あなたは正しい。.私は文字通りOPの例を取っ​​ていたが..誰かが後でそれが役立つと思う場合、私はそれを残すつもりです..
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またはインデントに依存しないファイルの場合は問題になりません。このスクリプトはRAMドライブを使用するため、これらの一時ファイルの書き込みと読み取りは、メモリでの作業と同じくらい高速です。


1

また、特定のマーカーの前のいくつかの行を除外する場合は、次を使用できます。

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

/programming//a/1492538のグレンジャックマン)

いくつかのコマンドをパイプすることで、ビフォーアフター/アフタービヘイビアを取得できます。

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逆のファイルで使用して、結果の前に行に影響を与え、結果を再び逆にする場合に、後続の行を処理します。
カルマカゼ

0

これを実現する1つの方法として、おそらく最も簡単な方法は、変数を作成して次のことを行うことです。

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

このように、あなたはまだあなたの構造を持っています。また、1つのライナーから、何を除去しようとしているかを簡単に確認できます。

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

heemaylと同じ解決策、およびdon_crisstiで説明されているのと同じ問題:これは、入力ファイルに重複行があり、それらの一部が範囲外にあり、他がその範囲内にあるかのように、kos(現在削除済み)の解決策と同じ問題がありますこれにより、それらはすべて削除されます。また、パターンが複数出現する場合、入力ファイル(範囲外)に次のような行があると、区切り文字のために削除されます-複数行がパターンに一致する場合、grepの出力に表示されます(後者は高度です)ありそうもないが、言及する価値があると思います)。
ボド・ティーセン

0

一致するものが1つのみの場合:

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.