Skip to content

Cómo trabajar con diferentes sistemas de archivos

Node.js expone muchas características de los sistemas de archivos. Pero no todos los sistemas de archivos son iguales. Las siguientes son las mejores prácticas sugeridas para mantener su código simple y seguro cuando trabaje con diferentes sistemas de archivos.

Comportamiento del sistema de archivos

Antes de poder trabajar con un sistema de archivos, necesita saber cómo se comporta. Los diferentes sistemas de archivos se comportan de manera diferente y tienen más o menos características que otros: distinción entre mayúsculas y minúsculas, no distinción entre mayúsculas y minúsculas, conservación de mayúsculas y minúsculas, conservación de la forma Unicode, resolución de marca de tiempo, atributos extendidos, inodos, permisos de Unix, flujos de datos alternativos, etc.

Tenga cuidado al inferir el comportamiento del sistema de archivos de process.platform. Por ejemplo, no asuma que porque su programa se está ejecutando en Darwin, por lo tanto, está trabajando en un sistema de archivos que no distingue entre mayúsculas y minúsculas (HFS+), ya que el usuario puede estar usando un sistema de archivos que distingue entre mayúsculas y minúsculas (HFSX). Del mismo modo, no asuma que porque su programa se está ejecutando en Linux, por lo tanto, está trabajando en un sistema de archivos que admite permisos de Unix e inodos, ya que puede estar en una unidad externa particular, USB o unidad de red que no lo hace.

El sistema operativo puede no facilitar la inferencia del comportamiento del sistema de archivos, pero no todo está perdido. En lugar de mantener una lista de todos los sistemas de archivos y comportamientos conocidos (que siempre estará incompleta), puede sondear el sistema de archivos para ver cómo se comporta realmente. La presencia o ausencia de ciertas características que son fáciles de sondear, a menudo son suficientes para inferir el comportamiento de otras características que son más difíciles de sondear.

Recuerde que algunos usuarios pueden tener diferentes sistemas de archivos montados en varias rutas en el árbol de trabajo.

Evite un enfoque de mínimo común denominador

Podría sentirse tentado a hacer que su programa actúe como un sistema de archivos de mínimo común denominador, normalizando todos los nombres de archivo a mayúsculas, normalizando todos los nombres de archivo a la forma Unicode NFC y normalizando todas las marcas de tiempo de los archivos a una resolución de 1 segundo. Este sería el enfoque de mínimo común denominador.

No haga esto. Solo podría interactuar de forma segura con un sistema de archivos que tenga exactamente las mismas características de mínimo común denominador en todos los aspectos. No podría trabajar con sistemas de archivos más avanzados de la manera que esperan los usuarios, y se encontraría con colisiones de nombres de archivo o marcas de tiempo. Sin duda, perdería y corrompería los datos del usuario a través de una serie de eventos dependientes complicados, y crearía errores que serían difíciles, si no imposibles, de resolver.

¿Qué sucede cuando luego necesita admitir un sistema de archivos que solo tiene una resolución de marca de tiempo de 2 segundos o 24 horas? ¿Qué sucede cuando el estándar Unicode avanza para incluir un algoritmo de normalización ligeramente diferente (como ha sucedido en el pasado)?

Un enfoque de mínimo común denominador tendería a tratar de crear un programa portátil utilizando solo llamadas al sistema "portátiles". Esto conduce a programas que tienen fugas y que, de hecho, no son portátiles.

Adopte un Enfoque de Superconjunto

Aproveche al máximo cada plataforma que admita adoptando un enfoque de superconjunto. Por ejemplo, un programa de copia de seguridad portátil debería sincronizar correctamente los btimes (la hora de creación de un archivo o carpeta) entre sistemas Windows, y no debería destruir ni alterar los btimes, aunque los btimes no sean compatibles con los sistemas Linux. El mismo programa de copia de seguridad portátil debería sincronizar correctamente los permisos de Unix entre los sistemas Linux, y no debería destruir ni alterar los permisos de Unix, aunque los permisos de Unix no sean compatibles con los sistemas Windows.

Maneje diferentes sistemas de archivos haciendo que su programa actúe como un sistema de archivos más avanzado. Admita un superconjunto de todas las características posibles: distinción entre mayúsculas y minúsculas, preservación de mayúsculas y minúsculas, distinción entre formas Unicode, preservación de formas Unicode, permisos de Unix, marcas de tiempo de nanosegundos de alta resolución, atributos extendidos, etc.

Una vez que tenga la preservación de mayúsculas y minúsculas en su programa, siempre podrá implementar la no distinción entre mayúsculas y minúsculas si necesita interactuar con un sistema de archivos que no distingue entre mayúsculas y minúsculas. Pero si renuncia a la preservación de mayúsculas y minúsculas en su programa, no podrá interactuar de forma segura con un sistema de archivos que preserva las mayúsculas y minúsculas. Lo mismo ocurre con la preservación de la forma Unicode y la preservación de la resolución de la marca de tiempo.

Si un sistema de archivos le proporciona un nombre de archivo en una mezcla de minúsculas y mayúsculas, mantenga el nombre de archivo exactamente en el caso dado. Si un sistema de archivos le proporciona un nombre de archivo en forma Unicode mixta o NFC o NFD (o NFKC o NFKD), mantenga el nombre de archivo en la secuencia de bytes exacta dada. Si un sistema de archivos le proporciona una marca de tiempo en milisegundos, mantenga la marca de tiempo en resolución de milisegundos.

Cuando trabaje con un sistema de archivos menor, siempre puede submuestrear apropiadamente, con funciones de comparación según lo requiera el comportamiento del sistema de archivos en el que se ejecuta su programa. Si sabe que el sistema de archivos no admite permisos de Unix, no debe esperar leer los mismos permisos de Unix que escribe. Si sabe que el sistema de archivos no conserva las mayúsculas y minúsculas, debe estar preparado para ver ABC en un listado de directorio cuando su programa crea abc. Pero si sabe que el sistema de archivos conserva las mayúsculas y minúsculas, entonces debe considerar ABC como un nombre de archivo diferente a abc, al detectar cambios de nombre de archivo o si el sistema de archivos distingue entre mayúsculas y minúsculas.

Conservación de Mayúsculas y Minúsculas

Puede crear un directorio llamado test /abc y sorprenderse al ver a veces que fs.readdir('test') devuelve ['ABC']. Esto no es un error en Node. Node devuelve el nombre de archivo tal como lo almacena el sistema de archivos, y no todos los sistemas de archivos admiten la conservación de mayúsculas y minúsculas. Algunos sistemas de archivos convierten todos los nombres de archivo a mayúsculas (o minúsculas).

Conservación de la Forma Unicode

La conservación de mayúsculas y minúsculas y la conservación de la forma Unicode son conceptos similares. Para comprender por qué se debe conservar la forma Unicode, asegúrese de comprender primero por qué se deben conservar las mayúsculas y minúsculas. La conservación de la forma Unicode es igual de simple cuando se comprende correctamente. Unicode puede codificar los mismos caracteres utilizando varias secuencias de bytes diferentes. Varias cadenas pueden verse iguales, pero tener diferentes secuencias de bytes. Cuando trabaje con cadenas UTF-8, tenga cuidado de que sus expectativas estén en línea con cómo funciona Unicode. Así como no esperaría que todos los caracteres UTF-8 se codifiquen en un solo byte, no debe esperar que varias cadenas UTF-8 que se ven iguales a la vista humana tengan la misma representación de bytes. Esta puede ser una expectativa que puede tener de ASCll, pero no de UTF-8.

Puede crear un directorio llamado test/ café (forma Unicode NFC con secuencia de bytes <63 61 66 c3 a9> y string.length ===5) y sorprenderse al ver a veces que fs.readdir('test') devuelve ['café'] (forma Unicode NFD con secuencia de bytes <63 61 66 65 cc 81> y string.length ===6). Esto no es un error en Node. Node.js devuelve el nombre de archivo tal como lo almacena el sistema de archivos, y no todos los sistemas de archivos admiten la conservación de la forma Unicode. HFS+, por ejemplo, normalizará todos los nombres de archivo a una forma casi siempre la misma que la forma NFD. No espere que HFS+ se comporte igual que NTFS o EXT 4 y viceversa. No intente cambiar los datos permanentemente a través de la normalización como una abstracción con fugas para encubrir las diferencias de Unicode entre los sistemas de archivos. Esto crearía problemas sin resolver ninguno. Más bien, conserve la forma Unicode y use la normalización solo como una función de comparación.

Insensibilidad a la Forma Unicode

La insensibilidad a la forma Unicode y la preservación de la forma Unicode son dos comportamientos diferentes del sistema de archivos que a menudo se confunden entre sí. Al igual que la insensibilidad a mayúsculas y minúsculas a veces se ha implementado incorrectamente normalizando permanentemente los nombres de archivo a mayúsculas al almacenar y transmitir nombres de archivo, también la insensibilidad a la forma Unicode a veces se ha implementado incorrectamente normalizando permanentemente los nombres de archivo a una determinada forma Unicode (NFD en el caso de HFS+) al almacenar y transmitir nombres de archivo. Es posible y mucho mejor implementar la insensibilidad a la forma Unicode sin sacrificar la preservación de la forma Unicode, utilizando la normalización Unicode solo para la comparación.

Comparación de diferentes formas Unicode

Node.js proporciona string.normalize ('NFC' / 'NFD') que puede usar para normalizar una cadena UTF-8 a NFC o NFD. Nunca debe almacenar la salida de esta función, sino utilizarla como parte de una función de comparación para probar si dos cadenas UTF-8 se verían iguales para el usuario. Puede usar string1.normalize('NFC')=== string2.normalize('NFC') o string1.normalize('NFD')=== string2.normalize('NFD') como su función de comparación. No importa qué forma use.

La normalización es rápida, pero es posible que desee utilizar una memoria caché como entrada para su función de comparación para evitar normalizar la misma cadena muchas veces. Si la cadena no está presente en la memoria caché, normalícela y almacénela en la memoria caché. Tenga cuidado de no almacenar ni persistir la memoria caché, utilícela solo como memoria caché.

Tenga en cuenta que el uso de normalize () requiere que su versión de Node.js incluya ICU (de lo contrario, normalize () simplemente devolverá la cadena original). Si descarga la última versión de Node.js del sitio web, incluirá ICU.

Resolución de la marca de tiempo

Puede establecer el mtime (la hora de modificación) de un archivo en 1444291759414 (resolución de milisegundos) y sorprenderse al ver a veces que fs.stat devuelve el nuevo mtime como 1444291759000 (resolución de 1 segundo) o 1444291758000 (resolución de 2 segundos). Esto no es un error en Node. Node.js devuelve la marca de tiempo tal como la almacena el sistema de archivos, y no todos los sistemas de archivos admiten la resolución de nanosegundos, milisegundos o 1 segundo de la marca de tiempo. Algunos sistemas de archivos incluso tienen una resolución muy gruesa para la marca de tiempo atime en particular, por ejemplo, 24 horas para algunos sistemas de archivos FAT.

No Corrompa Nombres de Archivos y Marcas de Tiempo Mediante la Normalización

Los nombres de archivos y las marcas de tiempo son datos del usuario. Así como nunca reescribiría automáticamente los datos de un archivo de usuario para poner los datos en mayúsculas o normalizar los finales de línea CRLF a LF, tampoco debería cambiar, interferir o corromper nunca los nombres de archivos o las marcas de tiempo mediante la normalización de mayúsculas/minúsculas/forma Unicode/marca de tiempo. La normalización solo debe utilizarse para la comparación, nunca para alterar los datos.

La normalización es, en efecto, un código hash con pérdida. Puede utilizarla para comprobar ciertos tipos de equivalencia (por ejemplo, si varias cadenas tienen el mismo aspecto aunque tengan secuencias de bytes diferentes), pero nunca puede utilizarla como sustituto de los datos reales. Su programa debe transmitir los datos de nombres de archivo y marcas de tiempo tal cual.

Su programa puede crear nuevos datos en NFC (o en cualquier combinación de forma Unicode que prefiera) o con un nombre de archivo en minúsculas o mayúsculas, o con una marca de tiempo de resolución de 2 segundos, pero su programa no debe corromper los datos de usuario existentes imponiendo la normalización de mayúsculas/minúsculas/forma Unicode/marca de tiempo. En cambio, adopte un enfoque de superconjunto y conserve las mayúsculas/minúsculas, la forma Unicode y la resolución de la marca de tiempo en su programa. De este modo, podrá interactuar de forma segura con los sistemas de archivos que hacen lo mismo.

Utilice las Funciones de Comparación de Normalización Adecuadamente

Asegúrese de utilizar las funciones de comparación de mayúsculas/minúsculas/forma Unicode/marca de tiempo de forma adecuada. No utilice una función de comparación de nombres de archivo que no distinga entre mayúsculas y minúsculas si está trabajando en un sistema de archivos que distingue entre mayúsculas y minúsculas. No utilice una función de comparación insensible a la forma Unicode si está trabajando en un sistema de archivos sensible a la forma Unicode (por ejemplo, NTFS y la mayoría de los sistemas de archivos Linux que conservan tanto NFC como NFD o formas Unicode mixtas). No compare las marcas de tiempo con una resolución de 2 segundos si está trabajando en un sistema de archivos con una resolución de marca de tiempo de nanosegundos.

Prepárese para Ligeras Diferencias en las Funciones de Comparación

Tenga cuidado de que sus funciones de comparación coincidan con las del sistema de archivos (o sondee el sistema de archivos si es posible para ver cómo se compararía realmente). La distinción entre mayúsculas y minúsculas, por ejemplo, es más compleja que una simple comparación toLowerCase(). De hecho, toUpperCase() suele ser mejor que toLowerCase() (ya que maneja ciertos caracteres de lenguas extranjeras de forma diferente). Pero aún mejor sería sondear el sistema de archivos, ya que cada sistema de archivos tiene su propia tabla de comparación de mayúsculas y minúsculas integrada.

Como ejemplo, el HFS+ de Apple normaliza los nombres de archivo a la forma NFD, pero esta forma NFD es en realidad una versión más antigua de la forma NFD actual y a veces puede ser ligeramente diferente de la forma NFD del estándar Unicode más reciente. No espere que HFS+ NFD sea exactamente igual a Unicode NFD todo el tiempo.