다양한 파일 시스템 사용 방법
Node.js는 파일 시스템의 많은 기능을 노출합니다. 하지만 모든 파일 시스템이 동일하지는 않습니다. 다음은 다양한 파일 시스템을 사용할 때 코드를 간단하고 안전하게 유지하기 위한 권장 모범 사례입니다.
파일 시스템 동작
파일 시스템을 사용하기 전에 작동 방식을 알아야 합니다. 파일 시스템마다 동작이 다르고 다른 파일 시스템보다 기능이 많거나 적습니다. 대소문자 구분, 대소문자 구분 안 함, 대소문자 보존, 유니코드 형식 보존, 타임스탬프 해상도, 확장 속성, 아이노드, Unix 권한, 대체 데이터 스트림 등
process.platform
에서 파일 시스템 동작을 추론하는 데 주의하십시오. 예를 들어 프로그램이 Darwin에서 실행 중이기 때문에 사용자가 대소문자 구분 파일 시스템(HFSX)을 사용할 수 있으므로 대소문자를 구분하지 않는 파일 시스템(HFS+)에서 작업하고 있다고 가정하지 마십시오. 마찬가지로 프로그램이 Linux에서 실행 중이기 때문에 특정 외부 드라이브, USB 또는 네트워크 드라이브에 있을 수 있으므로 Unix 권한 및 아이노드를 지원하는 파일 시스템에서 작업하고 있다고 가정하지 마십시오.
운영 체제에서 파일 시스템 동작을 쉽게 추론할 수 없지만 모든 것이 사라진 것은 아닙니다. 알려진 모든 파일 시스템 및 동작 목록을 유지하는 대신(항상 불완전할 것임) 파일 시스템을 프로브하여 실제로 어떻게 작동하는지 확인할 수 있습니다. 프로브하기 쉬운 특정 기능의 존재 또는 부재는 프로브하기 더 어려운 다른 기능의 동작을 추론하는 데 충분한 경우가 많습니다.
일부 사용자는 작업 트리의 다양한 경로에 다른 파일 시스템을 마운트할 수 있습니다.
최저 공통 분모 접근 방식 피하기
모든 파일 이름을 대문자로 정규화하고, 모든 파일 이름을 NFC 유니코드 형식으로 정규화하고, 모든 파일 타임스탬프를 1초 해상도로 정규화하여 프로그램을 최저 공통 분모 파일 시스템처럼 작동하도록 유도할 수 있습니다. 이것이 최저 공통 분모 접근 방식입니다.
이렇게 하지 마십시오. 모든 면에서 정확히 동일한 최저 공통 분모 특성을 가진 파일 시스템과만 안전하게 상호 작용할 수 있습니다. 사용자가 예상하는 방식으로 더 고급 파일 시스템을 사용할 수 없으며 파일 이름 또는 타임스탬프 충돌이 발생합니다. 일련의 복잡한 종속 이벤트를 통해 사용자 데이터를 잃고 손상시킬 가능성이 매우 높으며 해결하기 어렵거나 불가능한 버그가 발생합니다.
나중에 2초 또는 24시간 타임스탬프 해상도만 있는 파일 시스템을 지원해야 하는 경우 어떻게 됩니까? 유니코드 표준이 발전하여 약간 다른 정규화 알고리즘이 포함되면 어떻게 됩니까(과거에 발생한 것처럼)?
최저 공통 분모 접근 방식은 "이식 가능한" 시스템 호출만 사용하여 이식 가능한 프로그램을 만들려고 시도하는 경향이 있습니다. 이것은 누출이 심하고 실제로 이식 가능하지 않은 프로그램으로 이어집니다.
Superset 접근 방식 채택
Superset 접근 방식을 채택하여 지원하는 각 플랫폼을 최대한 활용하십시오. 예를 들어, 휴대용 백업 프로그램은 Windows 시스템 간에 btime(파일 또는 폴더의 생성 시간)을 올바르게 동기화해야 하며, btime이 Linux 시스템에서 지원되지 않더라도 btime을 파괴하거나 변경해서는 안 됩니다. 동일한 휴대용 백업 프로그램은 Linux 시스템 간에 Unix 권한을 올바르게 동기화해야 하며, Unix 권한이 Windows 시스템에서 지원되지 않더라도 Unix 권한을 파괴하거나 변경해서는 안 됩니다.
프로그램이 더 고급 파일 시스템처럼 작동하도록 하여 다양한 파일 시스템을 처리하십시오. 대소문자 구분, 대소문자 보존, 유니코드 형식 구분, 유니코드 형식 보존, Unix 권한, 고해상도 나노초 타임스탬프, 확장 속성 등 가능한 모든 기능의 Superset을 지원하십시오.
프로그램에 대소문자 보존 기능이 있으면 대소문자를 구분하지 않는 파일 시스템과 상호 작용해야 하는 경우 언제든지 대소문자 구분 기능을 구현할 수 있습니다. 그러나 프로그램에서 대소문자 보존을 포기하면 대소문자를 보존하는 파일 시스템과 안전하게 상호 작용할 수 없습니다. 유니코드 형식 보존 및 타임스탬프 해상도 보존도 마찬가지입니다.
파일 시스템이 소문자와 대문자가 혼합된 파일 이름을 제공하는 경우 제공된 정확한 대소문자로 파일 이름을 유지하십시오. 파일 시스템이 혼합된 유니코드 형식 또는 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와 정확히 동일할 것이라고 기대하지 마십시오.