Ruby段模式与Awk和Perl

时间:2017-07-09 18:55:57

标签: ruby bash perl awk record

假设您有一个文件,其中包含五个数据块,由两个或多个\n分隔,以分隔记录(通用文本格式)。

如果您使用RS=""运行,则设置awk以将块分隔为记录。然后,您可以设置FS=\n以将块的行分隔为字段。

示例:

$ cat lines
f1, r1
f2, r1 then 2 \n:

f1, r2 then 3 \n:


f1,r3
f2,r3 then 4 \n:



f1, r4
f2,r4 then 6 \n: 





f1,r5

使用awk将块分隔成记录并将行分隔成字段的idiomatic方法是:

$ awk 'BEGIN{RS=""; FS="\n"; OFS="|"}
       {$1=$1; printf "NR: %d, NF: %d, record: \"%s\"\n", NR, NF, $0 }' lines
NR: 1, NF: 2, record: "f1, r1|f2, r1 then 2 \n:"
NR: 2, NF: 1, record: "f1, r2 then 3 \n:"
NR: 3, NF: 2, record: "f1,r3|f2,r3 then 4 \n:"
NR: 4, NF: 2, record: "f1, r4|f2,r4 then 6 \n: "
NR: 5, NF: 1, record: "f1,r5"

无论\n分隔多少块,只要2个或更多,它就是一条记录。

可以通过设置RS="\n\n+"代替RS=""来获得完全相同的结果,因为gawk支持正则表达式来分隔记录。感谢Ed Morton指出了POSIX awk和gawk之间的差异)

虽然不支持将正则表达式用于输入记录分隔符,但有两种方法可以设置等效的段落模式。您可以使用-00命令行开关或将输入记录分隔符$/设置为空字符串:

$ perl  -00 -F"\n" -lane 'BEGIN{ $\=""; $,="|"} 
                    printf "NR: %d, NF: %d, record: \"%s\"\n", $., scalar(@F), join($,,@F)' lines
NR: 1, NF: 2, record: "f1, r1|f2, r1 then 2 \n:"
NR: 2, NF: 1, record: "f1, r2 then 3 \n:"
NR: 3, NF: 2, record: "f1,r3|f2,r3 then 4 \n:"
NR: 4, NF: 2, record: "f1, r4|f2,r4 then 6 \n: "
NR: 5, NF: 1, record: "f1,r5"

或者,

$ perl -F"\n" -lane 'BEGIN{ $\=""; $,="|"; $/=""} 
                     printf "NR: %d, NF: %d, record: \"%s\"\n", $., scalar(@F), join($,,@F)' lines  

也有效 - 相同的输出。

Ruby 有一个段落模式,但与Perl和awk不同,它有一个可能很重要的行为差异。如果有\n个以上,则\n的运行不会被忽略。它等同于Ruby中的正则表达式/\n\n/与awk和Perl中的/\n\n+/。它会将相同输入的字段计数和记录计数搞砸。

演示:

$ ruby -00 -F"\n" -lane 'BEGIN{$\=""; $,="|"}; 
                        printf "NR: %d, NF: %d, record: \"%s\"\n", $.,$F.length,$F.join' lines
NR: 1, NF: 2, record: "f1, r1|f2, r1 then 2 \n:"
NR: 2, NF: 1, record: "f1, r2 then 3 \n:"
NR: 3, NF: 3, record: "|f1,r3|f2,r3 then 4 \n:"
NR: 4, NF: 0, record: ""
NR: 5, NF: 2, record: "f1, r4|f2,r4 then 6 \n: "
NR: 6, NF: 0, record: ""
NR: 7, NF: 0, record: ""
NR: 8, NF: 1, record: "f1,r5"

因此,当Perl和Awk认为它有5个记录和8个总字段时,Ruby的-00段模式认为相同的内容有8个记录,总共9个字段。

有没有办法用Ruby获得与Perl和Awk相同的结果?

2 个答案:

答案 0 :(得分:2)

如果您使用$/=""代替-00

,则可以使用
$ ruby -F"\n" -lane 'BEGIN{$/=""; $\=""; $,="|"; $i=1};
                     print "#{$F.join($,)}\t\t#{$i}\n"; $i+=1;' lines

这相当于Perl命令:

$ perl -F"\n" -lane 'BEGIN{$/=""; $\=""; $,="|"; $i=1}
                     print join($,,@F)."\t\t$i\n"; $i++;' lines 

两个输出:

f1, r1|f2, r1 then 2 \n:        1
f1, r2 then 3 \n:       2
f1,r3|f2,r3 then 4 \n:      3
f1, r4|f2,r4 then 6 \n:     4
f1,r5       5

答案 1 :(得分:0)

与Perl一样,Ruby仅支持$/的单个八进制字符来分隔记录。 (Ruby和Perl共享类似的全局变量。)

所以这些是三种解决方法:

  1. 设置$/=""。在Ruby中,$/=""的行为与Perl相同,其中\n的运行被视为单个记录分隔符(与ruby -00形成对比)。 (感谢Stefan

  2. '啜食'该文件然后使用正则表达式将文本分成记录和字段。 (对于perl,POSIX awk或ruby中非单个八进制字符或\n\n+的记录之间的任何中断,您需要执行此操作。)

  3. 通过awk提取文件以删除多余的\n并将中断重新定义为\n\n

  4. #1

    的示例
    $ ruby -F"\n" -lane 'BEGIN{$\=""; $/=""; $,="|"}; 
                           printf "NR: %d, NF: %d, record: \"%s\"\n", $.,$F.length,$F.join' lines
    NR: 1, NF: 2, record: "f1, r1|f2, r1 then 2 \n:"
    NR: 2, NF: 1, record: "f1, r2 then 3 \n:"
    NR: 3, NF: 2, record: "f1,r3|f2,r3 then 4 \n:"
    NR: 4, NF: 2, record: "f1, r4|f2,r4 then 6 \n: "
    NR: 5, NF: 1, record: "f1,r5"
    

    #2

    的示例
    $ ruby -e 'i=0
          $<.read.split(/\n\n+/)
            .map {|record| record.split(/\n/)}
            .map {|f| i+=1; printf "NR: %d, NF: %d, record: \"%s\"\n", i,f.length,f.join
                      }' lines
    

    #3

    的示例
    $ ruby -00 -F"\n" -lane 'BEGIN{$/=""; $\=""; $,="|"; $i=1}; 
                         printf "NR: %d, NF: %d, record: \"%s\"\n", $.,$F.length,$F.join' <(awk 'BEGIN{RS=""} {print $0 ORS}' lines) 
    

    所有产生与第一个产生相同的输出。

    ruby -00的行为与perl等价物相同;它等同于打破正则表达式/\n\n/如果您知道数据块仅由两个-00分隔,则仅使用\n

    (至少在-00 ruby -0上修复...)

    (注意:ruby -0[some octal value]0x00不同前者将输入记录分隔符设置为find . print0 | ruby -0 -lane 'puts $_'的文字值,以便与可以提供Nul终止字符串的其他Unix实用程序一起使用,例如/images/image_id=abc123