Como criar o caminho completo com o fs.mkdirSync do nó?


159

Estou tentando criar um caminho completo se ele não existir.

O código fica assim:

var fs = require('fs');
if (!fs.existsSync(newDest)) fs.mkdirSync(newDest); 

Esse código funciona muito bem, desde que haja apenas um subdiretório (um newDest como 'dir1'), mas quando existe um caminho de diretório como ('dir1 / dir2'), ele falha com o Erro: ENOENT, nenhum arquivo ou diretório desse tipo

Eu gostaria de poder criar o caminho completo com o mínimo de linhas de código necessárias.

Eu li que há uma opção recursiva no fs e tentei assim

var fs = require('fs');
if (!fs.existsSync(newDest)) fs.mkdirSync(newDest,'0777', true);

Eu sinto que deveria ser tão simples criar recursivamente um diretório que não existe. Estou faltando alguma coisa ou preciso analisar o caminho e verificar cada diretório e criá-lo, se ele ainda não existir?

Eu sou muito novo no Node. Talvez eu esteja usando uma versão antiga do FS?


1
github.com/substack/node-mkdirp e todos os tipos de outras soluções nesta pesquisa do Google .
jfriend00

4
Pergunta @AndyRay Este StackOverflow agora é o primeiro resultado no Google para esta pergunta, o que é engraçado, porque isso significa que é recursi ....
Matt Parkins

1
Isso era um problema em versões antigas do Node, a atualização para o nó 12+ resolve o problema
MrJomp

Respostas:


48

Uma opção é usar o módulo shelljs

npm install shelljs

var shell = require('shelljs');
shell.mkdir('-p', fullPath);

A partir dessa página:

Opções disponíveis:

p: caminho completo (criará dirs intermediários, se necessário)

Como outros observaram, há outros módulos mais focados. Mas, fora do mkdirp, ele tem várias outras operações úteis do shell (como qual, grep etc ...) e funciona no windows e * nix


2
Obrigado! Acabei usando exec (eu já estava usando isso) e funcionou como um encanto. var exec = require ('processo_filho'). exec; var comando = "mkdir -p '" + newDest + "'"; var opções = {}; var after = function (erro, stdout, stderr) {console.log ('erro', erro); console.log ('stdout', stdout); console.log ('stderr', stderr); } exec (comando, opções, depois);
David Silva Smith

24
Essa opção pode ser interrompida nas plataformas node.js. que não possuem uma instância mkdir da linha de comando (por exemplo, hosts que não sejam do Linux) e, portanto, não é portátil, se isso importa.
cshotton

1
@cshotton - você está se referindo ao comentário ou à resposta? O shelljs funciona mesmo no Windows. exec mkdir -p (o comentário) claro que não.
Bryanmac

Você pode usar esta função interessante com Promise ou retorno de chamada de sua escolha.
Илья Зеленько

1
isso não é uma solução, é uma alternativa à solução. contexto: pics.onsizzle.com/…
Nika Kasradze

413

Editar

A versão do NodeJS 10.12.0adicionou um suporte nativo para ambos mkdire mkdirSyncpara criar um diretório recursivamente com a recursive: trueopção como a seguir:

fs.mkdirSync(targetDir, { recursive: true });

E se você preferir fs Promises API, você pode escrever

fs.promises.mkdir(targetDir, { recursive: true });

Resposta original

Crie diretórios recursivamente se eles não existirem! ( Zero dependências )

const fs = require('fs');
const path = require('path');

function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) {
  const sep = path.sep;
  const initDir = path.isAbsolute(targetDir) ? sep : '';
  const baseDir = isRelativeToScript ? __dirname : '.';

  return targetDir.split(sep).reduce((parentDir, childDir) => {
    const curDir = path.resolve(baseDir, parentDir, childDir);
    try {
      fs.mkdirSync(curDir);
    } catch (err) {
      if (err.code === 'EEXIST') { // curDir already exists!
        return curDir;
      }

      // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows.
      if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure.
        throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`);
      }

      const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1;
      if (!caughtErr || caughtErr && curDir === path.resolve(targetDir)) {
        throw err; // Throw if it's just the last created dir.
      }
    }

    return curDir;
  }, initDir);
}

Uso

// Default, make directories relative to current working directory.
mkDirByPathSync('path/to/dir');

// Make directories relative to the current script.
mkDirByPathSync('path/to/dir', {isRelativeToScript: true});

// Make directories with an absolute path.
mkDirByPathSync('/path/to/dir');

Demo

Tente!

Explicações

  • [UPDATE] Esta solução lida com erros específicos da plataforma, como EISDIRno Mac EPERMe EACCESno Windows. Obrigado a todos os comentários de @PediT., @JohnQ, @ deed02392, @robyoder e @Almenon.
  • Esta solução lida com caminhos relativos e absolutos . Graças ao comentário @john.
  • No caso de caminhos relativos, os diretórios de destino serão criados (resolvidos) no diretório de trabalho atual. Para resolvê-los em relação ao diretório de script atual, passe {isRelativeToScript: true}.
  • Usando path.sepe path.resolve()não apenas /concatenação, para evitar problemas entre plataformas.
  • Usando fs.mkdirSynce manusear o erro com try/catchse jogado para lidar com as condições de corrida: outro processo pode adicionar o arquivo entre as chamadas para fs.existsSync()e fs.mkdirSync()e causa uma exceção.
    • A outra maneira de conseguir isso seria verificar se existe um arquivo e criá-lo, ou seja if (!fs.existsSync(curDir) fs.mkdirSync(curDir);,. Mas esse é um antipadrão que deixa o código vulnerável às condições de corrida. Graças ao comentário de @GershomMaes sobre a verificação de existência do diretório.
  • Requer o Nó v6 e mais recente para suportar a desestruturação. (Se você tiver problemas para implementar esta solução com versões antigas do Node, deixe-me um comentário)

7
Voto positivo pela resposta fácil e recursiva que não requer uma biblioteca ou abordagem adicional!
MikingTheViking

1
Instruções de requisição ausentes: const fs = require ('fs'); const path = require ('caminho');
Christopher Touro

1
@ChristopherBull, intencionalmente não adicionado apenas para focar na lógica, mas, de qualquer forma, eu os adicionei. Graças;)
Mouneer

1
12 linhas de código sólido, zero dependências, eu aceito todas as vezes.
Moodboom 8/07

1
@Mouneer no Mac OS X 10.12.6, o erro gerado ao tentar criar "/" depois de passar em um caminho absoluto é "EISDIR" (Erro: EISDIR: operação ilegal em um diretório, mkdir '/'). Eu acho que provavelmente verificar a existência de diretórios ainda é o melhor caminho a seguir (reconhecendo que será mais lento).
John Q

78

Uma resposta mais robusta é usar o uso mkdirp .

var mkdirp = require('mkdirp');

mkdirp('/path/to/dir', function (err) {
    if (err) console.error(err)
    else console.log('dir created')
});

Em seguida, continue a escrever o arquivo no caminho completo com:

fs.writeFile ('/path/to/dir/file.dat'....

Prefere esta resposta desde que você está importando apenas o que você precisa, não uma biblioteca inteira
Juan Mendes

1
Parabéns pelo distintivo populista ;-)
janos

1
Obrigado. É o melhor método.
Stepan Rafael


48

fs-extra adiciona métodos de sistema de arquivos que não estão incluídos no módulo fs nativo. É uma queda no substituto do fs.

Instalar fs-extra

$ npm install --save fs-extra

const fs = require("fs-extra");
// Make sure the output directory is there.
fs.ensureDirSync(newDest);

Existem opções de sincronização e assíncrona.

https://github.com/jprichardson/node-fs-extra/blob/master/docs/ensureDir.md


5
Esta é a melhor resposta! A maioria de nós já tem o fs-extra no aplicativo de qualquer maneira.
Página

Isso seria ótimo se oferecesse a possibilidade de usar memfspara testes de unidade. Não funciona :-( github.com/jprichardson/node-fs-extra/issues/274
schnatterer

31

Usando reduzir podemos verificar se cada caminho existe e criá-lo, se necessário, também desta forma acho que é mais fácil seguir. Editado, graças a @Arvin, devemos usar path.sep para obter o separador de segmento de caminho específico da plataforma.

const path = require('path');

// Path separators could change depending on the platform
const pathToCreate = 'path/to/dir'; 
pathToCreate
 .split(path.sep)
 .reduce((prevPath, folder) => {
   const currentPath = path.join(prevPath, folder, path.sep);
   if (!fs.existsSync(currentPath)){
     fs.mkdirSync(currentPath);
   }
   return currentPath;
 }, '');

4
Ao dar uma resposta, é preferível dar uma explicação sobre POR QUE sua resposta é essa.
Stephen Rauch

Desculpe, você está certo, eu acho que essa forma é mais limpo e mais fácil de seguir
josebui

4
@josebui Acho melhor usar "path.sep" em vez de barra (/) para evitar problemas específicos do ambiente.
Arvin

boa solução porque não requer nó> = 10 como as outras respostas
Karim

29

Esse recurso foi adicionado ao node.js na versão 10.12.0, portanto, é tão fácil quanto passar uma opção {recursive: true}como segundo argumento para a fs.mkdir()chamada. Veja o exemplo nos documentos oficiais .

Não há necessidade de módulos externos ou sua própria implementação.


1
Achei o relacionado solicitação de recebimento github.com/nodejs/node/pull/23313
nurettin

1
Irá gerar um erro quando o diretório existir e parar. O uso de um bloco try catch pode continuar criando outra pasta inexistente.
Choco Li

1
Essa deve ser a resposta aceita. Ele não dispara se o diretório já existe e pode ser usado com async / waitit via fs.promises.mkdir.
Rich Apodaca

7

Eu sei que esta é uma pergunta antiga, mas o nodejs v10.12.0 agora suporta isso nativamente com a recursiveopção definida como true. fs.mkdir

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


2

Exemplo para Windows (sem dependências extras e tratamento de erros)

const path = require('path');
const fs = require('fs');

let dir = "C:\\temp\\dir1\\dir2\\dir3";

function createDirRecursively(dir) {
    if (!fs.existsSync(dir)) {        
        createDirRecursively(path.join(dir, ".."));
        fs.mkdirSync(dir);
    }
}

createDirRecursively(dir); //creates dir1\dir2\dir3 in C:\temp

2

Você pode simplesmente verificar se a pasta existe ou não no caminho recursivamente e criar a pasta conforme você verifica se elas não estão presentes. ( SEM BIBLIOTECA EXTERNA )

function checkAndCreateDestinationPath (fileDestination) {
    const dirPath = fileDestination.split('/');
    dirPath.forEach((element, index) => {
        if(!fs.existsSync(dirPath.slice(0, index + 1).join('/'))){
            fs.mkdirSync(dirPath.slice(0, index + 1).join('/')); 
        }
    });
}

2

Você pode usar a próxima função

const recursiveUpload = (caminho: string) => {caminhos constantes = caminho.split ("/")

const fullPath = paths.reduce((accumulator, current) => {
  fs.mkdirSync(accumulator)
  return `${accumulator}/${current}`
  })

  fs.mkdirSync(fullPath)

  return fullPath
}

Então, o que faz:

  1. Crio paths variável, onde ele armazena todos os caminhos por si só como um elemento da matriz.
  2. Adiciona "/" no final de cada elemento da matriz.
  3. Faz para o ciclo:
    1. Cria um diretório a partir da concatenação dos elementos da matriz cujos índices são de 0 à iteração atual. Basicamente, é recursivo.

Espero que ajude!

A propósito, no Nó v10.12.0, você pode usar a criação de caminhos recursivos, fornecendo-o como argumento adicional.

fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => { if (err) throw err; });

https://nodejs.org/api/fs.html#fs_fs_mkdirsync_path_options


1

Muitas respostas, mas aqui está uma solução sem recursão que funciona dividindo o caminho e construindo da esquerda para a direita novamente

function mkdirRecursiveSync(path) {
    let paths = path.split(path.delimiter);
    let fullPath = '';
    paths.forEach((path) => {

        if (fullPath === '') {
            fullPath = path;
        } else {
            fullPath = fullPath + '/' + path;
        }

        if (!fs.existsSync(fullPath)) {
            fs.mkdirSync(fullPath);
        }
    });
};

Para aqueles preocupados com a compatibilidade do Windows com o Linux, basta substituir a barra com barra invertida dupla '\' nas duas ocorrências acima, mas TBH estamos falando sobre a linha de comando do nó fs not windows e a primeira é bastante tolerante e o código acima simplesmente funciona Windows e é mais uma solução completa entre plataformas.


os arquivos no Windows são tratados com barra invertida e não barra dianteira. Seu código simplesmente não funcionará lá. C: \ test \ data ...
DDD

Editado, mas sugiro que você valide seu comentário. No nó, tente o seguinte e veja o que acontece var fs = require ('fs') fs.mkdirSync ('test') fs.mkdirSync ('test \\ test1') fs.mkdirSync ('test / test2')
Hamiora

Tudo o que você está dizendo ..., meu voto negativo ainda permanece até você aprender a escrever um código melhor.
DDD

Haha Ok, vou trabalhar muito duro para aprender a escrever um código melhor. Entre a maioria das respostas acima, incluindo o OP, use barras. Sugira que você pare de trollar.
11309 Hamiora

1
path.sepestá vindo como / ou \\ para mim. path.delimiteré: ou;.
Josh Anderson Slate

1
const fs = require('fs');

try {
    fs.mkdirSync(path, { recursive: true });
} catch (error) {
    // this make script keep running, even when folder already exist
    console.log(error);
}

0

Uma maneira assíncrona de criar diretórios recursivamente:

import fs from 'fs'

const mkdirRecursive = function(path, callback) {
  let controlledPaths = []
  let paths = path.split(
    '/' // Put each path in an array
  ).filter(
    p => p != '.' // Skip root path indicator (.)
  ).reduce((memo, item) => {
    // Previous item prepended to each item so we preserve realpaths
    const prevItem = memo.length > 0 ? memo.join('/').replace(/\.\//g, '')+'/' : ''
    controlledPaths.push('./'+prevItem+item)
    return [...memo, './'+prevItem+item]
  }, []).map(dir => {
    fs.mkdir(dir, err => {
      if (err && err.code != 'EEXIST') throw err
      // Delete created directory (or skipped) from controlledPath
      controlledPaths.splice(controlledPaths.indexOf(dir), 1)
      if (controlledPaths.length === 0) {
        return callback()
      }
    })
  })
}

// Usage
mkdirRecursive('./photos/recent', () => {
  console.log('Directories created succesfully!')
})

0

Aqui está a minha versão imperativa do mkdirpfor nodejs.

function mkdirSyncP(location) {
    let normalizedPath = path.normalize(location);
    let parsedPathObj = path.parse(normalizedPath);
    let curDir = parsedPathObj.root;
    let folders = parsedPathObj.dir.split(path.sep);
    folders.push(parsedPathObj.base);
    for(let part of folders) {
        curDir = path.join(curDir, part);
        if (!fs.existsSync(curDir)) {
            fs.mkdirSync(curDir);
        }
    }
}

0

Que tal essa abordagem:

if (!fs.existsSync(pathToFile)) {
            var dirName = "";
            var filePathSplit = pathToFile.split('/');
            for (var index = 0; index < filePathSplit.length; index++) {
                dirName += filePathSplit[index]+'/';
                if (!fs.existsSync(dirName))
                    fs.mkdirSync(dirName);
            }
        }

Isso funciona para o caminho relativo.


0

Com base na resposta das dependências zero do montanhista , aqui está uma Typescriptvariante um pouco mais amigável para iniciantes , como um módulo:

import * as fs from 'fs';
import * as path from 'path';

/**
* Recursively creates directories until `targetDir` is valid.
* @param targetDir target directory path to be created recursively.
* @param isRelative is the provided `targetDir` a relative path?
*/
export function mkdirRecursiveSync(targetDir: string, isRelative = false) {
    const sep = path.sep;
    const initDir = path.isAbsolute(targetDir) ? sep : '';
    const baseDir = isRelative ? __dirname : '.';

    targetDir.split(sep).reduce((prevDirPath, dirToCreate) => {
        const curDirPathToCreate = path.resolve(baseDir, prevDirPath, dirToCreate);
        try {
            fs.mkdirSync(curDirPathToCreate);
        } catch (err) {
            if (err.code !== 'EEXIST') {
                throw err;
            }
            // caught EEXIST error if curDirPathToCreate already existed (not a problem for us).
        }

        return curDirPathToCreate; // becomes prevDirPath on next call to reduce
    }, initDir);
}

0

Tão limpo quanto isso :)

function makedir(fullpath) {
  let destination_split = fullpath.replace('/', '\\').split('\\')
  let path_builder = destination_split[0]
  $.each(destination_split, function (i, path_segment) {
    if (i < 1) return true
    path_builder += '\\' + path_segment
    if (!fs.existsSync(path_builder)) {
      fs.mkdirSync(path_builder)
    }
  })
}

0

Como tive problemas com a opção recursiva do fs.mkdir, criei uma função que faz o seguinte:

  1. Cria uma lista de todos os diretórios, começando com o diretório de destino final e trabalhando até o pai raiz.
  2. Cria uma nova lista de diretórios necessários para a função mkdir funcionar
  3. Faz com que cada diretório seja necessário, incluindo o final

    function createDirectoryIfNotExistsRecursive(dirname) {
        return new Promise((resolve, reject) => {
           const fs = require('fs');
    
           var slash = '/';
    
           // backward slashes for windows
           if(require('os').platform() === 'win32') {
              slash = '\\';
           }
           // initialize directories with final directory
           var directories_backwards = [dirname];
           var minimize_dir = dirname;
           while (minimize_dir = minimize_dir.substring(0, minimize_dir.lastIndexOf(slash))) {
              directories_backwards.push(minimize_dir);
           }
    
           var directories_needed = [];
    
           //stop on first directory found
           for(const d in directories_backwards) {
              if(!(fs.existsSync(directories_backwards[d]))) {
                 directories_needed.push(directories_backwards[d]);
              } else {
                 break;
              }
           }
    
           //no directories missing
           if(!directories_needed.length) {
              return resolve();
           }
    
           // make all directories in ascending order
           var directories_forwards = directories_needed.reverse();
    
           for(const d in directories_forwards) {
              fs.mkdirSync(directories_forwards[d]);
           }
    
           return resolve();
        });
     }

-1

Exec pode ser confuso no Windows. Existe uma solução mais "nodie". Fundamentalmente, você tem uma chamada recursiva para ver se existe um diretório e mergulhar no filho (se ele existir) ou criá-lo. Aqui está uma função que criará os filhos e chamará uma função quando terminar:

fs = require('fs');
makedirs = function(path, func) {
 var pth = path.replace(/['\\]+/g, '/');
 var els = pth.split('/');
 var all = "";
 (function insertOne() {
   var el = els.splice(0, 1)[0];
   if (!fs.existsSync(all + el)) {
    fs.mkdirSync(all + el);
   }
   all += el + "/";
   if (els.length == 0) {
    func();
   } else {
     insertOne();
   }
   })();

}


-1

Esta versão funciona melhor no Windows do que a resposta principal, porque entende as duas /e path.seppara que as barras funcionem no Windows como deveriam. Oferece suporte a caminhos absolutos e relativos (relativos ao process.cwd).

/**
 * Creates a folder and if necessary, parent folders also. Returns true
 * if any folders were created. Understands both '/' and path.sep as 
 * path separators. Doesn't try to create folders that already exist,
 * which could cause a permissions error. Gracefully handles the race 
 * condition if two processes are creating a folder. Throws on error.
 * @param targetDir Name of folder to create
 */
export function mkdirSyncRecursive(targetDir) {
  if (!fs.existsSync(targetDir)) {
    for (var i = targetDir.length-2; i >= 0; i--) {
      if (targetDir.charAt(i) == '/' || targetDir.charAt(i) == path.sep) {
        mkdirSyncRecursive(targetDir.slice(0, i));
        break;
      }
    }
    try {
      fs.mkdirSync(targetDir);
      return true;
    } catch (err) {
      if (err.code !== 'EEXIST') throw err;
    }
  }
  return false;
}

O voto negativo por dar suporte ao Windows corretamente? Eu mencionei que ele funciona em outros sistemas operacionais também?
Qwertie
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.