基于正则表达式递归重命名文件和文件夹
Rename files and folders recursively based on regex
我有这个文件结构:
FolderName/
[NAME]/
[NAME].controller.js
[NAME].html
使用 Node.js 我想用一个变量替换 [NAME]。
这是我试过的方法:
const shell = require("shelljs");
shell.ls('-Rl', '.').forEach(entry => {
if (entry.name.includes(`[NAME]`)) {
let newName = entry.name.replace(/\[NAME\]/, "Test");
shell.mv(entry.name, newName);
}
});
这只会将文件夹 [NAME]
重命名为 Test
,并保持文件不变。并输出:
mv: no such file or directory: FolderName/[NAME]/[NAME].controller.js
mv: no such file or directory: FolderName/[NAME]/[NAME].html
问题
当 运行ning shell.mv(entry.name, newName);
在您的示例的上下文中时,它试图 move/change 一条不再存在的路径,因为它在循环的前一轮中已被更改。这导致错误:
mv: no such file or directory: FolderName/[NAME]/[NAME].controller.js
mv: no such file or directory: FolderName/[NAME]/[NAME].html
解决方案 A
要避免错误,请尝试以下方法:
- 利用
shelljs
find command to obtain the paths instead of ls。这将确保生成的路径包括基本目录。
- 迭代每条路径并
filter
排除其资产不包含要查找的字符串的任何路径(例如 [NAME]
。同时排除任何隐藏资产(即那些以点开头的名称 .
)
- 按深度降序对路径数组进行排序。
- 仅将要查找的字符串的最后一个实例(例如
[NAME]
)替换为每个路径中的替换字符串(例如 TEST
)。然后最后使用 shelljs
mv 命令应用新路径。
注意: 作为安全措施,(在第 4 步),asset/path 仅在尚未采用新的结果路径时才重命名。如果新路径已经存在,则报告那些不应重命名的路径。例如,为了更好地理解这一点,假设我们有一个结构如下的初始目录:
初始目录结构。
.
└── [NAME]
├── [NAME].controller.js
├── [NAME].html
└── TEST.html
...如果我们 运行 脚本搜索要用字符串 TEST
替换的字符串 [NAME]
- 那么我们就有一个潜在的问题。如果我们要将 [NAME].html
重命名为 TEST.html
,我们将覆盖现有的 TEST.html
。我们生成的目录结构如下:
可能的结果目录结构。
.
└── TEST
├── TEST.controller.js
└── TEST.html
通过仅在尚未采用新的结果路径的情况下重命名资产,我们避免了这种丢失数据的潜在有害情况。
实际生成的目录结构。
.
└── TEST
├── TEST.controller.js
├── [NAME].html
└── TEST.html
当不应重命名资产时(因为这会导致数据丢失),脚本当前会报告这些实例。给定初始目录结构(如上),以下内容将记录到您的控制台:
1 path(s) not renamed. Name is already taken:
+ FolderName/TEST/[NAME].js --> FolderName/TEST/TEST.js
以下要点使用上述方法。该解决方案是在 ES5 中编写的,因此它适用于旧版本的 nodejs,但是它可以简单地修改为使用 ES6 语法。
示例节点脚本 1
var shell = require('shelljs');
var ROOT_DIR = './FolderName/'; // <-- Directory to search in relative to cwd.
var FIND_STR = '[NAME]'; // <-- String to find
var REPLACE_STR = 'TEST'; // <-- Replacement string
var issues = [];
// 1. Obtain all paths in the root directory.
shell.find(ROOT_DIR)
// 2. Exclude:
// - hidden files/folders (i.e. assets names starting with a dot)
// - Assets (i.e. at the end of the path) that do not contain `FIND_STR`
.filter(function(_path) {
var isVisible = _path.split('/').pop().indexOf('.', 0) !== 0,
assetHasFindStr = _path.split('/').pop().indexOf(FIND_STR) > -1;
return (assetHasFindStr && isVisible);
})
// 3. Sort paths by its depth in descending order.
.sort(function(a, b) {
return (b.split('/') || []).length - (a.split('/') || []).length;
})
// 4. Replace last instance of string to find with replace string and rename.
.forEach(function (_path) {
var firstPart = _path.substring(0, _path.lastIndexOf(FIND_STR)),
lastPart = _path.substring(_path.lastIndexOf(FIND_STR, _path.length)),
newPath = firstPart + lastPart.replace(FIND_STR, REPLACE_STR);
// Only rename if `newPath` is not already taken otherwise log them.
if (!shell.test('-e', newPath)) {
shell.mv(_path, newPath);
} else {
issues.push(_path + ' --> ' + newPath);
}
});
// 5. Log any paths that were not renamed because its name is already taken.
if (issues.length) {
shell.echo(issues.length + ' path(s) not renamed. Name is already taken:');
issues.forEach(function(issue) {
shell.echo('+ ' + issue);
});
}
解决方案 B
您的要求也可以通过安装和使用renamer来实现。
$ npm i -D renamer
然后使用shelljs调用renamer
命令。
示例节点脚本 2
const shell = require("shelljs");
shell.exec('node_modules/.bin/renamer --find \"[NAME]\" --replace \"TEST\" \"FolderName/**\"', { silent:true });
示例节点脚本 3
如果你需要一点更简洁的东西,(尽管它会产生额外的依赖),你可以利用shelljs-nodecli:
$ npm i -D shelljs-nodecli
然后调用renamer
命令,如下所示:
const nodeCLI = require("shelljs-nodecli");
nodeCLI.exec('renamer', '--find \"[NAME]\" --replace \"TEST\" \"FolderName/**\"', { silent:true });
注意,使用 shelljs-nodecli
可以避免手动查看 node_modules
目录来查找二进制 renamer
文件。即
shell.exec('node_modules/.bin/renamer ...
变成...
nodeCLI.exec('renamer' ...
我有这个文件结构:
FolderName/
[NAME]/
[NAME].controller.js
[NAME].html
使用 Node.js 我想用一个变量替换 [NAME]。
这是我试过的方法:
const shell = require("shelljs");
shell.ls('-Rl', '.').forEach(entry => {
if (entry.name.includes(`[NAME]`)) {
let newName = entry.name.replace(/\[NAME\]/, "Test");
shell.mv(entry.name, newName);
}
});
这只会将文件夹 [NAME]
重命名为 Test
,并保持文件不变。并输出:
mv: no such file or directory: FolderName/[NAME]/[NAME].controller.js
mv: no such file or directory: FolderName/[NAME]/[NAME].html
问题
当 运行ning shell.mv(entry.name, newName);
在您的示例的上下文中时,它试图 move/change 一条不再存在的路径,因为它在循环的前一轮中已被更改。这导致错误:
mv: no such file or directory: FolderName/[NAME]/[NAME].controller.js
mv: no such file or directory: FolderName/[NAME]/[NAME].html
解决方案 A
要避免错误,请尝试以下方法:
- 利用
shelljs
find command to obtain the paths instead of ls。这将确保生成的路径包括基本目录。 - 迭代每条路径并
filter
排除其资产不包含要查找的字符串的任何路径(例如[NAME]
。同时排除任何隐藏资产(即那些以点开头的名称.
) - 按深度降序对路径数组进行排序。
- 仅将要查找的字符串的最后一个实例(例如
[NAME]
)替换为每个路径中的替换字符串(例如TEST
)。然后最后使用shelljs
mv 命令应用新路径。
注意: 作为安全措施,(在第 4 步),asset/path 仅在尚未采用新的结果路径时才重命名。如果新路径已经存在,则报告那些不应重命名的路径。例如,为了更好地理解这一点,假设我们有一个结构如下的初始目录:
初始目录结构。
.
└── [NAME]
├── [NAME].controller.js
├── [NAME].html
└── TEST.html
...如果我们 运行 脚本搜索要用字符串 TEST
替换的字符串 [NAME]
- 那么我们就有一个潜在的问题。如果我们要将 [NAME].html
重命名为 TEST.html
,我们将覆盖现有的 TEST.html
。我们生成的目录结构如下:
可能的结果目录结构。
.
└── TEST
├── TEST.controller.js
└── TEST.html
通过仅在尚未采用新的结果路径的情况下重命名资产,我们避免了这种丢失数据的潜在有害情况。
实际生成的目录结构。
.
└── TEST
├── TEST.controller.js
├── [NAME].html
└── TEST.html
当不应重命名资产时(因为这会导致数据丢失),脚本当前会报告这些实例。给定初始目录结构(如上),以下内容将记录到您的控制台:
1 path(s) not renamed. Name is already taken:
+ FolderName/TEST/[NAME].js --> FolderName/TEST/TEST.js
以下要点使用上述方法。该解决方案是在 ES5 中编写的,因此它适用于旧版本的 nodejs,但是它可以简单地修改为使用 ES6 语法。
示例节点脚本 1
var shell = require('shelljs');
var ROOT_DIR = './FolderName/'; // <-- Directory to search in relative to cwd.
var FIND_STR = '[NAME]'; // <-- String to find
var REPLACE_STR = 'TEST'; // <-- Replacement string
var issues = [];
// 1. Obtain all paths in the root directory.
shell.find(ROOT_DIR)
// 2. Exclude:
// - hidden files/folders (i.e. assets names starting with a dot)
// - Assets (i.e. at the end of the path) that do not contain `FIND_STR`
.filter(function(_path) {
var isVisible = _path.split('/').pop().indexOf('.', 0) !== 0,
assetHasFindStr = _path.split('/').pop().indexOf(FIND_STR) > -1;
return (assetHasFindStr && isVisible);
})
// 3. Sort paths by its depth in descending order.
.sort(function(a, b) {
return (b.split('/') || []).length - (a.split('/') || []).length;
})
// 4. Replace last instance of string to find with replace string and rename.
.forEach(function (_path) {
var firstPart = _path.substring(0, _path.lastIndexOf(FIND_STR)),
lastPart = _path.substring(_path.lastIndexOf(FIND_STR, _path.length)),
newPath = firstPart + lastPart.replace(FIND_STR, REPLACE_STR);
// Only rename if `newPath` is not already taken otherwise log them.
if (!shell.test('-e', newPath)) {
shell.mv(_path, newPath);
} else {
issues.push(_path + ' --> ' + newPath);
}
});
// 5. Log any paths that were not renamed because its name is already taken.
if (issues.length) {
shell.echo(issues.length + ' path(s) not renamed. Name is already taken:');
issues.forEach(function(issue) {
shell.echo('+ ' + issue);
});
}
解决方案 B
您的要求也可以通过安装和使用renamer来实现。
$ npm i -D renamer
然后使用shelljs调用renamer
命令。
示例节点脚本 2
const shell = require("shelljs");
shell.exec('node_modules/.bin/renamer --find \"[NAME]\" --replace \"TEST\" \"FolderName/**\"', { silent:true });
示例节点脚本 3
如果你需要一点更简洁的东西,(尽管它会产生额外的依赖),你可以利用shelljs-nodecli:
$ npm i -D shelljs-nodecli
然后调用renamer
命令,如下所示:
const nodeCLI = require("shelljs-nodecli");
nodeCLI.exec('renamer', '--find \"[NAME]\" --replace \"TEST\" \"FolderName/**\"', { silent:true });
注意,使用 shelljs-nodecli
可以避免手动查看 node_modules
目录来查找二进制 renamer
文件。即
shell.exec('node_modules/.bin/renamer ...
变成...
nodeCLI.exec('renamer' ...