【问题标题】:Replacing all occurrence after nth occurrence in a line in perl替换perl中一行中第n次出现后的所有出现
【发布时间】:2021-08-31 02:42:54
【问题描述】:

我需要在 Unix 文件的每一行中替换第 n 次后出现的所有字符串。

我的文件数据:

:account_id:12345:6789:Melbourne:Aus
:account_id:98765:43210:Adelaide:Aus

我的输出数据:

:account_id:123456789MelbourneAus
:account_id:9876543210AdelaideAus

尝试使用 sed:sed 's/://3g' test.txt

很遗憾,出现该事件的 g 选项未按预期工作。相反,它正在替换所有出现。

【问题讨论】:

  • 专门寻找 perl,因为我有一系列 perl 命令要合并。谢谢
  • 你能澄清一下你的记录结构吗?根据您给我们的信息,我想我可能不会在这项工作中使用正则表达式,而是使用split
  • 我不明白。您给出的sed 命令与您的问题以及到目前为止给出的答案完全相同。或者可能是您的措辞不对,您是否尝试使用 sed 命令与 GNU sed 或其他版本?
  • @nag 是的,我认为你没有 GNU sed,最好在你的问题中提及 AIX
  • 如果您想要主题中所说的 perl 解决方案,为什么要使用 awk 和 sed 标记问题?

标签: perl awk sed


【解决方案1】:

使用awk的另一种方法

awk -v c=':' -v n=2 'BEGIN{
                       FS=OFS=""
                     }
                     {
                       j=0;
                       for(i=0; ++i<=NF;)
                         if($i==c && j++>=n)$i=""
                     }1' file 
$ cat file 
:account_id:12345:6789:Melbourne:Aus
:account_id:98765:43210:Adelaide:Aus

$ awk -v c=':' -v n=2 'BEGIN{FS=OFS=""}{j=0;for(i=0; ++i<=NF;)if($i==c && j++>=n)$i=""}1' file 
:account_id:123456789MelbourneAus
:account_id:9876543210AdelaideAus

【讨论】:

    【解决方案2】:

    使用 GNU awk,使用 gensub 请尝试关注。这完全基于您显示的示例,其中 OP 希望从第 3 次开始删除 :。根据 OP 的要求,使用gensub 分隔部分匹配值并删除其中第二部分(从第三个冒号开始)的所有冒号。

    awk -v regex="^([^:]*:)([^:]*:)(.*)" '
    {
      firstPart=restPart=""
      firstPart=gensub(regex, "\\1 \\2", "1", $0)
      restPart=gensub(regex,"\\3","1",$0)
      gsub(/:/,"",restPart)
      print firstPart restPart
    }
    ' Input_file
    

    【讨论】:

      【解决方案3】:

      我根据您提供给我们的有限数据进行了推断,因此这可能行不通。但我不会使用正则表达式来完成这项工作。您所拥有的是冒号分隔的字段。

      所以我会使用split 来提取数据,然后使用某种形式的字符串格式化来重新组装你喜欢的东西:

      #!/usr/bin/perl
      
      use strict;
      use warnings;
      
      while (<DATA>) {
        chomp;
        my ( undef, $first, @rest ) = split /:/; 
        print ":$first:", join ( "", @rest ),"\n";
      }
      
      __DATA__
      :account_id:12345:6789:Melbourne:Aus
      :account_id:98765:43210:Adelaide:Aus
      

      这会给你想要的结果,而对于下一个读者来说,IMO 比复杂的正则表达式要清晰得多。

      【讨论】:

        【解决方案4】:

        你可以使用perl这样的解决方案

        perl -pe 's~^(?:[^:]*:){2}(*SKIP)(?!)|:~~g if /^:account_id:/' test.txt
        

        请参阅online demoregex demo

        ^(?:[^:]*:){2}(*SKIP)(?!)|: 正则表达式的意思是:

        • ^(?:[^:]*:){2}(*SKIP)(?!) - 匹配
          • ^ - 字符串开头(这里是一行)
          • (?:[^:]*:){2} - 除了 :: 字符之外的任何零个或多个字符出现两次
          • (*SKIP)(?!) - 跳过匹配并继续从失败位置搜索下一个匹配
        • | - 或
        • : - 匹配 : 字符。

        并且仅当当前行以:account_id: 开头时才运行替换(请参阅if /^:account_id:/')。

        或类似awk 的解决方案

        awk 'BEGIN{OFS=FS=":"} /^:account_id:/ {result="";for (i=1; i<=NF; ++i) { result = result (i > 2 ? $i : $i OFS)}; print result}' test.txt
        

        this online demo。详情:

        • BEGIN{OFS=FS=":"} - 将输入/输出字段分隔符设置为 :
        • /^:account_id:/ - 行必须以 :account_id: 开头
        • result="" - 将 result 变量设置为空字符串
        • for (i=1; i&lt;=NF; ++i) { result = result (i &gt; 2 ? $i : $i OFS)}; print result} - 遍历字段,如果字段编号大于2,只需将当前字段值附加到result,否则,附加值+输出字段分隔符;然后打印result

        【讨论】:

        • 我想这样做是行仅以“:account_id”开头。也有以“:customer_id”开头的行。
        • @nag 我更新了perlawk 解决方案。
        【解决方案5】:

        如果 n 固定并等于 2 以下方式,我将使用 GNU AWK 以下方式,让 file.txt 内容为

        :account_id:12345:6789:Melbourne:Aus
        :account_id:98765:43210:Adelaide:Aus
        

        然后

        awk 'BEGIN{FS=":";OFS=""}{$2=FS $2 FS;print}' file.txt
        

        输出

        :account_id:123456789MelbourneAus
        :account_id:9876543210AdelaideAus
        

        说明:使用: 作为字段分隔符,不使用任何输出字段分隔符,这本身会删除所有:,所以我添加了必须保留的::第一个(第二列之前)和第二个(第二个之后柱子)。请注意,我仅针对这些数据进行了测试,因此如果您想使用它,您应该首先使用更多可能的输入进行测试。

        (在 gawk 4.2.1 中测试)

        【讨论】:

          【解决方案6】:

          这可能对你有用(GNU sed):

          sed 's/:/\n/3;h;s/://g;H;g;s/\n.*\n//' file
          

          用换行符替换第三次出现的:

          复制该行。

          删除所有出现的:

          将修改后的行附加到副本中。

          通过删除从副本的第三次出现到修改的行的第三次出现的所有内容来连接这两行。

          注意在 sed 的情况下,使用换行符是最好的分隔符,因为提供给 seds 命令的行最初没有换行符。然而,分隔符的重要属性是它是唯一的,因此可以是任何这样的字符,只要它在数据集中的任何地方都找不到。

          另一种解决方案使用循环删除前两个之后的所有:

          sed -E ':a;s/^(([^:]*:){2}[^:]*):/\1/;ta' file
          

          【讨论】:

            【解决方案7】:

            使用 GNU awk 将第三个 arg 用于 match() 和 gensub():

            $ awk 'match($0,/(:[^:]+:)(.*)/,a){ $0=a[1] gensub(/:/,"","g",a[2]) } 1' file
            :account_id:123456789MelbourneAus
            :account_id:9876543210AdelaideAus
            

            并且在每个 Unix 机器上的任何 shell 中使用任何 awk:

            $ awk 'match($0,/:[^:]+:/){ tgt=substr($0,1+RLENGTH); gsub(/:/,"",tgt); $0=substr($0,1,RLENGTH) tgt } 1' file
            :account_id:123456789MelbourneAus
            :account_id:9876543210AdelaideAus
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2014-03-31
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-09-17
              • 2018-09-08
              • 2011-03-05
              相关资源
              最近更新 更多