Рекурсивно копировать папку в node.js

155

Есть более простой способ , чтобы скопировать папку и все ее содержимое , не вручную делать последовательность fs.readir, fs.readfile,fs.writefile рекурсивно?

Просто интересно, если я пропускаю функцию, которая в идеале будет работать, как это

fs.copy("/path/to/source/folder","/path/to/destination/folder");
lostsource
источник
3
Есть ли способ сделать это без каких-либо модулей? Может быть, рекурсивная функция / код snip-it?
Sukima
@Sukima - Смотрите мой ответ здесь .
jmort253

Ответы:

121

Вы можете использовать модуль ncp . Я думаю это то что тебе нужно

shift66
источник
2
Отлично! npm install ncpи работает менее чем за 30 лет. Спасибо.
Aebsubis
1
Ключ лучше для меня, потому что он поддерживает больше опций. С NCP вы не можете разрешить символические ссылки, например.
Слава Фомин II
3
В качестве удивительного бонуса можно использовать ncp в кроссплатформенных сценариях запуска npm.
Ciantic
У меня есть несколько простых случаев, когда ncp не идет в моем обратном вызове, где fs-extra правильно делает.
Bumpmann
40
Обратите внимание, что ncp, кажется, не поддерживается . fs-extra - это, пожалуй, лучший вариант.
Крис
74

Это мой подход к решению этой проблемы без каких-либо дополнительных модулей. Просто с помощью встроенных fsи pathмодулей.

Примечание. При этом используются функции чтения / записи fs, поэтому он не копирует метаданные (время создания и т. Д.). Начиная с узла 8.5, copyFileSyncдоступны функции, которые вызывают функции копирования ОС и, следовательно, также копируют метаданные. Я еще не тестировал их, но это должно сработать, чтобы просто заменить их. (См. Https://nodejs.org/api/fs.html#fs_fs_copyfilesync_src_dest_flags )

var fs = require('fs');
var path = require('path');

function copyFileSync( source, target ) {

    var targetFile = target;

    //if target is a directory a new file with the same name will be created
    if ( fs.existsSync( target ) ) {
        if ( fs.lstatSync( target ).isDirectory() ) {
            targetFile = path.join( target, path.basename( source ) );
        }
    }

    fs.writeFileSync(targetFile, fs.readFileSync(source));
}

function copyFolderRecursiveSync( source, target ) {
    var files = [];

    //check if folder needs to be created or integrated
    var targetFolder = path.join( target, path.basename( source ) );
    if ( !fs.existsSync( targetFolder ) ) {
        fs.mkdirSync( targetFolder );
    }

    //copy
    if ( fs.lstatSync( source ).isDirectory() ) {
        files = fs.readdirSync( source );
        files.forEach( function ( file ) {
            var curSource = path.join( source, file );
            if ( fs.lstatSync( curSource ).isDirectory() ) {
                copyFolderRecursiveSync( curSource, targetFolder );
            } else {
                copyFileSync( curSource, targetFolder );
            }
        } );
    }
}
Саймон Зикс
источник
он не копирует папки, если в их именах есть пробел
31415926
Для меня это копирование папок с пробелами в их именах. Возможно это было вызвано ошибкой, исправленной @victor. Поскольку я использую эту функцию довольно регулярно (в текущем состоянии, поскольку я забыл обновить ту же самую коррекцию, которую сделал Victor), я совершенно уверен, что она работает в целом.
Саймон Цайкс
1
Также необходимо: javascript var fs = require('fs'); var path = require('path');
Тайлер
2
Это на самом деле не копирует файлы. Он читает их, затем пишет их. Это не копирование. Копирование включает в себя дату создания, а также другие потоки метаданных, которые поддерживаются как Windows, так и MacOS и не копируются этим кодом. Начиная с узла 8.5, вы должны вызывать fs.copyили, fs.copySyncпоскольку они фактически вызывают функции копирования на уровне ОС в MacOS и Windows, и, таким образом, фактически копировать файлы.
Человек
1
извините, но fs.copyFileесли вы покопаетесь в исходном узле, который вы увидите на Mac и Windows, они вызовут специальную функцию ОС для копирования файла
gman
52

Есть несколько модулей, которые поддерживают копирование папок с их содержимым. Самым популярным был бы гаечный ключ

// Deep-copy an existing directory
wrench.copyDirSyncRecursive('directory_to_copy', 'location_where_copy_should_end_up');

Альтернативой будет узел-фс-экстра

fs.copy('/tmp/mydir', '/tmp/mynewdir', function (err) {
  if (err) {
    console.error(err);
  } else {
    console.log("success!");
  }
}); //copies directory, even if it has subdirectories or files
zemirco
источник
3
ключ не работает, если каталог для копирования содержит символическую ссылку
DoubleMalt
2
он также не работает в Windows, если каталог уже существует, ncp работал прямо из сумки.
Блент
6
у меня работает node-fs-extra. Он наследует оригинальную fs, и мне понравился способ управления процессом. Меньше кода для обновления в приложении.
dvdmn
15
Обратите внимание, что wrenchэто устарело и должно быть заменено на node-fs-extra( github.com/jprichardson/node-fs-extra )
Ambidex
1
Гаечный ключ на самом деле не копирует файлы. Он читает их, затем пишет их, затем копирует их дату. Это не копирование. Копирование включает в себя другие потоки метаданных, которые поддерживаются как Windows, так и MacOS и не копируются гаечным ключом.
Человек
38

Вот функция, которая рекурсивно копирует каталог и его содержимое в другой каталог:

const fs = require("fs")
const path = require("path")

/**
 * Look ma, it's cp -R.
 * @param {string} src The path to the thing to copy.
 * @param {string} dest The path to the new copy.
 */
var copyRecursiveSync = function(src, dest) {
  var exists = fs.existsSync(src);
  var stats = exists && fs.statSync(src);
  var isDirectory = exists && stats.isDirectory();
  if (isDirectory) {
    fs.mkdirSync(dest);
    fs.readdirSync(src).forEach(function(childItemName) {
      copyRecursiveSync(path.join(src, childItemName),
                        path.join(dest, childItemName));
    });
  } else {
    fs.copyFileSync(src, dest);
  }
};
Линдси Саймон
источник
3
Даже если вы вставите настоящую функцию копирования, вы не должны fs.lstatSyncfs.statSync
переходить
3
что могло вызвать эту путаницу, так это то, что fs.unlink удаляет файлы, но fs.link не копирует, а ссылается.
Саймон Зикс
3
@SimonSeyock: верно .. ЭТО linkingне копирует .. Проблема в том, что когда вы изменяете содержимое связанного файла, оригинальный файл тоже меняется.
Abdennour TOUMI
30

fs-extraу меня сработало когда ncpи wrenchне получилось

https://www.npmjs.com/package/fs-extra

Будет
источник
3
Кроме того, разработчик wrenchнаправляет пользователей использовать, fs-extraпоскольку он устарел в своей библиотеке.
mozillalives
23

Для ОС Linux / Unix вы можете использовать синтаксис оболочки

const shell = require('child_process').execSync ; 

const src= `/path/src`;
const dist= `/path/dist`;

shell(`mkdir -p ${dist}`);
shell(`cp -r ${src}/* ${dist}`);

Это оно!

Абденнур ТУМИ
источник
1
Добро пожаловать Ab
Abdennour TOUMI
1
Это самое простое решение. Не нужно заново изобретать инструменты UNIX!
Майкл
11
так как nodejs работает на OSX / linux / windows, это только ответ для 2 не всех 3.
mjwrazor
2
@AbdennourTOUMI что делать, если вы работаете на сервере Windows.
mjwrazor
3
Вот почему я начал ответ: «Для linux / unix OS вы можете использовать синтаксис оболочки ...» Ab
Abdennour TOUMI
19

Модуль FS-Extra работает как шарм.

Установить фс-экстра

$ npm install fs-extra

Ниже приведена программа для копирования исходного каталога в целевой каталог.

// include fs-extra package
var fs = require("fs-extra");

var source = 'folderA'
var destination = 'folderB'

// copy source folder to destination
fs.copy(source, destination, function (err) {
    if (err){
        console.log('An error occured while copying the folder.')
        return console.error(err)
    }
    console.log('Copy completed!')
});

Ссылки

fs-extra: https://www.npmjs.com/package/fs-extra

Пример: учебник по NodeJS - копирование папки Node.js

Малликарджун М
источник
этот процесс заменяет каталог или объединяется с ним?
С.М. Шахинул Ислам
14

Вот как я бы сделал это лично:

function copyFolderSync(from, to) {
    fs.mkdirSync(to);
    fs.readdirSync(from).forEach(element => {
        if (fs.lstatSync(path.join(from, element)).isFile()) {
            fs.copyFileSync(path.join(from, element), path.join(to, element));
        } else {
            copyFolderSync(path.join(from, element), path.join(to, element));
        }
    });
}

работает для папок и файлов


источник
3
Это решение является кратким и простым. Это было бы почти точно, как я это сделаю, так что +1 от меня. Вы должны улучшить свой ответ с помощью комментариев в своем коде и описать, почему это решение предпочтительнее других и какие у него могут быть недостатки. - Также обновите, какие модули ему требуются. («путь», «фс»)
Андрей
проверить, существует ли папка вверху ... спасет жизни ;-) if (! fs.existsSync (to)) fs.mkdirSync (to);
Тобиас
9

Я создал небольшой рабочий пример, который копирует исходную папку в другую папку назначения всего за несколько шагов (на основе ответа @ shift66 с использованием ncp):

Шаг 1 - Установите модуль ncp:

npm install ncp --save

Шаг 2 - создайте copy.js (измените srcPath и destPath в соответствии с вашими потребностями):

var path = require('path');
var ncp = require('ncp').ncp;

ncp.limit = 16;

var srcPath = path.dirname(require.main.filename); //current folder
var destPath = '/path/to/destination/folder'; //Any destination folder

console.log('Copying files...');
ncp(srcPath, destPath, function (err) {
  if (err) {
    return console.error(err);
  }
  console.log('Copying files complete.');
});

шаг 3 - беги

node copy.js
Шахар
источник
7

Это довольно легко с узлом 10.

const FSP = require('fs').promises;

async function copyDir(src,dest) {
    const entries = await FSP.readdir(src,{withFileTypes:true});
    await FSP.mkdir(dest);
    for(let entry of entries) {
        const srcPath = Path.join(src,entry.name);
        const destPath = Path.join(dest,entry.name);
        if(entry.isDirectory()) {
            await copyDir(srcPath,destPath);
        } else {
            await FSP.copyFile(srcPath,destPath);
        }
    }
}

Это предполагает, destчто не существует.

mpen
источник
3
Мы можем заставить это работать в Node 8.x, используя require('util').promisifyс fs.mkdirи fs.copyFileвместо require('fs').promises, который все еще экспериментален в v11.1.
Сан Трён-Нгуён
@sntran 8.x есть withFileTypesопция? Потому что это спасает вас от statзвонка
mpen
К сожалению, 8.x не имеет withFileTypesопции.
Сан
@ SơnTrần-Nguyễn 8.x завершает свою работу 31 декабря 2019 года - возможно,
пришло
6

Я знаю так много ответов уже здесь, но никто не ответил на это простым способом. Что касается официальной документации fs-exra , вы можете сделать это очень легко

const fs = require('fs-extra')

// copy file
fs.copySync('/tmp/myfile', '/tmp/mynewfile')

// copy directory, even if it has subdirectories or files
fs.copySync('/tmp/mydir', '/tmp/mynewdir')
Фредди Дэниел
источник
не забудьте установить рекурсивную опцию. fs.copySync ('/ tmp / mydir', '/ tmp / mynewdir', {recursive: true})
Dheeraj Kumar
Я не могу найти вариант { recursive: true }из упомянутого вами github doc , не знаю, работает ли он.
Фредди Даниэль
Я предполагаю, что мы говорим о fs-extra, но ваша ссылка на github указывает на node-fs-extra. Может ли быть другая библиотека?
Dheeraj Kumar
@DheerajKumar, он показывает node-fs-extra в github, но fs-extra в npm . Я не знаю, что оба одинаковы, пожалуйста, обратитесь пакет от npm
Фредди Даниэль
Fs-extra заменяет fs?
Мэтт
4

Поскольку я просто создаю простой скрипт для узла, я не хотел, чтобы пользователям скрипта приходилось импортировать кучу внешних модулей и зависимостей, поэтому я остановился на мысли и выполнил поиск запущенных команд из bash. оболочка.

Этот фрагмент кода node.js рекурсивно копирует папку с именем node-webkit.app в папку под названием build:

   child = exec("cp -r node-webkit.app build", function(error, stdout, stderr) {
        sys.print("stdout: " + stdout);
        sys.print("stderr: " + stderr);
        if(error !== null) {
            console.log("exec error: " + error);
        } else {

        }
    });

Спасибо Лэнсу Полларду из dzone за то, что я начал.

Приведенный выше фрагмент кода ограничен платформами на основе Unix, такими как Mac OS и Linux, но подобный метод может работать для Windows.

jmort253
источник
4

@ mallikarjun-м спасибо!

fs-extra сделал это, и он может даже вернуть Promise, если вы не предоставите обратный вызов! :)

const path = require('path')
const fs = require('fs-extra')

let source = path.resolve( __dirname, 'folderA')
let destination = path.resolve( __dirname, 'folderB')

fs.copy(source, destination)
  .then(() => console.log('Copy completed!'))
  .catch( err => {
    console.log('An error occured while copying the folder.')
    return console.error(err)
  })
Luckylooke
источник
2

Тот, кто поддерживает символическую ссылку +, не генерирует, если каталог существует.

function copyFolderSync(from, to) {
  try {
    fs.mkdirSync(to);
  } catch(e) {}

  fs.readdirSync(from).forEach((element) => {
    const stat = fs.lstatSync(path.join(from, element));
    if (stat.isFile()) {
      fs.copyFileSync(path.join(from, element), path.join(to, element));
    } else if (stat.isSymbolicLink()) {
      fs.symlinkSync(fs.readlinkSync(path.join(from, element)), path.join(to, element));
    } else if (stat.isDirectory()) {
      copyFolderSync(path.join(from, element), path.join(to, element));
    }
  });
}
Allx
источник
1

Этот код будет работать нормально, рекурсивно копируя любую папку в любое место. Только для Windows

var child=require("child_process");
function copySync(from,to){
    from=from.replace(/\//gim,"\\");
    to=to.replace(/\//gim,"\\");
    child.exec("xcopy /y /q \""+from+"\\*\" \""+to+"\\\"");
}

Идеально подходит для моей текстовой игры для создания новых игроков.

ModerateJavaScriptDev
источник
1

Я попытался fs-extra и copy-dir для копирования-папки-рекурсивно. но я хочу это

  1. работает нормально (copy-dir выдает неустранимую ошибку)
  2. предоставляет два аргумента в фильтре: filepath и filetype (fs-extra не сообщает filetype)
  3. имеет проверку dir-to-subdir и dir-to-file

Поэтому я написал свой собственный:

//node module for node 8.6+
var path=require("path");
var fs=require("fs");

function copyDirSync(src,dest,options){
  var srcPath=path.resolve(src);
  var destPath=path.resolve(dest);
  if(path.relative(srcPath,destPath).charAt(0)!=".")
    throw new Error("dest path must be out of src path");
  var settings=Object.assign(Object.create(copyDirSync.options),options);
  copyDirSync0(srcPath,destPath,settings);
  function copyDirSync0(srcPath,destPath,settings){
    var files=fs.readdirSync(srcPath);
    if (!fs.existsSync(destPath)) {
      fs.mkdirSync(destPath);
    }else if(!fs.lstatSync(destPath).isDirectory()){
      if(settings.overwrite)
        throw new Error(`Cannot overwrite non-directory '${destPath}' with directory '${srcPath}'.`);
      return;
    }
    files.forEach(function(filename){
      var childSrcPath=path.join(srcPath,filename);
      var childDestPath=path.join(destPath,filename);
      var type=fs.lstatSync(childSrcPath).isDirectory()?"directory":"file";
      if(!settings.filter(childSrcPath,type))
        return;
      if (type=="directory") {
        copyDirSync0(childSrcPath,childDestPath,settings);
      } else {
        fs.copyFileSync(childSrcPath, childDestPath, settings.overwrite?0:fs.constants.COPYFILE_EXCL);
        if(!settings.preserveFileDate)
          fs.futimesSync(childDestPath,Date.now(),Date.now());
      }
    });
  }
}
copyDirSync.options={
  overwrite: true,
  preserveFileDate: true,
  filter: function(filepath,type){return true;}
};

и аналогичная функция mkdirs, которая является альтернативой mkdirp

function mkdirsSync(dest) {
  var destPath=path.resolve(dest);
  mkdirsSync0(destPath);
  function mkdirsSync0(destPath){
    var parentPath=path.dirname(destPath);
    if(parentPath==destPath)
      throw new Error(`cannot mkdir ${destPath}, invalid root`);
    if (!fs.existsSync(destPath)) {
      mkdirsSync0(parentPath);
      fs.mkdirSync(destPath);
    }else if(!fs.lstatSync(destPath).isDirectory()){
      throw new Error(`cannot mkdir ${destPath}, a file already exists there`);
    }
  }
}
fuweichin
источник
0

Я написал эту функцию как для копирования (copyFileSync), так и для перемещения (renameSync) файлов рекурсивно между каталогами:

//copy files
copyDirectoryRecursiveSync(sourceDir, targetDir);
//move files
copyDirectoryRecursiveSync(sourceDir, targetDir, true);


function copyDirectoryRecursiveSync(source, target, move) {
if (!fs.lstatSync(source).isDirectory()) return;

var operation = move ? fs.renameSync : fs.copyFileSync;
fs.readdirSync(source).forEach(function (itemName) {
    var sourcePath = path.join(source, itemName);
    var targetPath = path.join(target, itemName);

    if (fs.lstatSync(sourcePath).isDirectory()) {
        fs.mkdirSync(targetPath);
        copyDirectoryRecursiveSync(sourcePath, targetDir);
    }
    else {
        operation(sourcePath, targetPath);
    }
});}
EladTal
источник
0

Если вы работаете в Linux, и производительность не является проблемой, вы можете использовать execфункцию из child_processмодуля, чтобы выполнить команду bash:

const { exec } = require('child_process');
exec('cp -r source dest', (error, stdout, stderr) => {...});

В некоторых случаях я нашел это решение более чистым, чем загрузка всего модуля или даже использование fsмодуля.

Эмилио Гризолиа
источник
0

ncp блокирует дескриптор файла и запускает обратный вызов, когда он еще не разблокирован. Я рекомендую использовать модуль рекурсивного копирования . Он поддерживает события, и вы можете быть уверены в окончании копирования.

Андрей Проскурин
источник
0

Будьте осторожны при выборе посылки. Некоторые пакеты, такие как copy-dir, не поддерживают копирование больших файлов длиной более 0x1fffffe8. Это выдаст ошибку вроде:

buffer.js:630 Uncaught Error: Cannot create a string longer than 0x1fffffe8 characters 

Я испытал нечто подобное в одном из моих проектов. В конечном итоге мне пришлось изменить пакет, который я использовал, и откорректировать много кода. Я бы сказал, что это не очень приятный опыт.

Если требуется несколько исходных и несколько целевых копий, вы можете использовать более точное копирование и написать что-то вроде этого:

// copy from multiple source into a directory
bCopy(['/path/to/your/folder1', '/path/to/some/file.txt'], '/path/to/destination/folder');

или даже :

// copy from multiple source into multiple destination
bCopy(['/path/to/your/folder1', '/path/to/some/file.txt'], ['/path/to/destination/folder', '/path/to/another/folder']);
Донован П
источник
-1

ДА, ncpэто coolхотя ...

Возможно, вы захотите / должны пообещать его функцию, чтобы сделать это super cool. Поскольку вы на это, добавьте его в toolsфайл, чтобы использовать его снова.

Ниже приводится рабочая версия, которая есть Asyncи использует Promises.


index.js

const {copyFolder} = require('./tools/');

return copyFolder(
    yourSourcePath,
    yourDestinationPath
)
.then(() => {
    console.log('-> Backup completed.')
}) .catch((err) => {
    console.log("-> [ERR] Could not copy the folder: ", err);
})

tools.js

const ncp = require("ncp");

/**
 * Promise Version of ncp.ncp()
 * 
 * This function promisifies ncp.ncp().
 * We take the asynchronous function ncp.ncp() with 
 * callback semantics and derive from it a new function with
 * promise semantics.
 */
ncp.ncpAsync = function (sourcePath, destinationPath) {
  return new Promise(function (resolve, reject) {
      try {
          ncp.ncp(sourcePath, destinationPath, function(err){
              if (err) reject(err); else resolve();
          });
      } catch (err) {
          reject(err);
      }
  });
};

/**
 * Utility function to copy folders asynchronously using
 * the Promise returned by ncp.ncp(). 
 */
const copyFolder = (sourcePath, destinationPath) => {
    return ncp.ncpAsync(sourcePath, destinationPath, function (err) {
        if (err) {
            return console.error(err);
        }
    });
}
module.exports.copyFolder = copyFolder;
Мик
источник
-1

Самый простой подход для решения этой проблемы - использовать только модули «fs» и «Path» и некоторую логику .....

Все файлы в корневой папке копируются с новым именем, если вы хотите просто установить номер версии, т.е. ....................... "var v = 'Ваш каталог Название'"

в префикс имени файла V добавлен контент с именем файла.

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

var c = 0;
var i =0 ;
var v = "1.0.2";
var copyCounter = 0;
var directoryCounter = 0; 
var directoryMakerCounter = 0;
var recursionCounter = -1;
var Flag = false;
var directoryPath = [] ;
var directoryName = [] ;
var directoryFileName = [];
var fileName;
var directoryNameStorer;
var dc = 0;
var route ;



if (!fs.existsSync(v)){
   fs.mkdirSync(v);
}

var basePath = path.join(__dirname, v);


function walk(dir){

  fs.readdir(dir, function(err, items) {

    items.forEach(function(file){

        file = path.resolve(dir, file);

        fs.stat(file, function(err, stat){
            if(stat && stat.isDirectory()){

                directoryNameStorer = path.basename(file);
                route = file;
                route = route.replace("gd",v);

                directoryFileName[directoryCounter] = route;
                directoryPath[directoryCounter] = file;
                directoryName[directoryCounter] = directoryNameStorer;

                directoryCounter++;
                dc++;

                if (!fs.existsSync(basePath+"/"+directoryName[directoryMakerCounter])){
                    fs.mkdirSync(directoryFileName[directoryMakerCounter]);
                    directoryMakerCounter++;
                }

            }else{

                    fileName = path.basename(file);
                    if(recursionCounter >= 0){
                        fs.copyFileSync(file, directoryFileName[recursionCounter]+"/"+v+"_"+fileName, err => {
                            if(err) return console.error(err);
                        });
                        copyCounter++;
                    }else{
                        fs.copyFileSync(file, v+"/"+v+"_"+fileName, err => {
                            if(err) return console.error(err);
                        });
                        copyCounter++;    
                    }

                }
                if(copyCounter + dc == items.length && directoryCounter > 0 && recursionCounter < directoryMakerCounter-1){
                    console.log("COPY COUNTER :             "+copyCounter);
                    console.log("DC COUNTER :               "+dc);                        
                    recursionCounter++;
                    dc = 0;
                    copyCounter = 0;
                    console.log("ITEM DOT LENGTH :          "+items.length);
                    console.log("RECURSION COUNTER :        "+recursionCounter);
                    console.log("DIRECOTRY MAKER COUNTER :  "+directoryMakerCounter);
                    console.log(": START RECURSION :        "+directoryPath[recursionCounter]);
                    walk(directoryPath[recursionCounter]); //recursive call to copy sub-folder

                }

        })
    })
 });

}
 walk('./gd', function(err, data){ //Just Pass The Root Directory Which You Want to Copy
 if(err) throw err;
 console.log("done");
})
М.М. Фуркан
источник
-1

Вот как я это сделал:

let fs = require('fs');
let path = require('path');

затем:

let filePath = //your FilePath

let fileList = []
        var walkSync = function(filePath, filelist) 
        {
          let files = fs.readdirSync(filePath);
          filelist = filelist || [];
          files.forEach(function(file) 
          {
            if (fs.statSync(path.join(filePath, file)).isDirectory()) 
            {
              filelist = walkSync(path.join(filePath, file), filelist);
            }
            else 
            {
              filelist.push(path.join(filePath, file));
            }
          });

          // Ignore hidden files
          filelist = filelist.filter(item => !(/(^|\/)\.[^\/\.]/g).test(item));

          return filelist;
        };

Затем вызовите метод:

This.walkSync(filePath, fileList)
uyghurbeg
источник