Copie a pasta recursivamente no node.js

154

Existe uma maneira mais fácil de copiar uma pasta e todo o seu conteúdo sem fazer manualmente uma sequência de fs.readir, fs.readfile, fs.writefilerecursivamente?

Basta saber se estou faltando uma função que idealmente funcionaria assim

fs.copy("/path/to/source/folder","/path/to/destination/folder");
lostsource
fonte
3
Existe uma maneira de fazer isso sem nenhum módulo? Talvez uma função recursiva / código snip-it?
Sukima 08/08
@Sukima - Veja minha resposta aqui .
precisa saber é o seguinte

Respostas:

121

Você pode usar o módulo ncp . Eu acho que é disso que você precisa

shift66
fonte
2
Perfeito! npm install ncpe trabalhando em menos de 30 anos. Obrigado.
Aebsubis
1
A chave inglesa é melhor para mim, pois suporta mais opções. Com o NCP, você não pode resolver links simbólicos, por exemplo.
Slava Fomin II
3
Como um bônus incrível, pode-se usar o ncp em scripts de execução do npm de plataforma cruzada.
Ciantic
Eu tenho alguns casos simples em que o ncp não entra no meu retorno de chamada, onde o fs-extra faz corretamente.
bumpmann
40
Observe que o ncp parece não ter manutenção . fs-extra é provavelmente a melhor opção.
chris
74

Esta é a minha abordagem para resolver esse problema sem nenhum módulo extra. Apenas usando o built-in fse os pathmódulos.

Nota: Isso usa as funções de leitura / gravação do fs para que não copie nenhum metadado (hora da criação, etc.). A partir do nó 8.5, existem copyFileSyncfunções disponíveis que chamam as funções de cópia do SO e, portanto, também copiam metadados. Ainda não os testei, mas deve funcionar apenas para substituí-los. (Consulte https://nodejs.org/api/fs.html#fs_fs_copyfilesync_src_dest_flags )

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

function copyFileSync( source, target ) {

    var targetFile = target;

    //if target is a directory a new file with the same name will be created
    if ( fs.existsSync( target ) ) {
        if ( fs.lstatSync( target ).isDirectory() ) {
            targetFile = path.join( target, path.basename( source ) );
        }
    }

    fs.writeFileSync(targetFile, fs.readFileSync(source));
}

function copyFolderRecursiveSync( source, target ) {
    var files = [];

    //check if folder needs to be created or integrated
    var targetFolder = path.join( target, path.basename( source ) );
    if ( !fs.existsSync( targetFolder ) ) {
        fs.mkdirSync( targetFolder );
    }

    //copy
    if ( fs.lstatSync( source ).isDirectory() ) {
        files = fs.readdirSync( source );
        files.forEach( function ( file ) {
            var curSource = path.join( source, file );
            if ( fs.lstatSync( curSource ).isDirectory() ) {
                copyFolderRecursiveSync( curSource, targetFolder );
            } else {
                copyFileSync( curSource, targetFolder );
            }
        } );
    }
}
Simon Zyx
fonte
não copiar pastas se eles têm espaço em seus nomes
31415926
Para mim, copia pastas com espaços em seus nomes. Talvez tenha sido causado pelo erro corrigido pelo @victor. Como estou usando essa função regularmente (no estado atual, como esqueci de atualizar a mesma correção que o victor fez), tenho certeza de que ele funciona em geral.
Simon Zyx
1
Também precisa: javascript var fs = require('fs'); var path = require('path');
Tyler
2
Na verdade, isso não copia arquivos. Ele lê e depois escreve. Isso não está copiando. A cópia inclui a data de criação, bem como outros fluxos de metadados aos quais o Windows e o MacOS suportam e não são copiados por esse código. No nó 8.5, você deve chamar fs.copyou, fs.copySynccomo elas chamam, as funções de cópia no nível do SO no MacOS e Windows e, assim, copiar arquivos.
gman
1
Desculpe é fs.copyFilee se sua escavação através da fonte nó que você vai ver em Mac e Windows que chamam a função OS específica para copiar um arquivo
gman
52

Existem alguns módulos que oferecem suporte à cópia de pastas com seu conteúdo. O mais popular seria chave

// Deep-copy an existing directory
wrench.copyDirSyncRecursive('directory_to_copy', 'location_where_copy_should_end_up');

Uma alternativa seria node-fs-extra

fs.copy('/tmp/mydir', '/tmp/mynewdir', function (err) {
  if (err) {
    console.error(err);
  } else {
    console.log("success!");
  }
}); //copies directory, even if it has subdirectories or files
zemirco
fonte
3
Chave falhar se o diretório de cópia contém um link simbólico
DoubleMalt
2
ele também falha no Windows, se o diretório já existir, o ncp funcionou imediatamente.
blended
6
node-fs-extra funcionou para mim. Ele herda o fs original e eu gostei da maneira de lidar com o processo. Menos código para atualizar no aplicativo.
Dvdmn
15
Por favor note que wrenchestá obsoleto e deverá ser substituído por node-fs-extra( github.com/jprichardson/node-fs-extra )
Ambidex
1
Na verdade, o Wrench não copia arquivos. Ele lê e depois escreve e depois copia a data. Isso não está copiando. A cópia inclui outros fluxos de metadados aos quais o Windows e o MacOS suportam e não são copiados por chave inglesa.
gman
38

Aqui está uma função que copia recursivamente um diretório e seu conteúdo para outro diretório:

const fs = require("fs")
const path = require("path")

/**
 * Look ma, it's cp -R.
 * @param {string} src The path to the thing to copy.
 * @param {string} dest The path to the new copy.
 */
var copyRecursiveSync = function(src, dest) {
  var exists = fs.existsSync(src);
  var stats = exists && fs.statSync(src);
  var isDirectory = exists && stats.isDirectory();
  if (isDirectory) {
    fs.mkdirSync(dest);
    fs.readdirSync(src).forEach(function(childItemName) {
      copyRecursiveSync(path.join(src, childItemName),
                        path.join(dest, childItemName));
    });
  } else {
    fs.copyFileSync(src, dest);
  }
};
Lindsey Simon
fonte
3
Mesmo se você inserir uma função de cópia real, você não deve seguir links simbólicos (uso fs.lstatSyncem vez de fs.statSync)
Simon Zyx
3
o que pode ter causado essa confusão é que o fs.unlink exclui arquivos, mas o fs.link não copia, mas vincula.
Simon Zyx 25/09
3
@ SimonSeyock: está certo .. linkingNão está copiando .. O problema é que quando você modifica o conteúdo do arquivo vinculado, o arquivo original também muda.
Abdennour TOUMI
30

fs-extratrabalhou para mim quando ncpe wrenchficou aquém:

https://www.npmjs.com/package/fs-extra

Vai
fonte
3
além disso, o desenvolvedor do wrenchinstrui os usuários a usarem fs-extracomo ele reprovou sua biblioteca.
mozillalives
22

Para sistemas operacionais Linux / unix, você pode usar a sintaxe do shell

const shell = require('child_process').execSync ; 

const src= `/path/src`;
const dist= `/path/dist`;

shell(`mkdir -p ${dist}`);
shell(`cp -r ${src}/* ${dist}`);

É isso aí!

Abdennour TOUMI
fonte
1
Você é bem-vindo Abd
Abdennour TOUMI
1
Esta é a solução mais simples. Não há necessidade de reinventar as ferramentas UNIX!
22817 Michael Franzl
11
desde NodeJS é executado em OSX / Linux / Windows isto só é a resposta para não 2 all 3.
mjwrazor
2
@AbdennourTOUMI e se você estiver executando no servidor Windows.
Mjwrazor
3
Foi por isso que comecei a resposta com "Para SO Linux / unix, você pode usar a sintaxe do shell .." Abd
Abdennour TOUMI
19

O módulo fs-extra funciona como um encanto.

Instale o fs-extra

$ npm install fs-extra

A seguir está o programa para copiar o diretório de origem no diretório de destino.

// include fs-extra package
var fs = require("fs-extra");

var source = 'folderA'
var destination = 'folderB'

// copy source folder to destination
fs.copy(source, destination, function (err) {
    if (err){
        console.log('An error occured while copying the folder.')
        return console.error(err)
    }
    console.log('Copy completed!')
});

Referências

fs-extra: https://www.npmjs.com/package/fs-extra

Exemplo: Tutorial do NodeJS - Node.js Copiar uma Pasta

Mallikarjun M
fonte
esse processo substitui o diretório ou mescla-o?
SM Shahinul Islam
14

É assim que eu faria pessoalmente:

function copyFolderSync(from, to) {
    fs.mkdirSync(to);
    fs.readdirSync(from).forEach(element => {
        if (fs.lstatSync(path.join(from, element)).isFile()) {
            fs.copyFileSync(path.join(from, element), path.join(to, element));
        } else {
            copyFolderSync(path.join(from, element), path.join(to, element));
        }
    });
}

funciona para pastas e arquivos


fonte
3
Esta solução é concisa e direta. Isso seria quase exatamente como eu faria, então um +1 de mim. Você deve melhorar sua resposta com comentários no seu código e descrever por que essa solução é preferível a outras e quais as desvantagens que ela pode ter. - Atualize também os módulos necessários. ("path", "fs")
Andrew
verifique se a pasta existe na parte superior ... salvará vidas ;-) if (! fs.existsSync (to)) fs.mkdirSync (to);
Tobias
9

Criei um pequeno exemplo de trabalho que copia uma pasta de origem para outra pasta de destino em apenas algumas etapas (com base na resposta @ shift66 usando o ncp):

etapa 1 - Instale o módulo ncp:

npm install ncp --save

etapa 2 - crie copy.js (modifique os vars srcPath e destPath para o que você precisar):

var path = require('path');
var ncp = require('ncp').ncp;

ncp.limit = 16;

var srcPath = path.dirname(require.main.filename); //current folder
var destPath = '/path/to/destination/folder'; //Any destination folder

console.log('Copying files...');
ncp(srcPath, destPath, function (err) {
  if (err) {
    return console.error(err);
  }
  console.log('Copying files complete.');
});

passo 3 - executar

node copy.js
Shahar
fonte
7

Isso é muito fácil com o nó 10.

const FSP = require('fs').promises;

async function copyDir(src,dest) {
    const entries = await FSP.readdir(src,{withFileTypes:true});
    await FSP.mkdir(dest);
    for(let entry of entries) {
        const srcPath = Path.join(src,entry.name);
        const destPath = Path.join(dest,entry.name);
        if(entry.isDirectory()) {
            await copyDir(srcPath,destPath);
        } else {
            await FSP.copyFile(srcPath,destPath);
        }
    }
}

Isso pressupõe destque não existe.

mpen
fonte
3
Podemos fazer isso funcionar no Nó 8.x usando require('util').promisifycom fs.mkdire em fs.copyFilevez de require('fs').promises, que ainda é experimental na v11.1.
Sần Trần-Nguyễn
@sntran 8.x tem a withFileTypesopção? Porque isso economiza uma statchamada
mpen
Infelizmente, 8.x não tem withFileTypesopção.
Sần Trần-Nguyễn
@ SơnTrần-Nguyễn 8.x chega ao fim da vida útil em 31 de dezembro de 2019 - pode ser hora de atualizar :-)
mpen
6

Eu já sei muitas respostas aqui, mas ninguém respondeu de maneira simples. Em relação à documentação oficial fs-exra , você pode fazer isso com muita facilidade

const fs = require('fs-extra')

// copy file
fs.copySync('/tmp/myfile', '/tmp/mynewfile')

// copy directory, even if it has subdirectories or files
fs.copySync('/tmp/mydir', '/tmp/mynewdir')
Freddy Daniel
fonte
certifique-se de definir a opção recursiva. fs.copySync ('/ tmp / mydir', '/ tmp / mynewdir', {recursive: true})
Dheeraj Kumar -
Não consigo encontrar a opção { recursive: true }do documento do github que você mencionou, não sei se funciona.
Freddy Daniel
Acho que estamos falando de fs-extra, mas o link do github aponta para node-fs-extra. Poderia ser uma biblioteca diferente?
Dheeraj Kumar 11/11/19
@DheerajKumar, mostra node-fs-extra no github, mas fs-extra no npm . Eu não sei ambos são mesmo consulte pacote de npm
Freddy Daniel
Fs-extra substitui fs?
Matt
4

Como estou apenas criando um script de nó simples, não queria que os usuários do script precisassem importar vários módulos e dependências externas, então coloquei meu pensamento e fiz uma pesquisa para executar comandos a partir do bash Concha.

Este trecho de código node.js copia recursivamente uma pasta chamada node-webkit.app para uma pasta chamada build:

   child = exec("cp -r node-webkit.app build", function(error, stdout, stderr) {
        sys.print("stdout: " + stdout);
        sys.print("stderr: " + stderr);
        if(error !== null) {
            console.log("exec error: " + error);
        } else {

        }
    });

Agradeço a Lance Pollard no dzone por me ajudar a começar.

O trecho acima é limitado a plataformas baseadas em Unix, como Mac OS e Linux, mas uma técnica semelhante pode funcionar no Windows.

jmort253
fonte
4

@ mallikarjun-m obrigado!

fs-extra fez a coisa e pode até retornar o Promise se você não fornecer retorno de chamada! :)

const path = require('path')
const fs = require('fs-extra')

let source = path.resolve( __dirname, 'folderA')
let destination = path.resolve( __dirname, 'folderB')

fs.copy(source, destination)
  .then(() => console.log('Copy completed!'))
  .catch( err => {
    console.log('An error occured while copying the folder.')
    return console.error(err)
  })
Luckylooke
fonte
2

Aquele com suporte a link simbólico + não lança se o diretório existir.

function copyFolderSync(from, to) {
  try {
    fs.mkdirSync(to);
  } catch(e) {}

  fs.readdirSync(from).forEach((element) => {
    const stat = fs.lstatSync(path.join(from, element));
    if (stat.isFile()) {
      fs.copyFileSync(path.join(from, element), path.join(to, element));
    } else if (stat.isSymbolicLink()) {
      fs.symlinkSync(fs.readlinkSync(path.join(from, element)), path.join(to, element));
    } else if (stat.isDirectory()) {
      copyFolderSync(path.join(from, element), path.join(to, element));
    }
  });
}
allx
fonte
1

Esse código funcionará bem, copiando recursivamente qualquer pasta para qualquer local. Apenas Windows.

var child=require("child_process");
function copySync(from,to){
    from=from.replace(/\//gim,"\\");
    to=to.replace(/\//gim,"\\");
    child.exec("xcopy /y /q \""+from+"\\*\" \""+to+"\\\"");
}

Funciona perfeitamente no meu jogo baseado em texto para criar novos jogadores.

ModerateJavaScriptDev
fonte
1

Eu tentei fs-extra e copy-dir para copiar pasta-recursivamente. mas eu quero isso

  1. funciona normalmente (copy-dir gera erro irracional)
  2. fornece dois argumentos no filtro: caminho do arquivo e tipo de arquivo (fs-extra não informa o tipo de arquivo)
  3. possui verificação dir-to-subdir e verificação dir-to-file

Então eu escrevi o meu:

//node module for node 8.6+
var path=require("path");
var fs=require("fs");

function copyDirSync(src,dest,options){
  var srcPath=path.resolve(src);
  var destPath=path.resolve(dest);
  if(path.relative(srcPath,destPath).charAt(0)!=".")
    throw new Error("dest path must be out of src path");
  var settings=Object.assign(Object.create(copyDirSync.options),options);
  copyDirSync0(srcPath,destPath,settings);
  function copyDirSync0(srcPath,destPath,settings){
    var files=fs.readdirSync(srcPath);
    if (!fs.existsSync(destPath)) {
      fs.mkdirSync(destPath);
    }else if(!fs.lstatSync(destPath).isDirectory()){
      if(settings.overwrite)
        throw new Error(`Cannot overwrite non-directory '${destPath}' with directory '${srcPath}'.`);
      return;
    }
    files.forEach(function(filename){
      var childSrcPath=path.join(srcPath,filename);
      var childDestPath=path.join(destPath,filename);
      var type=fs.lstatSync(childSrcPath).isDirectory()?"directory":"file";
      if(!settings.filter(childSrcPath,type))
        return;
      if (type=="directory") {
        copyDirSync0(childSrcPath,childDestPath,settings);
      } else {
        fs.copyFileSync(childSrcPath, childDestPath, settings.overwrite?0:fs.constants.COPYFILE_EXCL);
        if(!settings.preserveFileDate)
          fs.futimesSync(childDestPath,Date.now(),Date.now());
      }
    });
  }
}
copyDirSync.options={
  overwrite: true,
  preserveFileDate: true,
  filter: function(filepath,type){return true;}
};

e uma função semelhante mkdirs, que é uma alternativa ao mkdirp

function mkdirsSync(dest) {
  var destPath=path.resolve(dest);
  mkdirsSync0(destPath);
  function mkdirsSync0(destPath){
    var parentPath=path.dirname(destPath);
    if(parentPath==destPath)
      throw new Error(`cannot mkdir ${destPath}, invalid root`);
    if (!fs.existsSync(destPath)) {
      mkdirsSync0(parentPath);
      fs.mkdirSync(destPath);
    }else if(!fs.lstatSync(destPath).isDirectory()){
      throw new Error(`cannot mkdir ${destPath}, a file already exists there`);
    }
  }
}
fuweichin
fonte
0

Eu escrevi essa função para copiar (copiarFileSync) ou mover (renomearSync) arquivos recursivamente entre diretórios:

//copy files
copyDirectoryRecursiveSync(sourceDir, targetDir);
//move files
copyDirectoryRecursiveSync(sourceDir, targetDir, true);


function copyDirectoryRecursiveSync(source, target, move) {
if (!fs.lstatSync(source).isDirectory()) return;

var operation = move ? fs.renameSync : fs.copyFileSync;
fs.readdirSync(source).forEach(function (itemName) {
    var sourcePath = path.join(source, itemName);
    var targetPath = path.join(target, itemName);

    if (fs.lstatSync(sourcePath).isDirectory()) {
        fs.mkdirSync(targetPath);
        copyDirectoryRecursiveSync(sourcePath, targetDir);
    }
    else {
        operation(sourcePath, targetPath);
    }
});}
EladTal
fonte
0

Se você está no Linux e o desempenho não é um problema, você pode usar a execfunção do child_processmódulo para executar um comando bash:

const { exec } = require('child_process');
exec('cp -r source dest', (error, stdout, stderr) => {...});

Em alguns casos, achei essa solução mais limpa do que baixar um módulo inteiro ou até mesmo usá-lo fs.

Emilio Grisolía
fonte
0

O ncp bloqueia o descritor de arquivo e aciona o retorno de chamada quando ele ainda não foi desbloqueado. Eu recomendo usar o módulo de cópia recursiva . Ele suporta eventos e você pode ter certeza do final da cópia.

Andrey Proskurin
fonte
0

Tenha cuidado ao escolher seu pacote. Alguns pacotes, como copy-dir, não suportam arquivos grandes de cópia com mais de 0x1fffffe8 caracteres. Irá gerar algum erro como:

buffer.js:630 Uncaught Error: Cannot create a string longer than 0x1fffffe8 characters 

Eu experimentei algo assim em um dos meus projetos. Por fim, tive que mudar o pacote que estava usando e ajustar muito código. Eu diria que essa não é uma experiência muito agradável.

Se desejar várias cópias de origem e de destino, é possível usar uma cópia melhor e escrever algo como isto:

// copy from multiple source into a directory
bCopy(['/path/to/your/folder1', '/path/to/some/file.txt'], '/path/to/destination/folder');

ou até:

// copy from multiple source into multiple destination
bCopy(['/path/to/your/folder1', '/path/to/some/file.txt'], ['/path/to/destination/folder', '/path/to/another/folder']);
Donovan P
fonte
-1

SIM, ncpé cool...

Você pode querer / deve prometer sua função de fazer é super cool. Como você está nisso, adicione-o a um toolsarquivo para reutilizá-lo.

Abaixo está uma versão de trabalho que é Asynce usa Promises.


index.js

const {copyFolder} = require('./tools/');

return copyFolder(
    yourSourcePath,
    yourDestinationPath
)
.then(() => {
    console.log('-> Backup completed.')
}) .catch((err) => {
    console.log("-> [ERR] Could not copy the folder: ", err);
})

tools.js

const ncp = require("ncp");

/**
 * Promise Version of ncp.ncp()
 * 
 * This function promisifies ncp.ncp().
 * We take the asynchronous function ncp.ncp() with 
 * callback semantics and derive from it a new function with
 * promise semantics.
 */
ncp.ncpAsync = function (sourcePath, destinationPath) {
  return new Promise(function (resolve, reject) {
      try {
          ncp.ncp(sourcePath, destinationPath, function(err){
              if (err) reject(err); else resolve();
          });
      } catch (err) {
          reject(err);
      }
  });
};

/**
 * Utility function to copy folders asynchronously using
 * the Promise returned by ncp.ncp(). 
 */
const copyFolder = (sourcePath, destinationPath) => {
    return ncp.ncpAsync(sourcePath, destinationPath, function (err) {
        if (err) {
            return console.error(err);
        }
    });
}
module.exports.copyFolder = copyFolder;
Mick
fonte
-1

A abordagem mais fácil para esse problema é usar apenas os módulos 'fs' e 'Path' e alguma lógica ...

Todos os arquivos na pasta raiz copiam com o Novo Nome, se você quiser apenas definir o número da versão, ou seja, ....................... "var v = 'Seu Diretório Nome'"

no prefixo Nome do arquivo V conteúdo adicionado com o nome do arquivo.

var fs = require('fs-extra');
var path = require('path');

var c = 0;
var i =0 ;
var v = "1.0.2";
var copyCounter = 0;
var directoryCounter = 0; 
var directoryMakerCounter = 0;
var recursionCounter = -1;
var Flag = false;
var directoryPath = [] ;
var directoryName = [] ;
var directoryFileName = [];
var fileName;
var directoryNameStorer;
var dc = 0;
var route ;



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

var basePath = path.join(__dirname, v);


function walk(dir){

  fs.readdir(dir, function(err, items) {

    items.forEach(function(file){

        file = path.resolve(dir, file);

        fs.stat(file, function(err, stat){
            if(stat && stat.isDirectory()){

                directoryNameStorer = path.basename(file);
                route = file;
                route = route.replace("gd",v);

                directoryFileName[directoryCounter] = route;
                directoryPath[directoryCounter] = file;
                directoryName[directoryCounter] = directoryNameStorer;

                directoryCounter++;
                dc++;

                if (!fs.existsSync(basePath+"/"+directoryName[directoryMakerCounter])){
                    fs.mkdirSync(directoryFileName[directoryMakerCounter]);
                    directoryMakerCounter++;
                }

            }else{

                    fileName = path.basename(file);
                    if(recursionCounter >= 0){
                        fs.copyFileSync(file, directoryFileName[recursionCounter]+"/"+v+"_"+fileName, err => {
                            if(err) return console.error(err);
                        });
                        copyCounter++;
                    }else{
                        fs.copyFileSync(file, v+"/"+v+"_"+fileName, err => {
                            if(err) return console.error(err);
                        });
                        copyCounter++;    
                    }

                }
                if(copyCounter + dc == items.length && directoryCounter > 0 && recursionCounter < directoryMakerCounter-1){
                    console.log("COPY COUNTER :             "+copyCounter);
                    console.log("DC COUNTER :               "+dc);                        
                    recursionCounter++;
                    dc = 0;
                    copyCounter = 0;
                    console.log("ITEM DOT LENGTH :          "+items.length);
                    console.log("RECURSION COUNTER :        "+recursionCounter);
                    console.log("DIRECOTRY MAKER COUNTER :  "+directoryMakerCounter);
                    console.log(": START RECURSION :        "+directoryPath[recursionCounter]);
                    walk(directoryPath[recursionCounter]); //recursive call to copy sub-folder

                }

        })
    })
 });

}
 walk('./gd', function(err, data){ //Just Pass The Root Directory Which You Want to Copy
 if(err) throw err;
 console.log("done");
})
MM Furkan
fonte
-1

Foi assim que eu fiz:

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

então:

let filePath = //your FilePath

let fileList = []
        var walkSync = function(filePath, filelist) 
        {
          let files = fs.readdirSync(filePath);
          filelist = filelist || [];
          files.forEach(function(file) 
          {
            if (fs.statSync(path.join(filePath, file)).isDirectory()) 
            {
              filelist = walkSync(path.join(filePath, file), filelist);
            }
            else 
            {
              filelist.push(path.join(filePath, file));
            }
          });

          // Ignore hidden files
          filelist = filelist.filter(item => !(/(^|\/)\.[^\/\.]/g).test(item));

          return filelist;
        };

Em seguida, chame o método:

This.walkSync(filePath, fileList)
uyghurbeg
fonte