仅当重复行匹配模式时删除重复行

5

这个问题有一个很好的答案,它说你可以使用awk '!seen[$0]++' file.txt从文件中删除非连续重复行。如果它们包含字符串"#####",我怎样才能只删除文件中匹配模式的非连续重复行?

示例输入

deleteme.txt ##########
1219:                            'PCM BE PTP'
deleteme.txt ##########
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
deleteme2.txt ##########
1222:                          , 'PCM BE PTP UT'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
deleteme2.txt ##########
1223:                          , 'PCM BE PTP'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
deleteme2.txt ##########
1225:                          , 'PCM FE/MID PTP'

期望的输出结果

deleteme.txt ##########
1219:                            'PCM BE PTP'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
deleteme2.txt ##########
1222:                          , 'PCM BE PTP UT'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
1223:                          , 'PCM BE PTP'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
1225:                          , 'PCM FE/MID PTP'

请在您的问题中添加示例输入和期望输出。该输入应为样本数据,以便更好地理解您的问题。 - Cyrus
你尝试了什么? - ctac_
1
确保您发布的每个问题都可以独立理解,并且您在问题中发布的代码为此特定问题提供了一个 [mcve],提供链接到其他答案中的某些代码可能比您的问题更多,这不是尝试获得帮助的最佳方式。 - Ed Morton
如果你使用grep和sed获取文件,然后在末尾添加awk并不是最好的方法。所有操作都可以用awk完成。 - ctac_
4个回答

8
您可以使用:
awk '!/#####/ || !seen[$0]++'

或者,如Ed Morton建议的那样,可以使用同义词。
awk '!(/#####/ && seen[$0]++)'

在这里,!seen[$0]++ 做的事情和通常一样,它将删除任何重复行。 !/#####/ 部分匹配包含 ##### 模式的行并否定匹配。 两个模式与 || 结合使用将删除所有具有 ##### 模式的重复行。请参见在线 awk 演示: https://ideone.com/3es03t
s="deleteme.txt ##########
1219:                            'PCM BE PTP'
deleteme.txt ##########
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
deleteme2.txt ##########
1222:                          , 'PCM BE PTP UT'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
deleteme2.txt ##########
1223  #####:                          , 'PCM BE PTP'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
deleteme2.txt ##########
1225:                          , 'PCM FE/MID PTP'"
awk '!/#####/ || !seen[$0]++' <<< "$s"

输出:

deleteme.txt ##########
1219:                            'PCM BE PTP'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
deleteme2.txt ##########
1222:                          , 'PCM BE PTP UT'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
1223  #####:                          , 'PCM BE PTP'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
1225:                          , 'PCM FE/MID PTP'

我投票支持这个答案,因为它完全符合要求,并且比我的回答更简单。 - Jamin Kortegard

2
尝试使用文件读取模式的Perl命令行正则表达式解决方案。将原始答案翻译为“最初的回答”。
perl -0777 -ne ' $z=$y=$_; 
                 while( $y ne $x) 
                 { $z=~s/(^[^\n]+?\s+##########.*?$)(.+?)\K(\1\n)//gmse ; $x=$y ;$y=$z } ; 
                 print "$z" '

最初的回答
根据给定的输入进行操作。
$ cat toucan.txt
deleteme.txt ##########
1219:                            'PCM BE PTP'
deleteme.txt ##########
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
deleteme2.txt ##########
1222:                          , 'PCM BE PTP UT'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
deleteme2.txt ##########
1223:                          , 'PCM BE PTP'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
deleteme2.txt ##########
1225:                          , 'PCM FE/MID PTP'

$ perl -0777 -ne ' $z=$y=$_; while( $y ne $x) { $z=~s/(^[^\n]+?\s+##########.*?$)(.+?)\K(\1\n)//gmse ; $x=$y ;$y=$z } ; print "$z" ' toucan.txt
deleteme.txt ##########
1219:                            'PCM BE PTP'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
deleteme2.txt ##########
1222:                          , 'PCM BE PTP UT'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
1223:                          , 'PCM BE PTP'
1221:                          , 'PCM FE/MID PTP UT','PCM IA 1 PTP'
1225:                          , 'PCM FE/MID PTP'

$

这看起来不错,但出于简单起见,我更喜欢使用awk。 - anubhava
1
谢谢@anubhava..是的,awk解决方案简单明了..我只是试图看看单个正则表达式s///是否可以解决它.?. - stack0114106

0
这可能适用于您(GNU sed):
sed '/#$/{G;/^\(\S*\s\).*\1/!P;h;d}' file

除了感兴趣的行之外,所有其他行都会正常打印。

将以前有兴趣的行附加到当前行,并使用模式匹配,如果以前没有遇到过这样的行,则打印它。然后将模式空间存储回保持空间,准备进行下一次匹配并删除模式空间。


0
每当我想到匹配模式和选择性打印时,我就会想到实用提取和报告语言 Perl!这是一个 Perl 单行程序,可以完成你正在询问的功能。你应该能够将其复制粘贴到 shell 中并使其正常工作:
perl -wnle 'BEGIN { $rows_with_five_hashes = {}; } $thisrow = $_; if ($thisrow =~ /[#]{5}/) { if (!exists $rows_with_five_hashes->{$thisrow}) { print; } $rows_with_five_hashes->{$thisrow}++; } else { print; }' input.txt

以下是格式清晰且带有注释的 Perl 代码(注意:此代码不能直接运行):

BEGIN {
  # create a counter for rows that match the pattern
  $rows_with_five_hashes = {}; 
} 
# capture the row from the input file
$thisrow = $_;
if ($thisrow =~ /[#]{5}/) { 
  if (!exists $rows_with_five_hashes->{$thisrow}) { 
    # this row matches the pattern and we haven't seen it before
    print; 
  } 
  # Increment the counter for rows that match the pattern.
  # Do this AFTER we print, or else our "exists" print logic fails.
  $rows_with_five_hashes->{$thisrow}++;
} 
else { 
  # print all rows that don't match the pattern
  print;
}

Ruby具有类似的“一行代码”功能,可以直接在命令行上运行代码(其中大部分是从Perl借鉴来的)。

有关wnle命令行开关的更多信息,请查看Perl文档。如果您有许多文件需要进行就地修改,并使用单个Perl命令保留原始备份副本,请查看这些文档中的-i开关。

如果您经常运行此操作并希望保留一个方便的可执行脚本,则可以轻松地将其适应于几乎任何具有Perl解释器的系统。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接