Linuxにはtailというファイルの末尾を表示するコマンドがありますが、これをScalaでもやってみました。

scala.sys.processを使う


はじめは下記のようにやってました。

1
2
3
4
5
6
import scala.sys.process._

//サンプルなので例外処理とかは書いてません
def tail(): String = {
Process(s"tail -n 100 /etc/logs/example.log").!!
}

上記だと/etc/logs/example.logファイルを末尾から100行取得することになります。

しかしながら、このやり方ではプラットフォームに依存してしまうという問題があります。同様のことをWindowsでやろうと思うと、下記のように記述しなければなりません。

1
2
3
4
5
6
import scala.sys.process._

//サンプルなので例外処理とかは書いてません
def tail(): String = {
Process(s"powershell -Command Get-Content -Path C:/example.log -Tail 100").!!
}

さらに、LinuxかWindowsかのチェックも必要になります。これはめんどくさいですね。めんどくさいので、なんらかのパッケージを利用してプラットフォームに依存しない形で実現できないか調べてみました。

Apache Commons IOを使う


で、探したらApache Commons IOReversedLinesFileReaderで実現できそうなのがわかりました。のでやってみます。

build.sbtの依存ライブラリに下記を記述します。バージョンは使うものにあわせてください。

1
"commons-io" % "commons-io" % "2.6"

こちらのライブラリを使用したコードは下記のような感じです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.io.File
import java.nio.charset.Charset
import scala.collection.mutable.ArrayBuffer
import org.apache.commons.io.input.ReversedLinesFileReader

def tail(): ArrayBuffer[String] = {

//サンプルなので例外処理とかは書いてません
val reversedReader = new ReversedLinesFileReader(new File("/etc/logs/example.log"),
Charset.defaultCharset())
try {
var counter: Int = 0
var line: String = ""
var lines = ArrayBuffer[String]()
while ({ line = reversedReader.readLine(); line != null } && counter <= 100) {
lines += line
counter += 1
}
lines.reverse
} finally {
if (reversedReader != null) reversedReader.close()
}

}
  • ファイルがEOLかどうかをnullで判断するようなのでnullになるまでもしくは100行読み込むまで繰り返します
  • 末尾から読み込んでいるので、表示する際に古い順で並べたい場合はreverseで反転させます
  • finallycloseさせます

一応これでやりたいことはできました。ですが、あんまりScalaっぽくないコードなのが「う~ん。ちょっとな~。」というかんじです。もうちょっとなんとかできないものかと思うのですが、私だとちょっとこれ以上は思いつかないというのが正直なところです。

あんまりScalaっぽくないコードなので書き直してみたのが下記です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.File
import java.nio.charset.Charset
import scala.collection.mutable.ArrayBuffer
import org.apache.commons.io.input.ReversedLinesFileReader

def tail(): List[String] = {

//サンプルなので例外処理とかは書いてません
val reversedReader = new ReversedLinesFileReader(new File("/etc/logs/example.log"),
Charset.defaultCharset())
try {
val counter = Iterator.from(0)
Iterator.continually(reversedReader.readLine())
.takeWhile(_ != null && { counter.next < 100 })
.toList
.reverse
} finally {
if (reversedReader != null) reversedReader.close()
}

}

いちおう、これで同等に動くので問題ないんだと思います。たぶん。