【问题标题】:How do I use variables to do substitution in Perl?如何在 Perl 中使用变量进行替换?
【发布时间】:2010-07-21 00:16:41
【问题描述】:

我有几个文本文件,它们曾经是数据库中的表,现在已被反汇编。我正在尝试重新组装它们,一旦我将它们变成可用的形式,这将很容易。第一个文件“keys.text”只是一个标签列表,格式不一致。喜欢:

Sa 1 #
Sa 2
U 328 #*

它总是字母、[空格]、数字、[空格],有时还有符号。与这些键匹配的文本文件是相同的,然后是一行文本,也由空格分隔或定界。

Sa 1 # Random line of text follows.
Sa 2 This text is just as random.
U 328 #* Continuing text...

我在下面的代码中尝试做的是将“keys.text”中的键与 .txt 文件中的相同键匹配,并在键和文本之间放置一个制表符。我确定我忽略了一些非常基本的东西,但我得到的结果看起来与源 .txt 文件相同。

提前感谢任何线索或帮助!

#!/usr/bin/perl

use strict;
use warnings;
use diagnostics;
open(IN1, "keys.text");

my $key;

# Read each line one at a time
while ($key = <IN1>) {

# For each txt file in the current directory
foreach my $file (<*.txt>) {
  open(IN, $file) or die("Cannot open TXT file for reading: $!");
  open(OUT, ">temp.txt") or die("Cannot open output file: $!");

  # Add temp modified file into directory 
  my $newFilename = "modified\/keyed_" . $file;
  my $line;

  # Read each line one at a time
  while ($line = <IN>) {

     $line =~ s/"\$key"/"\$key" . "\/t"/;
     print(OUT "$line");

  }
  rename("temp.txt", "$newFilename");
 }   
}

编辑:澄清一下,如果有的话,结果也应该保留键中的符号。所以它们看起来像:

Sa 1 #      Random line of text follows.
Sa 2        This text is just as random.
U 328 #*    Continuing text...

【问题讨论】:

  • 我猜您的输出与您的输入相同,因为您的正则表达式不匹配任何内容。查看下面的 cHao 的答案来解决这个问题。如果您知道每个“数据”文件都将以密钥开头,那么您就不能在不知道密钥的情况下将标签推入吗?密钥将始终匹配 /\w+\s\w+\s[*|#]*/ 或 [*|#] 中的任何其他内容?

标签: perl variables substitution


【解决方案1】:

对我来说,正则表达式似乎很奇怪。不会

$line =~ s/$key/$key\t/;

工作得更好?

另外,IIRC,&lt;IN1&gt; 将在您的 $key 末尾留下换行符。 chomp $key 摆脱它。

并且不要在 print args 周围加上括号,尤其是在写入文件句柄时。不管是不是,它看起来都是错误的,并且会分散人们对真正问题的注意力。

【讨论】:

    【解决方案2】:

    如果 Perl 不是必须的,你可以使用这个 awk one liner

    $ cat keys.txt
    Sa 1 #
    Sa 2
    U 328 #*
    
    $ cat mytext.txt
    Sa 1 # Random line of text follows.
    Sa 2 This text is just as random.
    U 328 #* Continuing text...
    
    $ awk 'FNR==NR{ k[$1 SEP $2];next }($1 SEP $2 in k) {$2=$2"\t"}1 ' keys.txt mytext.txt
    Sa 1     # Random line of text follows.
    Sa 2     This text is just as random.
    U 328    #* Continuing text...
    

    【讨论】:

      【解决方案3】:

      使用split 而不是s/// 可以让问题变得简单。在下面的代码中,read_keyskeys.text 中提取键并将它们记录在哈希中。

      然后对于所有在命令行命名的文件,在特殊的 Perl 数组@ARGV 中可用,我们检查每一行以查看它是否以键开头。如果不是,我们不理会它,否则在键和文本之间插入一个 TAB。

      请注意,由于 Perl 方便的 -i 选项,我们可以就地编辑文件:

      -i[扩展名]

      指定由&lt;&gt; 构造处理的文件将被就地编辑。它通过重命名输入文件、按原始名称打开输出文件并选择该输出文件作为print 语句的默认值来实现此目的。扩展名(如果提供)用于修改旧文件的名称以制作备份副本……

      split " ", $_, 3 行将当前行精确地分成三个字段。这对于保护行的文本部分中可能存在的空白是必要的。

      #! /usr/bin/perl -i.bak
      
      use warnings;
      use strict;
      
      sub usage { "Usage: $0 text-file\n" }
      
      sub read_keys {
        my $path = "keys.text";
        open my $fh, "<", $path
          or die "$0: open $path: $!";
      
        my %key;
        while (<$fh>) {
          my($text,$num) = split;
          ++$key{$text}{$num} if defined $text && defined $num;
        }
      
        wantarray ? %key : \%key;
      }
      
      die usage unless @ARGV;
      my %key = read_keys;
      
      while (<>) {
        my($text,$num,$line) = split " ", $_, 3;
        $_ = "$text $num\t$line" if defined $text &&
                                    defined $num &&
                                    $key{$text}{$num};
        print;
      }
      

      示例运行:

      $ ./add-tab 输入
      
      $ diff -u input.bak 输入
      --- input.bak 2010-07-20 20:47:38.688916978 -0500
      +++ 输入 2010-07-20 21:00:21.119531937 -0500
      @@ -1,3 +1,3 @@
      -Sa 1 # 随机文本行如下。
      -Sa 2 这个文本是随机的。
      -U 328 #* 继续文本...
      +Sa 1 # 随机文本行跟随。
      +Sa 2 这个文本是随机的。
      +U 328 #* 继续文字...

      【讨论】:

        【解决方案4】:

        有趣的答案:

        $line =~ s/(?<=$key)/\t/;
        

        (?&lt;=XXXX) 是 XXXX 的 zero-width positive lookbehind。这意味着它匹配就在 XXXX之后,而不是被替换的匹配的一部分。

        还有:

        $line =~ s/$key/$key . "\t"/e;
        

        末尾的/e 标志意味着在填写之前对s/// 的后半部分中的内容执行一个eval

        重要提示:我不推荐其中任何一种,它们会混淆程序。但它们很有趣。 :-)

        【讨论】:

          【解决方案5】:

          如何对每个文件进行两次单独的 slurp。对于第一个文件,您打开密钥并创建初步哈希。对于第二个文件,您需要做的就是将文本添加到哈希中。

          use strict;
          use warnings;
          
          my $keys_file = "path to keys.txt";
          my $content_file = "path to content.txt";
          my $output_file = "path to output.txt";
          
          my %hash = ();
          
          my $keys_regex = '^([a-zA-Z]+)\s*\(d+)\s*([^\da-zA-Z\s]+)';
          
          open my $fh, '<', $keys_file or die "could not open $key_file";
          while(<$fh>){
              my $line = $_;
              if ($line =~ /$keys_regex/){
                  my $key = $1;
                  my $number = $2;
                  my $symbol = $3;
                  $hash{$key}{'number'} = $number;
                  $hash{$key}{'symbol'} = $symbol;
              }
          }
          close $fh;
          
          open my $fh, '<', $content_file or die "could not open $content_file";
          while(<$fh>){
              my $line = $_;
              if ($line =~ /^([a-zA-Z]+)/){
                  my $key = $1;
          // strip content_file line from keys/number/symbols to leave text
                  line =~ s/^$key//;
                  line =~ s/\s*$hash{$key}{'number'}//;
                  line =~ s/\s*$hash{$key}{'symbol'}//;
                  $line =~ s/^\s+//g;
                  $hash{$key}{'text'} = $line;
              }
          }
          close $fh;
          
          open my $fh, '>', $output_file or die "could not open $output_file";
          for my $key (keys %hash){
              print $fh $key . " " . $hash{$key}{'number'} . " " . $hash{$key}{'symbol'} . "\t" . $hash{$key}{'text'} . "\n";
          }
          close $fh;
          

          我还没有机会对其进行测试,所有正则表达式的解决方案似乎有点老套,但可能会让您了解可以尝试的其他方法。

          【讨论】:

            【解决方案6】:

            这看起来像是 Perl 中 map 函数的完美位置!将整个文本文件读入一个数组,然后在整个数组中应用 map 函数。您可能想要做的唯一另一件事是使用 quotemeta 函数转义键中任何可能的正则表达式。

            使用map 非常有效。我还将密钥读入一个数组,以便不必在循环中不断打开和关闭密钥文件。这是一个 O^2 算法,但如果你的密钥不是那么大,它应该不会太糟糕。

            #! /usr/bin/env perl
            
            use strict;
            use vars;
            use warnings;
            
            open (KEYS, "keys.text")
                or die "Cannot open 'keys.text' for reading\n";
            my @keys = <KEYS>;
            close (KEYS);
            
            foreach my $file (glob("*.txt")) {
                open (TEXT, "$file")
                    or die "Cannot open '$file' for reading\n";
                my @textArray = <TEXT>;
                close (TEXT);
            
                foreach my $line (@keys) {
                    chomp $line;
                    map($_ =~ s/^$line/$line\t/, @textArray);
                }
                open (NEW_TEXT, ">$file.new") or
                    die qq(Can't open file "$file" for writing\n);
            
                print TEXT join("\n", @textArray) . "\n";
            close (TEXT);
            }
            

            【讨论】:

            • 对我自己的回答发表评论:您可以进行连接并立即编写整个文件,而不是循环写入文件。我将编辑我的命令以显示。
            猜你喜欢
            • 1970-01-01
            • 2010-10-29
            • 2010-09-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-09-27
            • 1970-01-01
            相关资源
            最近更新 更多