【引数解析の実践】WScript.Arguments.Named と Unnamed を使い分けたプロ仕様のコマンドラインパーサー構築
開発現場でよく見かける光景がある。VBScript製ツールの実行時に「第1引数に何、第2引数に何を指定するか忘れた」「設定ファイルを指定したいだけなのに、全引数を順番通りに渡さなければならない」。そして、引数の数が合わないという理由で、スクリプトの冒頭で `WScript.Quit` が冷酷に呼び出される。
プロの業務自動化エンジニアにとって、これは「設計の敗北」だ。
真に堅牢なWSH(Windows Script Host)スクリプトとは、オペレーターのミスを誘発せず、オプションの順序に依存せず、必須パラメータの欠落を自ら検知して親切なUsage(使い方)を提示するものでなければならない。
今回は、`WScript.Arguments.Named` と `WScript.Arguments.Unnamed` の特性を極限まで引き出し、大規模なエンタープライズ環境でも耐えうる「プロダクション品質のコマンドラインパーサー」の構築手法を伝授する。
—
なぜ素の `WScript.Arguments` ではダメなのか?
VBScriptで引数を扱う際、多くの初心者は単一のコレクションである `WScript.Arguments` をループさせ、インデックスで値を取り出す。
‘ 【アンチパターン】これだから保守性最悪と言われる
If WScript.Arguments.Count < 2 Then
WScript.Echo "引数が足りません"
WScript.Quit
End If
Dim inputPath: inputPath = WScript.Arguments(0)
Dim outputPath: outputPath = WScript.Arguments(1)
このアプローチには致命的な欠陥がある。
1. 拡張性の欠如: 後から「デバッグモード(`/debug`)」や「設定ファイル指定(`/config:path`)」を追加した瞬間、インデックスがすべてズレて既存のバッチ処理やタスクスケジューラの登録が全滅する。
2. 順序への強制的依存: 引数の順序を覚える必要があるため、人間工学的に最悪である。
ここでモダンなCLI(Command Line Interface)の設計思想を持ち込もう。引数は大きく2つに分類すべきだ。
- Named(名前付き引数): `/key:value` または `/switch` の形式。順序に依存せず、オプションや設定値に用いる。
- Unnamed(位置引数): 単なる値。主に「入力ファイル」や「対象ディレクトリ」など、スクリプトの根幹となる必須リソースに用いる。
WSHのランタイムは、この2つをネイティブで分離して保持する機能(`WScript.Arguments.Named` / `Unnamed`)を持っている。これを使いこなさない手はない。
—
プロ仕様コマンドラインパーサーの設計要件
今回構築するモジュールが満たすべき要件は以下の通りだ。
1. デュアル・パース: `/config:` のような名前付き引数と、ターゲットパスのような位置引数を完璧に分離して取得する。
2. デフォルト値のフォールバック: 指定されなかった名前付き引数には、安全なデフォルト値を自動適用する。
3. 厳格なバリデーション: 不足している必須引数を検知した場合、即座に処理を中断し、分かりやすいヘルプ(Usage)を表示する。
4. 堅牢なエラーハンドリング: `On Error Resume Next` を戦略的に駆使し、予期せぬ型ミスマッチやオブジェクト不在を防ぐ。
—
実装コード:プロダクション対応パーサーモジュール
以下のコードを `CommandLineParser.vbs` として保存してほしい。そのままインクルードして使えるクラスライブラリ、あるいはメインスクリプトのテンプレートとして機能する。
‘ ==============================================================================
‘ Script Name: CommandLineParser.vbs
‘ Description: Named/Unnamed引数を完璧に制御するプロ仕様のコマンドラインパーサー
‘ Author: Enterprise Automation Architect
‘ ==============================================================================
Option Explicit
‘ メイン処理の実行
Main
Sub Main()
‘ 1. パーサーの初期化と定義
Dim parser
Set parser = New CommandLineParser
‘ — 引数の仕様定義 —
‘ 位置引数 (Unnamed) の必要数を設定 (例: 今回は入力ファイルと出力先の2つを必須とする)
parser.RequiredUnnamedCount = 2
‘ 名前付き引数 (Named) のデフォルト値および存在チェックの設定
‘ AddNamed(キー名, デフォルト値, 必須フラグ)
Call parser.AddNamed(“config”, “C:\Config\default.ini”, False)
Call parser.AddNamed(“encoding”, “Shift_JIS”, False)
Call parser.AddNamed(“debug”, “false”, False)
‘ 2. パース実行(不正な場合はここでUsageを表示して終了)
If Not parser.Parse() Then
Exit Sub
End If
‘ 3. パース結果の取得とビジネスロジックへの引き渡し
Dim inputTarget, outputTarget
inputTarget = parser.GetUnnamed(0)
outputTarget = parser.GetUnnamed(1)
Dim configPath, encoding, isDebug
configPath = parser.GetNamed(“config”)
encoding = parser.GetNamed(“encoding”)
isDebug = CBool(parser.GetNamed(“debug”))
‘ — 【実務での処理シミュレーション】 —
WScript.Echo “=== 実行パラメータ確認 ===”
WScript.Echo “入力対象 : ” & inputTarget
WScript.Echo “出力対象 : ” & outputTarget
WScript.Echo “設定文件 : ” & configPath
WScript.Echo “文字エンコード: ” & encoding
WScript.Echo “デバッグモード: ” & isDebug
‘ ここに実際の業務処理(ファイル操作、DB接続など)を記述する
End Sub
‘ ==============================================================================
‘ Class: CommandLineParser
‘ WSHのArgumentsオブジェクトをカプセル化し、堅牢な検証を提供するクラス
‘ ==============================================================================
Class CommandLineParser
Private m_NamedDict
Private m_DefaultDict
Private m_RequiredNamedDict
Private m_RequiredUnnamedCount
‘ コンストラクタ
Private Sub Class_Initialize()
Set m_NamedDict = CreateObject(“Scripting.Dictionary”)
Set m_DefaultDict = CreateObject(“Scripting.Dictionary”)
Set m_RequiredNamedDict = CreateObject(“Scripting.Dictionary”)
m_RequiredUnnamedCount = 0
End Sub
‘ デストラクタ
Private Sub Class_Terminate()
Set m_NamedDict = Nothing
Set m_DefaultDict = Nothing
Set m_RequiredNamedDict = Nothing
End Sub
‘ プロパティ: 必須の位置引数の数
Public Property Let RequiredUnnamedCount(val)
m_RequiredUnnamedCount = CInt(val)
End Property
‘ 名前付き引数の登録
Public Sub AddNamed(ByVal key, ByVal defaultValue, ByVal isRequired)
key = LCase(key) ‘ キーは大文字小文字を区別しない
m_DefaultDict(key) = defaultValue
m_RequiredNamedDict(key) = CBool(isRequired)
End Sub
‘ パース実行メインメソッド
Public Function Parse()
Dim argNamed, argUnnamed
Set argNamed = WScript.Arguments.Named
Set argUnnamed = WScript.Arguments.Unnamed
‘ 1. 位置引数の数チェック
If argUnnamed.Count < m_RequiredUnnamedCount Then
Call ShowUsage("エラー: 必要な位置引数が不足しています。")
Parse = False
Exit Function
End If
' 2. 名前付き引数の解決(デフォルト値の適用と必須チェック)
Dim key, keys
keys = m_DefaultDict.Keys()
For Each key in keys
If argNamed.Exists(key) Then
' コマンドラインから渡された値を使用
m_NamedDict(key) = argNamed(key)
Else
' 必須チェック
If m_RequiredNamedDict(key) = True Then
Call ShowUsage("エラー: 必須の名前付き引数 '/" & key & "' が指定されていません。")
Parse = False
Exit Function
End If
' デフォルト値をフォールバック
m_NamedDict(key) = m_DefaultDict(key)
End If
Next
Parse = True
End Function
' 位置引数の取得 (インデックス指定)
Public Function GetUnnamed(ByVal index)
Dim argUnnamed
Set argUnnamed = WScript.Arguments.Unnamed
If index >= 0 And index < argUnnamed.Count Then
GetUnnamed = argUnnamed(index)
Else
GetUnnamed = ""
End If
End Function
' 名前付き引数の取得 (キー指定)
Public Function GetNamed(ByVal key)
key = LCase(key)
If m_NamedDict.Exists(key) Then
GetNamed = m_NamedDict(key)
Else
GetNamed = ""
End If
End Function
' ヘルプ(Usage)の動的生成と表示
Private Sub ShowUsage(ByVal errorMessage)
WScript.Echo "=================================================="
WScript.Echo errorMessage
WScript.Echo "=================================================="
WScript.Echo "【使い方 (Usage)】"
WScript.Echo "cscript.exe " & WScript.ScriptName & " [位置引数1] [位置引数2] [オプション...]"
WScript.Echo vbCrLf & "【必須の位置引数】"
WScript.Echo " 1. 入力リソースのパス"
WScript.Echo " 2. 出力先のパス"
WScript.Echo vbCrLf & "【名前付き引数 (オプション)】"
Dim key, keys
keys = m_DefaultDict.Keys()
For Each key in keys
Dim reqStr
If m_RequiredNamedDict(key) Then reqStr = " (必須)" Else reqStr = " (任意)"
WScript.Echo " /" & key & ":<値>” & reqStr & ” [デフォルト: ” & m_DefaultDict(key) & “]”
Next
WScript.Echo “==================================================”
End Sub
End Class
—
コードの解説:プロが仕込んだ堅牢性のポイント
このコードが「単なるサンプル」ではなく「プロダクションコード」たる所以を解説する。
1. `Scripting.Dictionary` による大文字小文字の吸収
コマンドライン引数で `/Config:test.ini` と書かれようが `/CONFIG:test.ini` と書かれようが、開発者が指定するキーの揺れでバグが出てはならない。
モジュール内では `LCase(key)` を通してDictionaryに格納・取得しているため、大文字小文字の差異を完全に吸収する。
2. 二段階のバリデーション(Fail-Fast原則)
処理の最初の段階(`Parse`メソッドの冒頭)で、引数の不足や不正をすべて弾いている。
業務自動化スクリプトにおいて、「途中でファイルがないことに気づいて異常終了する」よりも「起動した瞬間にパラメータの不備を指摘して即座に終了する(Fail-Fast)」方が、ジョブ管理システム(Acrobat, Task Scheduler, JP1など)からの監視において圧倒的に優れている。
3. 自己文書化するUsage生成
`ShowUsage` メソッドは、クラスに登録されたメタデータ(デフォルト値や必須フラグ)を動的に読み取ってヘルプ画面を構築する。
これにより、「コードを変更したのにヘルプの記述を書き忘れて運用者が混乱する」という保守上のアンチパターンを構造的に防いでいる。
—
現場で即座に使える実行コマンド例
上記のスクリプトを保存したら、コマンドプロンプトやバッチファイルから以下のように実行して挙動を確認してほしい。
パターンA: 最小限の引数(デフォルト値が適用される)
cscript //nologo CommandLineParser.vbs “C:\Data\input.csv” “C:\Data\output.csv”
パターンB: 名前付き引数をフル活用するケース
cscript //nologo CommandLineParser.vbs “C:\Data\input.csv” “C:\Data\output.csv” /config:”D:\Project\custom.ini” /encoding:”UTF-8″ /debug:true
パターンC: 必須引数が足りずにUsageが表示されるケース
cscript //nologo CommandLineParser.vbs “C:\Data\input.csv”
(結果として、親切なエラーメッセージと使い方ガイドがコンソールに出力され、安全に終了する)
—
アーキテクトからの最終助言
VBScriptはレガシーな言語として語られることが多いが、適切な設計パターン(カプセル化、関心の分離、エラーの事前検知)を適用すれば、現代の複雑なWindowsインフラストラクチャの中でも極めて軽量で強靭な自動化の武器となる。
「とりあえず動けばいいや」という雑な引数処理は、半年後の自分、あるいは引き継いだ後任のエンジニアへの呪いとなる。
今回紹介した `CommandLineParser` クラスをあなたのツールボックスの標準装備とし、クオリティの高い自動化ライフを構築してほしい。
