我使用 Ubuntu 11.04 并编写了一个小脚本,在文本文件中搜索某些“标记”,并用同名模板文件中的一些预先编写的片段替换。
正在搜索的文本文件将具有每个标记的两个且仅有两个实例。第一个是纯文本,第二个是 html 版本,每个版本都有单独的片段。
这是脚本:
for f in `ls -1 .templates/template_text`;
do
g=`cat .templates/template_text/$f`
find to_process/ -type f | xargs perl -i.old -p -e "s/$f/$g/";
done
for f in `ls -1 .templates/template_html`;
do
g=`cat .templates/template_html/$f`
find to_process/ -type f | xargs perl -i.old -p -e "s/$f/$g/g";
done
我遇到了一个问题,即使我没有在第一个正则表达式中指定“全局”,它仍然会替换这两个标记。我不确定这是否是因为我调用 perl 的方式、错误或其他问题。
任何帮助,将不胜感激。
更新:我能够通过使用 sed 而不是 perl 来使脚本工作。
for f in `ls -1 .templates/template_text`;
do
g=`cat .templates/template_text/$f`
h=`cat .templates/template_html/$f`
find to_process/ -type f -print0 | xargs -0 -I {} sed -i -e "0,/$f/s/$f/$g/" -e "0,/$f/s/$f/$h/" {}
done
但仍然对如何让它与 perl 命令一起工作感兴趣。
答案1
这是因为 perl 一次读取文本文件一行,并将替换模式应用到每一行——因此,如果不同行中多次出现该标记,它们都会被替换。
要仅替换文件中的第一个匹配项,您可以添加该-0
选项,该选项将输入记录分隔符设置为空字符,并使 perl 在进行替换之前读取整个文件。
答案2
s/$f/$g/
替换每行中第一次出现的$f
by 。$g
如果您只想替换$f
整个文件中第一次出现的 ,则需要这么说。这就是您最终所做的sed
(0,/$f/ s/$f/$g/
替换$f
为$g
直到并包括第一次出现的$f
)。在 Perl 中,您可以用更详细但更容易理解的方式编写它(注意:请参阅下面的引用问题):
perl -i -pe 'if ($n==0) {s/$f/$g/; $n=1;} elsif ($n==1) {s/$f/$h/; $n=2}'
您的代码存在许多引用问题;如果您的文件名包含空格、通配字符或不可打印的字符(例如当前语言环境中不存在的字节序列),您将会遇到麻烦。幸运的是,这些问题很容易解决。
首先,一些通用的 shell 问题。始终使用双引号变量替换"$foo"
和命令替换"$(foo)"
除非您知道为什么需要不将它们括起来。如果您不将它们括起来,则结果将在包含空格的地方拆分为单独的单词,并且每个单词都被视为一个 glob 模式。因此,除非变量恰好包含一个以空格分隔的 glob 模式列表,否则请将其括在双引号中。此外,我建议使用$(…)
而不是`…`
;它们是等效的,只是内部嵌套的引号`…`
不可靠(而且,`
很容易与 混淆'
)。
不解析 的输出ls
。如果您需要对目录中的所有文件进行操作,shell 有一个可以使用的内置结构:globbing。相反$(ls /path/to/directory)
,写/path/to/directory/*
.这会生成带有目录路径的文件名;无论如何,这几乎总是您所需要的,如果您不需要,您可以cd
提前调用或删除全部或部分目录。下面,我使用${f#*/*/}
,这意味着删除$f
最短的前缀匹配。*/*/
for f in .templates/template_text/*; do
g=$(cat "$f")
h=$(cat ".templates/template_html/${f#*/*/}")
find to_process/ -type f …
done
通过find
,您可以使用更简单的构造-exec
,但也可以-print0
与xargs -0
作品结合使用。不要使用xargs
without ,因为它期望以一种不会产生的-0
特殊方式引用输入。find
find to_process/ -type f -exec perl … {} +
下一个问题是您要直接在 sed 或 perl 正则表达式中插入 strings $f
,$g
和。$h
这是错误的:这些变量不包含带引号的分隔符(/
在两种情况下)的正则表达式。使用 sed,您需要对字符串进行一次引用,在任何 in 之前/*.\[
以及$f
任何\&/
in$g
和之前添加反斜杠$h
。使用 Perl,有一种更简单的方法:通过环境传递值,并确保告诉 Perl 您拥有的是字符串而不是正则表达式。
export f g h
find to_process/ -type f -exec perl -i -e '
if ($n==0) {s/\Q$ENV{f}/$ENV{g}/; $n=1;}
elsif ($n==1) {s/\Q$ENV{f}/$ENV{h}/; $n=2}}
' {} +