【固定長データ解析】LenB・MidB 関数と ADODB.Stream を駆使したマルチバイト文字の文字化けしないバイト単位切り出し
エンタープライズの現場において、レガシーシステムとの連携や金融・流通業界の基幹電文処理でいまだに現役として君臨するのが「固定長データ(テキスト)」だ。
「たかがテキストの切り出し」と侮ってはいけない。
VBScriptの標準関数である `Len` や `Mid` をそのまま使っていれば、全角文字(マルチバイト文字)が混入した瞬間に文字数とバイト数の乖離が発生し、パース位置が盛大にズレる。文字化けやデータ欠損という名のシステム障害が、静かに夜間バッチを蝕むことになる。
今回は、VBScriptとWSHの限界領域を知り尽くしたアーキテクトの視点から、Shift-JIS環境下におけるマルチバイト文字を完全に制御し、バイト単位で正確無比に固定長データを切り出す極限のテクニックを伝授する。
—
なぜ従来の `Mid` 関数では破綻するのか?
多くの開発者が陥る最初の罠が、文字単位で処理を行う `Mid` や `Len` の使用だ。
VBScriptの文字列は内部でUnicode(UTF-16)として保持されている。
そのため、`Mid(str, start, length)` は「文字数」を基準に切り出す。しかし、多くの固定長電文の仕様書は「バイト数」で定義されている。
- 半角英数(A-Z, 0-9):1文字 = 1バイト
- 全角文字(漢字、かな):1文字 = 2バイト(Shift-JISの場合)
この非対称性を無視して文字数で切り出しを行うと、全角文字の途中でパースが分断され、いわゆる「化け文字(制御コードへの化け)」が発生する。さらに厄介なことに、エラーで止まらずに「しれっとデータがズレたまま後続処理に流れる」のがこのバグの最も恐ろしいところだ。
バイト単位処理の二大アプローチ
VBScriptでバイト単位の操作を行うには、主に2つのアプローチが存在する。
1. `LenB` / `MidB` 関数 (要 `CByte` 変換等の型配慮)
2. `ADODB.Stream` オブジェクト によるバイナリ直接制御
レガシーなVBScriptの作法として `LenB` / `MidB` があるが、これらはBSTR(Unicode)の内部バイナリを直接叩くため、Shift-JIS等のマルチバイト文字列を扱う際にはコードページ変換の意識が不可欠となる。
実務の堅牢性を考慮した場合、明示的に文字コードを指定してバイト配列を操作できる `ADODB.Stream` を組み合わせる設計が、最もモダンでバグを生みにくい。
—
現場で即戦力となるプロダクションコード
以下のコードは、Shift-JISでエンコードされた固定長ファイルから、指定した「バイト位置」と「バイト長」に基づき、文字化けを起こさずに安全にフィールドを切り出す実用スクリプトだ。
そのまま実務のバッチ処理やファイルコンバーターに組み込めるよう、エラーハンドリングとオブジェクトのライフサイクル管理を徹底している。
Option Explicit
‘ ==============================================================================
‘ スクリプト名: FixedLengthParser.vbs
‘ 概要: ADODB.StreamとBSTR制御を駆使した、マルチバイト対応・固定長データ切り出しエンジン
‘ ==============================================================================
Dim objFSO, targetFilePath
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
‘ テスト用ファイルのパス(実務ではインプットパラメータ等に置き換えてください)
targetFilePath = objFSO.BuildPath(objFSO.GetParentFolderName(WScript.ScriptFullName), “sample_denbun.dat”)
‘ ファイルが存在しない場合は即座に異常終了
If Not objFSO.FileExists(targetFilePath) Then
WScript.Echo “[ERROR] 対象の固定長データファイルが見つかりません: ” & targetFilePath
WScript.Quit 1
End If
‘ メイン処理の実行
Call ParseFixedLengthFile(targetFilePath)
Set objFSO = Nothing
WScript.Quit 0
‘ ==============================================================================
‘ メインパーロジック
‘ ==============================================================================
Sub ParseFixedLengthFile(filePath)
Const adTypeBinary = 1
Const adTypeText = 2
Dim objStream
Set objStream = CreateObject(“ADODB.Stream”)
On Error Resume Next
‘ 1. バイナリモードでファイルを開く(文字コード変換の事故を防ぐため一度生データとして読み込む)
objStream.Type = adTypeBinary
objStream.Open
objStream.LoadFromFile filePath
If Err.Number <> 0 Then
WScript.Echo “[FATAL] ファイルのオープンに失敗しました: ” & Err.Description
Exit Sub
End If
Dim binData
binData = objStream.Read()
objStream.Close
On Error Goto 0
Set objStream = Nothing
‘ 2. バイナンス(バイト配列)をShift-JISとして安全にBSTR(Unicode文字列)へ変換
‘ ※ VBScript標準のReadTextはマルチバイトの境界で事故が起きやすいため、
‘ ADODB.Streamを再度テキストモードで経由させるか、BinaryStreamでバイナリ切り出しを行う。
Dim textContent
textContent = ConvertByteArrayToString(binData, “Shift_JIS”)
‘ 3. レコード単位(改行区切り)でループ処理
Dim records, i, lineStr
records = Split(textContent, vbCrLf)
WScript.Echo “=== 固定長データ解析結果 ===”
For i = 0 To UBound(records)
lineStr = records(i)
If Len(lineStr) > 0 Then
‘ 【電文レイアウト定義の例】
‘ 1-5バイト目: ID (半角)
‘ 6-20バイト目: 顧客名 (全角・半角混在)
‘ 21-28バイト目: 金額 (半角数値)
Dim fieldID, fieldName, fieldAmount
fieldID = GetBytesSubString(lineStr, 1, 5, “Shift_JIS”)
fieldName = GetBytesSubString(lineStr, 6, 15, “Shift_JIS”)
fieldAmount = GetBytesSubString(lineStr, 21, 8, “Shift_JIS”)
WScript.Echo “レコード[” & (i + 1) & “] -> ID: [” & fieldID & “] 顧客名: [” & fieldName & “] 金額: [” & fieldAmount & “]”
End If
Next
End Sub
‘ ==============================================================================
‘ 核心関数: 指定した「バイト位置」と「バイト長」で安全に文字列を切り出す
‘ ==============================================================================
Function GetBytesSubString(sourceStr, startByte, lengthBytes, charset)
Dim objStream
Set objStream = CreateObject(“ADODB.Stream”)
‘ テキストモードで一度指定文字コードのバイナリに変換
objStream.Type = 2 ‘ adTypeText
objStream.CharSet = charset
objStream.Open
objStream.WriteText sourceStr
‘ バイナリモードに切り替えてバイト配列を取得
objStream.Position = 0
objStream.Type = 1 ‘ adTypeBinary
Dim fullBytes
fullBytes = objStream.Read()
objStream.Close
‘ バイト配列の切り出し(VBScriptのMidBは1オリジン、配列は0オリジン)
‘ ※ ADODB.StreamのRead結果(Variantバイト配列)を直接MidBに渡すための処理
Dim targetBytes
‘ 中継用のStreamを使って指定レンジのバイナリを切り出す
Dim subStream
Set subStream = CreateObject(“ADODB.Stream”)
subStream.Type = 1
subStream.Open
subStream.Write fullBytes
subStream.Position = startByte – 1 ‘ 0始まりのオフセットに調整
‘ 指定バイト数分だけ読み込み
Dim extractedBytes
extractedBytes = subStream.Read(lengthBytes)
subStream.Close
Set subStream = Nothing
‘ 切り出したバイト配列を再度指定文字コードで文字列(BSTR)に変換
Set objStream = CreateObject(“ADODB.Stream”)
objStream.Type = 1 ‘ adTypeBinary
objStream.Open
objStream.Write extractedBytes
objStream.Position = 0
objStream.Type = 2 ‘ adTypeText
objStream.CharSet = charset
GetBytesSubString = Trim(objStream.ReadText())
objStream.Close
Set objStream = Nothing
End Function
‘ ==============================================================================
‘ 補助関数: バイト配列を文字列へ変換
‘ ==============================================================================
Function ConvertByteArrayToString(byteArray, charset)
Dim objStream
Set objStream = CreateObject(“ADODB.Stream”)
objStream.Type = 1 ‘ adTypeBinary
objStream.Open
objStream.Write byteArray
objStream.Position = 0
objStream.Type = 2 ‘ adTypeText
objStream.CharSet = charset
ConvertByteArrayToString = objStream.ReadText()
objStream.Close
Set objStream = Nothing
End Function
—
アーキテクトが解説する実装の急所と設計思想
なぜ、ここまで周到に `ADODB.Stream` の型変換を往復させているのか。ここには明確な理由がある。
1. `MidB` 関数単体への依存リスクを排除する
VBScript標準の `MidB` 関数は、対象が「メモリ上のBSTR(Unicode)」であるため、Shift-JIS環境であっても内部的にはUTF-16のバイト単位で切り出してしまう。結果として、全角文字の半端な位置でスライスされた場合、BSTRの整合性が壊れ、VBScriptランタイムレベルで文字化けや予期せぬパニックを引き起こす。
上記のコードでは、「文字列 $\rightarrow$ Shift-JISのバイナリ配列 $\rightarrow$ バイト位置での物理切り出し $\rightarrow$ 再度Shift-JISとして文字列化」という厳密なパイプラインを通すことで、この物理的な矛盾を完全にハックしている。
2. オブジェクトのライフサイクルとメモリ管理
WSH環境におけるスクリプト実行では、メモリリークはプロセス終了時に回収されるとはいえ、ループ内で `ADODB.Stream` などのCOMコンポーネントを乱雑に生成・放置すると、長時間のバッチ処理でメモリフットプリントが肥大化する。
上記のサンプルでは、処理が終わるごとに確実に `.Close` を呼び出し、スコープを意識した設計にしている。プロフェッショナルな現場のコードとは、リソースの解放作法まで美しく統制されているもののことだ。
3. パディング(空白埋め)のハンドリング
固定長データ特有の「右側パディング(スペース埋め)」に対しても、切り出し直後に `Trim()` をかける設計にすることで、後続のデータベース登録やCSV出力時の無駄な空白スペースをスマートに除去している。業務システムで頻発する「DBのNot Null制約違反」や「不要なスペースによるJOINミス」を水際で阻止する布陣だ。
—
まとめ
VBScriptは「古い言語」とやゆされることもあるが、OSの標準機能だけで動作し、インフラの追加投資なしで基幹システムの自動化を支えられる極めて強力なツールである。
しかし、そのパワーを正しく引き出すためには、文字コードとメモリ上のバイナリ構造に対する深い洞察が不可欠だ。今回紹介した `ADODB.Stream` を駆使したバイト単位切り出しの知見をあなたのプロジェクトにインストールすれば、マルチバイト文字に起因する文字化けや位置ズレのトラブルとは永遠に決別できるはずだ。
現場の信頼を勝ち取る堅牢な自動化スクリプトを、ぜひその手で実装してほしい。
