次のエントリを含むテキストファイルを考えます。
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
、pattern = fff
の出力は次のようになります。
aaa
bbb
ccc
hhh
iii
Grepまたは他のコマンドラインツールでこれを行うにはどうすればよいですか?
注:私が試したとき:
grep -v 'fff' -A1 -B2 file.txt
欲しいものが手に入りません。私は代わりに得ます:
aaa
bbb
ccc
ddd
eee
fff
--
--
fff
ggg
hhh
iii
gnu grep
を-A
および-B
と一緒に使用して、除外するファイルの部分を正確に出力しますが、-n
スイッチを追加して行番号も出力し、出力をフォーマットしてコマンドスクリプトとしてsed
に渡します。それらの行を削除するには:
grep -n -A1 -B2 PATTERN infile | \
sed -n 's/^\([0-9]\{1,\}\).*/\1d/p' | \
sed -f - infile
これは、-f
を介してgrep
に渡されたパターンのファイルでも機能する必要があります。例:
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
は行のみを出力します2nd FILEに固有で、次にcut
はnl
によって追加された部分(つまり、最初のフィールドと区切り文字:
)を削除します
with join
:
join -t: -j1 -v1 <(nl -ba -nrz -s: infile | sort) \
<(grep PATTERN -A1 -B2 <(nl -ba -nrz -s: infile) | sort) | cut -d: -f2-
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行下(これら2つについては :he cmdline-ranges
を参照)d
-行を削除します。+w !tee
は標準出力に書き込みます。+q!
は変更を保存せずに終了します。変数をスキップして、パターンと数値を直接使用できます。目的を明確にするために使用しました。
いかがですか(GNU grep
およびbash
を使用):
$ grep -vFf - file.txt < <(grep -B2 -A1 'fff' file.txt)
aaa
bbb
ccc
hhh
iii
ここでは、grep -B2 -A1 'fff' file.txt
によって破棄される行を見つけ、これを入力ファイルとして使用して、これらを破棄する必要な行を見つけています。
一時ファイルを使用することで、十分な結果を得ることができます。
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
結果はgood-enoughです。これは、プロセスでインデントを緩めることができるためですが、それがxmlまたはインデントに依存しないファイルであれば、問題にはなりません。このスクリプトはRAMドライブを使用するため、これらの一時ファイルの書き込みと読み取りはメモリでの作業と同じくらい高速です。
また、特定のマーカーの前のいくつかの行を除外する場合は、次のように使用できます。
awk -v nlines=2 '/Exception/ {for (i=0; i<nlines; i++) {getline}; next} 1'
(glenn jackman at https://stackoverflow.com/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つしかない場合:
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)
これを達成する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