Last updated
Last updated
❗️ 번역 날짜: 2024년 12월 22일 공식 문서 원문은 아래를 참고하세요. >
Node.js 는 파일시스템의 많은 기능을 제공합니다. 하지만 모든 파일시스템이 동일하지는 않습니다.
다양한 파일시스템을 다룰 때 코드를 간단하고 안전하게 유지하기 위한 권장 모범 사례는 다음과 같습니다.
파일시스템을 사용하기 전에, 해당 파일시스템이 어떻게 동작하는지 알아야 합니다. 파일시스템마다 동작 방식이 다르고, 지원하는 기능도 다를 수 있습니다.
예를 들면
대소문자 구분 여부
대소문자 비구분 및 대소문자 보존 여부
Unicode 형식 보존 여부
타임스탬프의 해상도
확장 속성
인덱스 노드 (inodes)
Unix 권한 (permissions)
대체 데이터 스트림 등
process.platform
값을 통해 파일시스템의 동작을 추론하는 것은 위험할 수 있습니다. 예를 들면, Darwin(Mac OS) 에서 실행된다고 해서 반드시 대소문자를 구분하지 않는 파일시스템(HFS+)를 사용한다고 단정지을 수 없습니다. 사용자가 대소문자를 구분하는 파일시스템(HFSX)를 사용하고 있을 수도 있기 때문입니다.
또는, Linux에서 실행된다고 해서 Unix 권한과 인덱스 노드를 지원하는 파일시스템을 사용한다고 단정지을 수 없습니다. 외부 드라이브, USB 또는 네트워크 드라이브처럼 해당 기능을 지원하지 않는 파일시스템일 수도 있기 때문입니다.
운영체제는 파일시스템 동작을 추론하기 어렵게 만들 수 있지만, 모든 것이 불가능한 것은 아닙니다. 모든 파일시스템과 동작 목록을 유지하는 대신(항상 불완전할 수 있음), 파일시스템의 실제 동작을 프로브(probe)하여 확인할 수 있습니다. 쉽게 확인할 수 있는 특정 기능들의 존재 여부를 통해, 확인하기 더 어려운 다른 기능들의 동작을 유추할 수 있는 경우가 많습니다.
사용자에 따라 작업 트리의 여러 경로에 다양한 파일시스템이 마운트되어 있을 수 있음을 기억하세요.
프로그램을 만들 때 모든 파일명을 대문자로 정규화하고, 모든 파일명을 NFC Unicode 형식으로 정규화하며, 모든 파일 타임스탬프를 1초 해상도로 정규화하는 등 최소 공통분모 파일시스템처럼 동작하도록 만들고 싶은 유혹을 받을 수 있습니다. 이것이 최소 공통분모 접근방식입니다.
하지만 이렇게 하지 마세요. 이런 방식은 모든 면에서 정확히 같은 최소 공통분모 특성을 가진 파일시스템과만 안전하게 상호작용할 수 있게 됩니다. 사용자가 기대하는 방식으로 더 발전된 파일시스템과 작업할 수 없게 되며, 파일명이나 타임스탬프 충돌이 발생할 것입니다. 복잡한 연관 이벤트들을 통해 사용자 데이터를 잃거나 손상시킬 수 있으며, 해결하기 어렵거나 불가능한 버그를 만들게 될 것입니다.
나중에 2초 또는 24시간 타임스탬프 해상도만 지원하는 파일시스템을 지원해야 할 때는 어떻게 될까요? Unicode 표준이 발전하여 약간 다른 정규화 알고리즘을 포함하게 될 때는 어떻게 될까요? (과거에 이런 일이 있었습니다.)
최소 공통분모 접근 방식은 "이식 가능한(portable)" 시스템 호출만을 사용하여 이식 가능한 프로그램을 만들려고 하는 경향이 있습니다. 이는 불완전하고 실제로는 이식 가능하지 않은 프로그램으로 이어집니다.
각 플랫폼을 최대한 활용하려면 상위집합 접근 방식을 채택하세요. 예를 들어, 이식 가능한 백업 프로그램은 Windows 시스템 간에 btime(파일이나 폴더의 생성 시간)을 올바르게 동기화해야 하며, Linux 시스템에서 btime이 지원되지 않더라도 btime을 파괴하거나 변경해서는 안 됩니다. 같은 이식 가능한 백업 프로그램은 Linux 시스템 간에 Unix 권한을 올바르게 동기화해야 하며, Windows 시스템에서 Unix 권한이 지원되지 않더라도 Unix 권한을 파괴하거나 변경해서는 안 됩니다.
프로그램이 더 발전된 파일시스템처럼 동작하도록 하여 다른 파일시스템을 처리하세요. 대소문자 구분, 대소문자 보존, Unicode 형식 구분, Unicode 형식 보존, Unix 권한, 고해상도 나노초 타임스탬프, 확장 속성 등 가능한 모든 기능의 상위집합을 지원하세요: 대소문자 구분, 대소문자 보존, Unicode 형식 구분, Unicode 형식 보존, Unix 권한, 고해상도 나노초 타임스탬프, 확장 속성 등.
프로그램에서 대소문자 보존을 구현하면, 대소문자를 구분하지 않는 파일시스템과 상호작용해야 할 때 대소문자 구분 없음을 구현할 수 있습니다. 하지만 프로그램에서 대소문자 보존을 포기하면, 대소문자를 보존하는 파일시스템과 안전하게 상호작용할 수 없습니다. Unicode 형식 보존과 타임스탬프 해상도 보존에도 동일한 원칙이 적용됩니다.
파일시스템이 대소문자가 혼합된 파일명을 제공하면, 제공된 그대로의 대소문자를 유지하세요. 파일시스템이 혼합된 Unicode 형식이나 NFC 또는 NFD(또는 NFKC나 NFKD) 형식의 파일명을 제공하면, 제공된 정확한 바이트 시퀀스를 유지하세요. 파일시스템이 밀리초 단위의 타임스탬프를 제공하면, 밀리초 해상도로 타임스탬프를 유지하세요.
더 낮은 수준의 파일시스템과 작업할 때는, 프로그램이 실행되는 파일시스템의 동작에 따라 필요한 비교 함수를 사용하여 적절하게 다운샘플링할 수 있습니다. 파일시스템이 Unix 권한을 지원하지 않는다는 것을 알고 있다면, 작성한 것과 동일한 Unix 권한을 읽을 수 있을 것이라 기대하지 마세요. 파일시스템이 대소문자를 보존하지 않는다는 것을 알고 있다면, 프로그램이 abc
를 생성했을 때 디렉토리 목록에서 ABC
가 보일 수 있다는 것을 예상해야 합니다. 하지만 파일시스템이 대소문자를 보존한다는 것을 알고 있다면, 파일 이름 변경을 감지할 때나 파일시스템이 대소문자를 구분할 때 abc
와 ABC
를 서로 다른 파일명으로 간주해야 합니다.
test/abc
라는 디렉토리를 생성했는데 때때로 fs.readdir('test')
가 ['ABC']
를 반환하는 것을 보고 놀랄 수 있습니다. 이는 Node의 버그가 아닙니다. Node는 파일시스템이 저장한 그대로의 파일명을 반환하며, 모든 파일시스템이 대소문자 보존을 지원하는 것은 아닙니다. 일부 파일시스템은 모든 파일명을 대문자(또는 소문자)로 변환합니다.
대소문자 보존과 Unicode 형식 보존은 비슷한 개념입니다. Unicode 형식을 왜 보존해야 하는지 이해하려면, 먼저 대소문자를 왜 보존해야 하는지 이해해야 합니다. Unicode 형식 보존은 올바르게 이해하면 똑같이 단순합니다.
Unicode는 동일한 문자를 여러 다른 바이트 시퀀스를 사용해 인코딩할 수 있습니다. 여러 문자열이 똑같아 보이더라도 다른 바이트 시퀀스를 가질 수 있습니다. UTF-8 문자열로 작업할 때는 Unicode의 작동 방식에 맞게 기대치를 조정해야 합니다. 모든 UTF-8 문자가 단일 바이트로 인코딩될 것이라 기대하지 않듯이, 사람의 눈에 동일해 보이는 여러 UTF-8 문자열이 같은 바이트 표현을 가질 것이라 기대해서는 안 됩니다. 이는 ASCII에서는 가능한 기대일 수 있지만, UTF-8에서는 그렇지 않습니다.
test/café
디렉토리를 생성할 때(바이트 시퀀스 <63 61 66 c3 a9>
, string.length === 5
의 NFC Unicode 형식), 때때로 fs.readdir('test')
가 ['café']
를 반환하는 것을 보고 놀랄 수 있습니다 (바이트 시퀀스 <63 61 66 65 cc 81>
, string.length === 6
의 NFD Unicode 형식). 이는 Node의 버그가 아닙니다. Node.js는 파일시스템이 저장한 그대로의 파일명을 반환하며, 모든 파일시스템이 Unicode 형식 보존을 지원하는 것은 아닙니다.
예를 들어, HFS+는 모든 파일명을 거의 항상 NFD 형식과 같은 형태로 정규화합니다. HFS+가 NTFS나 EXT4처럼 동작할 것이라 기대하면 안 되며, 그 반대도 마찬가지입니다. 파일시스템 간의 Unicode 차이를 가리기 위해 정규화를 통해 데이터를 영구적으로 변경하려 하지 마세요. 이는 어떤 문제도 해결하지 못한 채 새로운 문제만 만들어낼 것입니다. 대신, Unicode 형식을 보존하고 정규화는 비교 함수로만 사용하세요.
Unicode 형식 구분 없음과 Unicode 형식 보존은 종종 서로 혼동되는 두 가지 다른 파일시스템 동작입니다. 대소문자 구분 없음이 때때로 파일명을 저장하고 전송할 때 영구적으로 대문자로 정규화하는 방식으로 잘못 구현된 것처럼, Unicode 형식 구분 없음도 때때로 파일명을 저장하고 전송할 때 특정 Unicode 형식(HFS+의 경우 NFD)으로 영구적으로 정규화하는 방식으로 잘못 구현되어 왔습니다. Unicode 정규화를 비교용으로만 사용함으로써, Unicode 형식 보존을 희생하지 않고도 Unicode 형식 구분 없음을 구현하는 것이 가능하며 더 나은 방법입니다.
Node.js는 UTF-8 문자열을 NFC나 NFD로 정규화할 수 있는 string.normalize('NFC' / 'NFD')
를 제공합니다. 이 함수의 출력을 저장해서는 안 되며, 오직 두 UTF-8 문자열이 사용자에게 동일하게 보일지 테스트하는 비교 함수의 일부로만 사용해야 합니다.
비교 함수로 string1.normalize('NFC') === string2.normalize('NFC')
또는 string1.normalize('NFD') === string2.normalize('NFD')
를 사용할 수 있습니다. 어떤 형식을 사용하든 상관없습니다.
정규화는 빠르지만, 동일한 문자열을 여러 번 정규화하는 것을 피하기 위해 비교 함수의 입력으로 캐시를 사용하고 싶을 수 있습니다. 문자열이 캐시에 없다면 정규화하고 캐시하세요. 캐시를 저장하거나 영구 보존하지 않도록 주의하고, 오직 캐시로만 사용하세요.
normalize()
를 사용하려면 Node.js 버전에 ICU가 포함되어 있어야 합니다 (그렇지 않으면 normalize()
는 단순히 원래 문자열을 반환합니다). 웹사이트에서 최신 버전의 Node.js를 다운로드하면 ICU가 포함되어 있습니다.
파일의 mtime
(수정 시간)을 1444291759414
(밀리초 해상도) 로 설정했는데 때때로 fs.stat
이 새로운 mtime을 1444291759000
(1초 해상도) 또는 1444291758000
(2초 해상도)으로 반환하는 것을 보고 놀랄 수 있습니다. 이는 Node의 버그가 아닙니다. Node.js는 파일시스템이 저장한 그대로의 타임스탬프를 반환하며, 모든 파일시스템이 나노초, 밀리초 또는 1초 타임스탬프 해상도를 지원하는 것은 아닙니다. 일부 파일시스템은 특히 atime 타임스탬프에 대해 매우 낮은 해상도를 가지고 있습니다(예: 일부 FAT 파일시스템의 경우 24시간).
파일명과 타임스탬프는 사용자 데이터입니다. 사용자 파일 데이터를 자동으로 대문자로 바꾸거나 CRLF
를 LF
줄 끝으로 정규화하지 않는 것처럼, 대소문자 / Unicode 형식 / 타임스탬프 정규화를 통해 파일명이나 타임스탬프를 변경하거나 간섭하거나 손상시켜서는 안 됩니다. 정규화는 오직 비교를 위해서만 사용되어야 하며, 절대 데이터를 변경하기 위해 사용되어서는 안 됩니다.
정규화는 사실상 손실이 있는 해시 코드입니다. 특정 종류의 동등성을 테스트하기 위해 사용할 수 있지만(예: 여러 문자열이 서로 다른 바이트 시퀀스를 가지고 있더라도 같아 보이는지), 실제 데이터의 대체물로는 절대 사용할 수 없습니다. 프로그램은 파일명과 타임스탬프 데이터를 있는 그대로 전달해야 합니다.
프로그램은 새로운 데이터를 NFC(또는 선호하는 Unicode 형식의 조합)로 생성하거나, 소문자나 대문자 파일명으로 생성하거나, 2초 해상도 타임스탬프로 생성할 수 있지만, 대소문자/Unicode 형식/타임스탬프 정규화를 강제하여 기존 사용자 데이터를 손상시켜서는 안 됩니다. 대신, 상위집합 접근 방식을 채택하고 프로그램에서 대소문자, Unicode 형식, 타임스탬프 해상도를 보존하세요. 이렇게 하면 동일한 방식으로 작동하는 파일시스템과 안전하게 상호작용할 수 있습니다.
대소문자 / Unicode 형식 / 타임스탬프 비교 함수를 적절히 사용하도록 하세요. 대소문자를 구분하는 파일시스템에서 작업할 때는 대소문자를 구분하지 않는 파일명 비교 함수를 사용하지 마세요. Unicode 형식을 구분하는 파일시스템(예: NFC와 NFD 또는 혼합된 Unicode 형식을 모두 보존하는 NTFS와 대부분의 Linux 파일시스템)에서 작업할 때는 Unicode 형식을 구분하지 않는 비교 함수를 사용하지 마세요. 나노초 타임스탬프 해상도 파일시스템에서 작업할 때는 2초 해상도로 타임스탬프를 비교하지 마세요.
비교 함수가 파일시스템의 비교 방식과 일치하도록 주의하세요(가능하다면 파일시스템이 실제로 어떻게 비교하는지 확인하기 위해 탐색해보세요). 예를 들어, 대소문자 구분 없음은 단순한 toLowerCase()
비교보다 더 복잡합니다. 실제로 toUpperCase()
가 toLowerCase()
보다 더 나은 경우가 많습니다(특정 외국어 문자를 다르게 처리하기 때문입니다). 하지만 모든 파일시스템이 자체적인 대소문자 비교 테이블을 내장하고 있으므로, 파일시스템을 직접 탐색하는 것이 더 좋습니다.
예를 들어, Apple의 HFS+는 파일명을 NFD 형식으로 정규화하지만, 이 NFD 형식은 실제로 현재 NFD 형식의 이전 버전이며 최신 Unicode 표준의 NFD 형식과 약간 다를 수 있습니다. HFS+ NFD가 항상 Unicode NFD와 정확히 동일할 것이라고 기대하지 마세요.