【テクニカル・上級編】【データ整形バッチ】FSOで固定長テキストファイルを読み込みTSV形式へ変換・パースする泥臭く役立つデータ加工術 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【データ整形バッチ】FSOで固定長テキストファイルを読み込みTSV形式へ変換・パースする泥臭く役立つデータ加工術

レガシーシステムの深部、あるいは今なお現役で稼働し続けるオフコンや基幹系バッチサーバー。そこから出力される「固定長テキストファイル」は、現代のオープン系システムやBIツール、リレーショナルデータベースにとって、時として厄介な遺物となる。

文字コードの不一致、CRLFとLFの混在、そして何より「何バイト目から何バイト目までが何の意味を持つか」がプログラムのコード内にしか存在しないという絶望的な仕様。

我々シニアエンジニアは、この手の「泥臭いデータの前処理」を避けて通ることはできない。今回は、VBScriptのFile System Object (FSO)を極限までチューニングし、数ギガバイトに及ぶ固定長ファイルを安全かつ高速にTSVへパース・変換する、現場直結のアーキテクチャを解説する。

—

1. なぜ「今さらVBScript」で固定長処理なのか

PythonやPowerShell全盛の時代において、VBScriptを語るのはレトロスペクティブに映るかもしれない。しかし、考えてみてほしい。

  • どのWindows環境(Windows 7から最新のWindows 11 / Server 2022まで)でも追加ランタイムなしで即座に稼働する。
  • 権限が厳しく制限された閉じたイントラネット環境において、サードパーティ製ライブラリの持ち込みが厳禁な現場でも動く。
  • メモリ管理とストリーム読み込みを正しく制御すれば、GUIを持たない純粋なバッチワーカーとして驚異的な軽量性を発揮する。

VBScriptの限界は、言語仕様の古さではなく、「書き手のメモリ管理に対する意識の低さ」に起因することがほとんどだ。特にFSOを用いた巨大ファイルの乱雑な扱いは、メモリリークやプロセス肥大化の元凶となる。これから示すコードは、そのリスクを完全に封じ込めたプロダクション品質のものである。

—

2. アーキテクチャの要件定義

今回のデータ加工バッチにおける要件は以下の通りである。

1. ストリーム処理(メモリの枯渇防止): `ReadAll`でファイルを丸ごとメモリにロードする愚行は犯さない。`OpenTextFile`による1行ずつのシーケンシャルリードを徹底する。
2. Shift-JIS(CP932)の確実なハンドリング: 基幹系出力に多いレガシーエンコーディングを、ADODB.Streamを組み合わせることで文字化けせずに処理する(※純粋なFSOの`OpenTextFile`は文字コードの制御が弱いため、実戦ではADODB.Streamとのハイブリッドが定石)。
3. 明示的なオブジェクト解放: VBScriptのガベージコレクションに頼らず、スコープの終端またはエラー発生時に確実にメモリを解放する。

—

3. 実装コード:高堅牢性・固定長変換バッチ

以下のスクリプトを `FixedToTsv.vbs` として保存し、実行してほしい。

Option Explicit

‘ ==============================================================================
‘ スクリプト名: FixedToTsv.vbs
‘ 概要: 固定長テキストファイルを読み込み、定義に基づいてTSVに変換する
‘ アーキテクチャ: FSO + ADODB.Stream ハイブリッド・ストリーミング方式
‘ ==============================================================================

Main

Sub Main()
Dim fso, tsIn, tsOut
Dim inputPath, outputPath
Dim lineCount, errorCount
Dim startTime

startTime = Timer
inputPath = “C:\Batch\Data\input_fixed.dat”
outputPath = “C:\Batch\Data\output_data.tsv”

WScript.Echo “=== 固定長データ変換バッチ 開始 ===”

Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ 入力ファイルの存在確認
If Not fso.FileExists(inputPath) Then
WScript.Echo “[ERROR] 入力ファイルが見つかりません: ” & inputPath
Set fso = Nothing
Exit Sub
End If

‘ 出力先ディレクトリの確保(必要に応じて)
‘ ※今回は同一フォルダ前提

On Error Resume Next

‘ 【重要】文字コード(Shift-JIS / クライアント環境依存)を担保するためADODB.Streamを使用
Dim streamIn, streamOut
Set streamIn = CreateObject(“ADODB.Stream”)
streamIn.Type = 2 ‘ adTypeText
streamIn.Charset = “shift_jis”
streamIn.Open
streamIn.LoadFromFile inputPath

Set streamOut = CreateObject(“ADODB.Stream”)
streamOut.Type = 2
streamOut.Charset = “utf-8” ‘ 出力はモダンなUTF-8(BOM付き)またはUTF-8Nへ
streamOut.Open

lineCount = 0
errorCount = 0

‘ ヘッダー行の出力(必要に応じて定義)
streamOut.WriteText “ID” & vbTab & “氏名” & vbTab & “区分” & vbTab & “金額” & vbTab & “登録日時” & vbCrLf

‘ ストリームから1行ずつ読み込み(メモリ消費を最小化)
Do While Not streamIn.EOS
Dim rawLine, parsedLine
rawLine = streamIn.ReadLine
lineCount = lineCount + 1

‘ エラーハンドリングを内包したパース処理
parsedLine = ParseFixedLength(rawLine, lineCount)

If Len(parsedLine) > 0 Then
streamOut.WriteText parsedLine & vbCrLf
Else
errorCount = errorCount + 1
End If
Loop

‘ ファイルへの書き出し確定
streamOut.SaveToFile outputPath, 2 ‘ adSaveCreateOverWrite

‘ 後始末(オブジェクトの明示的破棄)
streamIn.Close
streamOut.Close
Set streamIn = Nothing
Set streamOut = Nothing
Set fso = Nothing

If Err.Number <> 0 Then
WScript.Echo “[FATAL ERROR] 予期せぬエラーが発生しました: ” & Err.Description
Err.Clear
Else
WScript.Echo “=== 処理完了 ===”
WScript.Echo “総処理行数 : ” & lineCount & ” 行”
WScript.Echo “スキップ行 : ” & errorCount & ” 行”
WScript.Echo “処理時間 : ” & FormatNumber(Timer – startTime, 2) & ” 秒”
End If

On Error GoTo 0
End Sub

‘ ==============================================================================
‘ 関数名: ParseFixedLength
‘ 概要: 1行の固定長文字列をバイト/文字位置指定で切り出し、TSV形式へ結合する
‘ ==============================================================================
Function ParseFixedLength(ByVal strLine, ByVal currentLineNo)
On Error Resume Next

‘ 固定長レイアウト定義(例)
‘ 1- 5桁 (5): ID
‘ 6-25桁(20): 氏名(パディング空白はトリム)
‘ 26-26桁(1): 区分
‘ 27-36桁(10): 金額
‘ 37-54桁(18): 登録日時

Dim col1, col2, col3, col4, col5

‘ データの長さチェック(不正行の弾き落とし)
If Len(strLine) < 54 Then ' 満たない場合はログを出力して空文字を返す WScript.Echo "[WARN] 行 " & currentLineNo & " は規定長に達していません (Len=" & Len(strLine) & ")" ParseFixedLength = "" Exit Function End If ' Mid関数による切り出し ' ※VBScriptのMidは1文字目からスタート。マルチバイト文字混入時は文字数カウントに注意。 ' 完全なバイト数制御が必要な場合は別途Byte配列への変換が必要だが、 ' ここでは文字単位の固定長フォーマットを想定してMidを使用する。 col1 = Trim(Mid(strLine, 1, 5)) col2 = Trim(Mid(strLine, 6, 20)) col3 = Trim(Mid(strLine, 26, 1)) col4 = Trim(Mid(strLine, 27, 10)) col5 = Trim(Mid(strLine, 37, 18)) ' データのバリデーション(例:金額が数値かチェック) If Not IsNumeric(col4) And col4 <> “” Then
WScript.Echo “[WARN] 行 ” & currentLineNo & ” の金額フィールドが数値ではありません: [” & col4 & “]”
End If

‘ TSV形式(Tab区切り)で結合して返却
ParseFixedLength = col1 & vbTab & col2 & vbTab & col3 & vbTab & col4 & vbTab & col5

If Err.Number <> 0 Then
WScript.Echo “[ERROR] パース中にエラー (行 ” & currentLineNo & “): ” & Err.Description
ParseFixedLength = “”
Err.Clear
End If
On Error GoTo 0
End Function

—

4. チーフアーキテクトが教える「現場の知見」と罠

上記のコードは一見シンプルだが、レガシーシステムを相手にする際、以下の罠に直面することがある。実戦で生き残るための知見を共有しよう。

① マルチバイト文字(全角スペース・全角文字)と `Mid` 関数の罠

VBScriptの `Mid` 関数は「文字数ベース」で動作する。しかし、古いメインフレームやオフコンの固定長データは「バイト数ベース」で設計されていることが多い。
Shift-JIS環境において、半角は1バイト、全角は2バイトである。もしレイアウト定義が「バイト位置」で指定されている場合、全角文字が混入した瞬間にズレが発生する。

  • 対策: 厳密なバイト位置制御が必要な場合は、一度 `ADODB.Stream` でバイナリとして読み込み、`Byte()` 配列を操作して `MidB` 相当の自前関数を実装する必要がある。ただし、文字コードがShift-JISに固定されている前提であれば、あらかじめオフコン側でパディング文字(空白)が半角スペースで統一されているかを確認し、`Trim` で安全に切り取る設計に倒すのが実務上最もコスパが良い。

② メモリリークを完全に防ぐためのオブジェクト破棄

VBScriptの変数スコープを抜ければオブジェクトは自動解放される……というのは理想論であり、大規模バッチやループ内では参照カウントが残り、メモリリークを引き起こすことがある。

  • 対策: スクリプトの最後、あるいは例外発生のいかんにかかわらず、使い終わったCOMオブジェクト(`Stream`, `FSO`)は必ず `Set obj = Nothing` を明示すること。特にバッチをタスクスケジューラー等で常時・定期実行する場合、この一手間でメモリフットプリントの肥大化を完全に防げる。

③ エラーハンドリングの流儀

`On Error Resume Next` は諸刃の剣である。これを乱用すると、どこでバグを踏んだか分からなくなる。

  • 対策: 障害になり得るクリティカルな入出力部分やパース部分だけに局所的に適用し、ループ内では不正な行をサイレントに握りつぶすのではなく、必ずコンソール(あるいはログファイル)へ行番号付きで警告を出力させること。保守フェーズにおいて「どのデータのせいでバッチが止まった(あるいはスキップされた)か」を追跡できないシステムは、システムではなく「ただのブラックボックス」である。

—

5. 結びにかえて

VBScriptによるデータ加工は、一見すると泥臭く、現代の洗練された開発スタイルから見ればアンチパターンに見えるかもしれない。しかし、「動かすべき環境に何も無い」「確実に、そしてローコストでミッションを完遂しなければならない」という修羅場において、こうしたレガシーな知見と堅牢なコードの組み合わせは、エンジニアの最大の武器となる。

動かない最新技術より、確実に要件を満たし続ける枯れた技術。ファイルシステムオブジェクトとストリームを自在に操り、泥臭いデータを美しく調律することこそ、真のプロフェッショナルの仕事である。