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.

bonbonez
fonte
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');
});
Miguel Sanchez Gonzalez
fonte
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;
    }
  }
}
Mike Schilling
fonte
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');
Timmerz
fonte
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');
});
Mikhail
fonte
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;
  }
}
agridoce
fonte
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));
Testador
fonte
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;
    }
  }
}
Jens Hauke
fonte
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);
}
Andrew Childs
fonte
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 :)

AYO O.
fonte
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"));
chpio
fonte
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);
            }
        });
    });
}
Pedro Rodrigues
fonte
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

Xin
fonte
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);
    });
};
mpen
fonte
@ 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);
}
ch271828n
fonte
-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.

stancikcom
fonte
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