Criar diretório ao gravar em arquivo no Node.js

134

Estive mexendo no Node.js e encontrei um pequeno problema. Eu tenho um script que reside em um diretório chamado data. Quero que o script grave alguns dados em um arquivo em um subdiretório dentro do datasubdiretório. No entanto, estou recebendo o seguinte erro:

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

O código é o seguinte:

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!");
    }
}); 

Alguém pode me ajudar a descobrir como fazer o Node.js criar a estrutura de diretórios se ela não sair para gravar em um arquivo?

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

Respostas:

127

Nó> 10.12.0

O fs.mkdir agora aceita uma { recursive: true }opção assim:

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

ou com uma promessa:

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

Nó <= 10.11.0

Você pode resolver isso com um pacote como mkdirp ou fs-extra . Se você não deseja instalar um pacote, consulte a resposta de Tiago Peres França abaixo.

David Weldon
fonte
4
É com quem eu vou ... essas estatísticas me conquistaram.
Aran Mulholland
observe que fs.promisesainda é experimental nodejs.org/dist/latest-v10.x/docs/api/… #
lasec0203
132

Se você não quiser usar nenhum pacote adicional, poderá chamar a seguinte função antes de criar seu arquivo:

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
fonte
2
Isso deve usar statSyncem vez de existsSync, com base em stackoverflow.com/questions/4482686/...
GavinR
1
pathtambém é um pacote que precisa ser exigido da seguinte maneira fs: var path = require('path')caso alguém esteja se perguntando. Consulte a documentação do nó .
Rafael Emshoff 14/10
9
fs.existsSyncnão está obsoleto , apenas fs.existsestá.
zzzzBov
6
Houve alguma confusão sobre se a função fs.existsSync foi preterida ou não. No começo, pelo meu entendimento, eu pensava que era, então atualizei a resposta para refletir isso. Mas agora, como apontado por @zzzzBov, a documentação afirma claramente que apenas o fs.exists foi preterido, o uso do fs.existsSync ainda é válido. Por esse motivo, excluí o código anterior e minha resposta agora contém apenas a solução mais simples (com o uso de fs.existsSync).
Tiago Peres França
1
@chrismarx imagine o seguinte caminho: "/home/documents/a/b/c/myfile.txt". "/ home / documents" existe, enquanto tudo à sua frente não existe. Quando a opção "allowDirectoryExistence" é chamada pela primeira vez, o nome do diretório é "/ home / documents / a / b / c". Não posso chamar fs.mkdirSync (dirname) agora porque "/ home / documents / a / b" também não existe. Para criar o diretório "c", preciso primeiro garantir a existência de "/ home / documents / a / b".
Tiago Peres França
43

Com o nó-fs-extra você pode fazer isso facilmente.

Instale-o

npm install --save fs-extra

Então use o outputFilemétodo Sua documentação diz:

Quase o mesmo que writeFile (ou seja, sobrescreve), exceto que, se o diretório pai não existir, ele será criado.

Você pode usá-lo de três maneiras:

Estilo de retorno de chamada

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 promessas

Se você usa promessas , e espero que sim, este é o código:

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

Versão de sincronização

Se você deseja uma versão de sincronização, basta usar este código:

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

Para uma referência completa, verifique a outputFiledocumentação e todos os métodos suportados pelo node-fs-extra .

lifeisfoo
fonte
26

Alerta de plugue sem vergonha!

Você precisará verificar cada diretório na estrutura de caminho desejada e criá-lo manualmente, caso não exista. Todas as ferramentas para isso já estão no módulo fs do Node, mas você pode fazer tudo isso simplesmente com o meu módulo mkpath: https://github.com/jrajav/mkpath

jrajav
fonte
1
isso criará o arquivo diretamente ou apenas a estrutura de diretórios? Estou procurando uma solução que crie o arquivo junto com a estrutura de diretórios ao criar o arquivo.
Hirvesh
Apenas a estrutura de diretórios. Primeiro, você usaria o mkdir / path e, se não houvesse erros, continuaria escrevendo seu arquivo. Seria o suficiente simples de escrever uma função para fazer as duas coisas ao mesmo tempo, dado um caminho completo para um arquivo para gravação - basta separar o nome de arquivo usando path.basename
jrajav
1
Na verdade, era tão simples que eu escrevi em 2 minutos . :) (Não testado)
jrajav
Atualização: testada e editada, tente novamente se não funcionar da primeira vez.
jrajav
8
@Kiyura Como isso difere do amplamente utilizado mkdirp ?
precisa
9

Como ainda não posso comentar, estou postando uma resposta aprimorada com base na solução fantástica @ tiago-peres-frança (obrigado!). Seu código não cria diretório em um caso em que apenas o último diretório esteja ausente no caminho, por exemplo, a entrada é "C: / test / abc" e "C: / test" já existe. Aqui está um trecho que funciona:

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

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

    fs.mkdirSync(filepath);
}
micx
fonte
1
Isso porque a solução do @ tiago espera um caminho de arquivo . No seu caso, abcé interpretado como o arquivo para o qual você precisa criar um diretório. Para também criar o abcdiretório, adicione um arquivo fictício ao seu caminho, por exemplo C:/test/abc/dummy.txt.
Sphinxxx
Use recursivo:fs.promises.mkdir(path.dirname(file), {recursive: true}).then(x => fs.promises.writeFile(file, data))
Offenso
1
@Offenso é a melhor solução, mas apenas para o Node.js. versão 10.12 e superior.
Nickensoul
8

Meu conselho é: tente não confiar nas dependências quando puder fazê-lo facilmente com poucas linhas de códigos

Aqui está o que você está tentando alcançar em 14 linhas 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.
fonte
1
A terceira linha não seria melhor assim? return fs.lstatSync(dpath).isDirectory(), caso contrário, o que aconteceria se isDirectory () retornar false?
Giorgio Aresu
2
Use recursivo:fs.promises.mkdir(path.dirname(file), {recursive: true}).then(x => fs.promises.writeFile(file, data))
Offenso
1
@Offenso não é suportado por um nó 8
Ievgen Naida
2

Acabei de publicar este módulo porque precisava dessa funcionalidade.

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

Ele funciona como um invólucro em torno dos métodos Node.js fs. Assim, você pode usá-lo exatamente da mesma maneira que você faria com fs.writeFilee fs.writeFileSync(ambos assíncrona e gravações síncronas)

Kev
fonte