본문 바로가기

Coding

코드에서 Path를 처리할 때

코드에서 Path를 처리할 때

코드를 작성하다보면 _File system_의 Path를 처리하거나 _URL_의 Path를 처리하거나 하는 등의 Path, 즉 **_경로_**를 처리해야하는 경우가 있습니다. 이럴 때 간혹 String Concat을 사용해 처리하는 경우가 보이는데, 현업에서 이런 작은 코드가 문제가 되는 경우를 많이 봤기 때문에 글로 옮겨적어봅니다.

“dir” + "/path"는 왜 문제가 되는가?

"dir"에 의존성이 생기기 때문입니다. "dir"이 동적으로 주입되거나 코드 전반에 걸쳐 사용될 때, "dir"의 변경은 다음과 같은 문제를 낳습니다.

  1. "dir"이 "/"로 끝나고, "path"가 "/"로 시작하는 경우
dir = "esevan-dir/"
path = "/path/to/object"

concat_path = dir + path # "esevan-dir//path/to/object"
  1. "dir"이 "/"로 끝나지 않고, "path"가 "/"로 시작하지 않는 경우
dir = "esevan-dir"
path = "path/to/object"

concat_path = dir + path # "esevan-dirpath/to/object"

Filesystem의 Path를 처리할 때는 Linux 기준 2번이 문제가 되고 URL을 처리할 때는 1, 2번 모두 문제가 됩니다.

언어에서 제공하는 솔루션

많은 언어에서 Path를 처리하기 위한 라이브러리를 제공하고 있습니다.
Python 기준으로 os.path.join(), urlparse.urljoin() or urllib.parse.join()이 이에 해당합니다.
그러나, 위 기능들은 정확히 알고 쓰지 않으면 또 다른 Side effect을 발생시킵니다.
"Path Join"기능은 단순히 "/"을 제거하고 붙이는 기능이 아니고, "Absolute Path"와 "Relative Path"에 따라 Path를 붙여줍니다.

위 예를 다시보면 2의 경우는 정확히 Handling해주지만 1의 경우는 문제가 됩니다.

import os

dir = "esevan-dir/"
path = "/path/to/object"

os.path.join(dir, path) # "/path/to/object"???

/path/to/object를 Absolute Path (절대경로)로 인식하기 때문에 dir값을 무시해버리는 결과를 볼 수 있습니다.

간단한 솔루션

Stack Overflow의 답변을 확인해봅시다.
https://stackoverflow.com/a/43185019/10194588

Google에서 제공하는 Java Solution이 있으니 Reference를 첨부합니다.

public static String simplifyPath(String pathname) {
   checkNotNull(pathname);
   if (pathname.length() == 0) {
     return ".";
   }

   // split the path apart
   Iterable<String> components =
       Splitter.on('/').omitEmptyStrings().split(pathname);
   List<String> path = new ArrayList<String>();

   // resolve ., .., and //
   for (String component : components) {
     if (component.equals(".")) {
       continue;
     } else if (component.equals("..")) {
       if (path.size() > 0 && !path.get(path.size() - 1).equals("..")) {
         path.remove(path.size() - 1);
       } else {
         path.add("..");
       }
     } else {
       path.add(component);
     }
   }

   // put it back together
   String result = Joiner.on('/').join(path);
   if (pathname.charAt(0) == '/') {
     result = "/" + result;
   }

   while (result.startsWith("/../")) {
     result = result.substring(3);
   }
   if (result.equals("/..")) {
     result = "/";
   } else if ("".equals(result)) {
     result = ".";
   }

   return result;
}

PythonJupyter Notebook에서 제공하는 Reference를 첨부합니다.
https://github.com/jupyter/notebook/blob/43df5af2b614088b4b297fae90a70b6505b9bf84/notebook/utils.py#L43-L56

def path_join(*pieces):
    """Join components of url into a relative url
    Use to prevent double slash when joining subpath. This will leave the
    initial and final / in place
    """
    initial = pieces[0].startswith('/')
    final = pieces[-1].endswith('/')
    stripped = [s.strip('/') for s in pieces]
    result = '/'.join(s for s in stripped if s)
    if initial: result = '/' + result
    if final: result = result + '/'
    if result == '//': result = '/'
    return result

결론

저는 매 프로젝트마다 위와 같은 Simple Path Join Util 함수를 생성하여 사용합니다.
위 함수를 사용하면 어떤 path이든 간단한 Parameter 전달만으로도 안전하게 Path를 붙여 생성할 수 있는 코드가 됩니다.

'Coding' 카테고리의 다른 글

Python으로 우리팀 MBTI 궁합 알아보기~  (1) 2021.03.13
Git Commit Message를 위한 VI Core editor 설정  (0) 2020.12.03
[pep8] Private Attributes  (0) 2018.07.09