Il modo più veloce per copiare il file in node.js


488

Il progetto su cui sto lavorando (node.js) implica molte operazioni con il file system (copia / lettura / scrittura ecc.). Mi piacerebbe sapere quali sono i metodi più veloci e sarei felice di ricevere un consiglio. Grazie.


42
È una buona domanda, anche se è interessante che ottenga 25 voti positivi quando altre domande di formato simile ottengono subito 3 o 4 voti negativi per non soddisfare gli "standard" SO (forse il tag javascript viene sottoposto a scansione da persone più gentili :)
Ben

22
Per lo più siamo solo nuovi ed entusiasti di questa intera attività "file" dopo anni di normalizzazione dei browser.
Erik Reppen,

3
L'unica risposta corretta sulla pagina è questa . Nessuna delle altre risposte copia effettivamente i file. I file su MacOS e Windows hanno altri metadati che vanno persi semplicemente copiando i byte. Esempi di dati non copiati da nessun'altra risposta in questa pagina, windows e macos . Anche su Unix le altre risposte non copiano la data di creazione, cosa che è spesso importante quando si copia un file.
Gman,

Risposte:


718

Questo è un buon modo per copiare un file in una riga di codice usando gli stream:

var fs = require('fs');

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

Nel nodo v8.5.0 è stato aggiunto copyFile

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
Basta ricordare che nella vita reale, che ci si vuole verificare sia la createReadStreame createWriteStreamper gli errori, in modo da non ottiene una battuta (anche se sarebbe comunque altrettanto veloce).
ebohlman,

18
Quanto è più veloce / più lento rispetto all'esecuzione del raw cp test.log newLog.logvia require('child_process').exec?
Lance Pollard,

41
Bene copynon è portatile su Windows, contrariamente a una soluzione completa Node.js.
Jean

12
Sfortunatamente sul mio sistema l'utilizzo di stream è estremamente lento rispetto a child_process.execFile('/bin/cp', ['--no-target-directory', source, target]).
Robert,

12
Ho usato questo metodo e tutto ciò che ho ottenuto è stato un file vuoto in scrittura. qualche idea sul perché? fs.createReadStream('./init/xxx.json').pipe(fs.createWriteStream('xxx.json'));
Timmerz,

293

Stesso meccanismo, ma questo aggiunge la gestione degli errori:

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 la pena notare che il flag cbCalled è necessario perché gli errori di pipe generano un errore su entrambi i flussi. Flussi di origine e destinazione.
Gaston Sanchez,

4
Come gestite l'errore se il file sorgente non esiste? Il file di destinazione viene comunque creato in quel caso.
Michel Hua,

1
Penso che un errore nel WriteStreamsolo cancellarlo. Dovresti chiamarti rd.destroy(). Almeno questo è quello che mi è successo. Purtroppo non c'è molta documentazione se non dal codice sorgente.
Robert,

cosa significa cb? cosa dovremmo passare come terzo argomento?
SaiyanGirl,

4
@SaiyanGirl 'cb' sta per "callback". Dovresti passare una funzione.
Brian J. Miller,

143

Non sono riuscito a far funzionare il createReadStream/createWriteStreammetodo per qualche motivo, ma usando il fs-extramodulo npm ha funzionato immediatamente. Non sono sicuro della differenza di prestazioni però.

FS-extra

npm install --save fs-extra

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

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

3
Questa è l'opzione migliore ora
Zain Rizvi,

11
L'uso del codice sincrono nel nodo uccide le prestazioni dell'applicazione.
mvillar,

3
Oh, per favore ... La domanda riguarda il metodo più veloce per copiare un file. Mentre il più veloce è sempre soggettivo, non credo che un pezzo di codice sincrono abbia degli affari qui.
sampathsris,

24
Il più veloce da implementare o il più veloce da eseguire? Priorità diverse indicano che questa è una risposta valida.
Patrick Gunderson,

14
fs-extra ha anche metodi asincroni, vale a dire fs.copy(src, dst, callback);, e questi dovrebbero risolvere la preoccupazione di @ mvillar.
Marc Durdin,

134

Da Node.js 8.5.0 abbiamo nuovi metodi fs.copyFile e fs.copyFileSync .

Esempio di utilizzo:

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
Questa è l'unica risposta corretta sulla pagina. Nessuna delle altre risposte copia effettivamente i file. I file su MacOS e Windows hanno altri metadati che vanno persi semplicemente copiando i byte. Esempi di dati non copiati da nessun'altra risposta in questa pagina, windows e macos . Anche su Unix l'altra risposta non copia la data di creazione, cosa che è spesso importante quando si copia un file.
Gman,

beh purtroppo questo non riesce a copiare tutto su Mac. Spero che lo
risolvano

A proposito, tieni presente che copyFile()viene intercettato durante la sovrascrittura di file più lunghi. Per gentile concessione di uv_fs_copyfile()till Node v8.7.0 (libuv 1.15.0). vedi github.com/libuv/libuv/pull/1552
Anton Rudeshko il

74

Veloce da scrivere e comodo da usare, con gestione promessa ed errore.

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

Lo stesso con la sintassi asincrona / wait:

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
Cosa succede quando non esiste più input (condivisione di rete interrotta), ma la scrittura riesce comunque? Saranno chiamati sia il rifiuto (dalla lettura) sia la risoluzione (dalla scrittura)? Cosa succede se la lettura / scrittura fallisce (settori del disco danneggiati durante la lettura, disco intero durante la scrittura)? Quindi il rifiuto verrà chiamato due volte. Una soluzione Promise basata sulla risposta di Mike con una bandiera (purtroppo) sembra essere l'unica soluzione praticabile che considera correttamente la gestione degli errori.
Lekensteyn,

La promessa viene risolta quando la copia ha esito positivo. Se viene rifiutato, il suo stato viene risolto e la chiamata rifiutata più volte non farà alcuna differenza.
benweet,

2
Ho appena testato 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 cercato le specifiche su questo e hai ragione: tentare di risolvere o rifiutare una promessa risolta non ha alcun effetto. Forse potresti estendere la tua risposta e spiegare perché hai scritto la funzione in questo modo? Grazie :-)
Lekensteyn,

2
A proposito, closedovrebbe essere finishper flussi scrivibili.
Lekensteyn,

E se ti chiedi perché la tua applicazione non si chiude mai dopo errori di pipe /dev/stdin, questo è un bug github.com/joyent/node/issues/25375
Lekensteyn

43

Bene, di solito è bene evitare operazioni asincrone sui file. Ecco l'esempio di sincronizzazione breve (ovvero nessuna gestione degli errori):

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

8
Dire che in generale è estremamente falso, soprattutto perché porta le persone a ri-slurping i file per ogni richiesta fatta al loro server. Questo può diventare costoso.
Catalyst,

8
usare i *Syncmetodi è totalmente contrario alla filosofia di nodejs! Penso anche che si stiano lentamente deprecando. L'idea generale di nodejs è che è single threaded e event-driven.
Gillyb,

11
@gillyb L'unica ragione che mi viene in mente per usarli è per semplicità: se stai scrivendo uno script veloce che userai solo una volta, probabilmente non ti preoccuperai affatto di bloccare il processo.
starbeamrainbowlabs

13
Non sono a conoscenza del fatto che siano deprecati. I metodi di sincronizzazione sono quasi sempre una pessima idea su un server Web ma a volte sono ideali in qualcosa come node-webkit in cui blocca solo l'azione nella finestra mentre i file vengono copiati. Lanciare una gif di caricamento e forse una barra di caricamento che si aggiorna in determinati punti e consentire ai metodi di sincronizzazione di bloccare tutte le azioni fino al completamento della copia. Non è in realtà una cosa delle migliori pratiche tanto quanto quando e dove hanno il loro posto.
Erik Reppen,

6
I metodi di sincronizzazione vanno bene quando stai interagendo con un'altra operazione di sincronizzazione o quello che vuoi è eseguire un'operazione sequenziale (ad es. Emuleresti comunque la sincronizzazione). Se le operazioni sono sequenziali, evita l'inferno di callback (e / o promettente) e usa il metodo di sincronizzazione. In generale, devono essere utilizzati con cautela sui server, ma vanno bene per la maggior parte dei casi che coinvolgono script CLI.
srcspider,

18

La soluzione di Mike Schilling con gestione degli errori con una scorciatoia per il gestore di eventi di errore.

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 non ti interessa che sia asincrono e non stai copiando file di dimensioni gigabyte e preferisci non aggiungere un'altra dipendenza solo per una singola funzione:

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

4
Mi piace questa risposta. Chiaro e semplice
Rob Gleeson,

7
@RobGleeson, e richiede tanta memoria quanto il contenuto del file ... Sono sorpreso dal conteggio dei voti lì.
Konstantin

Ho aggiunto un avvertimento "e non sto copiando file di dimensioni gigabyte".
Andrew Childs

La fs.existsSyncchiamata dovrebbe essere omessa. Il file potrebbe scomparire nel tempo tra la fs.existsSyncchiamata e la fs.readFileSyncchiamata, il che significa che la fs.existsSyncchiamata non ci protegge da nulla.
qntm

Inoltre, il ritorno in falsecaso di fs.existsSyncerrore è probabilmente una scarsa ergonomia perché pochi consumatori copySyncpensano di ispezionare manualmente il valore di ritorno ogni volta che viene chiamato, non più di quanto facciamo per fs.writeFileSync et al. . Generare un'eccezione è in realtà preferibile.
qntm

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

Questo è ciò che uso personalmente per copiare un file e sostituire un altro file usando node.js :)


1
Questo non risponde alla domanda, che riguarda come copiare in modo efficiente i file in un'applicazione IO pesante.
Jared Smith,

@JaredSmith Vero, ma la mia ricerca su google mi ha portato qui e questo è quello che volevo.
codepleb,

1

Per copie veloci dovresti usare il fs.constants.COPYFILE_FICLONE bandiera. Permette (per i filesystem che lo supportano) di non copiare effettivamente il contenuto del file. Viene creata solo una nuova voce di file, ma punta a una copia su scrittura "clone" del file di origine.

Non fare nulla / meno è il modo più veloce di fare qualcosa;)

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

Usando invece le promesse:

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

La soluzione di benweet che controlla la visibilità del file prima della copia:

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

Perché non utilizzare la funzione di copia integrata nodejs?

Fornisce sia la versione asincrona che quella di sincronizzazione:

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
Non effettuare l'upgrade perché questa risposta è un duplicato.
Qwertie,

-1

La soluzione di Mike , ma con le promesse:

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 Perché volevo una soluzione asincrona ...?
mpen

-1

Miglioramento di un'altra risposta.

Caratteristiche:

  • Se le cartelle dst non esistono, la creerà automaticamente. L'altra risposta genererà solo errori.
  • Restituisce a promise, che ne semplifica l'utilizzo in un progetto più ampio.
  • Ti permette di copiare più file e la promessa verrà fatta quando tutti saranno copiati.

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

Codice:

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

tutte le soluzioni di cui sopra che non verificano l'esistenza di un file sorgente sono pericolose ... ad es

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

in caso contrario, esiste un rischio in uno scenario nel caso in cui l'origine e la destinazione vengano sostituite per errore, i dati verranno persi in modo permanente senza notare alcun errore.


Anche questo ha una condizione di competizione: il file potrebbe essere distrutto tra la sua scrittura e la lettura / scrittura / copia. È sempre meglio provare l'operazione e affrontare qualsiasi errore risultante.
Jared Smith,

la verifica dell'esistenza del target prima di un'operazione di scrittura garantisce che non si sovrascriva accidentalmente il target, ad esempio copre uno scenario in cui la destinazione e l'origine sono impostate dall'utente per errore lo stesso ... è quindi tardi attendere che l'operazione di scrittura fallisca ... di chi mi ha dato (-1) rivedi la tua classifica una volta che questo incidente si verifica nel tuo progetto :-) ri. gare - su siti di traffico pesante si consiglia sempre di eseguire un processo di gestione delle operazioni che richiedono la garanzia della sincronizzazione - sì, si tratta quindi di colli di bottiglia delle prestazioni
stancikcom

Non ho votato a fondo perché hai torto , ho votato perché non è una risposta alla domanda. Dovrebbe essere un commento cautelativo su una risposta esistente.
Jared Smith,

bene - una soluzione es. andrew childs (con 18 voti positivi) si esaurirà sulle risorse su un server / file di grandi dimensioni ... Gli scriverei commenti ma non ho la reputazione di commentare - quindi hai visto il mio post autonomo. ... ma Jared il tuo downgrade significa un semplice passaggio per me - stai zitto e lascia che le persone scrivano e condividano codice pericoloso che per lo più "funziona" ...
stancikcom

Capisco, a nessuno piace il feedback negativo. Ma è solo un downvote. Sono fedele alla mia ragione per averlo dato, poiché questo non risponde alla domanda posta dall'OP ed è abbastanza breve per essere un commento. Puoi prenderlo come vuoi, ma se fai esplodere quel genere di cose sproporzionerai lo stack overflow per essere un'esperienza molto frustrante.
Jared Smith,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.