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

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

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

fs.copy("/path/to/source/folder","/path/to/destination/folder");

34 ответа

Решение

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

Начиная с Node v16.7.0 можно использовать fs.cp или fs.cpSync функция

      fs.cp(src, dest, {recursive: true});

Текущий индекс стабильности (в Node v17.0.1) равен 1.

Стабильность: 1 - экспериментальная . На эту функцию не распространяются правила семантического управления версиями. В любой будущей версии могут быть внесены изменения или удаление без обратной совместимости. Использование этой функции не рекомендуется в производственной среде.

Источник: Nodejs.org - Индекс стабильности

Вы можете проверить документацию fs для получения дополнительной информации.

Похоже, что ncp и гаечный ключ больше не обслуживаются. Наверное, лучший вариант - использовать fs-extra

Разработчик Wrench рекомендует пользователям использовать fs-extra поскольку он отказался от своей библиотеки

copySync и moveSync будут копировать и перемещать папки, даже если в них есть файлы или подпапки, и вы можете легко перемещать или копировать файлы с его помощью

const fse = require('fs-extra');

const srcDir = `path/to/file`;
const destDir = `path/to/destination/directory`;

// To copy a folder or file
fse.copySync(srcDir, destDir, function (err) {
  if (err) {
    console.error(err);
  } else {
    console.log("success!");
  }
});

ИЛИ ЖЕ

// To move a folder or file 
fse.moveSync(srcDir, destDir, function (err) {
  if (err) {
    console.error(err);
  } else {
    console.log("success!");
  }
});

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

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

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 );
            }
        } );
    }
}
/**
 * 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 (exists && isDirectory) {
    fs.mkdirSync(dest);
    fs.readdirSync(src).forEach(function(childItemName) {
      copyRecursiveSync(path.join(src, childItemName),
                        path.join(dest, childItemName));
    });
  } else {
    fs.linkSync(src, dest);
  }
};

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

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

Альтернативой будет node-fs-extra

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

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

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

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

fs-extra работал на меня, когда ncp а также wrench не хватило:

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

Для ОС 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}`);

Это оно!

Модуль 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

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

Это довольно легко с узлом 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 не существует.

Я знаю так много ответов здесь, но никто не ответил на это простым способом. Что касается официальной документации 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')

Я создал небольшой рабочий пример, который копирует исходную папку в другую папку назначения всего за несколько шагов (на основе ответа @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

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

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

Начиная с узла v16.7.0:

      import { cp } from 'fs/promises';
await cp(
  new URL('../path/to/src/', import.meta.url),
  new URL('../path/to/dest/', import.meta.url), {
    recursive: true,
  }
);

Обратите внимание на использование recursive: true. Это предотвращает ERR_FS_EISDIRошибка.

Подробнее читайте в документации по файловой системе Node .

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

Так как я просто создаю простой скрипт для узла, я не хотел, чтобы пользователям скрипта приходилось импортировать кучу внешних модулей и зависимостей, поэтому я остановился на мысли и выполнил поиск запущенных команд из 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.

Я написал эту функцию для рекурсивного копирования (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);
    }
});}

Я попытался 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`);
    }
  }
}

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

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

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

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

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

Используйте shelljs

npm i -D shelljs

      const bash = require('shelljs');
bash.cp("-rf", "/path/to/source/folder", "/path/to/destination/folder");

Версия TypeScript

      async function copyDir(source: string, destination: string): Promise<any> {
  const directoryEntries = await readdir(source, { withFileTypes: true });
  await mkdir(destination, { recursive: true });

  return Promise.all(
    directoryEntries.map(async (entry) => {
      const sourcePath = path.join(source, entry.name);
      const destinationPath = path.join(destination, entry.name);

      return entry.isDirectory()
        ? copyDir(sourcePath, destinationPath)
        : copyFile(sourcePath, destinationPath);
    })
  );
}

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

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

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

Если вы хотите рекурсивно скопировать все содержимое исходного каталога, вам необходимо передать recursive вариант как true и try catch задокументирован fs-extra для sync

В виде fs-extra полная замена fs поэтому вам не нужно импортировать базовый модуль

const fs = require('fs-extra');
let sourceDir = '/tmp/src_dir';
let destDir = '/tmp/dest_dir';
try {
  fs.copySync(sourceDir, destDir, { recursive: true })
  console.log('success!')
} catch (err) {
  console.error(err)
}

Этот код будет работать просто отлично, рекурсивно копируя любую папку в любое место. Только для 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+"\\\"");
}

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

Для более старых версий узлов, в которых нетfs.cp, я использую это в крайнем случае, чтобы не требовать сторонней библиотеки:

      const fs = require("fs").promises;
const path = require("path");

const cp = async (src, dest) => {
  const lstat = await fs.lstat(src).catch(err => false);

  if (!lstat) {
    return;
  }
  else if (await lstat.isFile()) {
    await fs.copyFile(src, dest);
  }
  else if (await lstat.isDirectory()) {
    await fs.mkdir(dest).catch(err => {});

    for (const f of await fs.readdir(src)) {
      await cp(path.join(src, f), path.join(dest, f));
    }
  }
};

// sample usage
(async () => {
  const src = "foo";
  const dst = "bar";

  for (const f of await fs.readdir(src)) {
    await cp(path.join(src, f), path.join(dst, f));
  }
})();

Преимущества (или отличия) по сравнению с существующими ответами:

  • асинхронный
  • игнорирует символические ссылки
  • не выдает, если каталог уже существует (не ловите mkdirкиньте если это нежелательно)
  • довольно краткий

Это может быть возможным решением с использованием функции асинхронного генератора и повторения с помощью for awaitпетля. Это решение включает возможность отфильтровывать некоторые каталоги, передавая их в качестве необязательного аргумента третьего массива.

      import path from 'path';
import { readdir, copy } from 'fs-extra';

async function* getCopyableFiles(srcDir: string, destDir: string, excludedFolders?: string[]): AsyncGenerator<string> {
  const directoryEntries = await readdir(srcDir, { withFileTypes: true });
  for (const entry of directoryEntries) {
    const fileName = entry.name;
    const filePath = path.join(srcDir, fileName);
    if (entry.isDirectory()) {
      if (!excludedFolders?.includes(filePath)) {
        yield* getCopyableFiles(filePath, path.join(destDir, fileName), excludedFolders);
      }
    } else {
      yield filePath;
    }
  }
}

Затем:

      for await (const filePath of getCopyableFiles(path1, path2, ['dir1', 'dir2'])) {
   await copy(filePath, filePath.replace(path1, path2));
}

Встроенная версия

      node -e "const fs=require('fs');const p=require('path');function copy(src, dest) {if (!fs.existsSync(src)) {return;} if (fs.statSync(src).isFile()) {fs.copyFileSync(src, dest);}else{fs.mkdirSync(dest, {recursive: true});fs.readdirSync(src).forEach(f=>copy(p.join(src, f), p.join(dest, f)));}}const args=Array.from(process.argv); copy(args[args.length-2], args[args.length-1]);" dist temp\dest

или узел 16.x+

      node -e "const fs=require('fs');const args=Array.from(process.argv); fs.cpSync(args[args.length-2], args[args.length-1], {recursive: true});" 

Протестировано на «узле 14.20.0», но если он работает на узле 10.x?

Из ответов пользователя8894303 и mpen: /questions/21271986/rekursivno-kopirovat-papku-v-nodejs/21272005#21272005

Обязательно \" экранируйте кавычки при использовании в скрипте package.json

пакет.json:

        "scripts": {
    "rmrf": "node -e \"const fs=require('fs/promises');const args=Array.from(process.argv); Promise.allSettled(args.map(a => fs.rm(a, { recursive: true, force: true })));\"",
    "cp": "node -e \"const fs=require('fs');const args=Array.from(process.argv);if (args.length>2){ fs.cpSync(args[args.length-2], args[args.length-1], {recursive: true});}else{console.log('args missing', args);}\""
    "copy": "node -e \"const fs=require('fs');const p=require('path');function copy(src, dest) {if (!fs.existsSync(src)) {return;} if (fs.statSync(src).isFile()) {fs.copyFileSync(src, dest);}else{fs.mkdirSync(dest, {recursive: true});fs.readdirSync(src).forEach(f=>copy(p.join(src, f), p.join(dest, f)));}}const args=Array.from(process.argv);if (args.length>2){copy(args[args.length-2], args[args.length-1]);}else{console.log('args missing', args);}\"",
    "mkdir": "node -e \"const fs=require('fs');const args=Array.from(process.argv);fs.mkdirSync(args[args.length-1],{recursive:true});\"",
    "clean": "npm run rmrf -- temp && npm run mkdir -- temp && npm run copy -- dist temp"
  }

примечание: для сценария rmrf требуется узел 14.20.x или 12.20.x?

бонус:

      deno eval "import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync } from 'node:fs';import { join } from 'node:path';function copy(src, dest) {if (!existsSync(src)) {return;} if (statSync(src).isFile()) {copyFileSync(src, dest);}else{mkdirSync(dest, {recursive: true});readdirSync(src).forEach(f=>copy(join(src, f), join(dest, f)));}}const args=Array.from(Deno.args);copy(args[0], args[1]);" dist temp\dest -- --allow-read --allow-write

поддержка дено ->npm i deno-binдля поддержки deno-bin в узле

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

ДА, 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;
Другие вопросы по тегам