Maneira mais rápida de copiar arquivo no node.js


488

O projeto em que estou trabalhando (node.js) implica muitas operações com o sistema de arquivos (cópia / leitura / gravação etc.). Gostaria de saber quais métodos são os mais rápidos e ficaria feliz em receber um conselho. Obrigado.


42
É uma boa pergunta, embora seja interessante que receba 25 votos positivos quando outras questões de formato semelhante receberão 3 ou 4 votos negativos imediatamente por não atender aos "padrões" de SO (talvez a tag javascript seja rastreada por pessoas mais gentis :)
Ben

22
Principalmente, somos novos e empolgados com esse negócio de "arquivos" depois de anos de normalização de navegadores.
precisa

3
A única resposta correta na página é essa . Nenhuma das outras respostas realmente copia arquivos. Os arquivos no MacOS e no Windows têm outros metadados que são perdidos apenas pela cópia de bytes. Exemplos de dados não copiados por qualquer outra resposta nesta página, janelas e macos . Mesmo no Unix, as outras respostas não copiam a data de criação, algo que geralmente é importante ao copiar um arquivo.
gman

Respostas:


717

Esta é uma boa maneira de copiar um arquivo em uma linha de código usando fluxos:

var fs = require('fs');

fs.createReadStream('test.log').pipe(fs.createWriteStream('newLog.log'));

No nó v8.5.0, copyFile foi adicionado

const fs = require('fs');

// destination.txt will be created or overwritten by default.
fs.copyFile('source.txt', 'destination.txt', (err) => {
  if (err) throw err;
  console.log('source.txt was copied to destination.txt');
});

64
Lembre-se de que, na vida real, você deseja verificar os erros createReadStreame os createWriteStreamerros, para não obter uma única linha (embora ainda seja tão rápido).
ebohlman

18
Quão mais rápido / mais lento é isso do que executar a cp test.log newLog.logvia bruta require('child_process').exec?
usar o seguinte código

41
O Well copynão é portátil no Windows, ao contrário de uma solução completa do Node.js.
Jean

12
Infelizmente, no meu sistema, o uso de fluxos é extremamente lento em comparação com child_process.execFile('/bin/cp', ['--no-target-directory', source, target]).
Robert

12
Eu usei esse método e tudo o que consegui foi um arquivo em branco na gravação. alguma idéia por que? fs.createReadStream('./init/xxx.json').pipe(fs.createWriteStream('xxx.json'));
Timmerz

293

O mesmo mecanismo, mas isso adiciona o tratamento de erros:

function copyFile(source, target, cb) {
  var cbCalled = false;

  var rd = fs.createReadStream(source);
  rd.on("error", function(err) {
    done(err);
  });
  var wr = fs.createWriteStream(target);
  wr.on("error", function(err) {
    done(err);
  });
  wr.on("close", function(ex) {
    done();
  });
  rd.pipe(wr);

  function done(err) {
    if (!cbCalled) {
      cb(err);
      cbCalled = true;
    }
  }
}

5
Vale ressaltar que o sinalizador cbCalled é necessário porque os erros de canal acionam um erro nos dois fluxos. Fluxos de origem e destino.
Gaston Sanchez

4
Como você lida com o erro se o arquivo de origem não existe? O arquivo de destino ainda é criado nesse caso.
Michel Hua

1
Acho que um erro no WriteStreamunpipe apenas. Você teria que se chamar rd.destroy(). Pelo menos foi o que aconteceu comigo. Infelizmente, não há muita documentação, exceto o código-fonte.
Robert

o que cbsignifica? o que devemos passar como terceiro argumento?
SaiyanGirl

4
@SaiyanGirl 'cb' significa "retorno de chamada". Você deve passar em uma função.
Brian J. Miller

143

Não consegui fazer o createReadStream/createWriteStreammétodo funcionar por algum motivo, mas, usando o fs-extramódulo npm, ele funcionou imediatamente. Não tenho certeza da diferença de desempenho.

fs-extra

npm install --save fs-extra

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

fs.copySync(path.resolve(__dirname,'./init/xxx.json'), 'xxx.json');

3
Esta é a melhor opção agora
Zain Rizvi

11
Usar código síncrono no nó reduz o desempenho do aplicativo.
Mvillar 21/05

3
Ah, por favor ... A pergunta é sobre o método mais rápido de copiar um arquivo. Embora o mais rápido seja sempre subjetivo, não acho que um pedaço de código síncrono tenha algum negócio aqui.
Sampathsris

24
Mais rápido de implementar ou mais rápido de executar? Prioridades diferentes significam que esta é uma resposta válida.
Patrick Gunderson

14
O fs-extra também possui métodos assíncronos, ou seja fs.copy(src, dst, callback);, e estes devem resolver a preocupação de @ mvillar.
Marc Durdin

134

Desde Node.js 8.5.0 temos novos fs.copyFile e fs.copyFileSync métodos.

Exemplo de uso:

var fs = require('fs');

// destination.txt will be created or overwritten by default.
fs.copyFile('source.txt', 'destination.txt', (err) => {
    if (err) throw err;
    console.log('source.txt was copied to destination.txt');
});

2
Esta é a única resposta correta na página. Nenhuma das outras respostas realmente copia arquivos. Os arquivos no MacOS e no Windows têm outros metadados que são perdidos apenas pela cópia de bytes. Exemplos de dados não copiados por qualquer outra resposta nesta página, janelas e macos . Mesmo no Unix, a outra resposta não copia a data de criação, algo que geralmente é importante ao copiar um arquivo.
gman

bem, infelizmente isso não consegue copiar tudo no mac. Espero que eles consertem: github.com/nodejs/node/issues/30575
gman

Entre, lembre-se de que o copyFile()erro ocorre ao substituir arquivos mais longos. Cortesia de uv_fs_copyfile()até Node v8.7.0 (libuv 1.15.0). veja github.com/libuv/libuv/pull/1552
Anton Rudeshko em

74

Rápido para escrever e conveniente de usar, com gerenciamento de promessas e erros.

function copyFile(source, target) {
  var rd = fs.createReadStream(source);
  var wr = fs.createWriteStream(target);
  return new Promise(function(resolve, reject) {
    rd.on('error', reject);
    wr.on('error', reject);
    wr.on('finish', resolve);
    rd.pipe(wr);
  }).catch(function(error) {
    rd.destroy();
    wr.end();
    throw error;
  });
}

O mesmo com sintaxe assíncrona / aguardada:

async function copyFile(source, target) {
  var rd = fs.createReadStream(source);
  var wr = fs.createWriteStream(target);
  try {
    return await new Promise(function(resolve, reject) {
      rd.on('error', reject);
      wr.on('error', reject);
      wr.on('finish', resolve);
      rd.pipe(wr);
    });
  } catch (error) {
    rd.destroy();
    wr.end();
    throw error;
  }
}

1
O que acontece quando não há mais entrada (compartilhamento de rede quebrado), mas a gravação ainda é bem-sucedida? Serão chamados ambos rejeitar (da leitura) e resolver (da gravação)? E se a leitura / gravação falhar (setores defeituosos do disco durante a leitura, disco cheio durante a gravação)? Em seguida, rejeitar será chamado duas vezes. Uma solução Promise baseada na resposta de Mike com um sinalizador (infelizmente) parece ser a única solução viável que considera adequadamente o tratamento de erros.
Lekensteyn

A promessa é resolvida assim que a cópia for bem-sucedida. Se for rejeitado, seu estado será estabelecido e a chamada rejeitar várias vezes não fará diferença.
benweet 24/05

2
Acabei de testar new Promise(function(resolve, reject) { resolve(1); resolve(2); reject(3); reject(4); console.log("DONE"); }).then(console.log.bind(console), function(e){console.log("E", e);});e procurei as especificações sobre isso e você está certo: Tentar resolver ou rejeitar uma promessa resolvida não tem efeito. Talvez você possa estender sua resposta e explicar por que você escreveu a função dessa maneira? Obrigado :-)
Lekensteyn

2
A propósito, closedeve ser finishpara fluxos graváveis.
Lekensteyn

E se você quer saber por que seu aplicativo nunca fecha após erros de tubulação em /dev/stdin, isso é um bug github.com/joyent/node/issues/25375
Lekensteyn

43

Bem, geralmente é bom evitar operações de arquivo assíncronas. Aqui está o exemplo de sincronização curto (isto é, sem manipulação de erros):

var fs = require('fs');
fs.writeFileSync(targetFile, fs.readFileSync(sourceFile));

8
Dizer que, em geral, é extremamente falso, principalmente porque leva as pessoas a reverter arquivos para cada solicitação feita ao servidor. Isso pode ficar caro.
Catalyst

8
O uso dos *Syncmétodos é totalmente contrário à filosofia dos nodejs! Eu também acho que eles estão sendo depreciados lentamente. A ideia do nodejs é que ele seja único e orientado a eventos.
gillyb

11
@gillyb A única razão pela qual posso pensar em usá-los é a simplicidade - se você estiver escrevendo um script rápido que usará apenas uma vez, provavelmente não ficará preocupado em bloquear o processo.
starbeamrainbowlabs

13
Eu não estou ciente deles serem preteridos. Os métodos de sincronização são quase sempre uma péssima idéia em um servidor web, mas às vezes são ideais em algo como node-webkit, onde apenas bloqueia a ação na janela enquanto os arquivos estão copiando. Crie um gif de carregamento e talvez uma barra de carregamento que seja atualizada em determinados pontos e permita que os métodos de sincronização bloqueiem todas as ações até que a cópia seja concluída. Não é realmente uma prática recomendada, mas sim quando e onde eles ocupam o lugar.
Erik Reppen

6
Os métodos de sincronização são bons quando você está interagindo com outra operação de sincronização ou o que você deseja é executar uma operação seqüencial (ou seja, você estaria emulando a sincronização de qualquer maneira). Se as operações forem seqüenciais, evite o inferno de retorno de chamada (e / ou prometa a sopa) e use o método de sincronização. Em geral, eles devem ser usados ​​com cuidado nos servidores, mas são adequados para a maioria dos casos que envolvem scripts da CLI.
Srcpider

18

A solução de Mike Schilling com tratamento de erros com um atalho para o manipulador de eventos de erro.

function copyFile(source, target, cb) {
  var cbCalled = false;

  var rd = fs.createReadStream(source);
  rd.on("error", done);

  var wr = fs.createWriteStream(target);
  wr.on("error", done);
  wr.on("close", function(ex) {
    done();
  });
  rd.pipe(wr);

  function done(err) {
    if (!cbCalled) {
      cb(err);
      cbCalled = true;
    }
  }
}

18

Se você não se importa em ser assíncrono e não está copiando arquivos do tamanho de gigabytes, prefere não adicionar outra dependência apenas para uma única função:

function copySync(src, dest) {
  var data = fs.readFileSync(src);
  fs.writeFileSync(dest, data);
}

4
Eu gosto desta resposta. Clara e simples.
Rob Gleeson

7
@ RobGleeson, e requer tanta memória quanto o conteúdo do arquivo ... Estou impressionado com a contagem de votos por lá.
21917 Konstantin

Adicionei uma ressalva "e não estou copiando arquivos do tamanho de gigabytes".
Andrew Childs

A fs.existsSyncchamada deve ser omitida. O arquivo pode desaparecer no período entre a fs.existsSyncligação e a fs.readFileSyncligação, o que significa que a fs.existsSyncligação não nos protege de nada.
qntm

Além disso, retornar falsese fs.existsSyncfalhar provavelmente é uma ergonomia ruim, porque poucos consumidores copySyncpensam em inspecionar manualmente o valor de retorno toda vez que é chamado, assim como não fazemos para fs.writeFileSync et al. . Lançar uma exceção é realmente preferível.
qntm 18/01

2
   const fs = require("fs");
   fs.copyFileSync("filepath1", "filepath2"); //fs.copyFileSync("file1.txt", "file2.txt");

É isso que eu pessoalmente uso para copiar um arquivo e substituir outro arquivo usando node.js :)


1
Isso não responde à pergunta, sobre como copiar arquivos com eficiência em um aplicativo pesado de IO.
Jared Smith

@ JaredSmith True, mas minha pesquisa no google me levou aqui e é isso que eu queria.
codepleb

1

Para cópias rápidas, você deve usar a fs.constants.COPYFILE_FICLONEbandeira. Ele permite que (para sistemas de arquivos que suportam isso) não copie o conteúdo do arquivo. Apenas uma nova entrada de arquivo é criada, mas aponta para uma cópia em gravação "clone" de do arquivo de origem.

Fazer nada / menos é a maneira mais rápida de fazer alguma coisa;)

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

let fs = require("fs");

fs.copyFile(
  "source.txt",
  "destination.txt",
  fs.constants.COPYFILE_FICLONE,
  (err) => {
    if (err) {
      // TODO: handle error
      console.log("error");
    }
    console.log("success");
  }
);

Em vez disso, usando promessas:

let fs = require("fs");
let util = require("util");
let copyFile = util.promisify(fs.copyFile);


copyFile(
  "source.txt",
  "destination.txt",
  fs.constants.COPYFILE_FICLONE
)
  .catch(() => console.log("error"))
  .then(() => console.log("success"));

fs.promises.copyFile
Gman #

0

solução do benweet, verificando a visibilidade do arquivo antes da cópia:

function copy(from, to) {
    return new Promise(function (resolve, reject) {
        fs.access(from, fs.F_OK, function (error) {
            if (error) {
                reject(error);
            } else {
                var inputStream = fs.createReadStream(from);
                var outputStream = fs.createWriteStream(to);

                function rejectCleanup(error) {
                    inputStream.destroy();
                    outputStream.end();
                    reject(error);
                }

                inputStream.on('error', rejectCleanup);
                outputStream.on('error', rejectCleanup);

                outputStream.on('finish', resolve);

                inputStream.pipe(outputStream);
            }
        });
    });
}

0

Por que não usar o nodejs construído na função de cópia?

Ele fornece as versões assíncrona e sincronizada:

const fs = require('fs');

// destination.txt will be created or overwritten by default.
fs.copyFile('source.txt', 'destination.txt', (err) => {
  if (err) throw err;
  console.log('source.txt was copied to destination.txt');
});

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


3
Não com direito a voto porque esta resposta é uma duplicata.
Qwertie

-1

A solução de Mike , mas com promessas:

const FileSystem = require('fs');

exports.copyFile = function copyFile(source, target) {
    return new Promise((resolve,reject) => {
        const rd = FileSystem.createReadStream(source);
        rd.on('error', err => reject(err));
        const wr = FileSystem.createWriteStream(target);
        wr.on('error', err => reject(err));
        wr.on('close', () => resolve());
        rd.pipe(wr);
    });
};

@ Royi Porque eu queria uma solução assíncrona ...?
mpen

-1

Melhoria de uma outra resposta.

Recursos:

  • Se as pastas dst não existirem, ela será criada automaticamente. A outra resposta jogará apenas erros.
  • Ele retorna a promise, o que facilita o uso em um projeto maior.
  • Permite copiar vários arquivos, e a promessa será feita quando todos eles forem copiados.

Uso:

var onePromise = copyFilePromise("src.txt", "dst.txt");
var anotherPromise = copyMultiFilePromise(new Array(new Array("src1.txt", "dst1.txt"), new Array("src2.txt", "dst2.txt")));

Código:

function copyFile(source, target, cb) {
    console.log("CopyFile", source, target);

    var ensureDirectoryExistence = function (filePath) {
        var dirname = path.dirname(filePath);
        if (fs.existsSync(dirname)) {
            return true;
        }
        ensureDirectoryExistence(dirname);
        fs.mkdirSync(dirname);
    }
    ensureDirectoryExistence(target);

    var cbCalled = false;
    var rd = fs.createReadStream(source);
    rd.on("error", function (err) {
        done(err);
    });
    var wr = fs.createWriteStream(target);
    wr.on("error", function (err) {
        done(err);
    });
    wr.on("close", function (ex) {
        done();
    });
    rd.pipe(wr);
    function done(err) {
        if (!cbCalled) {
            cb(err);
            cbCalled = true;
        }
    }
}

function copyFilePromise(source, target) {
    return new Promise(function (accept, reject) {
        copyFile(source, target, function (data) {
            if (data === undefined) {
                accept();
            } else {
                reject(data);
            }
        });
    });
}

function copyMultiFilePromise(srcTgtPairArr) {
    var copyFilePromiseArr = new Array();
    srcTgtPairArr.forEach(function (srcTgtPair) {
        copyFilePromiseArr.push(copyFilePromise(srcTgtPair[0], srcTgtPair[1]));
    });
    return Promise.all(copyFilePromiseArr);
}

-2

todas as soluções acima que não verificam a existência de um arquivo de origem são perigosas ... por exemplo

fs.stat(source, function(err,stat) { if (err) { reject(err) }

caso contrário, há um risco em um cenário, caso a origem e o destino sejam substituídos por engano, seus dados serão perdidos permanentemente sem perceber nenhum erro.


Isso também tem uma condição de corrida: o arquivo pode ser destruído entre a declaração e a leitura / gravação / cópia. É sempre melhor apenas tentar a operação e lidar com qualquer erro resultante.
Jared Smith

verificar a existência do destino antes de uma operação de gravação garante que você não substitua o destino por acidente, por exemplo, abrange um cenário em que o destino e a origem são definidos pelo usuário por engano da mesma forma ... é tarde para aguardar a falha da operação de gravação ... quem me deu (-1), reveja sua classificação assim que esse incidente acontecer no seu projeto :-) re. corridas - em sites trafic pesados é sempre recomendável ter operações que requerem garantia de sincronização lidar com um processo - sim, é então gargalo de desempenho
stancikcom

Não votei porque você está errado , votei porque essa não é uma resposta para a pergunta. Deve ser um comentário de advertência sobre uma resposta existente.
Jared Smith

bem - você está certo, por exemplo, a solução andrew childs (com 18 upvotes) ficará sem recursos em um servidor / arquivos grandes ... eu escreveria comentários para ele, mas não tenho reputação de comentar - portanto, você viu meu post independente. ... mas Jared seu rebaixamento significa um takeway simples para mim - manter em silêncio e deixar que as pessoas escrevem e compartilhar código perigoso que a maioria "obras" ...
stancikcom

Entendi, ninguém gosta de feedback negativo. Mas é apenas um voto negativo. Eu mantenho minha razão de dar, pois isso não responde à pergunta feita pelo OP e é curto o suficiente para ser um comentário. Você pode tomá-lo como quiser, mas se você exagerar esse tipo de coisa, você verá que o estouro de pilha é uma experiência muito frustrante.
Jared Smith
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.