【问题标题】:Matching the last K occurrences of a pattern in a line匹配一行中模式的最后 K 次出现
【发布时间】:2017-02-22 20:01:12
【问题描述】:

是否可以使用 sed/awk 匹配一行中模式的最后 k 次出现?

为简单起见,假设我只想匹配每行中的最后 3 个逗号,例如(请注意,这两行的总逗号数不同):

10, 5, "Sally went to the store, and then , 299, ABD, F, 10
10, 6, If this is the case, and also this happened, then, 299, A, F, 9

我只想匹配从299 开始直到行尾的逗号。

动机:我正在尝试将其中一个字段中带有杂散逗号的 CSV 文件转换为制表符分隔的文件。由于正确列的数量是固定的,我的想法是用制表符替换前几个逗号,直到麻烦的字段(这很简单),然后从行尾向后退以再次替换。这应该将所有正确的分隔符逗号转换为制表符,同时在有问题的字段中保持逗号完整。

可能有更聪明的方法可以做到这一点,但我认为无论如何这将是一个很好的 sed/awk 教学点。

【问题讨论】:

  • 您应该在需要时在正确的位置添加双引号,而不是放置制表符(这有可能最终将问题转换为另一个问题)。
  • 是的,这听起来像是一个经典的 XY 问题。修复生成无效 CSV 的代码。
  • 绝对是 XY 问题。向我们展示您正在尝试做的事情,而不是您尝试如何做,否则您将被展示如何实施一个坏主意。
  • @glennjackman:我无法控制 CSV 的生成方式。数据集是公开的,它们只是带有这个问题。
  • @gogurt:这取决于它们是否被转义(使用其他双引号或反斜杠)。您应该将几个有问题的行(真实示例)添加到您的问题中,否则无法为您提供有用的答案。

标签: regex csv awk sed


【解决方案1】:

匹配最后三个逗号中的每一个的一个正则表达式单独需要一个否定的前瞻,这 sed 不支持。 您可以使用以下 sed-regex 一次匹配最后三个字段和它们前面的逗号:

,[^,]*,[^,]*,[^,]*$

$ 匹配行尾。

[^,] 匹配除, 之外的任何内容。

组允许您重复使用 sed 中的字段值:

sed -r 's/,([^,]*),([^,]*),([^,]*)$/\t\1\t\2\t\3/'

对于 awk,请查看 How to print last two columns using awk

可能有更聪明的方法来做到这一点

如果你想要的逗号后面都有一个空格,而不需要的逗号没有,怎么样

sed 's/,[^ ]/./g'

这会将a, b, 12,3, c 转换为a, b, 12.3, c

【讨论】:

  • 如果您在第一个 sed 命令中不使用 -r-E 标志,则应转义括号,在这种情况下,([^,]) 可能会简化为 (.*)。此外,三个逗号中的第一个被删除,而不是替换。我建议你写sed -r 's/,(.*),(.*),(.*),(.*)$/\t\1\t\2\t\3\t\4/ ' file
  • @Kenavoz 关于(),您是对的。我修好了它。但在这种情况下,[^,]* 不能.* 替换。正则表达式的第一个逗号将始终与该行的第一个逗号匹配。第一个.* 将尽可能匹配,包括除最后三个逗号之外的每个逗号。试试a,b,c,d,e,f,g,h,i
  • 你是对的。 sed -r 's/(.*),(.*),(.*),(.*)$/\1\t\2\t\3\t\4/' file 应该会更好。请注意,三个逗号中的第一个仍然没有被替换,而是被您的 sed 删除。
  • 感谢您的第二次提醒。修复了问题。您的新版本命令看起来不错。
【解决方案2】:

这将满足您对 GNU awk 的要求,让第三个参数匹配():

$ cat tst.awk
{
    gsub(/\t/," ")
    match($0,/^(([^,]+,){2})(.*)((,[^,]+){3})$/,a)
    gsub(/,/,"\t",a[1])
    gsub(/,/,"\t",a[4])
    print a[1] a[3] a[4]
}

$ awk -f tst.awk file
10       5       "Sally went to the store, and then , 299        ABD     F       10
10       6       If this is the case, and also this happened, then, 299  A       F       9

但我不相信你要求的是一个好方法,所以 YMMV。

无论如何,请注意第一个 gsub() 确保您在输入行上没有制表符 - 如果您想将一些逗号转换为制表符以使用制表符作为输出字段分隔符,这一点至关重要!

【讨论】:

    【解决方案3】:

    要修复 CSV,我会这样做:

    echo '10, 5, "Sally went to the store, and then , 299, ABD, F, 10' |
      perl -lne '
        @F = split /, /;             # field separator is comma and space
        @start = splice @F, 0, 2;    # first 2 fields
        @end = splice @F, -4, 4;     # last 4 fields
        $string = join ", ", @F;     # the stuff in the middle
        $string =~ s/"/""/g;         # any double quotes get doubled
        print join(",", @start, "\"$string\"", @end);
      '
    

    输出

    10,5,"""Sally went to the store, and then ",299,ABD,F,10
    

    【讨论】:

      【解决方案4】:

      另一个sed 替代方案。用制表符替换最后 3 个逗号

      $ rev file | sed 's/,/\t/;s/,/\t/;s/,/\t/' | rev
      
      10, 5, "Sally went to the store, and then , 299  ABD     F       10
      

      使用 GNU sed,您可以简单地编写

      $ sed 's/,/\t/g5' file
      
      10, 5, "Sally went to the store, and then , 299  ABD     F       10
      

      从 5 号开始全部替换。

      【讨论】:

        【解决方案5】:

        您可以使用 Perl 将缺少的双引号添加到每一行:

        perl -aF, -ne '$F[-5] .= q("); print join ",", @F' < input > output
        

        或者,将逗号变成制表符:

         perl -aF'/,\s/' -ne 'splice @F, 2, -4, join ", ", @F[ 2 .. $#F - 4 ]; print join "\t", @F' < input > output
        
        • -n 逐行读取输入。
        • -a 按照-F 指定的模式将输入拆分为@F 数组。
        • 第一个解决方案将缺少的引号添加到右侧第五个字段;第二个用“,”连接的元素替换从右边第三个到第五个的项目,并用制表符分隔结果数组。

        【讨论】:

          【解决方案6】:

          你好,我想这是在做这项工作

          echo 'a,b,c,d,e,f' | awk -F',' '{i=3; for (--i;i>=0;i--) {printf "%s\t", $(NF-i) } print ""}'
          

          返回

          d    e    f
          

          但你需要确保你有超过 3 个参数

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多