[英]Replace all text within two strings on different lines using Perl
我是一名网络开发人员。 我工作的机构使用 Dreamweaver 的模板/库项目功能。 库项目对于更新导航栏或每个页面上相同的某些内容确实派上用场。 我们大部分时间使用静态 HTML。 因此,要更改菜单项,我们使用 Dreamweaver 库项,我们更新该项一次,然后按更新,它会在项目中的每个 html 页面上更改它。
但是我想制作一个可以从命令行运行的 perl 脚本,而不是打开 GUI,它只会更快。
例如,假设我有一个这样编码的菜单:
<!--MENUITEMS-->
<li><a href="products.html">Products</a></li>
<li><a href="about_us.html">About Us</a></li>
<li><a href="commercial.html">Commercial</a></li>
<li><a href="contact.html">Contact</a></li>
<!--MENUEND-->
我会将 li 项代码存储在它们自己的文件中:nav.lbi:
<li><a href="products.html">Products</a></li>
<li><a href="about_us.html">About Us</a></li>
<li><a href="commercial.html">Commercial</a></li>
<li><a href="contact.html">Contact</a></li>
perl 脚本需要在它扫描的每个文件中将<!--MENUITEMS-->
和<!--MENUEND-->
之间的所有文本替换为 nav.lbi 的内容。
我打算首先在 SED 中尝试这个,但 SED 是为逐行内容量身定制的。 我已经成功地使用 SED 在另一个文件的某处插入了整个文本文件,但这有点不同。 使用 Perl,我知道我应该能够用<!--MENUEND-->
的内容替换<!---MENUITEMS-->
和<!--MENUEND-->
每次出现之间的所有文本,即使它跨越多行。
如果我需要将<!--MENUITEMS-->
和<!--MENUEND-->
标签添加到实际的 nav.lbi 文件中,因为它进行了搜索和替换,如果这会更容易,那很好也。 这只是让我可以跨多个 html 文件更新导航栏,而无需触摸 Dreamweaver。
最后要注意的一件事是<!--MENUITEMS-->
和结束<!--MENUEND-->
因为页眉中的导航通常与页脚中的导航相同,所以我需要递归更新文件。
只需在正则表达式的末尾添加一个s
即可使点匹配新行。 见man perlre
:
# or use File::Slurp;
sub slurp {
my $fn = shift;
open(IN,$fn);
return join('',<IN>);
}
my $_ = slurp("in.html");
my $new_menu_items = slurp("nav.lbi");
s/<!--MENUITEMS-->(.*)<!--MENUEND-->/$new_menu_items/s;
print;
要将输出保存到新文件而不只是打印它,您需要指定输出文件并打印到它,因此上面的代码将变为:
sub slurp {
my $fn = shift;
open(IN,$fn);
return join('',<IN>);
}
my $_ = slurp("in.html");
my $new_menu_items = slurp("nav.lbi");
open my $output_file, '>', 'output.html';
s/<!--MENUITEM-->(.*)<!--MENUEND-->/$new_menu_items/s;
print $output_file $_;
有关在 Perl 中啜饮的更多信息:
http://www.perl.com/pub/2003/11/21/slurp.html
以下内容基于 Dov Grobgelds 的回答,但在他的回答的评论中添加了额外的功能。 准确地说,它对目录中的所有 html 文件运行多行查找和替换并覆盖原始文件:
#!/usr/bin/perl
use strict;
use warnings;
sub slurp {
my $fn = shift;
open(IN,$fn);
return join('',<IN>);
}
my @files = grep ( -f ,<*.html>);
for my $file (@files) {
print "$file\n";
my $_ = slurp("$file");
my $new_menu_items = slurp("nav.lbi");
open my $output_file, '>', "$file";
s/<!--MENUITEMS-->(.*)<!--MENUEND-->/$new_menu_items/s;
print $output_file $_;
}
解释:
第一个问题是如何将输出打印到文件,而不仅仅是终端。 (参考 Dov Grobgelds 的回答来比较原始代码)
1.指定输出文件是什么:
open my $output_file, '>', 'output.html';
然后将其输出打印到文件中:
print;
- 来自原始答案
变成:
print $output_file $_;
现在为了让你的脚本在目录中的所有 html 文件上运行,我们需要把这个脚本变成更动态的东西。 我们需要将 vars 中的所有 .html 文件存储在一个数组中,然后遍历该数组,将每个 .html 存储到 var,然后在每个文件上运行 slurp/regex 代码。
首先声明保存 html 文件的变量并用当前目录中的 .html 文件填充它:
my @files = grep ( -f ,<*.html>);
剩下的魔法是通过将原始 slurp 和正则表达式代码包装在 for 循环中来实现的:
#for each file in files array, file is stored via my $file:
for my $file (@files) {
#unecessary put prints out file filename loop is working on:
print "$file\n";
#default var becomes current file contents:
my $_ = slurp("$file");
#this part isn't dynamic, its same each loop, there might be a better
#place to put this:
my $new_menu_items = slurp("nav.lbi");
#makes your output filename the same as the input:
open my $output_file, '>', "$file";
#runs the regex replace on the file contents of orig file
s/<!--MENUITEMS-->(.*)<!--MENUEND-->/$new_menu_items/s;
#saves the contents back out to the original file
#(overwriting it with original data loaded after running regex on it:)
print $output_file $_;
}
现在假设您的结束标记不是唯一的,就像在 Dreamweaver 模板中一样,您可以添加 ? to (.*) 只匹配第一次出现,而不是最后一次:
s/<!--MENUITEMS-->(.*?)<!--LIBRARYITEMEND-->/$new_menu_items/s;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.