【问题标题】:Dynamic Perl find and replace using grep inside backticks在反引号内使用 grep 进行动态 Perl 查找和替换
【发布时间】:2012-02-17 08:47:21
【问题描述】:

我正在尝试在命令行上进行动态搜索并替换为 Perl,其中部分替换文本是反引号中 grep 命令的输出。这可以在命令行上执行吗,还是我需要编写一个脚本来执行此操作?

这是我认为可以解决问题的命令。我认为 Perl 会将反引号视为命令替换,但它只是将反引号和其中的内容视为字符串:

perl -p -i -e 's/example.xml/http:\/\/exampleURL.net\/`grep -ril "example_needle" *`\/example\/path/g' `grep -ril "example_needle" *`

更新:

感谢您提供有用的答案。是的,我原来的单行中有一个错字:grep 的目标文件应该是 *.

我根据 Schewrn 的示例编写了一个小脚本,但结果令人困惑。这是我写的脚本:

 #!/usr/bin/env perl -p -i

my $URL_First = "http://examplesite.net/some/path/";
my $URL_Last = "/example/example.xml";

my @files = `grep -ril $URL_Last .`;
chomp @files;

foreach my $val (@files) {
        @dir_names = split('/',$val);

        if(@dir_names[1] ne $0) {

            my $url = $URL_First .  @dir_names[1] . $URL_Last;

            open INPUT, "+<$val" or die $!;

            seek INPUT,0,0;

            while(<INPUT>) {
                    $_ =~ s{\Q$URL_Last}{$url}g;
                    print INPUT $_;
                    }
            close INPUT;
            }
    }

基本上我想做的是:

  1. 查找包含 $URL_Last 的文件。
  2. 将 $URL_Last 替换为 $URL_First 加上匹配文件所在目录的名称,再加上 $URL_Last。
  3. 将上述更改写入输入文件,而不修改输入文件中的任何其他内容。

运行我的脚本后,它完全混淆了输入文件中的 HTML 代码,并切断了文件中每一行的前几个字符。这很奇怪,因为我确定 $URL_Last 在每个文件中只出现一次,所以它应该只匹配一次并替换一次。这是因为seek函数的误用造成的吗?

【问题讨论】:

  • 如果我没记错的话,grep -ril "example_needle" 缺少目标文件,因为您无法在标准输入上打印文件名。也许您应该尝试解释一下您要做什么。
  • 花时间制作一个小样本文件,您可以将其编辑到您的问题中,并显示您需要的输出。否则,我们只是猜测。祝你好运。
  • Seek 绝对是不必要的,因为当您打开文件时,文件句柄位置已经在开头。至于你的问题描述,你会发现 1 个例子胜过 10 篇论文。例如,我假设您想要打开 foo/bar.html,将 /example/example.xml 替换为 http://examplesite.net/some/path/foo/example/example.xml?
  • 好吧,既然你没有再澄清就消失了,我只是将你指向File::Find,它除了为你省去麻烦的反引号之外,还可以让你提取文件名和目录。
  • 试试这个:codepad.org/BFpIwVtz

标签: perl bash command-line replace backticks


【解决方案1】:

您应该为 s/// 使用另一个分隔符,这样您就不需要在 URL 中转义斜杠:

perl -p -i -e '
s#example.xml#http://exampleURL.net/`grep -ril "example_needle"`/example/path#g'
    `grep -ril "example_needle" *`

您在正则表达式中的grep 命令不会被执行,因为它只是一个字符串,而反引号不是元字符。替换中的文本将就像在双引号字符串中一样。您需要 /e 标志来执行 shell 命令:

perl -p -i -e '
s#example.xml#
    qq(http://exampleURL.net/) . `grep -ril "example_needle"` . qq(/example/path)
    #ge'
    `grep -ril "example_needle" *`

但是,您究竟希望grep 命令做什么?它缺少目标文件。 -l 将打印匹配文件的文件名,而没有目标文件的grep 将使用标准输入,我怀疑这不起作用。

如果是拼写错误,并且您打算使用与参数列表相同的 grep,为什么不使用 @ARGV

perl -p -i -e '
s#example.xml#http://exampleURL.net/@ARGV/example/path#g'
    `grep -ril "example_needle" *`

这可能会或可能不会达到您的预期,具体取决于您是否希望字符串中有换行符。我不确定参数列表将被视为列表还是字符串。

【讨论】:

    【解决方案2】:

    看来你正在尝试做的是......

    1. 在树中查找包含给定字符串的文件。
    2. 使用该文件构建 URL。
    3. 用该 URL 替换字符串中的某些内容。

    您有三个部分,您可以将它们组合成一个正则表达式,但分三步完成要容易得多。当你需要添加它的时候,你不会在一周内讨厌自己。

    第一步是获取文件名。

    # grep -r needs a directory to search, even if it's just the current one
    my @files = `grep -ril $search .`;
    
    # strip the newlines off the filenames
    chomp @files;
    

    然后,如果您从grep 获得多个文件,您需要决定该怎么做。我将把这个选择留给你,我只选择第一个。

    my $file = $files[0];
    

    然后构建 URL。很简单...

    # Put it in a variable so it can be configured
    my $Site_URL = "http://www.example.com/";
    
    my $url = $Site_URL . $file;
    

    要执行更复杂的操作,您可以使用URI

    现在搜索和替换很简单。

    # The \Q means meta-characters like . are ignored.  Better than
    # remembering to escape them all.
    $whatever =~ s{\Qexample.xml}{$url}g;
    

    您想使用-p-i 编辑文件。幸运的是,我们可以模拟该功能。

    #!/usr/bin/env perl
    use strict;
    use warnings; # never do without these
    
    my $Site_URL   = "http://www.example.com/";
    my $Search     = "example-search";
    my $To_Replace = "example.xml";
    
    # Set $^I to edit files. With no argument, just show the output
    # script.pl .bak  # saves backup with ".bak" extension
    $^I = shift;
    
    my @files = `grep -ril $Search .`;
    chomp @files;
    my $file = $files[0];
    
    my $url = $Site_URL . $file;
    
    @ARGV = ($files[0]);  # set the file up for editing
    while (<>) {
        s{\Q$To_Replace}{$url}g;
    }
    

    【讨论】:

    • 在 shebang 中使用 -pi 很好,但在这种情况下,我不会那样做。如果您要使用长版本,则节省一些输入是没有意义的。您可以通过对文件中的每一行执行grep 来降低性能。在这种情况下,我只需在替换周围添加 while(&lt;&gt;)print,然后设置 $^I 进行就地编辑。
    • 哦,也许$^I = shift; push @ARGV, $files[0];
    • @TLP 哦,关于 grep 的好点子!我要去社区维基这个,所以如果你愿意,你可以修复它。
    • 在不知道他打算如何准确使用 grep 数据的情况下,这只是一个近似值,但它会模拟 -pi
    【解决方案3】:

    每个人的回答对我编写最终为我工作的脚本非常有帮助。我昨天实际上找到了一个 bash 脚本解决方案,但想发布一个 Perl 答案,以防其他人通过 Google 找到这个问题。

    @TLP 在http://codepad.org/BFpIwVtz 上发布的脚本是执行此操作的另一种方法。

    这是我最后写的:

    #!/usr/bin/perl
    
    use Tie::File;
    
    my $URL_First = 'http://example.com/foo/bar/';
    my $Search = 'path/example.xml';
    my $URL_Last = '/path/example.xml';
    
    # This grep returns a list of files containing "path/example.xml"
    my @files = `grep -ril $Search .`;
    chomp @files;
    
    foreach my $File_To_Edit (@files) {
    
    # The output of $File_To_Edit looks like this: "./some_path/index.html"
    # I only need the "some_path" part, so I'm going to split up the output and only use @output[1] ("some_path")
        @output = split('/',$File_To_Edit);
    
    # "some_path" is the parent directory of "index.html", so I'll call this "$Parent_Dir"
        my $Parent_Dir = @output[1];
    
    # Make sure that we don't edit the contents of this script by checking that $Parent_Dir doesn't equal our script's file name.
        if($Parent_Dir ne $0) {
    
                # The $File_To_Edit is "./some_path/index.html"
                tie @lines, 'Tie::File', $File_To_Edit or die "Can't read file: $!\n";
                foreach(@lines) {
                        # Finally replace "path/example.xml" with "http://example.com/foo/bar/some_path/path/example.xml" in the $File_To_Edit
                        s{$Search}{$URL_First$Parent_Dir$URL_Last}g;
                        }
                untie @lines;
                }
        }
    

    【讨论】:

      猜你喜欢
      • 2019-03-13
      • 2015-06-10
      • 1970-01-01
      • 2016-05-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-11
      相关资源
      最近更新 更多