La forma más rápida de copiar archivos en node.js

488

El proyecto en el que estoy trabajando (node.js) implica muchas operaciones con el sistema de archivos (copia / lectura / escritura, etc.). Me gustaría saber qué métodos son los más rápidos, y me gustaría recibir un consejo. Gracias.

bonbonez
fuente
42
Es una buena pregunta, aunque es interesante que obtenga 25 votos a favor cuando otras preguntas de formato similar obtendrán 3 o 4 votos a favor de inmediato por no cumplir con los "estándares" SO (tal vez la etiqueta javascript sea rastreada por personas más amables :)
Ben
22
En su mayoría, estamos nuevos y entusiasmados con todo este negocio de "archivos" después de años de normalización de los navegadores.
Erik Reppen
3
La única respuesta correcta en la página es esta . Ninguna de las otras respuestas copia realmente archivos. Los archivos en MacOS y Windows tienen otros metadatos que se pierden simplemente copiando bytes. Ejemplos de datos no copiados por ninguna otra respuesta en esta página, windows y macos . Incluso en Unix, las otras respuestas no copian la fecha de creación, algo que a menudo es importante al copiar un archivo.
gman

Respuestas:

718

Esta es una buena manera de copiar un archivo en una línea de código utilizando secuencias:

var fs = require('fs');

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

En el nodo v8.5.0, se agregó 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');
});
Miguel Sánchez González
fuente
64
Sólo recuerde que en la vida real, usted quiere comprobar tanto el createReadStreamy createWriteStreamde errores, por lo que no se pueden conseguir una sola línea (aunque seguiría siendo igual de rápido).
ebohlman
18
¿Cuánto más rápido / más lento es esto que ejecutar la cp test.log newLog.logvía RAW require('child_process').exec?
Lance Pollard
41
Bueno, copyno es portátil en Windows, al contrario de una solución completa de Node.js.
Jean
12
Lamentablemente, en mi sistema, el uso de flujos es extremadamente lento en comparación con child_process.execFile('/bin/cp', ['--no-target-directory', source, target]).
Robert
12
Usé este método y todo lo que obtuve fue un archivo en blanco al escribir. alguna idea de por qué? fs.createReadStream('./init/xxx.json').pipe(fs.createWriteStream('xxx.json'));
Timmerz
293

Mismo mecanismo, pero esto agrega manejo de errores:

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
fuente
55
Vale la pena señalar que se necesita el indicador cbCalled porque los errores de canalización desencadenan un error en ambas secuencias. Flujos de origen y destino.
Gaston Sanchez
44
¿Cómo maneja el error si el archivo fuente no existe? El archivo de destino todavía se crea en ese caso.
Michel Hua
1
Creo que un error en el WriteStreamsolo lo eliminará. Tendrías que llamarte a rd.destroy()ti mismo. Al menos eso es lo que me pasó. Lamentablemente, no hay mucha documentación, excepto del código fuente.
Robert
¿Qué significa el cbsoporte? ¿Qué debemos pasar como tercer argumento?
SaiyanGirl
44
@SaiyanGirl 'cb' significa "devolución de llamada". Deberías pasar una función.
Brian J. Miller
143

No pude hacer que el createReadStream/createWriteStreammétodo funcionara por alguna razón, pero usando el fs-extramódulo npm funcionó de inmediato. Sin embargo, no estoy seguro de la diferencia de rendimiento.

fs-extra

npm install --save fs-extra

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

fs.copySync(path.resolve(__dirname,'./init/xxx.json'), 'xxx.json');
Timmerz
fuente
3
Esta es la mejor opción ahora
Zain Rizvi
11
El uso de código sincrónico en el nodo mata el rendimiento de su aplicación.
mvillar
3
Oh por favor ... La pregunta es sobre el método más rápido para copiar un archivo. Si bien el más rápido siempre es subjetivo, no creo que un código síncrono tenga algo que ver aquí.
sampathsris
24
¿Más rápido de implementar o más rápido de ejecutar? Las diferentes prioridades significan que esta es una respuesta válida.
Patrick Gunderson el
14
fs-extra también tiene métodos asincrónicos, es decir fs.copy(src, dst, callback);, y estos deberían resolver la preocupación de @ mvillar.
Marc Durdin el
134

Desde Node.js 8.5.0 tenemos los nuevos métodos fs.copyFile y fs.copyFileSync .

Ejemplo 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
fuente
2
Esta es la única respuesta correcta en la página. Ninguna de las otras respuestas copia realmente archivos. Los archivos en MacOS y Windows tienen otros metadatos que se pierden simplemente copiando bytes. Ejemplos de datos no copiados por ninguna otra respuesta en esta página, windows y macos . Incluso en Unix, la otra respuesta no copia la fecha de creación, algo que a menudo es importante al copiar un archivo.
gman
Bueno, lamentablemente esto no puede copiar todo en Mac. Esperemos que lo arreglen: github.com/nodejs/node/issues/30575
gman
Por cierto, tenga en cuenta que se copyFile()produce un error al sobrescribir archivos más largos. Cortesía de uv_fs_copyfile()till Node v8.7.0 (libuv 1.15.0). ver github.com/libuv/libuv/pull/1552
Anton Rudeshko
74

Rápido de escribir y cómodo de usar, con gestión de promesas y errores.

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 mismo con la sintaxis async / await:

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
fuente
1
¿Qué sucede cuando ya no hay más entradas (recurso compartido de red roto), pero la escritura aún tiene éxito? ¿Se invocará tanto rechazar (de lectura) como resolver (de escritura)? ¿Qué sucede si fallan tanto la lectura / escritura (sectores de disco defectuosos durante la lectura, disco completo durante la escritura)? Entonces rechazar será llamado dos veces. Una solución Promesa basada en la respuesta de Mike con una bandera (desafortunadamente) parece ser la única solución viable que considera adecuadamente el manejo de errores.
Lekensteyn
La promesa se resuelve una vez que la copia tiene éxito. Si se rechaza, su estado se resuelve y llamar rechazar varias veces no hará ninguna diferencia.
benweet
2
Acabo de probar 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);});y busqué las especificaciones sobre esto y tienes razón: intentar resolver o rechazar una promesa resuelta no tiene ningún efecto. ¿Quizás podría extender su respuesta y explicar por qué ha escrito la función de esta manera? Gracias :-)
Lekensteyn
2
Por cierto, closedebe ser finishpara las secuencias de escritura.
Lekensteyn
Y si se pregunta por qué su aplicación nunca se cierra después de los errores de canalización /dev/stdin, ese es un error github.com/joyent/node/issues/25375
Lekensteyn
43

Bueno, generalmente es bueno evitar las operaciones asíncronas de archivos. Aquí está el breve ejemplo de sincronización (es decir, sin manejo de errores):

var fs = require('fs');
fs.writeFileSync(targetFile, fs.readFileSync(sourceFile));
Ensayador
fuente
8
Decir que, en general, es extremadamente falso, sobre todo porque lleva a que las personas vuelvan a sorber archivos por cada solicitud realizada a su servidor. Esto puede ser costoso.
Catalizador
8
¡usar los *Syncmétodos está totalmente en contra de la filosofía de nodejs! También creo que están siendo desaprobados lentamente. La idea general de nodejs es que es de un solo subproceso y está controlada por eventos.
gillyb
11
@gillyb La única razón por la que puedo pensar en usarlos es por simplicidad: si está escribiendo un script rápido que solo usará una vez, probablemente no se preocupe por bloquear el proceso.
starbeamrainbowlabs
13
No estoy al tanto de que estén en desuso. Los métodos de sincronización son casi siempre una idea terrible en un servidor web, pero a veces son ideales en algo como el kit de web de nodo donde solo bloquea la acción en la ventana mientras se copian los archivos. Lanza un gif de carga y tal vez una barra de carga que se actualiza en ciertos puntos y permite que los métodos de sincronización bloqueen todas las acciones hasta que se realice la copia. No es realmente una práctica recomendada, sino cuándo y dónde tienen su lugar.
Erik Reppen
66
Los métodos de sincronización están bien cuando interactúa con otra operación de sincronización o lo que desea es realizar una operación secuencial (es decir, estaría emulando la sincronización de todos modos). Si las operaciones son secuenciales, simplemente evite el infierno de devolución de llamada (y / o la sopa de promesa) y use el método de sincronización. En general, deben usarse con precaución en los servidores, pero están bien para la mayoría de los casos que involucran scripts CLI.
srcspider
18

La solución de Mike Schilling con manejo de errores con un atajo para el controlador de eventos de error.

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

Si no le importa que sea asíncrono, y no esté copiando archivos de tamaño gigabyte, y prefiera no agregar otra dependencia solo para una sola función:

function copySync(src, dest) {
  var data = fs.readFileSync(src);
  fs.writeFileSync(dest, data);
}
Andrew Childs
fuente
44
Me gusta esta respuesta Claro y simple.
Rob Gleeson
77
@RobGleeson, y requiere tanta memoria como el contenido del archivo ... Estoy sorprendido por la cantidad de votos a favor allí.
Konstantin
He agregado una advertencia "y no estoy copiando archivos de tamaño gigabyte".
Andrew Childs
La fs.existsSyncllamada debe ser omitida. El archivo podría desaparecer en el tiempo entre la fs.existsSyncllamada y la fs.readFileSyncllamada, lo que significa que la fs.existsSyncllamada no nos protege de nada.
qntm
Además, regresar falsesi fs.existsSyncfalla es probablemente una mala ergonomía porque pocos consumidores copySyncpensarán en inspeccionar manualmente el valor de retorno cada vez que se llame, más de lo que lo hacemos para fs.writeFileSync et al. . Lanzar una excepción es realmente preferible.
qntm
2
   const fs = require("fs");
   fs.copyFileSync("filepath1", "filepath2"); //fs.copyFileSync("file1.txt", "file2.txt");

Esto es lo que yo personalmente uso para copiar un archivo y reemplazar otro archivo usando node.js :)

AYO O.
fuente
1
Esto no responde a la pregunta, que trata sobre cómo copiar archivos de manera eficiente en una aplicación IO-heavy.
Jared Smith
@JaredSmith Es cierto, pero mi búsqueda en Google me llevó aquí y esto es lo que quería.
codepleb
1

Para copias rápidas, debe usar la fs.constants.COPYFILE_FICLONEbandera. Permite (para los sistemas de archivos que admiten esto) no copiar realmente el contenido del archivo. Solo se crea una nueva entrada de archivo, pero apunta a un "clon" de Copia en escritura del archivo fuente.

No hacer nada / menos es la forma más rápida de hacer algo;)

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 promesas en su lugar:

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

La solución de benweet verifica la visibilidad del archivo antes de la 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);
            }
        });
    });
}
Pedro Rodrigues
fuente
0

¿Por qué no usar nodejs integrado en la función de copia?

Proporciona versiones asíncronas y sincronizadas:

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
fuente
3
No votar porque esta respuesta es un duplicado.
Qwertie
-1

La solución de Mike , pero con promesas:

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
fuente
@Royi ¿Porque quería una solución asíncrona ...?
mpen
-1

Mejora de otra respuesta.

caracteristicas:

  • Si las carpetas dst no existen, la creará automáticamente. La otra respuesta solo arrojará errores.
  • Devuelve a promise, lo que facilita su uso en un proyecto más grande.
  • Le permite copiar múltiples archivos, y la promesa se hará cuando se copien todos.

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

Todas las soluciones anteriores que no comprueban la existencia de un archivo fuente son peligrosas ... por ejemplo

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

de lo contrario, existe un riesgo en un escenario en caso de que el origen y el destino sean reemplazados por error, sus datos se perderán permanentemente sin notar ningún error.

stancikcom
fuente
Esto también tiene una condición de carrera: el archivo podría destruirse entre el estado y la lectura / escritura / copia. Siempre es mejor probar la operación y lidiar con cualquier error resultante.
Jared Smith
verificar la existencia del objetivo antes de una operación de escritura asegura que no se sobrescriba el objetivo por accidente, por ejemplo, cubre un escenario en el que el destino y el origen son configurados por el mismo usuario por error ... luego es tarde para esperar que la operación de escritura falle ... quien me dio (-1) revise su clasificación una vez que este incidente ocurra en su proyecto :-) re. carreras: en sitios de tráfico pesado siempre se recomienda tener operaciones de manejo de un proceso que requieran garantía de sincronización, sí, entonces es un cuello de botella en el rendimiento
stancikcom
No voté en contra porque estás equivocado , voté en contra porque esta no es una respuesta a la pregunta. Debería ser un comentario de advertencia sobre una respuesta existente.
Jared Smith el
bueno, usted, por ejemplo, una solución de Andrew Childs (con 18 votos a favor) se quedará sin recursos en un servidor / archivos grandes ... Le escribiría comentarios, pero no tengo reputación para comentar, por lo tanto, ha visto mi publicación de forma independiente. ... pero Jared su rebaja significa un takeway simple para mí - se mantienen en silencio y dejar que la gente escribe y compartir código peligrosa que en su mayoría "obras" ...
stancikcom
Lo entiendo, a nadie le gustan los comentarios negativos. Pero es solo un voto negativo. Mantengo mi razón para darlo, ya que esto no responde a la pregunta que hizo el OP y es lo suficientemente breve como para ser un comentario. Puedes tomarlo como quieras, pero si desatascas ese tipo de cosas, encontrarás que el desbordamiento de la pila es una experiencia muy frustrante.
Jared Smith