本问答将涵盖一个非常具体但常见的情况:给定许多具有相同或相似名称的文件,每个文件都在其自己的目录中,在其原始目录中创建每个文件的副本,但使用另一个名称。
您可能想知道哪种罕见情况需要这样的操作。其实并不罕见。一些新兴软件,特别是那些基于 MVC(模型/视图/控制器)设计模式的软件,可能要求程序员在不同的目录中分别实现各个组件。
例子:
software root
|
|-- Model => MyCustomComponent.php
|
|-- View => MyCustomComponent.php
|
|-- Controller => MyCustomComponent.php
|
\-- Translations
|
|-- English => MyCustomComponent.php
|
|-- French => MyCustomComponent.php
|
|-- Italian => MyCustomComponent.php
|
|-- Spanish => MyCustomComponent.php
|
实现这种结构的相当著名的软件的一个例子是流行的电子商务应用程序OpenCart. 但还有很多其他的。
一种常见的做法是通过复制现有组件或核心组件来创建新组件,应用变体并保存它们。如果继承和类似方法无法帮助我们,我们最终不得不复制 10-20 个文件。这是一个极其无聊且容易出错的过程。
这是一个典型的例子:从银行转账脚本实现直接借记脚本,使用前者作为“样板代码”(不要抨击我,我没有创建这个架构)。在翻译、模型、视图和控制器管理等之间,在其深层目录中复制每个文件的数量很快就会变得繁琐。
答案1
这个小脚本正好可以帮上忙。
给定一个起始目录、一个文件名模式、一个要更改的原始文件名部分和要使用的目标文件名部分,它将:
- 深度复制所有匹配的文件并保留新文件的属性(复制)
或者
- 将所有匹配的文件深度重命名为新名称(重命名)
或者
- 仅显示可能受影响的文件的预览列表(预览)。
最后一个选项特别有用,因为选择正确的匹配来覆盖大量名称或扩展名有变化的文件可能很快就会成为一项挑战。在应用更改之前预览更改以避免陷入混乱!
脚本非常简单,并且具有不假装自己是最好的、最快的、最干净的、最安全的。这对我有用,我决定分享它,希望其他人也能从中受益。
如果你启动它时没有使用任何参数或者输入了错误的命令,它会显示一些简要的说明:
./copy_multi_files.sh
Usage:
./copy_multi_files.sh command path name_pattern src dest
If command is 'copy' then the script copies src to dest
If command is 'rename' then the script renames src to dest
If command is 'preview' then the script just shows the matched files
Inside path, for every file name matching name_pattern, it copies every src file to dest file
参数:
命令:可以是
copy
、rename
或之一preview
。复制对新文件执行递归深度复制,重命名仅批量重命名现有文件,预览仅显示哪些文件将受到影响以及如何受到影响。路径:这是起始目录。我建议在小目录上进行实验,以便在尝试 4 千万亿条目文件系统之前对脚本有信心。
Name_pattern:这决定了哪些文件会受到影响,因此选择一个合理的值很重要。例如,如果我想选择大量名称以“sendmail”开头的脚本,我可以
s*
为此参数指定。对于那些了解 shell 命令的人来说,这个模式在内部传递给find
。src:这是部分,在形成复制文件的名称时,用 dest 替换的文件名模板。
dest:这是在新复制的文件名中替换 src 的内容。
执行了一些完整性检查,以便name_pattern
和src
产生一致的结果。也就是说,只选择匹配name_pattern
且包含的文件。src
感到困惑?别担心,我准备了一些例子和“陷阱”。
简单,一个文件夹示例:
我们在子目录中有一些文件:
apps/res/
我们要将名称中包含单词:的所有文件复制到具有 的sendmail
新文件中。sendmail
newthing
部分内容列表如下apps/res/
:
robot.php
sendmail_test.php
sendmail_test2.php
sendmail_test3.php
showinfo.php
show_server_vars.php
version.php
我们可以发出以下命令:
./copy_multi_files.sh preview apps/res/ s* sendmail newthing
此命令行告诉我们,我们只需要一个preview
;起始目录是apps/res/
(即,它位于我们当前目录下),我们将搜索以s
( s*
) 开头的所有文件。在找到的文件中,我们将仅选择名称包含 的文件sendmail
,并将其复制到驻留在相同目录中但sendmail
替换为 的文件中newthing
。
以下是此预览的输出:
apps/res/sendmail_test.php => apps/res/newthing_test.php
apps/res/sendmail_test2.php => apps/res/newthing_test2.php
apps/res/sendmail_test3.php => apps/res/newthing_test3.php
你看,即使我们指定s*
搜索模式,脚本也足够“智能”,只选择包含“sendmail”的“以 s 开头”的文件子集。
让我们运行命令:
./copy_multi_files.sh copy apps/res/ s* sendmail newthing
ls apps/res/
将显示我们现在既拥有原始文件,又拥有相同的文件newthing
。如果我们在上面的行中使用“重命名”,我们将只拥有文件newthing
。
newthing_test.php
newthing_test2.php
newthing_test3.php
robot.php
sendmail_test.php
sendmail_test2.php
sendmail_test3.php
showinfo.php
show_server_vars.php
version.php
让我们再次更改文件。这次我们重命名它们:
./copy_multi_files.sh rename apps/res/ new* new even_newer_
注意各个参数使用的值。现在的目录如下:
even_newer_thing_test.php
even_newer_thing_test2.php
even_newer_thing_test3.php
robot.php
sendmail_test.php
sendmail_test2.php
sendmail_test3.php
showinfo.php
show_server_vars.php
version.php
更复杂的例子:让我们转到 OpenCart 的顶部安装文件夹并返回到银行转账 MVC 文件。我们需要创建这些文件的副本,每个副本都位于其各自的目录中。它们将用于实现直接借记支付模块。以下是命令行和输出摘录:
./copy_multi_files.sh preview . ban* bank_transfer.* direct_debit
./catalog/view/language/russian/payment/bank_transfer.php => ./catalog/view/language/russian/payment/direct_debit
./catalog/view/language/czech/payment/bank_transfer.php => ./catalog/view/language/czech/payment/direct_debit
./catalog/view/theme/default/template/payment/bank_transfer.tpl => ./catalog/view/theme/default/template/payment/direct_debit
哎呀!出错了!我们很幸运,因为我们使用了预览。上面的命令出了什么问题?“src”参数是错误的。事实上,bank_transfer.*
它在内部传递给直接“搜索和替换”函数(sed
)。它不是用“文件通配符”函数扩展的,而是作为正则表达式扩展的。因此,预览中的所有文件都转换为:direct_debit
。
现在我们知道了这个陷阱,我们可以重写命令行来实现所需的结果:
/var/www/copy_multi_files.sh preview . ban* bank_transfer direct_debit
./catalog/view/language/russian/payment/bank_transfer.php => ./catalog/view/language/russian/payment/direct_debit.php
./catalog/view/language/czech/payment/bank_transfer.php => ./catalog/view/language/czech/payment/direct_debit.php
./catalog/view/theme/default/template/payment/bank_transfer.tpl => ./catalog/view/theme/default/template/payment/direct_debit.tpl
现在它起作用了!现在扩展名即使发生变化(.php 和 .tpl),也能保留。
遵循小脚本源代码。我强烈建议在执行“实时”重命名之前保存备份,并始终使用预览功能。请记住,这是一个递归 Unix 脚本,其影响可能极其严重且不可逆转!
源代码:
#!/bin/sh
if [ "$#" -eq 0 ] || ( [ "$1" != "preview" ] && [ "$1" != "copy" ] && [ "$1" != "rename" ] )
then
echo "Usage:"
echo "$0 command path name_pattern src dest"
echo "If command is 'copy' then the script copies src to dest"
echo "If command is 'rename' then the script renames src to dest"
echo "If command is 'preview' then the script just shows the matched files"
echo "Inside path, for every file name matching name_pattern, it copies every src file to dest file"
echo
exit 1
fi
for f in $(find "$2" -type f -name "$3" -name "*$4*" )
do
z=`echo "$f" | sed s/"$4"/"$5"/`
case "$1" in
"preview") echo "$f => $z"
;;
"copy") cp -p "$f" "$z"
;;
"rename") mv "$f" "$z"
;;
esac
done