다양한 파일 시스템 작업 방법
Node.js는 파일 시스템의 다양한 기능을 제공합니다. 하지만 모든 파일 시스템이 동일한 것은 아닙니다. 다음은 다양한 파일 시스템으로 작업할 때 코드를 간단하고 안전하게 유지하기 위한 모범 사례입니다.
파일 시스템 동작
파일 시스템을 사용하기 전에 파일 시스템의 동작 방식을 알아야 합니다. 파일 시스템마다 동작 방식이 다르며, 대소문자 구분, 대소문자 미구분, 대소문자 유지, 유니코드 형식 유지, 타임스탬프 해상도, 확장 속성, inode, Unix 권한, 대체 데이터 스트림 등 기능이 다릅니다.
process.platform
에서 파일 시스템 동작을 추론하는 데 주의해야 합니다. 예를 들어 프로그램이 Darwin에서 실행 중이라고 해서 사용자가 대소문자를 구분하지 않는 파일 시스템(HFS+)을 사용한다고 가정하지 마십시오. 사용자는 대소문자를 구분하는 파일 시스템(HFSX)을 사용할 수 있습니다. 마찬가지로 프로그램이 Linux에서 실행 중이라고 해서 Unix 권한과 inode를 지원하는 파일 시스템에서 작업하고 있다고 가정하지 마십시오. 특정 외장 드라이브, USB 또는 네트워크 드라이브에서는 지원하지 않을 수 있습니다.
운영 체제에서 파일 시스템 동작을 추론하기가 쉽지 않을 수 있지만, 그렇다고 해서 모든 것이 실패한 것은 아닙니다. 모든 알려진 파일 시스템과 동작 목록을 유지하는 대신(항상 불완전함), 파일 시스템을 조사하여 실제 동작 방식을 확인할 수 있습니다. 조사하기 쉬운 특정 기능의 유무는 더 조사하기 어려운 다른 기능의 동작을 추론하기에 충분한 경우가 많습니다.
일부 사용자는 작업 트리의 다양한 경로에 다른 파일 시스템을 탑재할 수 있음을 기억하십시오.
최저 공통 분모 접근 방식 피하기
모든 파일 이름을 대문자로 정규화하고, 모든 파일 이름을 NFC 유니코드 형식으로 정규화하고, 모든 파일 타임스탬프를 예를 들어 1초 해상도로 정규화하여 프로그램이 최저 공통 분모 파일 시스템처럼 작동하도록 유도할 수 있습니다. 이것이 최저 공통 분모 접근 방식입니다.
이러한 접근 방식을 사용하지 마십시오. 모든 면에서 정확히 동일한 최저 공통 분모 특성을 가진 파일 시스템과만 안전하게 상호 작용할 수 있습니다. 사용자가 기대하는 방식으로 더 고급 파일 시스템을 사용할 수 없으며 파일 이름 또는 타임스탬프 충돌이 발생합니다. 일련의 복잡한 종속 이벤트로 인해 사용자 데이터를 잃고 손상시키고 해결하기 어렵거나 불가능한 버그를 만들 수 있습니다.
나중에 2초 또는 24시간 타임스탬프 해상도만 있는 파일 시스템을 지원해야 할 때는 어떻게 될까요? 유니코드 표준이 발전하여 약간 다른 정규화 알고리즘을 포함하게 되면(과거에 발생한 것처럼) 어떻게 될까요?
최저 공통 분모 접근 방식은 "이식 가능한" 시스템 호출만 사용하여 이식 가능한 프로그램을 만들려고 하는 경향이 있습니다. 이는 누출이 있고 실제로 이식 가능하지 않은 프로그램을 만듭니다.
상위 집합 접근 방식 채택
상위 집합 접근 방식을 채택하여 지원하는 각 플랫폼을 최대한 활용하십시오. 예를 들어, 이식 가능한 백업 프로그램은 Windows 시스템 간에 btime(파일 또는 폴더의 생성 시간)을 올바르게 동기화해야 하며, Linux 시스템에서는 btime이 지원되지 않더라도 btime을 삭제하거나 변경해서는 안 됩니다. 마찬가지로 이식 가능한 백업 프로그램은 Linux 시스템 간에 Unix 권한을 올바르게 동기화해야 하며, Windows 시스템에서는 Unix 권한이 지원되지 않더라도 Unix 권한을 삭제하거나 변경해서는 안 됩니다.
프로그램이 더 고급 파일 시스템처럼 작동하도록 하여 다양한 파일 시스템을 처리하십시오. 대/소문자 구분, 대/소문자 보존, 유니코드 형식 구분, 유니코드 형식 보존, Unix 권한, 고해상도 나노초 타임스탬프, 확장 속성 등 가능한 모든 기능의 상위 집합을 지원합니다.
프로그램에 대/소문자 보존이 구현되면 대/소문자를 구분하지 않는 파일 시스템과 상호 작용해야 하는 경우 언제든지 대/소문자를 구분하지 않도록 구현할 수 있습니다. 그러나 프로그램에서 대/소문자 보존을 포기하면 대/소문자를 보존하는 파일 시스템과 안전하게 상호 작용할 수 없습니다. 유니코드 형식 보존 및 타임스탬프 해상도 보존에도 동일하게 적용됩니다.
파일 시스템에서 소문자와 대문자가 혼합된 파일 이름을 제공하는 경우 제공된 정확한 대/소문자로 파일 이름을 유지합니다. 파일 시스템에서 혼합된 유니코드 형식, NFC, NFD(또는 NFKC 또는 NFKD)의 파일 이름을 제공하는 경우 제공된 정확한 바이트 시퀀스로 파일 이름을 유지합니다. 파일 시스템에서 밀리초 타임스탬프를 제공하는 경우 밀리초 해상도로 타임스탬프를 유지합니다.
더 적은 파일 시스템으로 작업할 때 프로그램이 실행 중인 파일 시스템의 동작에 따라 필요한 비교 함수를 사용하여 적절하게 다운샘플링할 수 있습니다. 파일 시스템이 Unix 권한을 지원하지 않는다는 것을 알고 있다면 작성한 Unix 권한을 읽을 수 있을 것이라고 예상해서는 안 됩니다. 파일 시스템이 대/소문자를 보존하지 않는다는 것을 알고 있다면 프로그램에서 abc
를 생성할 때 디렉터리 목록에 ABC
가 표시될 수 있습니다. 그러나 파일 시스템이 대/소문자를 보존한다는 것을 알고 있다면 파일 이름 변경을 감지하거나 파일 시스템이 대/소문자를 구분하는 경우 ABC
를 abc
와 다른 파일 이름으로 간주해야 합니다.
대소문자 유지
test /abc
라는 디렉토리를 만들고 때로는 fs.readdir('test')
가 ['ABC']
를 반환하는 것을 보고 놀랄 수도 있습니다. 이것은 Node의 버그가 아닙니다. Node는 파일 시스템에 저장된 대로 파일 이름을 반환하며, 모든 파일 시스템이 대소문자 유지를 지원하는 것은 아닙니다. 일부 파일 시스템은 모든 파일 이름을 대문자(또는 소문자)로 변환합니다.
유니코드 형식 유지
대소문자 유지와 유니코드 형식 유지는 비슷한 개념입니다. 유니코드 형식을 유지해야 하는 이유를 이해하려면 먼저 대소문자를 유지해야 하는 이유를 이해해야 합니다. 유니코드 형식 유지는 올바르게 이해하면 매우 간단합니다. 유니코드는 여러 다른 바이트 시퀀스를 사용하여 동일한 문자를 인코딩할 수 있습니다. 여러 문자열이 동일하게 보일 수 있지만 바이트 시퀀스가 다를 수 있습니다. UTF-8 문자열로 작업할 때, 사용자의 기대치가 유니코드가 작동하는 방식과 일치하는지 확인하십시오. 모든 UTF-8 문자가 단일 바이트로 인코딩될 것이라고 예상하지 않는 것처럼, 사람의 눈에 동일하게 보이는 여러 UTF-8 문자열이 동일한 바이트 표현을 가질 것이라고 기대해서는 안 됩니다. 이는 ASCll에서 가질 수 있는 기대치일 수 있지만 UTF-8에서는 아닙니다.
test/ café
라는 디렉토리(63 61 66 c3 a9
바이트 시퀀스가 있는 NFC 유니코드 형식, string.length ===5
)를 만들고 때로는 fs.readdir('test')
가 ['café']
(63 61 66 65 cc 81
바이트 시퀀스가 있는 NFD 유니코드 형식, string.length ===6
)를 반환하는 것을 보고 놀랄 수도 있습니다. 이것은 Node의 버그가 아닙니다. Node.js는 파일 시스템에 저장된 대로 파일 이름을 반환하며, 모든 파일 시스템이 유니코드 형식 유지를 지원하는 것은 아닙니다. 예를 들어, HFS+는 모든 파일 이름을 거의 항상 NFD 형식과 동일한 형식으로 정규화합니다. HFS+가 NTFS 또는 EXT 4와 동일하게 동작할 것이라고 예상하지 마십시오. 파일 시스템 간의 유니코드 차이를 덮기 위한 추상화 누출로 영구적으로 데이터 변경을 시도하지 마십시오. 이는 아무것도 해결하지 않고 문제를 만들 것입니다. 오히려 유니코드 형식을 유지하고 정규화를 비교 함수로만 사용하십시오.
유니코드 형식 비구분
유니코드 형식 비구분과 유니코드 형식 보존은 서로 다른 파일 시스템 동작이며 종종 혼동됩니다. 대소문자 비구분이 파일 이름을 저장하고 전송할 때 파일 이름을 영구적으로 대문자로 정규화하여 잘못 구현된 경우가 있듯이, 유니코드 형식 비구분은 파일 이름을 저장하고 전송할 때 특정 유니코드 형식(HFS+의 경우 NFD)으로 영구적으로 정규화하여 잘못 구현된 경우가 있습니다. 비교 목적으로만 유니코드 정규화를 사용하여 유니코드 형식 보존을 희생하지 않고 유니코드 형식 비구분을 구현하는 것이 가능하며 훨씬 더 좋습니다.
다른 유니코드 형식 비교
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로 정규화하지 않는 것처럼, 대소문자 / 유니코드 형식 / 타임스탬프 정규화를 통해 파일 이름이나 타임스탬프를 변경하거나 방해하거나 손상시키지 않아야 합니다. 정규화는 데이터 변경이 아닌 비교 목적으로만 사용해야 합니다.
정규화는 사실상 손실 해시 코드입니다. 특정 종류의 동등성을 테스트하는 데 사용할 수 있지만 (예: 여러 문자열이 서로 다른 바이트 시퀀스를 가지고 있더라도 동일하게 보이는지 여부) 실제 데이터를 대체할 수는 없습니다. 프로그램은 파일 이름과 타임스탬프 데이터를 그대로 전달해야 합니다.
프로그램에서 NFC(또는 선호하는 유니코드 형식 조합)로 또는 소문자 또는 대문자 파일 이름으로 또는 2초 해상도 타임스탬프로 새 데이터를 만들 수 있지만, 대소문자 / 유니코드 형식 / 타임스탬프 정규화를 강제하여 기존 사용자 데이터를 손상시켜서는 안 됩니다. 오히려, 슈퍼셋 접근 방식을 채택하여 프로그램에서 대소문자, 유니코드 형식 및 타임스탬프 해상도를 보존하십시오. 이렇게 하면 동일하게 작동하는 파일 시스템과 안전하게 상호 작용할 수 있습니다.
정규화 비교 함수를 적절하게 사용하십시오
대소문자 / 유니코드 형식 / 타임스탬프 비교 함수를 적절하게 사용해야 합니다. 대소문자를 구분하는 파일 시스템에서 작업하는 경우 대소문자를 구분하지 않는 파일 이름 비교 함수를 사용하지 마십시오. 유니코드 형식에 민감한 파일 시스템(예: NFC와 NFD 또는 혼합 유니코드 형식을 모두 보존하는 NTFS 및 대부분의 Linux 파일 시스템)에서 작업하는 경우 유니코드 형식에 둔감한 비교 함수를 사용하지 마십시오. 나노초 타임스탬프 해상도 파일 시스템에서 작업하는 경우 2초 해상도로 타임스탬프를 비교하지 마십시오.
비교 함수에서 약간의 차이에 대비하십시오
비교 함수가 파일 시스템의 함수와 일치하는지 확인하십시오 (또는 가능하면 파일 시스템을 탐색하여 실제로 어떻게 비교하는지 확인하십시오). 예를 들어, 대소문자 구분은 단순히 toLowerCase()
비교보다 복잡합니다. 실제로, toUpperCase()
가 일반적으로 toLowerCase()
보다 낫습니다 (특정 외국어 문자를 다르게 처리하기 때문입니다). 그러나 각 파일 시스템에 고유한 대소문자 비교 테이블이 내장되어 있으므로 파일 시스템을 탐색하는 것이 훨씬 더 좋습니다.
예를 들어, Apple의 HFS+는 파일 이름을 NFD 형식으로 정규화하지만 이 NFD 형식은 실제로 현재 NFD 형식의 이전 버전이며 때로는 최신 유니코드 표준의 NFD 형식과 약간 다를 수 있습니다. HFS+ NFD가 항상 유니코드 NFD와 정확히 동일하다고 기대하지 마십시오.