Как работать с различными файловыми системами
Node.js предоставляет множество возможностей для работы с файловыми системами. Но не все файловые системы одинаковы. Ниже приведены предлагаемые передовые методы, которые помогут вам сделать ваш код простым и безопасным при работе с различными файловыми системами.
Поведение файловой системы
Прежде чем вы сможете работать с файловой системой, вам нужно знать, как она себя ведет. Различные файловые системы ведут себя по-разному и имеют больше или меньше функций, чем другие: чувствительность к регистру, нечувствительность к регистру, сохранение регистра, сохранение формы Unicode, разрешение меток времени, расширенные атрибуты, inodes, разрешения Unix, альтернативные потоки данных и т. д.
Будьте осторожны, делая выводы о поведении файловой системы из process.platform
. Например, не предполагайте, что поскольку ваша программа работает на Darwin, вы работаете с файловой системой, нечувствительной к регистру (HFS+), поскольку пользователь может использовать файловую систему, чувствительную к регистру (HFSX). Точно так же не предполагайте, что поскольку ваша программа работает на Linux, вы работаете с файловой системой, которая поддерживает разрешения Unix и inodes, поскольку вы можете находиться на определенном внешнем диске, USB-накопителе или сетевом диске, который этого не делает.
Операционная система может затруднить определение поведения файловой системы, но не все потеряно. Вместо того чтобы вести список каждой известной файловой системы и поведения (который всегда будет неполным), вы можете исследовать файловую систему, чтобы увидеть, как она себя ведет на самом деле. Наличия или отсутствия определенных функций, которые легко проверить, часто достаточно, чтобы сделать вывод о поведении других функций, которые труднее проверить.
Помните, что у некоторых пользователей могут быть разные файловые системы, подключенные по различным путям в рабочем дереве.
Избегайте подхода наименьшего общего знаменателя
У вас может возникнуть соблазн заставить вашу программу вести себя как файловая система с наименьшим общим знаменателем, нормализуя все имена файлов к верхнему регистру, нормализуя все имена файлов к форме NFC Unicode и нормализуя все метки времени файлов, скажем, до разрешения в 1 секунду. Это был бы подход наименьшего общего знаменателя.
Не делайте этого. Вы сможете безопасно взаимодействовать только с файловой системой, которая имеет точно такие же характеристики наименьшего общего знаменателя во всех отношениях. Вы не сможете работать с более продвинутыми файловыми системами так, как ожидают пользователи, и столкнетесь с конфликтами имен файлов или меток времени. Вы, скорее всего, потеряете и повредите пользовательские данные в результате серии сложных зависимых событий и создадите ошибки, которые будет трудно, если не невозможно, решить.
Что произойдет, если вам позже потребуется поддержка файловой системы, которая имеет разрешение метки времени только 2 секунды или 24 часа? Что произойдет, когда стандарт Unicode продвинется вперед, включив немного другой алгоритм нормализации (как это уже было в прошлом)?
Подход наименьшего общего знаменателя будет склонен к попытке создать переносимую программу, используя только «переносимые» системные вызовы. Это приводит к программам, которые являются дырявыми и на самом деле не переносимыми.
Применение подхода расширенного набора функций
Чтобы максимально эффективно использовать каждую поддерживаемую платформу, применяйте подход расширенного набора функций. Например, портативная программа резервного копирования должна правильно синхронизировать btimes
(время создания файла или папки) между системами Windows и не должна уничтожать или изменять btimes
, даже если btimes
не поддерживаются в системах Linux. Та же портативная программа резервного копирования должна правильно синхронизировать разрешения Unix между системами Linux и не должна уничтожать или изменять разрешения Unix, даже если разрешения Unix не поддерживаются в системах Windows.
Обрабатывайте разные файловые системы, заставляя вашу программу вести себя как более продвинутая файловая система. Поддерживайте расширенный набор всех возможных функций: чувствительность к регистру, сохранение регистра, чувствительность к форме 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 так же просто, если его правильно понять. Unicode может кодировать одни и те же символы, используя несколько разных последовательностей байтов. Несколько строк могут выглядеть одинаково, но иметь разные последовательности байтов. При работе со строками UTF-8 будьте осторожны, чтобы ваши ожидания соответствовали тому, как работает Unicode. Так же, как вы не ожидаете, что все символы UTF-8 будут кодироваться одним байтом, вы не должны ожидать, что несколько строк UTF-8, которые выглядят одинаково для человеческого глаза, будут иметь одинаковое байтовое представление. Это может быть ожиданием, которое вы можете иметь от ASCll, но не от UTF-8.
Вы можете создать каталог с именем test/ café
(форма NFC Unicode с байтовой последовательностью <63 61 66 c3 a9>
и string.length ===5
) и удивиться, увидев, что иногда fs.readdir('test')
возвращает ['café']
(форма NFD Unicode с байтовой последовательностью <63 61 66 65 cc 81>
и string.length ===6
). Это не ошибка в Node. Node.js возвращает имя файла в том виде, в котором оно хранится в файловой системе, и не все файловые системы поддерживают сохранение формы Unicode. HFS+, например, нормализует все имена файлов до формы, почти всегда совпадающей с формой NFD. Не ожидайте, что HFS+ будет вести себя так же, как NTFS или EXT 4, и наоборот. Не пытайтесь изменить данные навсегда посредством нормализации в качестве "дырявой абстракции", чтобы скрыть различия Unicode между файловыми системами. Это создаст проблемы, не решив ни одной. Скорее, сохраняйте форму Unicode и используйте нормализацию только в качестве функции сравнения.
Нечувствительность к форме Unicode
Нечувствительность к форме Unicode и сохранение формы Unicode — это два разных поведения файловой системы, которые часто ошибочно принимают одно за другое. Подобно тому, как нечувствительность к регистру иногда некорректно реализуется путем постоянной нормализации имен файлов к верхнему регистру при хранении и передаче имен файлов, так и нечувствительность к форме Unicode иногда некорректно реализуется путем постоянной нормализации имен файлов к определенной форме Unicode (NFD в случае HFS+) при хранении и передаче имен файлов. Можно и гораздо лучше реализовать нечувствительность к форме Unicode без ущерба для сохранения формы Unicode, используя нормализацию Unicode только для сравнения.
Сравнение различных форм Unicode
Node.js предоставляет string.normalize ('NFC' / 'NFD')
, который вы можете использовать для нормализации строки UTF-8 либо в 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, например, 24 часа для некоторых файловых систем FAT.
Не Искажайте Имена Файлов и Метки Времени посредством Нормализации
Имена файлов и метки времени являются пользовательскими данными. Точно так же, как вы никогда не стали бы автоматически переписывать пользовательские данные файла, переводя данные в верхний регистр или нормализуя CRLF в окончания строк LF, так и вы никогда не должны изменять, вмешиваться или искажать имена файлов или метки времени посредством нормализации регистра / формы Unicode / метки времени. Нормализация должна использоваться только для сравнения, но никогда для изменения данных.
Нормализация - это фактически хеш-код с потерями. Вы можете использовать его для проверки определенных видов эквивалентности (например, выглядят ли несколько строк одинаково, даже если у них разные последовательности байтов), но вы никогда не сможете использовать его в качестве замены фактических данных. Ваша программа должна передавать данные имени файла и метки времени как есть.
Ваша программа может создавать новые данные в NFC (или в любой комбинации предпочитаемой ею формы Unicode), или с именем файла в нижнем или верхнем регистре, или с меткой времени с разрешением 2 секунды, но ваша программа не должна искажать существующие пользовательские данные, навязывая нормализацию регистра / формы Unicode / метки времени. Скорее, примите подход надмножества и сохраняйте регистр, форму Unicode и разрешение метки времени в своей программе. Таким образом, вы сможете безопасно взаимодействовать с файловыми системами, которые делают то же самое.
Используйте Функции Сравнения Нормализации Правильно
Убедитесь, что вы используете функции сравнения регистра / формы Unicode / метки времени правильно. Не используйте функцию сравнения имен файлов без учета регистра, если вы работаете в файловой системе с учетом регистра. Не используйте функцию сравнения без учета формы Unicode, если вы работаете в файловой системе, чувствительной к форме Unicode (например, NTFS и большинство файловых систем Linux, которые сохраняют как NFC, так и NFD или смешанные формы Unicode). Не сравнивайте метки времени с разрешением 2 секунды, если вы работаете в файловой системе с разрешением метки времени в наносекундах.
Будьте Готовы к Незначительным Различиям в Функциях Сравнения
Будьте внимательны к тому, чтобы ваши функции сравнения соответствовали функциям файловой системы (или проверьте файловую систему, если это возможно, чтобы увидеть, как она на самом деле будет сравнивать). Например, нечувствительность к регистру сложнее, чем простое сравнение toLowerCase()
. Фактически, toUpperCase()
обычно лучше, чем toLowerCase ()
(поскольку он по-разному обрабатывает определенные символы иностранных языков). Но еще лучше было бы проверить файловую систему, поскольку в каждой файловой системе есть своя собственная встроенная таблица сравнения регистров.
В качестве примера, Apple HFS+ нормализует имена файлов в форму NFD, но эта форма NFD на самом деле является более старой версией текущей формы NFD и иногда может немного отличаться от формы NFD последнего стандарта Unicode. Не ожидайте, что HFS+ NFD будет точно таким же, как Unicode NFD все время.