当未选择全局时,perl 正则表达式全局替换

当未选择全局时,perl 正则表达式全局替换

我使用 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/替换每行中第一次出现的$fby 。$g如果您只想替换$f整个文件中第一次出现的 ,则需要这么说。这就是您最终所做的sed0,/$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,但也可以-print0xargs -0作品结合使用。不要使用xargswithout ,因为它期望以一种不会产生的-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}}
' {} +

相关内容