os.path의 splitext 함수는 확장자를 검사하기 위해 쓰이는 함수입니다. 문서에 있는 표현을 잠시 빌리면, root하고 ext를 나눠버리기 위해서 쓰는데요. 예를 들어, a.jpg는 a와 .jpg로 분리할 수 있습니다. 뒤에 .jpg가 확장자이기 때문입니다.

 


 예제를 보면서 알아 봅시다.

 

 먼저, 'a/b/c'는 root로만 이루어져 있고, 확장자 정보는 없습니다. 따라서, root는 'a/b/c', ext는 ''로 나오게 됩니다. 정말 그렇게 나오는지는 결과를 확인해 보면 됩니다.

 

 네. 정말 그렇게 나오네요.

 

 

 이제, 'a/b/c.png'는 어떻게 나올까요? 확장자가 .png임은 한눈에 알 수 있습니다.

 

 결과는 위와 같이 나옵니다. 확장자 부분을 뺀 나머지가 root가 됨을 알 수 있습니다. 이제 조금 더 어려운 입력을 봅시다.

 

 'a/jo/a.php.jpg'입니다. 이중 확장자의 예인데요. 이것은 어떻게 나올까요? 뒤에 있는 '.jpg'가 확장자로 빠져버립니다. 나머지 부분이 root로 빠지게 됩니다.

 

 따라서, 'a/jo/a.php'와 '.jpg'로 나뉘게 됩니다.

 


 이제 splitext가 제 window10에서 어떻게 동작하는지 간단하게 봅시다. python 3.9.2 버전입니다.

 

 ntpath.py에 있는 splitext를 호출합니다. 무엇인지는 모르겠지만, nt계열인 경우 저 함수를 부르는 듯 합니다. 여기서 뭔가 여러가지 인자들이 있는 generictpath의 _splitext를 부르는데요. '\\'를 드라이브 구분자, '/'는 폴더 구분자, '.'는 확장자 구분자를 의미합니다.

 

 

 먼저 sepIndex는 sep이 마지막으로 나오는 위치입니다. 드라이브 구분자가 마지막으로 나오는 위치라. 인풋 예제에서는 없으니 -1이 됩니다. 만약에 'C:\\...' 이런 식으로 나오면 2가 될 겁니다.

 

 altsep은 '/'입니다. 폴더임을 구분하기 위한 문자인데요.

 

 

 131번째 줄을 보면, sepIndex와 altsepIndex의 max 값을 sepIndex에 넣음을 알 수 있습니다. 드라이브 구분자가 마지막으로 나오는 위치와 '/'이 마지막으로 나오는 위치 중 최댓값이 sepIndex라는 의미는 sepIndex 다음 부분은 순수 파일 부분임을 의미합니다.

 

 

 134번째 줄은 dotIndex가 sepIndex보다 큰 경우 후처리를 함을 의미합니다. 그렇지 않으면 root + ext, ''를 리턴합니다. 예를 들어 'a/b.c/d'와 같이 주어지면, dotIndex보다 sepIndex가 더 크기 때문에 'a/b.c/d', '' 이 두 쌍이 리턴됩니다. 그렇지 않다면, 134번째 줄에 걸려있는 if 블록에 들어가게 되는데요.

 

 filename 부분이 .로 시작하는 경우가 있습니다. 예를 들어 .zshrc라던지 .env와 같은 것들입니다. 이 경우, 137번째 while loop를 타지 않기 때문에 확장자 정보가 빈 상태로 return이 됩니다. 그렇지 않다면 어떨까요?

 

 

 먼저, 이 입력은 'a/jo/a....jpg'입니다. 137번째 줄에 있는 while 절에 걸리므로 한 글자씩 검사를 하게 됩니다. 처음에는 'a'가 나왔으니, 바로 139번째 줄이 수행되는데요. 확장자 구분자가 마지막으로 나온 위치가 입력에서 10번째 부분입니다. 따라서, 'a/jo/a...', '.jpg'로 분리되어서 리턴됩니다.

 

 

 어렵지 않지요?

 

 이 경우는 어떨까요? 파일 이름 부분이 '....jpg'입니다. '.'으로 시작하므로 확장자 정보는 없습니다.

 

 결과는 예상하다 시피, 'a/jo/....jpg'가 root로 뽑히고, ''가 확장자로 뽑히게 됩니다.

 

 

 이건 어떨까요? path traveral 공격에서 자주 보이는 패턴입니다. 이 경우 '/'가 나오는 마지막 위치 다음이 파일 이름이므로 파일 이름은 'a.jpg'가 됩니다. 여기서 1번째로 나오는 문자가 '.'이 아니므로, '.'이 나오는 마지막 위치를 기준으로 확장자와 root가 나뉘게 됩니다.

 

 결과는 위와 같습니다. os.path의 splitext는 잘 쓰면 되게 유용할 듯 합니다.