Najszybszy sposób na skopiowanie pliku w node.js

488

Projekt, nad którym pracuję (node.js) zakłada wiele operacji z systemem plików (kopiowanie / odczytywanie / zapisywanie itp.). Chciałbym wiedzieć, które metody są najszybsze i chętnie udzielę porady. Dzięki.

bonbonez
źródło
42
To dobre pytanie, choć interesujące jest to, że dostaje 25 ocen pozytywnych, gdy inne pytania o podobnym formacie otrzymają od razu 3 lub 4 oceny negatywne za niespełnienie „standardów” SO (może tag javascript jest indeksowany przez milszych ludzi :)
Ben
22
Przeważnie jesteśmy po prostu nowi i podekscytowani tym całym biznesem „plików” po latach normalizacji przeglądarek.
Erik Reppen
3
Jedyną poprawną odpowiedzią na stronie jest ta . Żadna z pozostałych odpowiedzi nie kopiuje plików. Pliki w systemie MacOS i Windows mają inne metadane, które są tracone przez samo kopiowanie bajtów. Przykłady danych, które nie zostały skopiowane przez żadną inną odpowiedź na tej stronie, w systemie Windows i makach . Nawet w Uniksie pozostałe odpowiedzi nie kopiują daty utworzenia, co jest często ważne podczas kopiowania pliku.
gman

Odpowiedzi:

717

Jest to dobry sposób na skopiowanie pliku w jednym wierszu kodu przy użyciu strumieni:

var fs = require('fs');

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

W węźle 8.5.0 dodano 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 Sanchez Gonzalez
źródło
64
Pamiętaj tylko, że w prawdziwym życiu chciałbyś sprawdzić zarówno błędy, jak createReadStreami createWriteStreambłędy, abyś nie dostał linijki (choć byłoby to tak samo szybkie).
ebohlman
18
Ile to jest szybsze / wolniejsze niż wykonywanie RAW cp test.log newLog.logprzez require('child_process').exec?
Lance Pollard
41
Cóż, copynie jest przenośny w systemie Windows, w przeciwieństwie do pełnego rozwiązania Node.js.
Jean
12
Niestety w moim systemie używanie strumieni jest bardzo wolne w porównaniu do child_process.execFile('/bin/cp', ['--no-target-directory', source, target]).
Robert
12
Użyłem tej metody i wszystko, co dostałem, to pusty plik podczas zapisu. jakieś pomysły dlaczego? fs.createReadStream('./init/xxx.json').pipe(fs.createWriteStream('xxx.json'));
Timmerz,
293

Ten sam mechanizm, ale dodaje obsługę błędów:

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
źródło
5
Warto zauważyć, że flaga cbCalled jest potrzebna, ponieważ błędy potoku powodują błąd w obu strumieniach. Strumienie źródłowe i docelowe.
Gaston Sanchez
4
Jak poradzić sobie z błędem, jeśli plik źródłowy nie istnieje? W takim przypadku nadal tworzony jest plik docelowy.
Michel Hua
1
Myślę, że błąd WriteStreamspowoduje tylko usunięcie z niego zapisów. Musisz zadzwonić do rd.destroy()siebie. Przynajmniej tak mi się stało. Niestety nie ma zbyt wiele dokumentacji oprócz kodu źródłowego.
Robert,
co oznacza cbskrót co powinniśmy przekazać jako trzeci argument?
SaiyanGirl
4
@SaiyanGirl „cb” oznacza „callback”. Powinieneś przekazać funkcję.
Brian J. Miller,
143

Z createReadStream/createWriteStreamjakiegoś powodu nie byłem w stanie uruchomić tej metody, ale przy użyciu fs-extramodułu npm zadziałało od razu. Nie jestem jednak pewien różnicy w wydajności.

fs-extra

npm install --save fs-extra

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

fs.copySync(path.resolve(__dirname,'./init/xxx.json'), 'xxx.json');
Timmerz
źródło
3
To jest teraz najlepsza opcja
Zain Rizvi
11
Używanie kodu synchronicznego w węźle zabija wydajność aplikacji.
mvillar
3
Och, proszę ... Pytanie dotyczy najszybszej metody kopiowania pliku. Chociaż najszybszy jest zawsze subiektywny, nie sądzę, że synchroniczny fragment kodu ma tu jakieś interesy.
sampathsris,
24
Najszybszy do wdrożenia lub najszybszy do wykonania? Różne priorytety oznaczają, że jest to poprawna odpowiedź.
Patrick Gunderson
14
fs-extra ma również metody asynchroniczne, tzn. fs.copy(src, dst, callback);powinny one rozwiązać problem @ mvillar.
Marc Durdin
134

Od wersji Node.js 8.5.0 mamy nowe metody fs.copyFile i fs.copyFileSync .

Przykład użycia:

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');
});
Michaił
źródło
2
To jedyna poprawna odpowiedź na stronie. Żadna z pozostałych odpowiedzi nie kopiuje plików. Pliki w systemie MacOS i Windows mają inne metadane, które są tracone przez samo kopiowanie bajtów. Przykłady danych, które nie zostały skopiowane przez żadną inną odpowiedź na tej stronie, w systemie Windows i makach . Nawet w Uniksie druga odpowiedź nie kopiuje daty utworzenia, co jest często ważne podczas kopiowania pliku.
gman
niestety to nie powiela wszystkiego na Macu. Mam nadzieję, że to naprawią: github.com/nodejs/node/issues/30575
gman
BTW pamiętaj, że copyFile()jest to błąd podczas nadpisywania dłuższych plików. Dzięki uprzejmości uv_fs_copyfile()till Node v8.7.0 (libuv 1.15.0). patrz github.com/libuv/libuv/pull/1552
Anton Rudeshko
74

Szybki do pisania i wygodny w użyciu, z zarządzaniem obietnicami i błędami.

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

To samo dotyczy składni 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;
  }
}
słodki
źródło
1
Co się stanie, gdy nie będzie już żadnych danych wejściowych (uszkodzony udział sieciowy), ale zapis nadal się powiedzie? Czy będą wywoływane zarówno odrzucenia (z odczytu), jak i rozwiązania (z zapisu)? Co się stanie, jeśli zarówno odczyt / zapis nie powiedzie się (złe sektory dysku podczas odczytu, pełny dysk podczas zapisu)? Następnie odrzucenie zostanie wywołane dwukrotnie. Rozwiązanie Promise oparte na odpowiedzi Mike'a z flagą (niestety) wydaje się być jedynym realnym rozwiązaniem, które właściwie uwzględnia obsługę błędów.
Lekensteyn
Obietnica zostaje rozwiązana po pomyślnym zakończeniu kopiowania. Jeśli zostanie odrzucony, jego stan zostanie ustalony, a wielokrotne wywołanie odrzucenia nie będzie miało znaczenia.
benweet
2
Właśnie przetestowałem 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);});i sprawdziłem specyfikację w tym zakresie i masz rację: próba rozwiązania lub odrzucenia rozwiązanej obietnicy nie ma żadnego efektu. Być może mógłbyś rozszerzyć swoją odpowiedź i wyjaśnić, dlaczego napisałeś tę funkcję w ten sposób? Dzięki :-)
Lekensteyn
2
Nawiasem mówiąc, closepowinno być finishdla zapisywalnych strumieni.
Lekensteyn,
A jeśli zastanawiasz się, dlaczego aplikacja nigdy nie zamyka się po błędzie potoku /dev/stdin, jest to błąd github.com/joyent/node/issues/25375
Lekensteyn
43

Zazwyczaj dobrze jest unikać asynchronicznych operacji na plikach. Oto krótki przykład synchronizacji (tzn. Bez obsługi błędów):

var fs = require('fs');
fs.writeFileSync(targetFile, fs.readFileSync(sourceFile));
Próbnik
źródło
8
Stwierdzenie, że ogólnie jest wyjątkowo fałszywe, szczególnie, że prowadzi to do przerzucania plików do każdego żądania wysłanego na ich serwer. To może stać się drogie.
Catalyst
8
stosowanie tych *Syncmetod jest całkowicie sprzeczne z filozofią Nodejsa! Myślę też, że są powoli przestarzałe. Cała idea nodejs polega na tym, że jest jednowątkowy i sterowany zdarzeniami.
gillyb
11
@gillyb Jedynym powodem, dla którego mogę wymyślić ich użycie, jest prostota - jeśli piszesz szybki skrypt, którego użyjesz tylko raz, prawdopodobnie nie będziesz się przejmować blokowaniem tego procesu.
starbeamrainbowlabs
13
Nie wiem, czy są przestarzałe. Metody synchronizacji są prawie zawsze okropnym pomysłem na serwerze internetowym, ale czasami idealnie nadają się do czegoś takiego jak węzeł-webkit, w którym blokuje tylko działanie w oknie podczas kopiowania plików. Rzuć ładujący gif i może pasek ładowania, który aktualizuje się w określonych punktach i pozwól metodom synchronizacji zablokować wszystkie działania, aż do zakończenia kopiowania. To nie jest tak naprawdę najlepsza praktyka, ale czas i miejsce, w którym mają swoje miejsce.
Erik Reppen
6
Metody synchronizacji są odpowiednie, gdy wchodzisz w interakcję z inną operacją synchronizacji lub chcesz wykonać operację sekwencyjną (tj. Emulujesz synchronizację mimo to). Jeśli operacje są sekwencyjne, po prostu unikaj piekła zwrotnego (i / lub zupy obiecanej) i użyj metody synchronizacji. Ogólnie należy ich używać ostrożnie na serwerach, ale są one odpowiednie w większości przypadków, które wymagają skryptów CLI.
srcspider
18

Rozwiązanie Mike'a Schillinga z obsługą błędów ze skrótem do obsługi zdarzenia błędu.

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
źródło
18

Jeśli nie obchodzi Cię asynchronizacja i nie kopiujesz plików o wielkości gigabajtów, a raczej nie dodałbyś innej zależności tylko dla jednej funkcji:

function copySync(src, dest) {
  var data = fs.readFileSync(src);
  fs.writeFileSync(dest, data);
}
Andrew Childs
źródło
4
Podoba mi się ta odpowiedź. Jasne i proste.
Rob Gleeson
7
@RobGleeson, i wymaga tyle pamięci, co zawartość pliku ... Jestem zdumiony liczbą tam pozytywnych opinii.
Konstantin
Dodałem zastrzeżenie „i nie kopiuję plików wielkości gigabajtów”.
Andrew Childs,
fs.existsSyncPołączenie powinno być pominięte. Plik może zniknąć w czasie między fs.existsSyncrozmową a fs.readFileSyncrozmową, co oznacza, że fs.existsSyncrozmowa nie chroni nas przed niczym.
qntm
Ponadto zwracanie w falseprzypadku fs.existsSyncniepowodzenia jest prawdopodobnie słabą ergonomią, ponieważ niewielu konsumentów copySyncpomyśli o ręcznym sprawdzaniu wartości zwrotu za każdym razem, gdy jest wywoływane, podobnie jak my fs.writeFileSync i inni. . Zgłaszanie wyjątku jest właściwie preferowane.
qntm
2
   const fs = require("fs");
   fs.copyFileSync("filepath1", "filepath2"); //fs.copyFileSync("file1.txt", "file2.txt");

To jest to, czego osobiście używam, aby skopiować plik i zastąpić inny plik za pomocą node.js :)

AYO O.
źródło
1
To nie odpowiada na pytanie, jak efektywnie kopiować pliki w aplikacji obciążonej IO.
Jared Smith,
@JaredSmith Prawda, ale moje wyszukiwanie w Google zaprowadziło mnie tutaj i tego właśnie chciałem.
codepleb
1

W przypadku szybkich kopii należy użyć fs.constants.COPYFILE_FICLONEflagi. Pozwala (dla systemów plików, które to obsługują) nie kopiować zawartości pliku. Tworzony jest tylko nowy wpis pliku, ale wskazuje on opcję Kopiuj przy zapisie „klon” pliku źródłowego .

Nic nie robić / to najszybszy sposób na zrobienie czegoś;)

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

Zamiast tego użyj obietnic:

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
źródło
fs.promises.copyFile
gman
0

rozwiązanie benweet sprawdzające widoczność pliku przed kopiowaniem:

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
źródło
0

Dlaczego nie skorzystać z wbudowanej funkcji kopiowania nodejs?

Zapewnia zarówno wersję asynchroniczną, jak i synchronizacyjną:

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
źródło
3
Nie głosuję pozytywnie, ponieważ ta odpowiedź jest duplikatem.
Qwertie,
-1

Rozwiązanie Mike'a , ale z obietnicami:

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
źródło
@Royi Ponieważ chciałem rozwiązania asynchronicznego ...?
mpen
-1

Ulepszenie jednej innej odpowiedzi.

Funkcje:

  • Jeśli foldery dst nie istnieją, automatycznie je utworzy. Druga odpowiedź spowoduje tylko błędy.
  • Zwraca a promise, co ułatwia użycie w większym projekcie.
  • Pozwala skopiować wiele plików, a obietnica zostanie spełniona, gdy wszystkie zostaną skopiowane.

Stosowanie:

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

Kod:

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
źródło
-2

wszystkie powyższe rozwiązania, które nie sprawdzają istnienia pliku źródłowego, są niebezpieczne ... np

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

w przeciwnym razie istnieje ryzyko w przypadku, gdy źródło i cel zostaną omyłkowo zastąpione, dane zostaną trwale utracone bez zauważenia błędu.

stancikcom
źródło
Ma to również warunek wyścigu: plik może zostać zniszczony między rejestrowaniem go a czytaniem / zapisywaniem / kopiowaniem. Zawsze lepiej jest po prostu wypróbować operację i rozwiązać każdy wynikowy błąd.
Jared Smith,
sprawdzanie istnienia obiektu docelowego przed operacją zapisu zapewnia, że ​​nie zostanie on przypadkowo nadpisany, np. obejmuje scenariusz, w którym użytkownik ustawia miejsce docelowe i źródło przez pomyłkę to samo ... wtedy jest już późno, aby operacja zapisu zakończyła się niepowodzeniem ... komu mnie dałeś (-1), proszę przejrzyj swój ranking, kiedy to zdarzenie wydarzy się w twoim projekcie :-) re. wyścigi - na stronach o dużym
natężeniu ruchu
Nie głosowałem, bo się mylisz , głosowałem, bo to nie jest odpowiedź na pytanie. Powinien to być komentarz ostrzegawczy do istniejącej odpowiedzi.
Jared Smith,
cóż - słuszne rozwiązanie np. andrew childs (z 18 pozytywnymi opiniami) zabraknie zasobów na serwerze / dużych plikach ... napiszę do niego komentarze, ale nie mam reputacji do komentowania - dlatego widziałeś mój samodzielny post. ... ale Jared twój downgrade oznacza dla mnie prosty sposób - milcz i pozwól ludziom pisać i dzielić się niebezpiecznym kodem, który w większości „działa” ...
stancikcom 13.08.19
Rozumiem, nikt nie lubi negatywnych opinii. Ale to tylko opinia negatywna. Podtrzymuję swój powód, aby go udzielić, ponieważ nie odpowiada to na pytanie zadane przez PO i jest wystarczająco krótki, aby być komentarzem. Możesz wziąć to, co chcesz, ale jeśli zdmuchniesz takie rzeczy nieproporcjonalnie, przekonasz się, że przepełnienie stosu będzie bardzo frustrującym doświadczeniem.
Jared Smith