テキストファイルの最終行を取得する


友人から電話で質問されたネタです。「何かいいアイデアはない?」と。

上図のようなログファイルがあります。毎日一回、何かをカウントした結果を記録するログファイルです。本日の数値は何らかの方法で計算するとして、その数値と一緒に前回との差も記録したい…というのが友人のリクエストでした。つまり、下のようにしたいと。

前回の数値は、ログファイルの最終行に記録されています。差を計算するには、この最終行を取得しなければなりません。オーソドックスにLine Inputステートメントを使うなら、次のようなコードになります。

Sub Sample1()
    Dim buf As String
    Const Target As String = "C:\Count.log"
    Open Target For Input As #1
        Do Until EOF(1)
            Line Input #1, buf
        Loop
    Close #1
    MsgBox "最終行のデータは、" & buf
End Sub

ファイルから最終行まで1行ずつ読み込めば、最後に読み込んだ行が変数bufに残ります。これが最終行のデータです。なるほど、確かに間違いはないのですが、ログファイルの行数が数千行とかになると、それなりに時間もかかりますし、何といっても"空読み込み"するという考え方は、あまり美しくありませんね。そこで、FileSystemObjectを使って全データを一括取得して、Split関数で分割する方法でやってみましょう。なお、ここでは解説を簡素化するために、上記のように4行しか入力されていないC:\Count.logを対象に話を進めます。

テキストファイルのデータを一気に取得するには次のようにします。

Sub Sample2()
    Dim buf As String, FSO As Object
    Const Target As String = "C:\Count.log"
    Set FSO = CreateObject("Scripting.FileSystemObject")
    With FSO.OpenTextFile(Target, 1)
        buf = .ReadAll
        .Close
    End With
    Debug.Print buf
End Sub

FileSystemObjectに関しては、下記のページをご覧ください。

FileSystemObjectの解説

ここからはイメージの世界です。このログファイルは、実際には次のように記録されています。

各行のデータが改行コード(vbCrLf)で区切られていますので、その改行コード(vbCrLf)を区切文字としてSplit関数で分割してやると、

という配列が得られます。配列のインデックス値が「0」から始まっている点に留意してください。Excelの標準的な設定では、配列の要素は「0」から始まるからです。また、データの最後が改行で終わっていると、配列の最後が「空の要素」になります。いま欲しいのは「データの最終行」ですから、その点も頭に入れておいてください。

変数bufに格納した全データを、Split関数を使って、改行コード(vbCrLf)で分割します。分割した結果は配列になりますから、配列の最終要素を取り出します。最終要素のインデクス番号は、UBound関数で調べます。

Sub Sample3()
    Dim buf As String, LastRow As Long, FSO As Object
    Const Target As String = "C:\Count.log"
    Set FSO = CreateObject("Scripting.FileSystemObject")
    With FSO.OpenTextFile(Target, 1)
        buf = .ReadAll
        .Close
    End With
    Set FSO = Nothing
    LastRow = UBound(Split(buf, vbCrLf)) - 1
    MsgBox Split(buf, vbCrLf)(LastRow)
End Sub

これで、テキストファイルの最終行を取得できました。

友人のリクエストでは、最終行に記録されている「2007/7/4 57833」の「57833」部分も取り出さなければなりません。日付と数値データの間は半角のスペースで区切られているので、InStr関数でスペースの位置を調べて、そこから後ろを抜き出して・・・でもいいんですが、こんなときも、Split関数なら一発で取得できます。この「2007/7/4 57833」をスペースで分割して、2番目の要素を調べればいいんです。

Sub Sample4()
    Dim buf As String, LastRow As Long, FSO As Object
    Const Target As String = "C:\Count.log"
    Set FSO = CreateObject("Scripting.FileSystemObject")
    With FSO.OpenTextFile(Target, 1)
        buf = .ReadAll
        .Close
    End With
    Set FSO = Nothing
    LastRow = UBound(Split(buf, vbCrLf)) - 1
    buf = Split(buf, vbCrLf)(LastRow)
    MsgBox Split(buf, " ")(1)
End Sub

最後の3行は「Split(Split(buf, vbCrLf)(UBound(Split(buf, vbCrLf)) - 1), " ")(1)」と1行で書くこともできますが、可読性が悪くなるので、1ステップずつ変数に入れた方がいいでしょう。