Le moyen le plus rapide de copier un fichier dans node.js

488

Le projet sur lequel je travaille (node.js) implique de nombreuses opérations avec le système de fichiers (copie / lecture / écriture, etc.). Je voudrais savoir quelles méthodes sont les plus rapides et je serais heureux de recevoir un conseil. Merci.

bonbonez
la source
42
C'est une bonne question, mais il est intéressant de noter qu'il obtient 25 votes positifs alors que d'autres questions de format similaire recevront immédiatement 3 ou 4 votes négatifs pour ne pas respecter les "normes" SO (peut-être que la balise javascript est explorée par des gens plus gentils :)
Ben
22
La plupart du temps, nous sommes tout juste nouveaux et enthousiastes à propos de toute cette entreprise de "fichiers" après des années de normalisation des navigateurs.
Erik Reppen du
3
La seule bonne réponse sur la page est celle-ci . Aucune des autres réponses ne copie réellement les fichiers. Les fichiers sur MacOS et Windows ont d'autres métadonnées qui sont perdues en copiant simplement des octets. Exemples de données non copiées par une autre réponse sur cette page, fenêtres et macos . Même sous Unix, les autres réponses ne copient pas la date de création, ce qui est souvent important lors de la copie d'un fichier.
gman

Réponses:

718

C'est un bon moyen de copier un fichier sur une seule ligne de code à l'aide de flux:

var fs = require('fs');

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

Dans le nœud v8.5.0, copyFile a été ajouté

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
la source
64
N'oubliez pas que dans la vraie vie, vous voudriez vérifier à la fois les erreurs createReadStreamet les createWriteStreamerreurs, de sorte que vous n'obtiendrez pas de doublure (bien que ce soit toujours aussi rapide).
ebohlman
18
Est-ce beaucoup plus rapide / lent que l'exécution du raw cp test.log newLog.logvia require('child_process').exec?
Lance Pollard
41
Eh bien copyn'est pas portable sur Windows, contrairement à une solution Node.js complète.
Jean
12
Malheureusement, sur mon système, l'utilisation des flux est extrêmement lente par rapport à child_process.execFile('/bin/cp', ['--no-target-directory', source, target]).
Robert
12
J'ai utilisé cette méthode et tout ce que j'ai obtenu était un fichier vierge en écriture. des idées pourquoi? fs.createReadStream('./init/xxx.json').pipe(fs.createWriteStream('xxx.json'));
Timmerz
293

Même mécanisme, mais cela ajoute la gestion des erreurs:

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
la source
5
Il convient de noter que l'indicateur cbCalled est nécessaire car les erreurs de canal déclenchent une erreur sur les deux flux. Flux source et de destination.
Gaston Sanchez
4
Comment gérez-vous l'erreur si le fichier source n'existe pas? Le fichier de destination est toujours créé dans ce cas.
Michel Hua
1
Je pense qu'une erreur dans la WriteStreamvolonté ne fait que la rediriger. Vous devez rd.destroy()vous appeler . C'est du moins ce qui m'est arrivé. Malheureusement, il n'y a pas beaucoup de documentation à l'exception du code source.
Robert
que signifie le cbstand? que devons-nous passer comme troisième argument?
SaiyanGirl
4
@SaiyanGirl 'cb' signifie "rappel". Vous devez passer une fonction.
Brian J. Miller,
143

Je n'ai pas pu faire fonctionner la createReadStream/createWriteStreamméthode pour une raison quelconque, mais en utilisant le fs-extramodule npm, cela a fonctionné tout de suite. Je ne suis pas sûr de la différence de performances.

fs-extra

npm install --save fs-extra

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

fs.copySync(path.resolve(__dirname,'./init/xxx.json'), 'xxx.json');
Timmerz
la source
3
C'est la meilleure option maintenant
Zain Rizvi
11
L'utilisation de code synchrone dans le nœud tue les performances de votre application.
mvillar
3
Oh s'il vous plaît ... La question concerne la méthode la plus rapide pour copier un fichier. Bien que le plus rapide soit toujours subjectif, je ne pense pas qu'un morceau de code synchrone ait quelque chose à voir ici.
sampathsris
24
Le plus rapide à mettre en œuvre ou le plus rapide à exécuter? Des priorités différentes signifient que c'est une réponse valable.
Patrick Gunderson
14
fs-extra dispose également de méthodes asynchrones, c'est-à-dire qui fs.copy(src, dst, callback);devraient résoudre les problèmes de @ mvillar.
Marc Durdin
134

Depuis Node.js 8.5.0, nous avons de nouvelles méthodes fs.copyFile et fs.copyFileSync .

Exemple d'utilisation:

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
la source
2
Ceci est la seule réponse correcte sur la page. Aucune des autres réponses ne copie réellement les fichiers. Les fichiers sur MacOS et Windows ont d'autres métadonnées qui sont perdues en copiant simplement des octets. Exemples de données non copiées par une autre réponse sur cette page, fenêtres et macos . Même sous Unix, l'autre réponse ne copie pas la date de création, ce qui est souvent important lors de la copie d'un fichier.
gman
bien malheureusement, cela ne parvient pas à tout copier sur mac. J'espère qu'ils le corrigeront: github.com/nodejs/node/issues/30575
gman
BTW gardez à l'esprit que le copyFile()bug est survenu lors du remplacement de fichiers plus longs. Avec l'aimable autorisation de uv_fs_copyfile()till Node v8.7.0 (libuv 1.15.0). voir github.com/libuv/libuv/pull/1552
Anton Rudeshko
74

Rapide à écrire et pratique à utiliser, avec gestion des promesses et des erreurs.

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

Idem avec la syntaxe async / 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;
  }
}
benweet
la source
1
Que se passe-t-il lorsqu'il n'y a plus d'entrée (partage réseau cassé), mais que l'écriture réussit toujours? Est-ce que le rejet (de la lecture) et la résolution (de l'écriture) seront appelés? Que faire si les deux lectures / écritures échouent (secteurs de disque défectueux pendant la lecture, disque plein pendant l'écriture)? Ensuite, le rejet sera appelé deux fois. Une solution Promise basée sur la réponse de Mike avec un indicateur (malheureusement) semble être la seule solution viable qui considère correctement la gestion des erreurs.
Lekensteyn
La promesse est résolue une fois la copie réussie. S'il est rejeté, son état est réglé et appeler plusieurs fois le rejet ne fera aucune différence.
benweet
2
Je viens de tester 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);});et de rechercher les spécifications à ce sujet et vous avez raison: tenter de résoudre ou de rejeter une promesse résolue n'a aucun effet. Peut-être pourriez-vous étendre votre réponse et expliquer pourquoi vous avez écrit la fonction de cette façon? Merci :-)
Lekensteyn
2
Soit dit en passant, closedevrait être finishpour les flux inscriptibles.
Lekensteyn
Et si vous vous demandez pourquoi votre application ne se ferme jamais après des erreurs de pipe /dev/stdin, c'est un bug github.com/joyent/node/issues/25375
Lekensteyn
43

Eh bien, il est généralement préférable d'éviter les opérations de fichiers asynchrones. Voici l'exemple de synchronisation courte (c'est-à-dire sans gestion d'erreur):

var fs = require('fs');
fs.writeFileSync(targetFile, fs.readFileSync(sourceFile));
Testeur
la source
8
Dire que, d'une manière générale, est extrêmement faux, d'autant plus que cela amène les gens à redistribuer des fichiers pour chaque demande faite à leur serveur. Cela peut coûter cher.
Catalyst
8
utiliser les *Syncméthodes est totalement contraire à la philosophie de nodejs! Je pense également qu'ils sont progressivement dépréciés. L'idée de nodejs est qu'il s'agit d'un seul thread et piloté par les événements.
gillyb
11
@gillyb La seule raison pour laquelle je peux penser à les utiliser est la simplicité - si vous écrivez un script rapide que vous n'utiliserez qu'une seule fois, vous n'allez probablement pas vous soucier de bloquer le processus.
starbeamrainbowlabs
13
Je ne suis pas au courant de leur dépréciation. Les méthodes de synchronisation sont presque toujours une idée terrible sur un serveur Web, mais parfois idéales dans quelque chose comme node-webkit où elles verrouillent uniquement l'action dans la fenêtre pendant la copie des fichiers. Lancez un gif de chargement et peut-être une barre de chargement qui se met à jour à certains points et laissez les méthodes de synchronisation bloquer toute action jusqu'à ce que la copie soit terminée. Ce n'est pas vraiment une bonne pratique, mais plutôt quand et où ils ont leur place.
Erik Reppen du
6
Les méthodes de synchronisation conviennent parfaitement lorsque vous interagissez avec une autre opération de synchronisation ou lorsque vous souhaitez effectuer une opération séquentielle (c.-à-d. Que vous émuleriez de toute façon la synchronisation). Si les opérations sont séquentielles, évitez l'enfer de rappel (et / ou la soupe aux promesses) et utilisez la méthode de synchronisation. En général, ils doivent être utilisés avec prudence sur les serveurs, mais ils conviennent à la plupart des cas impliquant des scripts CLI.
srcspider
18

Solution de Mike Schilling avec gestion des erreurs avec un raccourci pour le gestionnaire d'événements d'erreur.

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
la source
18

Si vous ne vous souciez pas que ce soit asynchrone, et que vous ne copiez pas de fichiers de taille gigaoctet, et préférez ne pas ajouter une autre dépendance juste pour une seule fonction:

function copySync(src, dest) {
  var data = fs.readFileSync(src);
  fs.writeFileSync(dest, data);
}
Andrew Childs
la source
4
J'aime cette réponse. Clair et simple.
Rob Gleeson
7
@RobGleeson, et nécessite autant de mémoire que le contenu du fichier ... Je suis étonné par le nombre de votes positifs là-bas.
Konstantin
J'ai ajouté une mise en garde «et je ne copie pas les fichiers de taille gigaoctet».
Andrew Childs,
L' fs.existsSyncappel doit être omis. Le fichier pourrait disparaître dans l'intervalle entre l' fs.existsSyncappel et l' fs.readFileSyncappel, ce qui signifie que l' fs.existsSyncappel ne nous protège de rien.
qntm
De plus, le retour en falsecas d' fs.existsSyncéchec est probablement une mauvaise ergonomie car peu de consommateurs copySyncpenseront à inspecter manuellement la valeur de retour à chaque appel, pas plus que nous ne le faisons pour fs.writeFileSync et al. . Il est préférable de lever une exception.
qntm
2
   const fs = require("fs");
   fs.copyFileSync("filepath1", "filepath2"); //fs.copyFileSync("file1.txt", "file2.txt");

C'est ce que j'utilise personnellement pour copier un fichier et remplacer un autre fichier en utilisant node.js :)

AYO O.
la source
1
Cela ne répond pas à la question, qui concerne la façon de copier efficacement des fichiers dans une application gourmande en E / S.
Jared Smith
@JaredSmith C'est vrai, mais ma recherche Google m'a conduit ici et c'est ce que je voulais.
codepleb
1

Pour des copies rapides, vous devez utiliser le fs.constants.COPYFILE_FICLONEdrapeau. Il permet (pour les systèmes de fichiers qui le prennent en charge) de ne pas réellement copier le contenu du fichier. Seule une nouvelle entrée de fichier est créée, mais elle pointe vers un «clone» de copie sur écriture du fichier source.

Ne rien faire / moins est le moyen le plus rapide de faire quelque chose;)

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

Utiliser des promesses à la place:

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
la source
fs.promises.copyFile
gman
0

La solution de benweet vérifiant la visibilité du fichier avant la copie:

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
la source
0

Pourquoi ne pas utiliser la fonction de copie intégrée de nodejs?

Il fournit une version asynchrone et sync:

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
la source
3
Pas de vote positif, car cette réponse est en double.
Qwertie
-1

La solution de Mike , mais avec des promesses:

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
la source
@Royi Parce que je voulais une solution asynchrone ...?
mpen
-1

Amélioration d'une autre réponse.

Fonctionnalités:

  • Si les dossiers dst n'existent pas, il les créera automatiquement. L'autre réponse ne générera que des erreurs.
  • Il renvoie un promise, ce qui le rend plus facile à utiliser dans un projet plus important.
  • Il vous permet de copier plusieurs fichiers, et la promesse sera faite lorsque tous seront copiés.

Usage:

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
la source
-2

toutes les solutions ci-dessus qui ne vérifient pas l'existence d'un fichier source sont dangereuses ... par exemple

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

sinon, il y a un risque dans un scénario au cas où la source et la cible sont remplacées par une erreur, vos données seront définitivement perdues sans remarquer aucune erreur.

stancikcom
la source
Cela a également une condition de concurrence: le fichier pourrait être détruit entre le stating et la lecture / écriture / copie. Il est toujours préférable de simplement essayer l'opération et de traiter toute erreur résultante.
Jared Smith
vérifier l'existence de la cible avant une opération d'écriture garantit que vous n'écrasez pas la cible par accident, par exemple couvre un scénario selon lequel la destination et la source sont définies par erreur de la même manière ... il est alors tard d'attendre que l'opération d'écriture échoue ... celui qui m'a donné (-1) veuillez revoir votre classement une fois que cet incident se produit dans votre projet :-) re. courses - sur les sites à fort trafic, il est toujours recommandé d'avoir un seul processus de gestion des opérations nécessitant une assurance de synchronisation - oui, il s'agit alors d'un goulot d'étranglement des performances
stancikcom
Je n'ai pas downvote parce que vous vous trompez , j'ai downvote parce que ce n'est pas une réponse à la question. Ce devrait être une mise en garde sur une réponse existante.
Jared Smith
bien - vous une bonne solution par exemple andrew childs (avec 18 votes positifs) va manquer de ressources sur un serveur / fichiers volumineux ... je lui écrirais des commentaires mais je n'ai pas la réputation de commenter - donc vous avez vu mon post en mode autonome. ... mais Jared votre rétrogradation signifie une voie simple pour moi - gardez le silence et laissez les gens écrire et partager du code dangereux qui "fonctionne" principalement ...
stancikcom
Je comprends, personne n'aime les commentaires négatifs. Mais c'est juste un downvote. Je maintiens ma raison de le donner, car cela ne répond pas à la question posée par le PO et est suffisamment court pour être un commentaire. Vous pouvez le prendre comme vous le souhaitez, mais si vous soufflez ce genre de chose hors de proportion, vous constaterez que le débordement de pile est une expérience très frustrante.
Jared Smith