【问题标题】:Ruby paragraph mode vs Awk and PerlRuby 段落模式与 Awk 和 Perl
【发布时间】:2017-12-13 11:18:54
【问题描述】:

假设您有一个文件,其中包含由两个或多个\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

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

$ 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 不同的是,它具有重要的行为差异。如果有超过 2 个 \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 相同的结果?

【问题讨论】:

  • ruby -F"\n" -lane 'BEGIN{$/=""; $\=""; $,="|"; $i=1}; print "#{$F.join($,)}\t\t#{$i}\n"; $i+=1;' lines 为我工作。使用相同的开关和输入/输出分隔符,Ruby 的行为很像 Perl。
  • @Stefan:Doh!请张贴。但是 - 请注意 -00 的行为与使用 ruby​​ 的 $\="" 不同,其中 perl 对每个都是相同的。

标签: awk gawk perl ruby bash perl awk record


【解决方案1】:

如果您使用$/="" 而不是-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

【讨论】:

  • 毫无疑问,ruby -00 的行为是一个错误。
【解决方案2】:

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

所以这是三种解决方法:

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

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

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

#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/ 如果您知道数据块仅由两个\n 分隔,则仅使用-00

(至少在-00 上的filed bug 修复之前...)


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

【讨论】:

  • FWIW awk 'BEGIN{RS=""} {print $0 ORS}' = awk -v RS= -v ORS='\n\n' '1'
猜你喜欢
  • 2017-05-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多