【テクニカル・上級編】【制御文字クリーニング】Chr / Asc 関数を用いた不可視の制御コードや外字の検出・一括除去ロジック – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【制御文字クリーニング】Chr / Asc 関数を用いた不可視の制御コードや外字の検出・一括除去ロジック

レガシーシステムとの連携、あるいは野良スクリプトが吐き出したCSVのインポートにおいて、最もエンジニアを絶望させるのは「目に見えないゴミ」である。

画面上は整然と並んでいるように見える文字列。しかし、データベースのUNIQUE制約違反、JSONパースのエラー、あるいはAPI連携時の不正リクエスト。原因を辿れば、そこには決まってサニタイズ漏れの制御文字、あるいは文字コードの境界で暴走した不可視のバイト列が潜んでいる。

VBScriptおよびWSH(Windows Script Host)のランタイムは、現代のモダンな言語に比べれば極めてプリミティブだ。しかし、だからこそメモリの挙動、文字コードの内部表現、そして`Asc` / `Chr`関数の限界を熟知していれば、OSの奥底まで這いずるような高速かつ確実なクレンジングエンジンを構築できる。

今回は、VBScriptの真髄である「文字列操作の極限の最適化」と「制御文字クリーニング」の実践知見を公開する。

1. 制御文字と外字がもたらす「見えない地獄」

外部連携テキストにおける障害の8割は、以下のいずれかに分類される。

1. ヌル文字 (`Chr(0)`): C言語由来の文字列終端文字が混入し、ADOやAPIが途中で文字列を切り捨てる。
2. 垂直タブ・フォームフィード (`Chr(11)`, `Chr(12)`): 印刷制御用の古いコードがテキストエディタやDBを汚染する。
3. 改行コードの混在 (`CrLf`, `Lf`, `Cr`): Windows (`\r\n`)、Linux (`\n`)、Mac古い版 (`\r`) が入り交じり、レコード長が崩壊する。
4. 外字・制御領域(OEM/CP932の特異なバイト): シフトJISのベンダ定義領域やUnicodeのプライベート使用領域(PUA)が、文字化けの温床となる。

これらを「何となく `Replace` で置換する」というアプローチは、コードの肥大化を招くだけでなく、パフォーマンスの致命的な低下を引く金棒となる。

2. VBScriptにおける文字列処理のパフォーマンス最適化原則

VBScript(OLEAUT32.BSTR)の文字列はイミュータブル(不変)である。
ループ内で `str = str & char` のような結合を繰り返すと、O(N^2)のメモリ再割り当てが発生し、数万行のテキストを処理するだけでCPUが焼き切れる。

制御文字の除去においても、一文字ずつ判定して結合するような愚行は避けるべきだ。
ここで真価を発揮するのが、RegExp(正規表現オブジェクト)のプリコンパイルと、バイナリレベルのASCIIスキャンのハイブリッドアプローチである。

3. 実装コード:極限まで最適化された制御文字クリーニングエンジン

以下のコードは、WSH環境(cscript.exe)で実行することを前提とした、実戦投入仕様のスクリプトである。
不要な制御文字(0x00〜0x1Fのうちタブ・改行を除く、および0x7F〜0x9FのC1制御文字)を完全駆逐する。

‘ ==============================================================================
‘ File: CleanControlChars.vbs
‘ Description: 外部テキストデータの不可視制御文字・不正バイト一括除去エンジン
‘ Architecture: WSH / VBScript 5.8
‘ ==============================================================================

Option Explicit

Sub Main()
Dim targetFile, outputFile
targetFile = “C:\Data\input_raw.csv”
outputFile = “C:\Data\output_clean.csv”

Dim fso, stream
Set fso = CreateObject(“Scripting.FileSystemObject”)

If Not fso.FileExists(targetFile) Then
WScript.Echo “[ERROR] 対象ファイルが存在しません: ” & targetFile
Exit Sub
End If

‘ ストリーム読み込み(UTF-8 / BOMなし or Shift-JISに対応するためADODB.Streamを推奨)
Dim rawData
rawData = ReadTextFileBSTR(targetFile, “UTF-8”)

‘ クリーニング実行
Dim cleanData
cleanData = CleanControlCharacters(rawData)

‘ 結果出力
Call WriteTextFileBSTR(outputFile, cleanData, “UTF-8”)

WScript.Echo “[SUCCESS] クリーニングが完了しました。”

Set fso = Nothing
End Sub

‘ ——————————————————————————
‘ 制御文字クリーニングコアロジック
‘ ——————————————————————————
Function CleanControlCharacters(ByVal strInput)
If IsNull(strInput) Or Len(strInput) = 0 Then
CleanControlCharacters = “”
Exit Function
End If

Dim objRegExp
Set objRegExp = New RegExp

‘ 【重要】
‘ 除去対象:
‘ – 0x00 – 0x08 (Null, Backspace等)
‘ – 0x0E – 0x1F (Shift-Out, Unit Separator等)
‘ ※ 0x09 (Tab), 0x0A (LF), 0x0D (CR) はデータ構造上必要なため除外
‘ – 0x7F (DEL)
‘ – 0x80 – 0x9F (C1制御文字)

‘ 正規表現パターン(Unicodeプロパティ未対応のVBScriptでも確実に対象を捉える)
objRegExp.Pattern = “[\x00-\x08\x0E-\x1F\x7F-\x9F]”
objRegExp.Global = True
objRegExp.IgnoreCase = True

‘ 不可視コードを空文字に置換
CleanControlCharacters = objRegExp.Replace(strInput, “”)

Set objRegExp = Nothing
End Function

‘ ——————————————————————————
‘ ADODB.Stream を用いた高速ファイル読込(メモリ効率の最適化)
‘ ——————————————————————————
Function ReadTextFileBSTR(ByVal filePath, ByVal charset)
Dim stream
Set stream = CreateObject(“ADODB.Stream”)
stream.Type = 2 ‘ adTypeText
stream.Charset = charset
stream.Open
stream.LoadFromFile filePath
ReadTextFileBSTR = stream.ReadText(-1) ‘ adReadAll
stream.Close
Set stream = Nothing
End Function

‘ ——————————————————————————
‘ ADODB.Stream を用いたファイル書込
‘ ——————————————————————————
Sub WriteTextFileBSTR(ByVal filePath, ByVal strContent, ByVal charset)
Dim stream
Set stream = CreateObject(“ADODB.Stream”)
stream.Type = 2
stream.Charset = charset
stream.Open
stream.WriteText strContent, 0 ‘ adWriteChar
stream.SaveToFile filePath, 2 ‘ adSaveCreateOverWrite
stream.Close
Set stream = Nothing
End Sub

‘ エントリポイント呼び出し
Call Main()

4. チーフアーキテクトが解説するコードの急所

A. なぜ `FileSystemObject.OpenTextFile` を使わないのか?

レガシーなVBScriptコードでは `FSO.OpenTextFile` が多用されるが、これには致命的な欠陥がある。文字コードの自動判定が貧弱であり、BOMなしUTF-8やShift-JIS混在環境において文字化けやデータ落ちを引き起こす。
本稿のコードでは `ADODB.Stream` を採用している。COMコンポーネントとしてのオーバヘッドはあるものの、マルチバイト文字のハンドリングとメモリ上のBSTR変換において圧倒的な信頼性を誇る。

B. `Asc` 関数と `Chr` 関数の限界とバイナリ的視点

VBScriptの `Asc` 関数はANSI(Windows-1252やShift-JIS等のロケール依存)を返すため、Unicode環境(BMP外やサロゲートペア)では正しくコードポイントを評価できない場合がある。
完全な制御文字の検出・監査を行いたい場合は、文字単位ではなく `ADODB.Stream` の `Type = 1 (adTypeBinary)` を使い、`Byte()` 配列としてバッファに展開し、バイトコード (`AscB` 相当の操作) で判定・スキャンするアプローチが最も堅牢である。
しかし、通常のテキストクレンジングであれば、上記スクリプトのように `RegExp` の16進数文字クラス (`\x00-\x1F` 等) を用いることで、BSTR内部のUTF-16コードユニット単位で高速かつ正確にフィルタリングが可能だ。

C. オブジェクトのライフサイクル管理

VBScriptはガベージコレクタの動作が非決定的(リファレンスカウンタ方式だが循環参照の弱点や解放タイミングの曖昧さがある)である。
スクリプトのスコープ内で生成したCOMオブジェクト(`Scripting.FileSystemObject` や `ADODB.Stream`、`VBScript.RegExp`)は、処理の終端で必ず明示的に `Set xxx = Nothing` を記述し、メモリリークを根絶すること。これがプロフェッショナルの作法である。

5. まとめ

VBScriptは過去の遺物と揶揄されることもある。しかし、Windows環境があれば追加のランタイムインストールなしで即座に動作し、OSの奥深くを直接叩けるこの言語の利便性は、インフラ自動化やデータ移行の現場において今なお唯一無二である。

不可視の制御文字という「目に見えない敵」に遭遇したとき、パニックを起こして場当たり的な置換コードを書くのではなく、文字コードの仕様とメモリモデルを理解した上で、今回紹介した堅牢なパイプラインを適用してほしい。それこそが、レガシーの荒野を生き抜くシニアエンジニアの流儀である。

タイトルとURLをコピーしました