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.


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

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

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

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

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

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

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

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

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 :)


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

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

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


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

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

-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.


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.