複数行grepする方法を調べてみた
複数行まとめてgrepしたいことがあったので、調べてみたら以下で説明で同じことをやろうとしていました。
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
と正しい行が出たので、とりあえずやりたいことはできました。