파일 목록을 화면에 보여 주거나 로그에 남길 때, 전체 경로 대신 파일 이름만 필요할 때가 많습니다. OpenFileDialog.FileName이나 Directory.GetFiles가 돌려주는 값은 전부 전체 경로라서 한 번 걸러 줘야 하죠. 방법은 두 가지가 있고, 저는 거의 모든 경우에 첫 번째 방법을 씁니다.

Path.GetFileName 쓰기

Private Function GetFileName(ByVal path As String) As String
   Dim _filename As String = System.IO.Path.GetFileName(path)
   Return _filename
End Function

System.IO.Path.GetFileName은 경로 문자열에서 마지막 디렉터리 구분자 뒤쪽, 즉 파일 이름과 확장자를 돌려줍니다. C:\Data\2023\report.xlsx를 넣으면 report.xlsx가 나옵니다. 윈도우에서는 \와 /를 모두 구분자로 인식하니, 웹 주소처럼 슬래시로 된 경로도 처리됩니다. 경로가 구분자로 끝나면(C:\Data\) 빈 문자열이 나오고, Nothing을 넣으면 Nothing이 돌아옵니다.

광고

이 메서드는 실제로 파일이 있는지 확인하지 않습니다. 문자열만 잘라 주는 거라 존재하지 않는 경로를 넣어도 결과가 나옵니다. 그래서 빠르고 예외 날 일도 거의 없습니다.

같은 이유로 폴더 경로를 넣어도 그냥 마지막 부분을 돌려줍니다. C:\Data 처럼 끝에 구분자가 없는 폴더 경로를 넣으면 Data 가 나와서, 파일 이름인지 폴더 이름인지 결과만 보고는 구분할 수 없습니다. 목록에 파일과 폴더가 섞여 있다면 File.Exists 나 Directory.Exists 로 따로 확인해야 합니다. 반대로 폴더 이름을 얻으려고 할 때는 이 성질을 그대로 이용해서, GetFileName(경로.TrimEnd("\"c)) 처럼 끝의 구분자만 떼고 넘기면 마지막 폴더 이름이 나옵니다.

Split으로 직접 자르기

Private Function GetFileName_OtherMethod(ByVal path As String) As String
    Dim _filename As String = ""
    Dim sep() As Char = {"/", "\", "//"}
    _filename = path.Split(sep).Last()
    Return _filename
End Function

구분자 문자들로 경로를 쪼갠 다음, 마지막 조각을 가져오는 방식입니다. Last()는 LINQ 확장 메서드라 System.Linq가 가져와져 있어야 합니다(VB 프로젝트는 보통 기본으로 들어 있습니다).

이 코드에서 하나 짚을 부분은 "//"입니다. 배열 형식이 Char()라서 요소 하나는 한 글자여야 하는데, Option Strict Off 상태에서는 "//"가 첫 글자 /로 바뀌어 들어가고 Option Strict On이면 컴파일 오류가 납니다. 이미 "/"가 있으니 실제로는 없어도 되는 항목입니다. 그리고 VB에서는 \가 이스케이프 문자가 아니어서 "\" 그대로 역슬래시 한 글자입니다. C# 코드를 옮길 때 "\\"로 쓰면 오히려 두 글자가 되니 헷갈리지 마세요.

직접 자르는 방식은 구분자를 내 마음대로 정할 수 있다는 점 말고는 장점이 별로 없습니다. URL에 ?a=b/c 같은 쿼리 문자열이 붙어 있으면 엉뚱한 조각이 나오는 건 두 방식 모두 마찬가지라서, 웹 주소에서 파일 이름을 뽑을 때는 먼저 New Uri(url).AbsolutePath로 경로 부분만 떼어 낸 뒤 GetFileName에 넘기는 게 안전합니다.

웹 주소의 한글 파일 이름

주소에 한글 파일 이름이 들어 있으면 하나 더 걸립니다. 브라우저 주소창에는 한.jpg 로 보여도 실제 URL은 %ED%95%9C.jpg 처럼 퍼센트 인코딩돼 있고, AbsolutePath 는 이 인코딩을 그대로 돌려줍니다. 그래서 위 방법대로 하면 파일 이름이 %ED%95%9C.jpg 로 나옵니다. 사람이 읽을 이름이 필요하면 Uri.UnescapeDataString 으로 한 번 풀어 주거나, 처음부터 디코딩된 경로를 돌려주는 LocalPath 를 쓰면 됩니다.

Dim u As New Uri("https://example.com/img/%ED%95%9C.jpg?w=300")
Dim encoded As String = IO.Path.GetFileName(u.AbsolutePath)   '// %ED%95%9C.jpg
Dim name As String = IO.Path.GetFileName(u.LocalPath)         '// 한.jpg

두 경우 모두 쿼리 문자열 ?w=300 은 경로에 포함되지 않습니다. 이 이름을 그대로 저장 파일 이름으로 쓸 거라면 윈도우 파일 이름에 쓸 수 없는 문자가 들어 있을 수 있으니, IO.Path.GetInvalidFileNameChars() 에 든 문자를 다른 글자로 바꾸는 처리를 한 번 거치는 게 좋습니다.

같이 알아 두면 편한 Path 메서드

메서드 C:\Data\report.xlsx 결과
Path.GetFileName report.xlsx
Path.GetFileNameWithoutExtension report
Path.GetExtension .xlsx
Path.GetDirectoryName C:\Data

확장자 관련 메서드는 마지막 점만 기준으로 봅니다. archive.tar.gz 를 GetFileNameWithoutExtension 에 넣으면 archive 가 아니라 archive.tar 가 나오고, 확장자가 없는 README 는 GetExtension 결과가 빈 문자열입니다. 점으로 시작하는 .gitignore 는 이름 전체가 확장자로 취급돼 GetExtension 이 .gitignore 를 돌려줍니다. 확장자로 파일 종류를 거르는 코드라면 이런 경우를 한 번씩 넣어 시험해 보세요.

확장자를 바꾼 새 경로가 필요하면 Path.ChangeExtension을, 폴더와 파일 이름을 합칠 때는 문자열을 &로 잇는 대신 Path.Combine을 쓰면 구분자 중복이나 누락을 신경 쓰지 않아도 됩니다. 다만 Path.Combine 의 두 번째 인자가 \sub\a.txt 처럼 구분자로 시작하면 루트 경로로 취급돼 앞의 폴더가 무시되고 \sub\a.txt 만 남으니, 이어 붙일 쪽 앞에 구분자가 붙어 있지 않은지 확인해야 합니다.