隣接する行で一致するものを見つける

隣接する行で一致するものを見つける

たとえば、パターンマッチングが次のような場合、隣接するマッチラインを探したいとします。

$ 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つではありません。

答え1

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ファイルの各行を暗黙的に繰り返します。プログラムは、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つの連続した一致行を印刷するセットとして扱います。

$ 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連続一致回数も計算される。したがって、f2以上の場合は一致する行を印刷します。

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のFILENAME変数はFNRファイル内の行番号を提供します。

各ファイルの先頭からゼロにFNR==1リセットされます。fこれにより、ファイルの最後の行は考慮されません。続けて次のファイルの最初の行に。

コードを複数行にわたって分散したい場合、上記のコードは次のようになります。

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

答え2

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回以上のペアで一致するため、重複一致が発生します。より良いオプションは、以前に一致した行数を追跡し、いくつかのテストコードを書いて、さまざまで複雑な場合(ファイルの末尾にあるブロックなど)が正しく処理されていることを確認することです。

#!/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;
  }
}

答え3

記録のために、次の方法でこれを行うこともできますsed

sed -s '$!N
/.*PATTERN.*\n/{/\n.*PATTERN/{x;/^1$/!s/.*/1/;b v};//!{x;/^1$/{s/./0/;b v};//!D}}
//!{${/PATTERN/{x;/^1$/{b v}}};D;};: v;x;P;D' file1 file2 ... fileN

それはgnu sed。他のsedファイルの場合は、一度に1つのファイルを処理する必要があります。

sed '$!N                   # if not on the last line pull in the next line
/.*PATTERN.*\n/{           # if first line in the pattern space matches
/\n.*PATTERN/{             # and if second line also matches                   
x                          # exchange pattern space with hold buffer
/^1$/!s/.*/1/              # replace everything with 1
b v                        # branch to label v
}
//!{                       # if second line does not match
x                          # exchange pattern space with hold buffer
/^1$/{                     # if it matches 1
s/.*/0/                    # replace with 0
b v                        # branch to label v
}
//!D                       # if it does not match 1 delete up to first newline
}
}
//!{                       # if first line does not match
${                         # if we're on the last line
/PATTERN/{                 # and if it matches
x                          # exchange pattern space with hold buffer
/^1$/{                     # if it matches 1
b v                        # branch to label v
}
}
}
D                          # else delete up to first newline
}
: v                        # label v
x                          # exchange pattern space with hold buffer
P                          # print up to first newline
D' infile                  # delete up to first newline

perlまたはawkそれほど柔軟ではありません。出力を完全にシミュレートすることはできません。つまり、行の前にファイル名と行番号を付けます。ただし、grep前に追加してから出力全体をパイピングしてgnu sedファイル名を取得することはできます。FPpaste -d: - -

答え4

こんにちは、最後の行を完成するのに役立つさまざまなコマンドがあります。これを試してみてください。

<grep command> | tail -1

または

awk '/result/ { save=$0 }END{ print save }' filename

関連情報