使用 awk sub 以数字方式为字符串添加前缀而不更改计数状态最多 5 个匹配“在每行具有多个匹配项的文本文件中”

Posted

技术标签:

【中文标题】使用 awk sub 以数字方式为字符串添加前缀而不更改计数状态最多 5 个匹配“在每行具有多个匹配项的文本文件中”【英文标题】:Prefixing numerically a string without change count status up to 5 matchs "In a text file with multiples matchs per line" using awk sub 【发布时间】:2021-12-15 17:49:53 【问题描述】:

假设我有以下文件,我想在5范围内的重复计数中加上一个数字前缀,并将重复的数字前缀为.dog

[.dog]
-house
.cat
.dog
foo.dogfish
[.dog]
-house
-house
.cat
foo.dogfish
.cat
.dog
[.dog]
-house

  [ -kitchen cat.dog 45_house-dog_.dogfish ]
 
    house_dogfish_cat

    'cat_.dog' -kitchen '
    :.;"house.cat()";
     food' today.cat


  [ -kitchen cat.dog ]
 
    house_dogfish_cat

    'cat_.dog' -kitchen '
    :.;"house.cat()";
     food' today.cat
 

  [ -kitchen cat.dog ]
 
    house_dogfish_cat

    'cat_.dog' -kitchen '
    :.;"house.cat()";
     food' today.cat

没有不应该更改.dog的情况,然后.dog应该更改为number.dog,即使foo.dogfish也更改为foo<number>.dogfish,所以比我的输出:

[1.dog]
-house
.cat
1.dog
foo1.dogfish
[1.dog]
-house
-house
.cat
foo1.dogfish
.cat
2.dog
[2.dog]
-house

  [ -kitchen cat2.dog 45_house-dog_2.dogfish ]
 
    house_dogfish_cat

    'cat_2.dog' -kitchen '
    :.;"house.cat()";
     food' today.cat


  [ -kitchen cat3.dog ]
 
    house_dogfish_cat

    'cat_3.dog' -kitchen '
    :.;"house.cat()";
     food' today.cat
 

  [ -kitchen cat3.dog ]
 
    house_dogfish_cat

    'cat_3.dog' -kitchen '
    :.;"house.cat()";
     food' today.cat

编辑更新 1: 尤其是当需要 [ -kitchen cat.dog 45_house-dog_.dogfish ] 时,它会更改为 [ -kitchen catnumber.dog 45_house-dog_number.dogfish ]。我认为避免执行的解决方案是使用BEGINIGNORECASE =1 /*not-match/之类的东西。

我有这个用户赛勒斯的代码:

 awk 'BEGIN count=1  /\.dog/ t=count; sub(/\..*/,"",t); sub(".dog", t "&"); count+=.2 1' file

唯一的问题是这段代码将[ -kitchen cat.dog 45_house-dog_.dogfish ] 更改为[ -kitchen cat2.dog 45_house-dog_.dogfish ] 而不是[ -kitchen cat2.dog 45_house-dog_2.dogfish ]我们可以总结出问题在于,出现.dog 的行一旦有正确的前缀,而在.dog 中出现的行不止一次,只有第一个出现的.dog 是数字前缀。

【问题讨论】:

【参考方案1】:

假设:

对于字符串 .dog 的每次出现,都为所述字符串添加一个整数 (pfx) 前缀 所述整数 (pfx) 以 @1 开头,并在每次使用 n=5 后递增 +1

一个awk想法:

awk -v n=5 '
 newline=""
  while ( x=index($0,".dog") ) 
        if (cnt++ % n == 0) pfx++                              # increment our prefix? cnt == number of times we have used pfx
        newline=newline substr($0,1,x-1) pfx substr($0,x,4)    # append pfx to this occurrence of ".dog"
        $0=substr($0,x+4)                                      # reset $0 to rest of line
  
  print newline $0                                             # print newline plus anything left in $0

' dog.dat

注意:4(在x,4x+4 中)指的是搜索字符串.dog 的长度;如果 OP 要搜索不同的字符串,则需要相应地更新 4's(例如,如果搜索 .dogs,则将两个 4's 更改为 5's

这会生成:

[1.dog]
-house
.cat
1.dog
foo1.dogfish
[1.dog]
-house
-house
.cat
foo1.dogfish
.cat
2.dog
[2.dog]
-house

  [ -kitchen cat2.dog 45_house-dog_2.dogfish ]

    house_dogfish_cat

    'cat_2.dog' -kitchen '
    :.;"house.cat()";
     food' today.cat


  [ -kitchen cat3.dog ]

    house_dogfish_cat

    'cat_3.dog' -kitchen '
    :.;"house.cat()";
     food' today.cat


  [ -kitchen cat3.dog ]

    house_dogfish_cat

    'cat_3.dog' -kitchen '
    :.;"house.cat()";
     food' today.cat

fwiw,使用 n=3 和一行输入 = ".dog .dog .dog .dog .dog .dog .dog .dog .dog .dog" 这会生成:

1.dog 1.dog 1.dog 2.dog 2.dog 2.dog 3.dog 3.dog 3.dog 4.dog

【讨论】:

fwiw, with n=3 and a single line of input = ...您对答案的评论是问题的核心,即。出现多个 .dog 的行。 @7beggars_nnnnm 是的,想了很多;如果样本输入(上一个问题)每行有多个实例,我相信您收到的答案会解决这个问题......因此需要让样本输入(尽可能接近)与现实世界的数据匹配 用户 EdMorton 警告我,没有通用的解决方案,每个案例都需要一个样本。我在您回答之前不久就注意到了这一点,但我不记得在我的帖子中对此发表过评论,但现在我编辑并发表了关于“每行多次出现”的评论。 折叠成一行意味着必须添加大量; 来告诉awk 一个命令在哪里结束,另一个命令在哪里开始,但不是在条件句和关联的单个命令之间;试试...newline=""; while (...) if(cnt%n==0) pfx++; newline=newline substr($0,1,x-1) pfx substr($0,x,4) ; $0=substr($0,x+4); cnt++ 更新了答案,解释了数字4的意义(搜索字符串的长度.dog【参考方案2】:

你可以这样做:

awk -v RS='\\.dog' -v NR=4 'ORS = int(NR/5)".dog"; print'

除了一个额外的尾随N.dog(在文件的最后)之外,这有效。

所以你可以用这个版本修复尾随的N.dog(或者更好的方法?(编辑:最后添加了更好的方法)):

awk -v RS='\\.dog' \
'
    lines[NR]=$0 int((NR+4)/5)".dog"


END 
        ORS = ""

        for(i=0; i<NR; i++) 
            print lines[i]
        

        print $0
'

说明:使用目标字符串(.dog)作为记录分隔符,统计记录数,在每条记录和记录分隔符之间打印count/5。

注意:POSIX 2018:

如果 RS 包含多个字符,则结果未指定。

但是,各种awks 确实为RS 实现了正则表达式。它记录在 mawkgawk 中。以上两个示例均在mawkgawkbusybox awk 中进行了测试。

编辑,更好的解决方案: 根据 cmets,这是一个完整的解决方案,它不会将输入文件复制到内存,也不会打印额外的 N.dog

awk -v RS='\\.dog' -v NR=4 \
'(NR != 5) print line
ORS = int(NR/5)".dog"; line=$0
END ORS = ""; print'

或更具可读性(相同):

awk -v RS='\\.dog' -v NR=4 \
'
    if (NR != 5) 
        print line
    

    ORS = int(NR/5)".dog"
    line=$0


END 
    ORS = ""
    print
'

【讨论】:

实际上第一个命令行创建了一个额外的.dog,14 次出现15 次。但是当您在第二个补充代码中打开自己时,这个额外的.dog 已被更正,您的回复效果很好! 我相信你的答案,虽然它确实有效,但比以前的 markp-fuso 用户答案需要更多的内存。做以下测试:考虑到我的问题要处理的文本是1.txt,所以复制这个文本一百万次perl -ne 'if (1..41) push @data,$_;ENDprint @data for 1..1000000;' 1.txt &gt; new_text.txt,尝试处理这个new_text.txt而不是我的问题的40行原始文本。 @7beggars_nnnnm 您可以将第一个版本通过管道传输到sed '$d'。我会更新答案。 @dan 我应该注意,虽然将第一个版本通过管道传输到 sed '$d' 会起作用,但如果输入数据的最后一行没有尾随的新行,它将被删除。我用更好的解决方案更新了答案,根本不打印最后一个ORS @7beggars_nnnnm 如果您确实有这么多数据,您实际上可能最好使用第一个版本,这是最有效的,并通过管道连接到 sed '$d'sed -E '$s/[0-9]+\.dog$//' 以删除最后N.dog。在我的笔记本电脑上,这比我最终的 awk 解决方案快 20%。

以上是关于使用 awk sub 以数字方式为字符串添加前缀而不更改计数状态最多 5 个匹配“在每行具有多个匹配项的文本文件中”的主要内容,如果未能解决你的问题,请参考以下文章

使用 sed 为文件中的所有数字加上字符串前缀

为啥十六进制数字以#而不是0x为前缀?

awk用法之:文本替换

awk数组与语法

用re.sub向数字串中添加+1。

用re.sub向数字串中添加+1。