Рекурсивно копировать папку в 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
не хватило:
Для ОС 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 для копирования-папки-рекурсивно. но я хочу это
- работает нормально (copy-dir выдает неустранимую ошибку)
- предоставляет два аргумента в фильтре: filepath и filetype (fs-extra не сообщает filetype)
- имеет проверку 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;