Der schnellste Weg, um eine Datei in node.js zu kopieren

488

Das Projekt, an dem ich arbeite (node.js), impliziert viele Operationen mit dem Dateisystem (Kopieren / Lesen / Schreiben usw.). Ich würde gerne wissen, welche Methoden am schnellsten sind, und würde mich über einen Rat freuen. Vielen Dank.

Bonbonez
quelle
42
Es ist eine gute Frage, obwohl es interessant ist, dass es 25 positive Stimmen erhält, wenn andere Fragen im ähnlichen Format sofort 3 oder 4 negative Stimmen erhalten, weil sie die SO "Standards" nicht erfüllen (vielleicht wird das Javascript-Tag von freundlicheren Leuten gecrawlt :)
Ben
22
Meistens sind wir nach Jahren der Normalisierung der Browser nur frisch und aufgeregt über dieses ganze "Dateigeschäft".
Erik Reppen
3
Die einzig richtige Antwort auf der Seite ist diese . Keine der anderen Antworten kopiert tatsächlich Dateien. Dateien unter MacOS und Windows haben andere Metadaten, die durch einfaches Kopieren von Bytes verloren gehen. Beispiele für Daten, die von keiner anderen Antwort auf dieser Seite kopiert wurden, Windows und Macos . Selbst unter Unix kopieren die anderen Antworten nicht das Erstellungsdatum, was beim Kopieren einer Datei oft wichtig ist.
Gman

Antworten:

717

Dies ist eine gute Möglichkeit, eine Datei mithilfe von Streams in eine Codezeile zu kopieren:

var fs = require('fs');

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

In Knoten v8.5.0 wurde copyFile hinzugefügt

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
quelle
64
Denken Sie daran, dass Sie im wirklichen Leben sowohl die createReadStreamals auch createWriteStreamauf Fehler überprüfen möchten , damit Sie keinen Einzeiler erhalten (obwohl dies immer noch genauso schnell ist).
Ebohlman
18
Wie viel schneller / langsamer ist dies als die Ausführung des Raw- cp test.log newLog.logVia require('child_process').exec?
Lance Pollard
41
Well copyist im Gegensatz zu einer vollständigen Node.js-Lösung unter Windows nicht portierbar.
Jean
12
Leider ist die Verwendung von Streams auf meinem System im Vergleich zu extrem langsam child_process.execFile('/bin/cp', ['--no-target-directory', source, target]).
Robert
12
Ich habe diese Methode verwendet und alles, was ich bekam, war eine leere Datei beim Schreiben. irgendwelche Ideen warum? fs.createReadStream('./init/xxx.json').pipe(fs.createWriteStream('xxx.json'));
Timmerz
293

Gleicher Mechanismus, aber dies fügt eine Fehlerbehandlung hinzu:

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
quelle
5
Es ist erwähnenswert, dass das Flag cbCalled benötigt wird, da Pipe-Fehler einen Fehler in beiden Streams auslösen. Quell- und Ziel-Streams.
Gaston Sanchez
4
Wie gehen Sie mit dem Fehler um, wenn die Quelldatei nicht vorhanden ist? In diesem Fall wird weiterhin eine Zieldatei erstellt.
Michel Hua
1
Ich denke ein Fehler im WriteStreamTestament wird es nur entpfeifen. Sie müssten sich rd.destroy()selbst anrufen . Zumindest ist mir das passiert. Leider gibt es außer dem Quellcode nicht viel Dokumentation.
Robert
Wofür steht das cb? Was sollen wir als drittes Argument eingeben?
SaiyanGirl
4
@SaiyanGirl 'cb' steht für "Rückruf". Sie sollten eine Funktion übergeben.
Brian J. Miller
143

Ich konnte die createReadStream/createWriteStreamMethode aus irgendeinem Grund nicht zum fs-extraLaufen bringen , aber mit dem npm-Modul funktionierte es sofort. Ich bin mir jedoch nicht sicher über den Leistungsunterschied.

fs-extra

npm install --save fs-extra

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

fs.copySync(path.resolve(__dirname,'./init/xxx.json'), 'xxx.json');
Timmerz
quelle
3
Dies ist jetzt die beste Option
Zain Rizvi
11
Die Verwendung von synchronem Code im Knoten beeinträchtigt Ihre Anwendungsleistung.
Mvillar
3
Oh bitte ... Die Frage ist nach der schnellsten Methode zum Kopieren einer Datei. Während der schnellste immer subjektiv ist, glaube ich nicht, dass ein synchroner Code hier etwas zu suchen hat.
Sampathsris
24
Am schnellsten zu implementieren oder am schnellsten auszuführen? Unterschiedliche Prioritäten bedeuten, dass dies eine gültige Antwort ist.
Patrick Gunderson
14
fs-extra hat auch asynchrone Methoden, dh fs.copy(src, dst, callback);diese sollten das Problem von @ mvillar lösen.
Marc Durdin
134

Seit Node.js 8.5.0 haben wir neue Methoden fs.copyFile und fs.copyFileSync .

Anwendungsbeispiel:

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
quelle
2
Dies ist die einzig richtige Antwort auf der Seite. Keine der anderen Antworten kopiert tatsächlich Dateien. Dateien unter MacOS und Windows haben andere Metadaten, die durch einfaches Kopieren von Bytes verloren gehen. Beispiele für Daten, die von keiner anderen Antwort auf dieser Seite kopiert wurden, Windows und Macos . Selbst unter Unix kopiert die andere Antwort nicht das Erstellungsdatum, was beim Kopieren einer Datei oft wichtig ist.
Gman
Nun, leider kann dies nicht alles auf den Mac kopieren. Hoffentlich werden sie es beheben: github.com/nodejs/node/issues/30575
gman
Übrigens, denken Sie daran, dass copyFile()beim Überschreiben längerer Dateien Fehler auftreten. Mit freundlicher Genehmigung von uv_fs_copyfile()till Node v8.7.0 (libuv 1.15.0). siehe github.com/libuv/libuv/pull/1552
Anton Rudeshko
74

Schnell zu schreiben und bequem zu bedienen, mit Versprechen und Fehlermanagement.

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

Gleiches gilt für die asynchrone / warten-Syntax:

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;
  }
}
gut
quelle
1
Was passiert, wenn keine Eingabe mehr vorhanden ist (defekte Netzwerkfreigabe), der Schreibvorgang jedoch weiterhin erfolgreich ist? Werden sowohl Ablehnen (vom Lesen) als auch Auflösen (vom Schreiben) aufgerufen? Was ist, wenn beide Lese- / Schreibvorgänge fehlschlagen (fehlerhafte Festplattensektoren beim Lesen, volle Festplatte beim Schreiben)? Dann wird die Ablehnung zweimal aufgerufen. Eine Promise-Lösung, die auf Mikes Antwort mit einem Flag basiert (leider), scheint die einzige praktikable Lösung zu sein, die die Fehlerbehandlung richtig berücksichtigt.
Lekensteyn
Das Versprechen wird gelöst, sobald die Kopie erfolgreich ist. Wenn es abgelehnt wird, wird sein Status festgelegt und das mehrfache Aufrufen von Ablehnen macht keinen Unterschied.
Benweet
2
Ich habe gerade 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);});die Spezifikation dazu getestet und nachgeschlagen, und Sie haben Recht: Der Versuch, ein gelöstes Versprechen zu lösen oder abzulehnen, hat keine Auswirkung. Vielleicht könnten Sie Ihre Antwort erweitern und erklären, warum Sie die Funktion auf diese Weise geschrieben haben? Danke :-)
Lekensteyn
2
By the way, closesollte finishfür beschreibbare Streams.
Lekensteyn
Und wenn Sie sich fragen, warum Ihre Anwendung nach Pipe-Fehlern nie geschlossen /dev/stdinwird, ist dies ein Fehler. Github.com/joyent/node/issues/25375
Lekensteyn
43

Normalerweise ist es gut, asynchrone Dateivorgänge zu vermeiden. Hier ist das kurze Synchronisierungsbeispiel (dh keine Fehlerbehandlung):

var fs = require('fs');
fs.writeFileSync(targetFile, fs.readFileSync(sourceFile));
Prüfer
quelle
8
Zu sagen, dass dies im Allgemeinen äußerst falsch ist, zumal es dazu führt, dass Benutzer Dateien für jede an ihren Server gesendete Anfrage erneut schlürfen. Dies kann teuer werden.
Katalysator
8
mit den *SyncMethoden sind völlig gegen die Philosophie von nodejs! Ich denke auch, dass sie langsam veraltet sind. Die ganze Idee von nodejs ist, dass es Single-Threaded und ereignisgesteuert ist.
Gillyb
11
@gillyb Der einzige Grund, warum ich mir vorstellen kann, sie zu verwenden, ist der Einfachheit halber - wenn Sie ein schnelles Skript schreiben, das Sie nur einmal verwenden, werden Sie sich wahrscheinlich nicht allzu sehr darum kümmern, den Prozess zu blockieren.
Starbeamrainbowlabs
13
Mir ist nicht bewusst, dass sie veraltet sind. Synchronisierungsmethoden sind auf einem Webserver fast immer eine schreckliche Idee, aber manchmal ideal in so etwas wie einem Node-Webkit, bei dem nur Aktionen im Fenster gesperrt werden, während Dateien kopiert werden. Rufen Sie ein Lade-GIF und möglicherweise eine Ladeleiste auf, die an bestimmten Punkten aktualisiert wird, und lassen Sie die Synchronisierungsmethoden alle Aktionen blockieren, bis der Kopiervorgang abgeschlossen ist. Es ist nicht wirklich eine Best-Practice-Sache, sondern vielmehr, wann und wo sie ihren Platz haben.
Erik Reppen
6
Synchronisierungsmethoden sind in Ordnung, wenn Sie mit einem anderen Synchronisierungsvorgang interagieren oder eine sequentielle Operation ausführen möchten (dh Sie würden die Synchronisierung trotzdem emulieren). Wenn die Vorgänge sequentiell sind, vermeiden Sie einfach die Rückrufhölle (und / oder die Versprechenssuppe) und verwenden Sie die Synchronisierungsmethode. Im Allgemeinen sollten sie auf Servern mit Vorsicht verwendet werden, sind jedoch in den meisten Fällen, in denen CLI-Skripte verwendet werden, in Ordnung.
srcspider
18

Mike Schillings Lösung mit Fehlerbehandlung mit einer Verknüpfung für den Fehlerereignishandler.

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
quelle
18

Wenn Sie sich nicht darum kümmern, dass es asynchron ist, und keine Dateien in Gigabyte-Größe kopieren und lieber keine weitere Abhängigkeit nur für eine einzelne Funktion hinzufügen möchten:

function copySync(src, dest) {
  var data = fs.readFileSync(src);
  fs.writeFileSync(dest, data);
}
Andrew Childs
quelle
4
Ich mag diese Antwort. Klar und einfach.
Rob Gleeson
7
@RobGleeson, und benötigt so viel Speicher wie der Dateiinhalt ... Ich bin erstaunt über die Anzahl der Upvotes dort.
Konstantin
Ich habe eine Einschränkung hinzugefügt "und kopiere keine Dateien in Gigabyte-Größe".
Andrew Childs
Der fs.existsSyncAnruf sollte weggelassen werden. Die Datei könnte in der Zeit zwischen dem fs.existsSyncAnruf und dem fs.readFileSyncAnruf verschwinden , was bedeutet, dass der fs.existsSyncAnruf uns vor nichts schützt.
Qntm
Darüber hinaus ist die Rückgabe falsebei fs.existsSyncFehlschlägen wahrscheinlich eine schlechte Ergonomie, da nur wenige Verbraucher copySyncdaran denken, den Rückgabewert bei jedem Aufruf manuell zu überprüfen, genauso wenig wie bei fs.writeFileSync et al. . Das Auslösen einer Ausnahme ist eigentlich vorzuziehen.
Qntm
2
   const fs = require("fs");
   fs.copyFileSync("filepath1", "filepath2"); //fs.copyFileSync("file1.txt", "file2.txt");

Dies ist, was ich persönlich benutze, um eine Datei zu kopieren und eine andere Datei mit node.js zu ersetzen :)

AYO O.
quelle
1
Dies beantwortet nicht die Frage, wie Dateien in einer E / A-lastigen Anwendung effizient kopiert werden können.
Jared Smith
@JaredSmith Stimmt, aber meine Google-Suche hat mich hierher geführt und das ist, was ich wollte.
Codepleb
1

Für schnelle Kopien sollten Sie die fs.constants.COPYFILE_FICLONEFlagge verwenden. Es ermöglicht (für Dateisysteme, die dies unterstützen), den Inhalt der Datei nicht tatsächlich zu kopieren. Es wird nur ein neuer Dateieintrag erstellt, der jedoch auf einen Copy-on-Write- "Klon" der Quelldatei verweist .

Nichts / weniger zu tun ist der schnellste Weg, etwas zu tun;)

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

Verwenden Sie stattdessen Versprechen:

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
quelle
fs.promises.copyFile
Gman
0

Die Lösung von benweet überprüft die Sichtbarkeit der Datei vor dem Kopieren:

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
quelle
0

Warum nicht NodeJs verwenden, die in die Kopierfunktion integriert sind?

Es bietet sowohl eine asynchrone als auch eine synchronisierte Version:

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
quelle
3
Nicht upvoting, da diese Antwort ein Duplikat ist.
Qwertie
-1

Mikes Lösung , aber mit Versprechungen:

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
quelle
@ Royi Weil ich eine asynchrone Lösung wollte ...?
Mpen
-1

Verbesserung einer anderen Antwort.

Eigenschaften:

  • Wenn die dst-Ordner nicht vorhanden sind, wird sie automatisch erstellt. Die andere Antwort wirft nur Fehler.
  • Es gibt a zurück promise, was die Verwendung in einem größeren Projekt erleichtert.
  • Sie können mehrere Dateien kopieren , und das Versprechen wird erfüllt, wenn alle kopiert wurden.

Verwendungszweck:

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

Code:

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
quelle
-2

Alle oben genannten Lösungen, die das Vorhandensein einer Quelldatei nicht überprüfen, sind gefährlich ... z

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

Andernfalls besteht in einem Szenario das Risiko, dass Ihre Daten dauerhaft verloren gehen, ohne dass ein Fehler bemerkt wird, wenn Quelle und Ziel versehentlich ersetzt werden.

stancikcom
quelle
Dies hat auch eine Race-Bedingung: Die Datei kann zwischen dem Angeben und dem Lesen / Schreiben / Kopieren zerstört werden. Es ist immer besser, einfach die Operation auszuprobieren und den daraus resultierenden Fehler zu beheben.
Jared Smith
Durch Überprüfen der Existenz des Ziels vor einem Schreibvorgang wird sichergestellt, dass Sie das Ziel nicht versehentlich überschreiben. Dies deckt beispielsweise ein Szenario ab, in dem Ziel und Quelle vom Benutzer versehentlich gleich festgelegt wurden. Es ist dann spät, zu warten, bis der Schreibvorgang fehlschlägt. Wer auch immer mir (-1) gegeben hat, überprüfen Sie bitte Ihr Ranking, sobald dieser Vorfall in Ihrem Projekt passiert :-) re. Rennen - auf stark frequentierten Standorten wird immer empfohlen, einen Prozess zur Handhabung von Prozessen durchzuführen, die eine Synchronisierungssicherheit erfordern - ja, es handelt sich dann um einen Leistungsengpass
stancikcom
Ich habe nicht abgelehnt, weil Sie falsch liegen . Ich habe abgelehnt, weil dies keine Antwort auf die Frage ist. Es sollte ein warnender Kommentar zu einer vorhandenen Antwort sein.
Jared Smith
Nun - Sie haben eine richtige Lösung, z. B. Andrew Childs-Lösung (mit 18 Upvotes), die Ressourcen auf einem Server / großen Dateien ausgehen ... Ich würde ihm Kommentare schreiben, aber ich habe keinen Ruf zu kommentieren - daher haben Sie meinen Beitrag als eigenständig gesehen. ... aber Jared Ihr Downgrade bedeutet für mich eine einfache Möglichkeit - schweigen Sie und lassen Sie die Leute gefährlichen Code schreiben und teilen, der meistens "funktioniert" ...
stancikcom
Ich verstehe, niemand mag negatives Feedback. Aber es ist nur eine Ablehnung. Ich stehe zu meinem Grund, es zu geben, da dies die vom OP gestellte Frage nicht beantwortet und kurz genug ist, um einen Kommentar abzugeben. Sie können es nehmen, wie Sie wollen, aber wenn Sie so etwas überproportional blasen, werden Sie feststellen, dass der Stapelüberlauf eine sehr frustrierende Erfahrung ist.
Jared Smith