【テクニカル・上級編】【マルチバイト文字幅正確算出】Shift-JISバイト数とUnicode文字数を考慮したコンソール出力用文字幅カウント関数 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

序文:VBScriptの深淵と、文字幅という名の「亡霊」

Windowsの運用自動化、とりわけレガシーなエンタープライズ環境において、VBScript(WSH)は今なお現役の「鋭いメス」である。PowerShellが台頭して久しいが、実行速度の初速、ランタイムの普遍性、そしてCOMコンポーネントを介したOSへの密着度において、VBScriptには独自の美学がある。

しかし、エンジニアを常に悩ませるのが、`CScript.exe`によるコンソール出力の「乱れ」だ。
VBScriptの内部文字列はすべてUnicode(UTF-16)である。一方、我々が対峙するWindowsのコマンドプロンプト(CP932/Shift-JIS環境)は、全角文字を2バイト、半角文字を1バイトとして扱う「表示幅」の世界で生きている。

`Len()`関数は文字数を返し、`LenB()`関数はメモリ上のバイト数(常に文字数×2)を返す。どちらもコンソール上の「見た目の幅」を制御するには無力だ。

本稿では、数多の基幹システムを支えてきたアーキテクトの視点から、ADODB.Streamを極限まで効率的に利用し、かつオブジェクトのライフサイクルを厳密に管理することで、1ミリの狂いもない表形式出力を実現する「究極の文字幅算出ロジック」を提示する。

—

1. 核心的課題:なぜLen()では不十分なのか

VBScriptにおいて、`Len(“A”)` は 1 であり、`Len(“あ”)` も 1 である。
しかし、コンソール出力においては “A” は1列、”あ” は2列を占有する。

これを解決するために、文字列をシステムの標準エンコーディング(日本語環境ならShift-JIS)に変換し、その「バイト数」をカウントする必要がある。この際、初心者が陥る罠が「変換のたびにADODB.Streamオブジェクトを生成・破棄する」という愚行だ。ループ内でこれをやれば、ガベージコレクションが追いつかず、メモリ空間を汚染し、パフォーマンスは見るに堪えないものとなる。

—

2. 極限の知見:静的オブジェクトと再利用の美学

プロフェッショナルのコードには、無駄なI/Oやオブジェクト生成は存在しない。
文字幅を算出する関数は、一度生成した `ADODB.Stream` インスタンスを「使い回す」設計にすべきだ。

実装のポイント:

1. ADODB.Stream の永続化: 関数呼び出しのたびに `CreateObject` しない。
2. 文字セットの固定: `Shift-JIS` を明示的に指定し、OSのロケールに依存しない堅牢性を確保する。
3. パディング処理の統合: 単に長さを測るだけでなく、指定幅に整形(PadRight/PadLeft)する機能まで持たせるのが実務的だ。

—

3. 実装:Shift-JISバイト数に基づく文字幅算出クラス

以下に、私が長年使い込んできた、堅牢かつ高速な整形ユーティリティを公開する。あえてクラス(Class)化することで、カプセル化とリソース管理を明確にしている。

‘
‘ Class: TextFormatter
‘ 概要: コンソール出力における文字幅(Shift-JISバイト数)を正確に制御する
‘
Class TextFormatter
Private m_objStream

‘ クラス初期化時にStreamオブジェクトを一度だけ生成
Private Sub Class_Initialize()
Set m_objStream = CreateObject(“ADODB.Stream”)
m_objStream.Type = 2 ‘ adTypeText
m_objStream.Charset = “Shift-JIS”
End Sub

‘ クラス終了時に確実にリソースを解放
Private Sub Class_Terminate()
If Not m_objStream Is Nothing Then
m_objStream.Close
Set m_objStream = Nothing
End If
End Sub

‘—————————————————————————
‘ GetVisualWidth: 文字列のコンソール表示幅(SJISバイト数)を返す
‘—————————————————————————
Public Function GetVisualWidth(ByVal strTarget)
If strTarget = “” Then
GetVisualWidth = 0
Exit Function
End If

m_objStream.Open
m_objStream.WriteText strTarget
m_objStream.Position = 0

‘ ADODB.StreamはBOM(Byte Order Mark)を付与する場合があるが
‘ Shift-JIS指定時は純粋なバイト数がSizeで取得可能
GetVisualWidth = m_objStream.Size
m_objStream.Close
End Function

‘—————————————————————————
‘ PadRight: 文字列を指定幅に右パディング(左寄せ)する
‘—————————————————————————
Public Function PadRight(ByVal strTarget, ByVal totalWidth)
Dim currentWidth, paddingCount
currentWidth = GetVisualWidth(strTarget)
paddingCount = totalWidth – currentWidth

If paddingCount > 0 Then
PadRight = strTarget & String(paddingCount, ” “)
Else
PadRight = strTarget
End If
End Function

‘—————————————————————————
‘ PadLeft: 文字列を指定幅に左パディング(右寄せ)する
‘—————————————————————————
Public Function PadLeft(ByVal strTarget, ByVal totalWidth)
Dim currentWidth, paddingCount
currentWidth = GetVisualWidth(strTarget)
paddingCount = totalWidth – currentWidth

If paddingCount > 0 Then
PadLeft = String(paddingCount, ” “) & strTarget
Else
PadLeft = strTarget
End If
End Function
End Class

—

4. 運用:実戦でのデータ出力例

このクラスを使い、実際に「美しい表形式」をコンソールに描画する例を示す。
ここでは、サーバーの監視ログやユーザーリストを想定した。

‘ メインロジック
Dim formatter, rows, row, i
Set formatter = New TextFormatter

‘ テストデータ:全角・半角・記号が混在
rows = Array(_
Array(“ID”, “UserName”, “Status”, “LastAccess”), _
Array(“001”, “管理者”, “Active”, “2023/10/01”), _
Array(“002”, “田中 太郎”, “Pending”, “2023/10/05”), _
Array(“003”, “Guest_User”, “Offline”, “2023/11/12”), _
Array(“004”, “アイウエオ株式会社”, “Active”, “2023/12/25”) _
)

WScript.Echo “— System Status Report —”
WScript.Echo “”

For i = 0 To UBound(rows)
row = rows(i)
‘ 各カラムの幅を厳密に指定(SJISバイト数基準)
WScript.StdOut.Write formatter.PadRight(row(0), 6)
WScript.StdOut.Write formatter.PadRight(row(1), 20)
WScript.StdOut.Write formatter.PadRight(row(2), 12)
WScript.StdOut.Write formatter.PadRight(row(3), 15)
WScript.StdOut.WriteLine “”

‘ ヘッダーの下に区切り線を引く
If i = 0 Then
WScript.Echo String(53, “-“)
End If
Next

Set formatter = Nothing ‘ 明示的解放

実行結果のイメージ

ID UserName Status LastAccess
—————————————————–
001 管理者 Active 2023/10/01
002 田中 太郎 Pending 2023/10/05
003 Guest_User Offline 2023/11/12
004 アイウエオ株式会社 Active 2023/12/25

※半角カナ(アイウエオ)もSJISでは1バイトとして計算されるため、全角文字と混在してもカラムが一切崩れない。

—

5. アーキテクトが語る「保守」の極意

この手法が優れているのは、単なる「見た目」の問題を解決するからではない。

1. 環境依存の排除: `ADODB.Stream` で `Shift-JIS` を明示することで、将来的にシステムロケールが UTF-8 に変更された Windows Server 環境(昨今の多言語対応の流れ)においても、期待したバイト数計算を維持できる。
2. メモリ効率: `Class_Initialize` と `Class_Terminate` によるオブジェクトのライフサイクル管理により、大規模なループ処理でもメモリリークのリスクを最小化している。
3. 可読性と拡張性: カラム幅の計算ロジックがビジネスロジックから分離されているため、後から「全角スペースを考慮したい」「サロゲートペアに対応したい」といった要件が出ても、クラス内の修正だけで完結する。

VBScriptは古い技術かもしれない。しかし、その制約の中でいかに「正解」を導き出すか。その姿勢こそが、システムの寿命を延ばし、保守コストを下げる唯一の道である。現場の最前線で戦う諸兄には、この「1バイトの重み」を大切にしていただきたい。

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