逆方向にgrepして「前」および「後」の行を除外する方法

逆方向にgrepして「前」および「後」の行を除外する方法

次のトピックを含むテキストファイルを検討してください。

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

答え1

gnu grepとを使用して除外したいファイルの部分を正確に印刷できますが、行番号を印刷するスイッチを追加して-A出力形式を指定し、コマンドスクリプトに渡してその行を削除します。-B-nsed

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入力に一致するものがいくつかあれば実行する価値はありません。



行の順序を維持せずに速度が遅くなる他の方法は次のとおりです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一意の行のみを印刷するために使用されます。2番目のファイル次に、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-

答え2

ほとんどの場合、これを行わない方が良いかもしれませんが、ファイルが次の場合にのみ可能です。本物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バッファリング - 印刷を試みる前に行数を計算します。

実際に前のポイントを明確にしなければならないようです。このソリューションと他のソリューションの主なパフォーマンス制限要因は、間隔に直接関連しています。このソリューションは、より大きな間隔のために遅くなります。サイズ、時間間隔が増加すると速度が遅くなります。頻度。つまり、入力ファイルが非常に大きくても、実際の間隔の発生が依然として非常にまれである場合、彼の解決策は正しい選択かもしれません。ただし、間隔サイズが比較的管理しやすく、頻繁に発生する可能性がある場合は、このソリューションを選択する必要があります。

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

  • $matchパターン空間で前のewlineが見つかると、\nその前の各ewlineは再帰的に削除されsedます。D\n
    • 以前はパターン空間を完全に空にし$matchましたが、重なっている部分を簡単に処理するには、ランドマークを残す方がうまくいくようです。
    • 私はs/.*\n.*\($match\)/\1/一度にそれを取得し、ループを避けようとしましたが、$A/$Bそれが大きいとき、Dエレテループははるかに速いことがわかりました。
  • N次に、ewline区切り文字の前にある追加の行を取得し、\n最近使用した正規表現を参照してaを削除しようとしますD/\n.*$match///
  • パターンスペースが一致すると、行の先頭でのみこれを実行$matchできます。前の行はすべて消去されました。 $match$B
    • だから私たちはサイクルを始めます$A
    • このループを実行するたびに、私たちは自分自身をパターン空間の行文字に置き換えようs///とします。&成功すると、estは私たちとバックバッファ全体をスクリプトから完全に分岐し、次の入力で一番上から再開します。行(存在する場合)。$A\nt$A
    • testが失敗した場合は、opラベルに戻り、別の入力行で繰り返します。bfterの収集中にこれが発生した場合は、:tループを再開することもできます。$match$A
  • 関数ループを通過すると、最後の行(最後の行の場合)を印刷しよ$matchうとし、そうでない場合はパターンスペースの行文字に自分自身を置き換えようとします。p$!s///&$B\n
    • また、これをテストし、成功するとrintタグtに移動します。:P
    • それ以外の場合は、opに分岐し、:t別の入力ラインをバッファに追加します。
  • :Pリントするようにしておくと、パターンスペースで最初の行をPリントしてからD削除し、残りの内容と一緒にトップからスクリプトを再実行します。\n

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

rint の最初の繰り返しのためのパターン空間は:P次のとおりです。

^1\n2\n3$

これがsedフロントバッファが収集される$B方法です。したがって、出力カウントラインsedで印刷してください。$B後ろに収集する入力です。これは、前の例に基づいて次のものを印刷するsedことを意味します。P1その後、Dそれを削除し、次のようにパターンスペースをスクリプトの先頭に戻します。

^2\n3$

...そしてNスクリプトの上部で外部入力行を検索するので、次の繰り返しは次のようになります。

^2\n3\n4$

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

^3\n4\n5$

その後、D選択ループが開始して完了すると、次のようになります。

^5$

N外部入力ラインを引くとEOFsedが発生し、終了します。その時点では、P1行と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

答え3

使用しても問題ない場合は、以下を使用してください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すべて)
  • d- 行を削除します。
  • +w !teeその後、標準出力に書き込みます。
  • +q!変更を保存せずに終了します。

変数をスキップし、パターンと数字を直接使用できます。私はそれらを明示的な目的にのみ使用します。

答え4

一時ファイルを使用すると、十分な結果が得られます。

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ドライブを使用するため、これらの一時ファイルの書き込みと読み取りはメモリで作業するのと同じくらい高速です。

関連情報