1. 개요

Java에서 파일 작업을 할 때 주어진 절대 경로에서 파일 이름을 추출해야 하는 경우가 많습니다.

이 사용방법(예제)에서는 파일 이름을 추출하는 방법을 살펴보겠습니다.

2. 문제 소개

문제는 매우 간단합니다. 절대 파일 경로 문자열이 주어졌다고 상상해 보십시오. 여기서 파일 이름을 추출하려고 합니다. 몇 가지 예를 통해 문제를 빠르게 설명할 수 있습니다.

String PATH_LINUX = "/root/with space/subDir/myFile.linux";
String EXPECTED_FILENAME_LINUX = "myFile.linux";
                                                                   
String PATH_WIN = "C:\\root\\with space\\subDir\\myFile.win";
String EXPECTED_FILENAME_WIN = "myFile.win";

우리가 본 것처럼 다른 파일 시스템은 다른 파일 구분 기호를 가질 수 있습니다 . 따라서 이 사용방법(예제)에서는 일부 플랫폼 독립적인 솔루션을 다룰 것입니다. 즉, 동일한 구현이 *nix 및 Windows 시스템 모두에서 작동합니다.

간단하게 하기 위해 단위 테스트 어설션을 사용하여 솔루션이 예상대로 작동하는지 확인합니다.

다음으로 실제로 작동하는 것을 봅시다.

3. 절대 경로를 문자열로 파싱

우선, 파일 시스템은 파일 이름에 파일 구분 기호를 포함하는 것을 허용하지 않습니다. 예를 들어 Linux의 Ext2, Ext3 또는 Ext4 파일 시스템에서는 이름에 "/"가 포함된 파일을 만들 수 없습니다.

$ touch "a/b.txt"
touch: cannot touch 'a/b.txt': No such file or directory

위의 예에서 파일 시스템은 "a/"를 디렉토리로 취급합니다. 이 규칙을 기반으로 문제를 해결하는 아이디어 는 마지막 파일 구분 기호에서 문자열 끝까지 부분 문자열을 제거하는 것입니다. 

문자열의 lastIndexOf() 메서드는 해당 문자열에서 하위 문자열의 마지막 인덱싱을 반환합니다. 그런 다음 absolutePath.substring(lastIndex+1) 을 호출하여 파일 이름을 간단히 얻을 수 있습니다 .

보시다시피 구현은 간단합니다. 그러나 솔루션을 시스템 독립적으로 만들기 위해 파일 구분 기호를 Windows의 경우 "\\" 또는 *nix 시스템의 경우 "/"로 하드 코딩하면 안 됩니다. 대신 코드에서 File.separator 를 사용하여 프로그램이 실행 중인 시스템에 자동으로 적응하도록 합시다.

int index = PATH_LINUX.lastIndexOf(File.separator);
String filenameLinux = PATH_LINUX.substring(index + 1);
assertEquals(EXPECTED_FILENAME_LINUX, filenameLinux);

위의 테스트는 Linux 시스템에서 실행하면 통과합니다. 마찬가지로 아래 테스트는 Windows 시스템에서 통과합니다.

int index = PATH_WIN.lastIndexOf(File.pathSeparator);
String filenameWin = PATH_WIN.substring(index + 1);
assertEquals(EXPECTED_FILENAME_WIN, filenameWin);

보시다시피 동일한 구현이 두 시스템에서 모두 작동합니다.

절대 경로를 문자열로 구문 분석하는 것 외에도 표준 File 클래스를 사용하여 문제를 해결할 수 있습니다.

4. File.getName() 메서드 사용

File 클래스는 파일 이름을 직접 가져올 수 있는 getName ( ) 메서드를 제공합니다. 또한 주어진 절대 경로 문자열에서 File 객체를 구성할 수 있습니다 .

먼저 Linux 시스템에서 테스트해 보겠습니다.

File fileLinux = new File(PATH_LINUX);
assertEquals(EXPECTED_FILENAME_LINUX, fileLinux.getName());

테스트를 실행하면 통과합니다. File내부적 으로 File.separator 를 사용 하므로 Windows 시스템에서 동일한 솔루션을 테스트하면 통과합니다.

File fileWin = new File(PATH_WIN);
assertEquals(EXPECTED_FILENAME_WIN, fileWin.getName());

5. Path.getFileName() 메서드 사용

파일java.io 패키지의 표준 클래스입니다. Java 1.7부터 최신 java.nio 라이브러리는 Path 인터페이스 와 함께 제공됩니다.

Path 객체 가 있으면 Path.getFileName() 메서드 를 호출하여 파일 이름을 가져올 수 있습니다 . File 클래스 와 달리 정적 Paths.get() 메서드 를 사용하여 Path  인스턴스를  만들 수 있습니다 .

다음으로 주어진 PATH_LINUX 문자열 에서 Path 인스턴스를 만들고 Linux에서 솔루션을 테스트해 보겠습니다.

Path pathLinux = Paths.get(PATH_LINUX);
assertEquals(EXPECTED_FILENAME_LINUX, pathLinux.getFileName().toString());

테스트를 실행하면 통과합니다. Path.getFileName() 이 Path 객체 를 반환 한다는 점을 언급할 가치가 있습니다. 따라서  명시적으로 toString()  메서드를 호출하여 문자열로 변환합니다.

동일한 구현이 PATH_WIN 을 경로 문자열로 사용하는 Windows 시스템에서도 작동합니다. 이는 Path 가 실행 중인 현재 FileSystem 을 감지할 수 있기 때문입니다.

Path pathWin = Paths.get(PATH_WIN);
assertEquals(EXPECTED_FILENAME_WIN, pathWin.getFileName().toString());

6. Apache Commons IO에서 FilenameUtils.getName() 사용

지금까지 절대 경로에서 파일 이름을 추출하는 세 가지 솔루션을 다루었습니다. 앞서 언급했듯이 플랫폼 독립적입니다. 그러나 이 세 가지 솔루션은 주어진 절대 경로가 프로그램이 실행 중인 시스템과 일치하는 경우에만 올바르게 작동합니다. 예를 들어 우리 프로그램은 Windows에서 실행되는 경우에만 Windows 경로를 처리할 수 있습니다.

6.1. 지능형 FilenameUtils.getName() 메서드

실제로 다른 시스템의 경로 형식을 구문 분석할 가능성은 상대적으로 낮습니다. 그러나 Apache Commons IOFilenameUtils 클래스는 다른 경로 형식에서 파일 이름을 "지능적으로" 추출할 수 있습니다 . 따라서 프로그램이 Windows에서 실행되는 경우 Linux 파일 경로에서도 작동할 수 있으며 그 반대의 경우도 마찬가지입니다.

다음으로 테스트를 만들어 보겠습니다.

String filenameLinux = FilenameUtils.getName(PATH_LINUX);
assertEquals(EXPECTED_FILENAME_LINUX, filenameLinux);
                                                         
String filenameWin = FilenameUtils.getName(PATH_WIN);
assertEquals(EXPECTED_FILENAME_WIN, filenameWin);

보시다시피 위의 테스트는 PATH_LINUXPATH_WIN 을 모두 구문 분석합니다 . 테스트는 Linux에서 실행하든 Windows에서 실행하든 상관없이 통과합니다.

다음으로 FilenameUtils 가 다른 시스템의 경로를 자동으로 처리 하는 방법을 알고 싶을 수 있습니다.

6.2. FilenameUtils.getName() 작동 방식

FilenameUtils.getName() 의 구현 을 살펴보면 그 논리는 "lastIndexOf" 파일 구분자 접근 방식과 비슷합니다. 차이점은 FilenameUtils 가 lastIndexOf() 메서드를 두 번 호출 한다는 점입니다 . 한 번은 *nix 구분 기호(/)로 호출한 다음 Windows 파일 구분 기호(\)로 호출합니다. 마지막으로 더 큰 인덱스를 "lastIndex"로 사용합니다.

...
final int lastUnixPos = fileName.lastIndexOf(UNIX_SEPARATOR); // UNIX_SEPARATOR = '/'
final int lastWindowsPos = fileName.lastIndexOf(WINDOWS_SEPARATOR); // WINDOWS_SEPARATOR = '\\'
return Math.max(lastUnixPos, lastWindowsPos);

따라서 FilenameUtils.getName() 은 현재 파일 시스템이나 시스템의 파일 구분 기호를 확인하지 않습니다 . 대신, 어떤 시스템에 속해 있든 관계없이 마지막 파일 구분 기호의 색인을 찾은 다음 이 색인에서 문자열의 끝까지 하위 문자열을 최종 결과로 추출합니다.

6.3. FilenameUtils.getName() 을 실패 하게 만드는 엣지 케이스

이제 우리는 FilenameUtils.getName() 이 어떻게 작동하는지 이해합니다. 실제로 영리한 솔루션이며 대부분의 경우 작동합니다. 그러나 많은 Linux 지원 파일 시스템에서는 파일 이름에 백슬래시('\')를 포함할 수 있습니다 .

$ echo 'Hi there!' > 'my\file.txt'

$ ls -l my*
-rw-r--r-- 1 kent kent 10 Sep 13 23:55 'my\file.txt'

$ cat 'my\file.txt'
Hi there!

지정된 Linux 파일 경로의 파일 이름에 백슬래시가 포함되어 있으면 FilenameUtils.getName() 이 실패합니다. 테스트를 통해 명확하게 설명할 수 있습니다.

String filenameToBreak = FilenameUtils.getName("/root/somedir/magic\\file.txt");
assertNotEquals("magic\\file.txt", filenameToBreak); // <-- filenameToBreak = "file.txt", but we expect: magic\file.txt

이 방법을 사용할 때 이 경우를 염두에 두어야 합니다.

7. 결론

이 기사에서는 주어진 절대 경로 문자열에서 파일 이름을 추출하는 방법을 배웠습니다.

항상 그렇듯이 예제의 전체 소스 코드는  GitHub 에서 사용할 수 있습니다 .

Generic footer banner