Crear directorio al escribir en un archivo en Node.js

135

He estado jugando con Node.js y encontré un pequeño problema. Tengo un script que reside en un directorio llamado data. Quiero que el script escriba algunos datos en un archivo en un subdirectorio dentro del datasubdirectorio. Sin embargo, recibo el siguiente error:

{ [Error: ENOENT, open 'D:\data\tmp\test.txt'] errno: 34, code: 'ENOENT', path: 'D:\\data\\tmp\\test.txt' }

El código es el siguiente:

var fs = require('fs');
fs.writeFile("tmp/test.txt", "Hey there!", function(err) {
    if(err) {
        console.log(err);
    } else {
        console.log("The file was saved!");
    }
}); 

¿Alguien puede ayudarme a descubrir cómo hacer que Node.js cree la estructura de directorios si no sale para escribir en un archivo?

Hirvesh
fuente
1
fs.promises.mkdir(path.dirname("tmp/test.txt"), {recursive: true}).then(x => fs.promises.writeFile("tmp/test.txt", "Hey there!"))
Offenso

Respuestas:

127

Nodo> 10.12.0

fs.mkdir ahora acepta una { recursive: true }opción como esta:

// Creates /tmp/a/apple, regardless of whether `/tmp` and /tmp/a exist.
fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
  if (err) throw err;
});

o con una promesa:

fs.promises.mkdir('/tmp/a/apple', { recursive: true }).catch(console.error);

Nodo <= 10.11.0

Puede resolver esto con un paquete como mkdirp o fs-extra . Si no desea instalar un paquete, consulte la respuesta de Tiago Peres França a continuación.

David Weldon
fuente
44
Ese es con el que voy ... esas estadísticas me convencieron.
Aran Mulholland
tenga en cuenta que fs.promisestodavía es experimental nodejs.org/dist/latest-v10.x/docs/api/…
lasec0203
132

Si no desea utilizar ningún paquete adicional, puede llamar a la siguiente función antes de crear su archivo:

var path = require('path'),
    fs = require('fs');

function ensureDirectoryExistence(filePath) {
  var dirname = path.dirname(filePath);
  if (fs.existsSync(dirname)) {
    return true;
  }
  ensureDirectoryExistence(dirname);
  fs.mkdirSync(dirname);
}
Tiago Peres França
fuente
2
Esto debería usarse en statSynclugar de existsSync, basado en stackoverflow.com/questions/4482686/…
GavinR
1
pathtambién es un paquete que debe ser requerido como fs: var path = require('path')en caso de que alguien se pregunte. Ver documentación del nodo .
Rafael Emshoff
9
fs.existsSyncno está en desuso , solo lo fs.existsestá.
zzzzBov
66
Ha habido cierta confusión sobre si la función fs.existsSync ha quedado en desuso o no. Al principio, según tengo entendido, pensé que era, así que actualicé la respuesta para reflejar esto. Pero ahora, como señaló @zzzzBov, la documentación establece claramente que solo fs.exists ha quedado en desuso, el uso de fs.existsSync sigue siendo válido. Por esta razón, eliminé el código anterior y mi respuesta ahora solo contiene la solución más simple (con el uso de fs.existsSync).
Tiago Peres França
1
@chrismarx imagina la siguiente ruta: "/home/documents/a/b/c/myfile.txt". "/ home / documents" existe, mientras que todo lo que tiene delante no. Cuando se llama a "sureDirectoryExistence" por primera vez, el nombre de directorio es "/ home / documents / a / b / c". No puedo llamar a fs.mkdirSync (dirname) en este momento porque "/ home / documents / a / b" tampoco existe. Para crear el directorio "c", primero debo asegurar la existencia de "/ home / documents / a / b".
Tiago Peres França
43

Con node-fs-extra puedes hacerlo fácilmente.

Instalarlo

npm install --save fs-extra

Luego usa el outputFilemétodo. Su documentación dice:

Casi lo mismo que writeFile (es decir, se sobrescribe), excepto que si el directorio padre no existe, se crea.

Puedes usarlo de tres maneras:

Estilo de devolución de llamada

const fse = require('fs-extra');

fse.outputFile('tmp/test.txt', 'Hey there!', err => {
  if(err) {
    console.log(err);
  } else {
    console.log('The file was saved!');
  }
})

Usando promesas

Si usa promesas , y eso espero, este es el código:

fse.outputFile('tmp/test.txt', 'Hey there!')
   .then(() => {
       console.log('The file was saved!');
   })
   .catch(err => {
       console.error(err)
   });

Versión de sincronización

Si quieres una versión de sincronización, solo usa este código:

fse.outputFileSync('tmp/test.txt', 'Hey there!')

Para obtener una referencia completa, consulte la outputFiledocumentación y todos los métodos soportados por node-fs-extra .

lifeisfoo
fuente
26

¡Alerta de enchufe descarado!

Deberá verificar cada directorio en la estructura de ruta que desee y crearlo manualmente si no existe. Todas las herramientas para hacerlo ya están allí en el módulo fs de Node, pero puede hacer todo eso simplemente con mi módulo mkpath: https://github.com/jrajav/mkpath

jrajav
fuente
1
¿creará eso el archivo directamente o solo la estructura del directorio? Estoy buscando una solución que cree el archivo junto con la estructura del directorio al crear el archivo.
Hirvesh
Solo la estructura del directorio. Primero usaría mkdir / path y, si no hubo ningún error, proceda a escribir su archivo. Sería bastante simple escribir una función para hacer las dos cosas simultáneamente, dada la ruta completa a un archivo para escribir, simplemente dividir el nombre de archivo usando path.basename
jrajav
1
De hecho, fue tan simple que lo escribí en 2 minutos . :) (Sin probar)
jrajav
Actualización: Probado y editado, intente nuevamente si no funcionó la primera vez.
jrajav
8
@Kiyura ¿Cómo es esto diferente del ampliamente utilizado mkdirp ?
David Weldon
9

Como todavía no puedo comentar, estoy publicando una respuesta mejorada basada en la fantástica solución @ tiago-peres-frança (¡gracias!). Su código no crea un directorio en un caso en el que solo falta el último directorio en la ruta, por ejemplo, la entrada es "C: / test / abc" y "C: / test" ya existe. Aquí hay un fragmento que funciona:

function mkdirp(filepath) {
    var dirname = path.dirname(filepath);

    if (!fs.existsSync(dirname)) {
        mkdirp(dirname);
    }

    fs.mkdirSync(filepath);
}
micx
fuente
1
Eso es porque la solución de @tiago espera una ruta de archivo . En su caso, abcse interpreta como el archivo para el que necesita crear un directorio. Para crear también el abcdirectorio, agregue un archivo ficticio a su ruta, por ejemplo C:/test/abc/dummy.txt.
Sphinxxx
Uso recursivo:fs.promises.mkdir(path.dirname(file), {recursive: true}).then(x => fs.promises.writeFile(file, data))
Offenso
1
@Offenso es la mejor solución, pero solo para Node.js versión 10.12 y superior.
Nickensoul
8

Mi consejo es: trate de no depender de las dependencias cuando pueda hacerlo fácilmente con pocas líneas de códigos

Esto es lo que está tratando de lograr en 14 líneas de código:

fs.isDir = function(dpath) {
    try {
        return fs.lstatSync(dpath).isDirectory();
    } catch(e) {
        return false;
    }
};
fs.mkdirp = function(dirname) {
    dirname = path.normalize(dirname).split(path.sep);
    dirname.forEach((sdir,index)=>{
        var pathInQuestion = dirname.slice(0,index+1).join(path.sep);
        if((!fs.isDir(pathInQuestion)) && pathInQuestion) fs.mkdirSync(pathInQuestion);
    });
};
Alex C.
fuente
1
¿No sería la tercera línea mejor así? return fs.lstatSync(dpath).isDirectory(), de lo contrario, ¿qué pasaría si isDirectory () devuelve falso?
Giorgio Aresu
2
Uso recursivo:fs.promises.mkdir(path.dirname(file), {recursive: true}).then(x => fs.promises.writeFile(file, data))
Offenso
1
@Offenso no es compatible con un nodo 8
Ievgen Naida
2

Acabo de publicar este módulo porque necesitaba esta funcionalidad.

https://www.npmjs.org/package/filendir

Funciona como un contenedor alrededor de los métodos Node.js fs. Así que se puede usar de la misma manera que lo haría con fs.writeFiley fs.writeFileSync(tanto asíncrona y escrituras sincrónicas)

Kev
fuente