【VBA】絶対パス・相対パス・UNCに対応!安全な「ファイル名抽出関数」の作り方(単体テストコード付き)
Excel VBAでファイル操作を行う際、ファイルパスから「ファイル名(拡張子付き)」だけを取り出したい場面は日常茶飯事です。
一見簡単そうに見えますが、実際の現場では以下のような多様なパス表記(表記ゆれ)やエッジケースが飛び交います。
C:\Folder\Sub\sample.xlsm(標準的な絶対パス).\sample.xlsmや..\sample.xlsm(相対パス)C:/Folder/Sub/sample.xlsm(Web由来やMac混在環境のスラッシュ区切り)\\Server\Share\sample.xlsm(UNCネットワークパス)C:\Folder\Sub\やC:\(末尾が \ で終わるフォルダ・ドライブ指定)
これらを安易な Split や Dir 関数で処理しようとすると、存在しないパスでエラーになったり、フォルダパスから誤った文字列を取得したりする原因になります。
今回は、「契約による設計(DbC)」のコメントフォーマットに則り、どんなパスが渡されても安全かつ超高速にファイル名のみを抽出する汎用関数と、その単体テストコードを解説します。
1. 実装:ファイル名抽出関数 (GetFileNameFromPath)
外部オブジェクト(FileSystemObject)を呼び出さず、VBA標準の文字列操作(InStrRev / Replace)のみで完結させているため、動作が非常に軽量です。
' ==============================================================================
' [機能名] パス文字列からのファイル名抽出
'
' [処理内容]
' ・絶対パス、相対パス(.\ や ..\)、UNCパス、/区切りなど様々なパス形式から
' ファイル名(拡張子含む)部分のみを抽出して返します。
' ・末尾がパス区切り文字(\ や /)の場合やドライブ直下など、ファイル名が
' 指定されていない場合は空文字 ("") を返します。
'
' [引数]
' @param {String} pathString : 必須 : 解析対象のパス文字列
'
' [事前条件 (Requires)]
' ・特になし(任意の文字列・空文字を受け付けます)。
'
' [事後条件 (Ensures)]
' ・ファイル名が存在する場合 : 拡張子を含むファイル名文字列を返す(例: "sample.xlsm")。
' ・ファイル名が存在しない場合 : 空文字 ("") を返す。
'
' [戻り値]
' @return {String} 抽出されたファイル名
'
' [例外処理 (Throws)]
' ・文字列操作のみを行うため実行時エラーは発生せず、不当な形式は空文字を返す。
'
' [履歴]
' 2026/10/08 [作成者名] 新規作成
' ==============================================================================
Public Function GetFileNameFromPath(ByVal pathString As String) As String
' 前後の空白を除去
Dim cleanPath As String
cleanPath = Trim(pathString)
' 1. 空文字チェック
If cleanPath = "" Then
GetFileNameFromPath = ""
Exit Function
End If
' 2. パス区切り文字を '\' に統一 (/ を \ に変換)
cleanPath = Replace(cleanPath, "/", "\")
' 3. 末尾が '\' または ':' の場合(ディレクトリ指定、ドライブ指定)はファイル名なしと判定
If Right$(cleanPath, 1) = "\" Or Right$(cleanPath, 1) = ":" Then
GetFileNameFromPath = ""
Exit Function
End If
' 4. 末尾から最後の '\' を検索
Dim lastSlashPos As Long
lastSlashPos = InStrRev(cleanPath, "\")
If lastSlashPos > 0 Then
' '\' より右側の文字列を取得
GetFileNameFromPath = Mid$(cleanPath, lastSlashPos + 1)
Else
' '\' が見つからない場合、ドライブ文字(例 "C:sample.xlsm")の可能性を考慮
Dim lastColonPos As Long
lastColonPos = InStrRev(cleanPath, ":")
If lastColonPos > 0 Then
GetFileNameFromPath = Mid$(lastColonPos + 1)
Else
' パス区切りもコロンもない場合は、入力文字列全体をファイル名とみなす
GetFileNameFromPath = cleanPath
End If
End If
End Function
' [機能名] パス文字列からのファイル名抽出
'
' [処理内容]
' ・絶対パス、相対パス(.\ や ..\)、UNCパス、/区切りなど様々なパス形式から
' ファイル名(拡張子含む)部分のみを抽出して返します。
' ・末尾がパス区切り文字(\ や /)の場合やドライブ直下など、ファイル名が
' 指定されていない場合は空文字 ("") を返します。
'
' [引数]
' @param {String} pathString : 必須 : 解析対象のパス文字列
'
' [事前条件 (Requires)]
' ・特になし(任意の文字列・空文字を受け付けます)。
'
' [事後条件 (Ensures)]
' ・ファイル名が存在する場合 : 拡張子を含むファイル名文字列を返す(例: "sample.xlsm")。
' ・ファイル名が存在しない場合 : 空文字 ("") を返す。
'
' [戻り値]
' @return {String} 抽出されたファイル名
'
' [例外処理 (Throws)]
' ・文字列操作のみを行うため実行時エラーは発生せず、不当な形式は空文字を返す。
'
' [履歴]
' 2026/10/08 [作成者名] 新規作成
' ==============================================================================
Public Function GetFileNameFromPath(ByVal pathString As String) As String
' 前後の空白を除去
Dim cleanPath As String
cleanPath = Trim(pathString)
' 1. 空文字チェック
If cleanPath = "" Then
GetFileNameFromPath = ""
Exit Function
End If
' 2. パス区切り文字を '\' に統一 (/ を \ に変換)
cleanPath = Replace(cleanPath, "/", "\")
' 3. 末尾が '\' または ':' の場合(ディレクトリ指定、ドライブ指定)はファイル名なしと判定
If Right$(cleanPath, 1) = "\" Or Right$(cleanPath, 1) = ":" Then
GetFileNameFromPath = ""
Exit Function
End If
' 4. 末尾から最後の '\' を検索
Dim lastSlashPos As Long
lastSlashPos = InStrRev(cleanPath, "\")
If lastSlashPos > 0 Then
' '\' より右側の文字列を取得
GetFileNameFromPath = Mid$(cleanPath, lastSlashPos + 1)
Else
' '\' が見つからない場合、ドライブ文字(例 "C:sample.xlsm")の可能性を考慮
Dim lastColonPos As Long
lastColonPos = InStrRev(cleanPath, ":")
If lastColonPos > 0 Then
GetFileNameFromPath = Mid$(lastColonPos + 1)
Else
' パス区切りもコロンもない場合は、入力文字列全体をファイル名とみなす
GetFileNameFromPath = cleanPath
End If
End If
End Function
設計のポイント
- 表記ゆれの自動吸収:
Replace(cleanPath, "/", "\")で区切り文字をあらかじめ統一することで、Linux/Mac由来やHTMLリンク形式のパス(/)も問題なく解析できます。 - 「ファイル名なし」のガード:末尾が
\や:で終わっている場合(例:C:\Folder\やC:)は「フォルダまたはドライブ全体の指図」とみなして即座に空文字""を返します。 - ファイルの実在に依存しない:
Dir関数を使わず純粋な文字列解析を行っているため、「まだ保存されていないパス」や「アクセス権限のないネットワークパス」であっても正確にファイル名を抽出できます。
2. 品質を担保する単体テストコード
実務で「本当にあらゆるパターンで動くか?」を検証するために、データ駆動型のテストプロシージャを用意します。
エントリーポイントには以前紹介した RAIIパターン(CAspect)を配置し、テスト実行ログと処理時間を自動で記録します。
' ==============================================================================
' [機能名] GetFileNameFromPath 関数の網羅的テスト
' ==============================================================================
Public Sub Test_GetFileNameFromPath()
' エントリーポイント用アスペクト(ログ・実行時間自動化)
Dim aspect As New CAspect: aspect.Begin "Test_GetFileNameFromPath"
' テストケース(入力パス と 期待される結果 のペア)
Dim testCases As Variant
testCases = Array( _
Array("C:\Folder\Sub\sample.xlsm", "sample.xlsm", "標準的な絶対パス"), _
Array("C:\sample.xlsm", "sample.xlsm", "ドライブ直下のファイル"), _
Array(".\sample.xlsm", "sample.xlsm", "カレント相対パス (.\)"), _
Array("..\Folder\sample.xlsm", "sample.xlsm", "親ディレクトリ相対パス (..\)"), _
Array("sample.xlsm", "sample.xlsm", "ファイル名のみ"), _
Array("C:/Folder/Sub/sample.xlsm", "sample.xlsm", "スラッシュ区切り (/)"), _
Array("\\Server\Share\sample.xlsm", "sample.xlsm", "UNCネットワークパス"), _
Array("C:\Folder\Sub\", "", "末尾が\(フォルダ指定)"), _
Array("C:\", "", "ドライブルート (C:\)"), _
Array("C:", "", "ドライブ指定 (C:)"), _
Array("\\Server\Share\", "", "UNC共有ルート"), _
Array("", "", "空文字") _
)
Debug.Print "=================================================="
Debug.Print " GetFileNameFromPath テスト開始"
Debug.Print "=================================================="
Dim passCount As Long: passCount = 0
Dim failCount As Long: failCount = 0
Dim i As Long
For i = LBound(testCases) To UBound(testCases)
Dim inputPath As String: inputPath = testCases(i)(0)
Dim expected As String: expected = testCases(i)(1)
Dim memo As String: memo = testCases(i)(2)
' 関数の実行
Dim actual As String
actual = GetFileNameFromPath(inputPath)
' 結果判定
If actual = expected Then
passCount = passCount + 1
Debug.Print "[PASS] " & memo & " => """ & actual & """"
Else
failCount = failCount + 1
Debug.Print "[FAIL] " & memo
Debug.Print " 入力: """ & inputPath & """"
Debug.Print " 期待: """ & expected & """"
Debug.Print " 実際: """ & actual & """"
End If
Next i
Debug.Print "--------------------------------------------------"
Debug.Print " 結果: SUCCESS=" & passCount & " / FAIL=" & failCount
Debug.Print "=================================================="
End Sub
' [機能名] GetFileNameFromPath 関数の網羅的テスト
' ==============================================================================
Public Sub Test_GetFileNameFromPath()
' エントリーポイント用アスペクト(ログ・実行時間自動化)
Dim aspect As New CAspect: aspect.Begin "Test_GetFileNameFromPath"
' テストケース(入力パス と 期待される結果 のペア)
Dim testCases As Variant
testCases = Array( _
Array("C:\Folder\Sub\sample.xlsm", "sample.xlsm", "標準的な絶対パス"), _
Array("C:\sample.xlsm", "sample.xlsm", "ドライブ直下のファイル"), _
Array(".\sample.xlsm", "sample.xlsm", "カレント相対パス (.\)"), _
Array("..\Folder\sample.xlsm", "sample.xlsm", "親ディレクトリ相対パス (..\)"), _
Array("sample.xlsm", "sample.xlsm", "ファイル名のみ"), _
Array("C:/Folder/Sub/sample.xlsm", "sample.xlsm", "スラッシュ区切り (/)"), _
Array("\\Server\Share\sample.xlsm", "sample.xlsm", "UNCネットワークパス"), _
Array("C:\Folder\Sub\", "", "末尾が\(フォルダ指定)"), _
Array("C:\", "", "ドライブルート (C:\)"), _
Array("C:", "", "ドライブ指定 (C:)"), _
Array("\\Server\Share\", "", "UNC共有ルート"), _
Array("", "", "空文字") _
)
Debug.Print "=================================================="
Debug.Print " GetFileNameFromPath テスト開始"
Debug.Print "=================================================="
Dim passCount As Long: passCount = 0
Dim failCount As Long: failCount = 0
Dim i As Long
For i = LBound(testCases) To UBound(testCases)
Dim inputPath As String: inputPath = testCases(i)(0)
Dim expected As String: expected = testCases(i)(1)
Dim memo As String: memo = testCases(i)(2)
' 関数の実行
Dim actual As String
actual = GetFileNameFromPath(inputPath)
' 結果判定
If actual = expected Then
passCount = passCount + 1
Debug.Print "[PASS] " & memo & " => """ & actual & """"
Else
failCount = failCount + 1
Debug.Print "[FAIL] " & memo
Debug.Print " 入力: """ & inputPath & """"
Debug.Print " 期待: """ & expected & """"
Debug.Print " 実際: """ & actual & """"
End If
Next i
Debug.Print "--------------------------------------------------"
Debug.Print " 結果: SUCCESS=" & passCount & " / FAIL=" & failCount
Debug.Print "=================================================="
End Sub
3. 実行結果(イミディエイトウィンドウ)
10:00:00 [START] Test_GetFileNameFromPath (User: developer)
==================================================
GetFileNameFromPath テスト開始
==================================================
[PASS] 標準的な絶対パス => "sample.xlsm"
[PASS] ドライブ直下のファイル => "sample.xlsm"
[PASS] カレント相対パス (.\) => "sample.xlsm"
[PASS] 親ディレクトリ相対パス (..\) => "sample.xlsm"
[PASS] ファイル名のみ => "sample.xlsm"
[PASS] スラッシュ区切り (/) => "sample.xlsm"
[PASS] UNCネットワークパス => "sample.xlsm"
[PASS] 末尾が\(フォルダ指定) => ""
[PASS] ドライブルート (C:\) => ""
[PASS] ドライブ指定 (C:) => ""
[PASS] UNC共有ルート => ""
[PASS] 空文字 => ""
--------------------------------------------------
結果: SUCCESS=12 / FAIL=0
==================================================
10:00:00 [END] Test_GetFileNameFromPath (0.001s)
==================================================
GetFileNameFromPath テスト開始
==================================================
[PASS] 標準的な絶対パス => "sample.xlsm"
[PASS] ドライブ直下のファイル => "sample.xlsm"
[PASS] カレント相対パス (.\) => "sample.xlsm"
[PASS] 親ディレクトリ相対パス (..\) => "sample.xlsm"
[PASS] ファイル名のみ => "sample.xlsm"
[PASS] スラッシュ区切り (/) => "sample.xlsm"
[PASS] UNCネットワークパス => "sample.xlsm"
[PASS] 末尾が\(フォルダ指定) => ""
[PASS] ドライブルート (C:\) => ""
[PASS] ドライブ指定 (C:) => ""
[PASS] UNC共有ルート => ""
[PASS] 空文字 => ""
--------------------------------------------------
結果: SUCCESS=12 / FAIL=0
==================================================
10:00:00 [END] Test_GetFileNameFromPath (0.001s)
文字列操作系の便利関数を作成する際は、このように「事前条件・事後条件を明記した汎用関数」と「配列を使った自動テスト」をセットで用意しておくことで、将来の改修でもデグレード(先祖返りバグ)を恐れずに開発を進めることができます。チームでのVBA開発や共通ライブラリ化にぜひ役立ててみてください。
PR