使用 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,4
和x+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 包含多个字符,则结果未指定。
但是,各种awk
s 确实为RS
实现了正则表达式。它记录在 mawk
和 gawk
中。以上两个示例均在mawk
、gawk
和busybox 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 > 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 个匹配“在每行具有多个匹配项的文本文件中”的主要内容,如果未能解决你的问题,请参考以下文章