複数行grepする方法を調べてみた

複数行まとめてgrepしたいことがあったので、調べてみたら以下で説明で同じことをやろうとしていました。

stackoverflow.com

grepに対して -P のオプションでPerl正規表現を使えるようにし、 -z のオプションでテキスト全体を一つの行として扱うことで複数行のgrepが実現できるようです。

windows10のwslで試しに実行してみたところ。

$ cat test.txt
xaaaxxxxxxxxx
xbbbxxxxxxxxx
xcccxxxxxxxxx
xaaaxxxxxxxxx
xbbbxxxxxxxxx
xaaaxxxxxxxxx
xcccxxxxxxxxx
xaaaxxxxxxxxx
xbbbxxxxxxxxx
xcccxxxxxxxxx

$ grep -Pzon  ".*aaa.*\n.*bbb.*\n.*aaa.*\n" test.txt
1:xaaaxxxxxxxxx
xbbbxxxxxxxxx
xaaaxxxxxxxxx

.*aaa.*\n.*bbb.*\n.*aaa.*\n の3行を対象にgrepできていることが確認できました。ただし、これだと -z でテキスト全体が1行という扱いになるため -n で行を表示しようとしてもうまくいかないようです。

やりたいことは難しくないので、試しにScalaで実装してみました。

import java.io.{BufferedReader, FileReader}

import scala.util.Using
import scala.util.Using.Releasable

object Mgrep extends App {

  object Cont {
    def pure[R, A](a: A): Cont[R, A] =
      Cont(ar => ar(a))
  }

  final case class Cont[R, A](run: (A => R) => R) {
    def flatMap[B](f: A => Cont[R, B]): Cont[R, B] =
      Cont(br => run(a => f(a).run(br)))

    def map[B](f: A => B): Cont[R, B] =
      flatMap(a => Cont.pure(f(a)))
  }

  def using[A <: AutoCloseable, B](a: => A, n: String)(f: A => B): B =
    try f(a) finally a.close()

  def resource[R, A](a: => A)(implicit r: Releasable[A]): Cont[R, A] =
    Cont(ar => Using.resource(a)(ar))


  def grep(fileName: String, checks: List[String]) = {
    def search(reader: BufferedReader, checks: List[String], action: (Int, List[String]) => Unit) = {
      var index = 0
      var checkedLines = List("")

      while (reader.ready() && index + 1 < checks.length) {
        checkedLines = checkedLines :+ reader.readLine()
        index += 1
      }
      while (reader.ready()) {
        checkedLines = checkedLines.tail :+ reader.readLine()
        index += 1
        if (checkedLines.zip(checks).count(a => a._1.matches(a._2)).equals(checks.length)) {
          action(index, checkedLines)
        }
      }
    }

    if (fileName.isEmpty || checks.isEmpty) {
      sys.error("please input search text and fileName")
    } else {

      {
        for {
          a <- resource(new BufferedReader(new FileReader(fileName)))
        } yield search(a, checks, (index: Int, lines: List[String]) =>
          print(s"${index - checks.length + 1}行目\n${lines.mkString("\n")}\n"))
      }.run(identity)
    }
  }

  val fileName = args.last
  val checks = args.take(args.length - 1).toList
  grep(fileName, checks)
}
> java -jar target\scala-2.13\mgrep-assembly-0.1.0-SNAPSHOT.jar .*aaa.* .*bbb.* .*aaa.* test.txt
4行目
xaaaxxxxxxxxx
xbbbxxxxxxxxx
xaaaxxxxxxxxx

と正しい行が出たので、とりあえずやりたいことはできました。