web-dev-qa-db-ja.com

隣接する行で一致を検索

隣接する一致する線を見つけたい、たとえば、パターンが一致する場合

$ grep -n pattern file1 file2 file3
file1:10: ...
file2:100: ...
file2:1000: ...
file2:1001: ...
file3:1: ...
file3:123: ...

真ん中の2つの一致を見つけたい:

file2:1000: ...
file2:1001: ...

しかし、最初の2つと最後の2つではありません。

3
sds

Thrigと同じテストファイルを使用します。

$ cat file
a
pat 1
pat 2
b
pat 3

これがawkソリューションです:

$ awk '/pat/ && last {print last; print} {last=""} /pat/{last=$0}' file
pat 1
pat 2

使い方

awkは、ファイル内のすべての行を暗黙的にループします。このプログラムは、1つの変数lastを使用します。これには、正規表現patと一致した場合の最後の行が含まれます。それ以外の場合は、空の文字列が含まれます。

  • /pat/ && last {print last; print}

    patがこの行と一致し、前の行lastも一致した場合は、両方の行を出力します。

  • {last=""}

    lastを空の文字列に置き換えます

  • /pat/ {last=$0}

    この行がpatと一致する場合は、lastをこの行に設定します。このようにして、次の行を処理するときに使用できるようになります。

2つ以上の連続した一致を1つのグループとして扱うための代替手段

この拡張テストファイルについて考えてみましょう。

$ cat file2
a
pat 1
pat 2
b
pat 3
c
pat 4
pat 5
pat 6
d

上記のソリューションとは異なり、このコードは3つの連続する一致する行を1つのグループとして印刷対象として扱います。

$ awk '/pat/{f++; if (f==2) print last; if (f>=2) print; last=$0; next} {f=0}' file2
pat 1
pat 2
pat 4
pat 5
pat 6

このコードは2つの変数を使用します。前と同じように、lastは前の行です。さらに、fは連続した一致の数をカウントします。したがって、fが2以上の場合、一致する行を出力します。

Grepのような機能の追加

質問に示されているgrep出力をエミュレートするために、このバージョンでは、一致する各行の前にファイル名と行番号を出力します。

$ awk 'FNR==1{f=0} /pat/{f++; if (f==2) printf "%s:%s:%s\n",FILENAME,FNR-1,last; if (f>=2) printf "%s:%s:%s\n",FILENAME,FNR,$0; last=$0; next} {f=0}' file file2
file:2:pat 1
file:3:pat 2
file2:2:pat 1
file2:3:pat 2
file2:7:pat 4
file2:8:pat 5
file2:9:pat 6

AwkのFILENAME変数はファイルの名前を提供し、awkのFNRはファイル内の行番号を提供します。

各ファイルの先頭で、FNR==1fをゼロにリセットします。これにより、あるファイルの最後の行が次のファイルの最初の行と一緒に連続と見なされるのを防ぎます。

複数行にまたがるコードが好きな人にとって、上記は次のようになります。

awk '
    FNR==1{f=0}
    /pat/ {f++
        if (f==2) printf "%s:%s:%s\n",FILENAME,FNR-1,last
        if (f>=2) printf "%s:%s:%s\n",FILENAME,FNR,$0
        last=$0
        next
    }

    {f=0}
    ' file file2
3
John1024

1つの方法は、前の行を保存し、現在の行と前の行の両方が一致したときに印刷することです。

bash-4.1$ (echo a; echo pat 1; echo pat 2; echo b; echo pat 3)
a
pat 1
pat 2
b
pat 3
bash-4.1$ (echo a; echo pat 1; echo pat 2; echo b; echo pat 3) | \
          Perl -nle 'print "$prev\n$_" if /pat/ and $prev =~ /pat/; $prev=$_'
pat 1
pat 2

ただし、これにより、一致する隣接する行が3つ以上ある場合、これらはペアごとに2回以上一致するため、重複一致が発生します。より良いオプションは、一致する前の行の数を追跡し、さまざまな厄介なEdgeケース(ファイルの終わりまでのブロックなど)が適切に処理されることを確認するためのテストコードを作成することです。

#!/usr/bin/env Perl
use strict;
use warnings;

my $prev;
my $pattern = qr/pat/;
my $have_matches = 0;

while (my $line = readline) {
  if ($line =~ /$pattern/) {
    print $prev if $have_matches == 1;
    print $line if $have_matches;
    $have_matches++;
    $prev = $line;
  } else {
    $have_matches = 0;
  }
}
2
thrig