查找没有匹配数字的行作为前一行

时间:2019-06-07 02:02:36

标签: bash perl awk sed

我正在尝试查找文件中的行,这些行中的任何数字都不在其前一行中。该文件大约有400000行。这是输入文件的示例:

320 5120
240 326 5120
240 326 5120
241 333 514
240 326 5120
240 326 5120
320 5120
240
100 112
240 326 5120
240 326 5120
320 5120 

预期的输出结果是:

241 333 514
240 326 5120
240
100 112
240 326 5120

到目前为止,我可以找到以下命令:

$ awk '!seen[$1]++' file 

320 5120
240 326 5120
241 333 514
100 112

可以获取第1列的唯一编号,并且可以对其他列分别进行相同操作。我能以某种方式从此命令中获取所需的信息吗?任何帮助将不胜感激。

6 个答案:

答案 0 :(得分:3)

一个Perl命令行程序(“一个”划线),假定文件中的数字以外的其他数字

perl -wnE'
    @n = /([0-9]+)/g; 
    say "@n" if not grep { exists $seen_nums{$_} } @n; 
    %seen_nums = map { $_ => 1 } @n
' data.txt

这将打印所需的输出。它还打印第一行(正确)。由于该程序会解析数字行,因此可用于带有标题的文件,纯文本(注释?)行等。

但是,如果数据肯定只有数字,那么我们可以使用Perl的-a switch@F数组中每行的单词都可用。也要缩小一点才能真正适合一条线

perl -wlanE'grep exists $n{$_}, @F or say; %n = map { $_=>1 } @F' data.txt

开关的简要说明(请参见上面链接的文档)

  • -w打开警告

  • -l会删除换行符,并且可以重新添加它,而且还可以添加一些细微之处

  • -a启用“自动拆分”功能(与-n-p一起使用时),因此程序中包含{线。在较新的Perls上,也会设置@F

  • -n对于处理文件或-n至关重要-打开资源并设置行循环。与STDIN一起运行以查看其作用

  • -MO=Deparse -E是它评估以下引号之间的所有内容(作为Perl代码)的原因。使用大写字母(-e)也会打开feature,这是我主要用于E的时间。 (这样做有弊端,因为它启用了 all 所有功能,并使内容不再向后兼容。)


注意:可以通过在打印品中添加条件say来省略第一行

答案 1 :(得分:1)

这是一个perl单线:

$ perl -M-warnings -lane 'print unless @F ~~ %prev; %prev = map { $_ => 1 } @F;' input.txt
320 512
241 333 514
240 326 512
240
100 112
240 326 512

它以简洁的名称使用皱着眉头的smart match运算符。使用smartmatch时,如果数组的任何元素是哈希中的键,则ARRAY ~~ HASH返回true,这对于此用例而言是完美的。但是,如果这是一个独立的脚本而不是一个脚本,那么我可能会使用其他方法。

(是否有一个示例输入的第一行即使符合critera也不在预期输出中的原因?)

答案 2 :(得分:1)

这是一个执行此操作的perl解决方案。它测试是否有任何数字出现在上一行。

这包括打印可能由Shawn指出的第一行。如果没有,只需在代码中排除print join(...行即可。

#!/usr/bin/perl
use strict;
use warnings;
use List::Util 'any';

open my $fh, '<', 'f0.txt' or die $!;

my @nums = split ' ', <$fh>;

my %seen = map{ $_ => 1} @nums;

print join(' ', @nums), "\n"; # print the first line

while (<$fh>) {
    @nums = split;
    print unless any {$seen{$_}} @nums;
    %seen = map{ $_ => 1} @nums;
}

close $fh or die $!;

输出为:

320 512
241 333 514
240 326 512
240
100 112
240 326 512

答案 3 :(得分:1)

这是一个awk解决方案:

$ awk 'NR>1{p=1; for (i=1;i<=NF;i++){if($i in a)p=0}} {delete a; for (i=1;i<=NF;i++)a[$i]} p' file
241 333 514
240 326 5120
240
100 112
240 326 5120

工作原理

  • NR>1{...}

    除了第一行外,对所有命令执行大括号。这些命令是:

    • p=1

      p初始化为true(非零)

    • for (i=1;i<=NF;i++){if($i in a)p=0}

      如果任何字段是数组a中的键,则将p设置为false(零)。

  • delete a

    删除数组a

  • for (i=1;i<=NF;i++)a[$i]

    在数组a中为当前行的每个字段创建一个键。

  • p

    如果p为true,则打印该行。

多行版本

或者,对于那些喜欢将代码分布在多行上的人:

awk '
    NR>1{
        p=1
        for (i=1;i<=NF;i++){
            if($i in a)p=0}
        }
    {
        delete a
        for (i=1;i<=NF;i++)
            a[$i]
    }

    p' file

答案 4 :(得分:0)

一个简单的awk,它通过正则表达式匹配检查数字是否在前一行中。这个想法是:

  • 上一行存储在变量t
  • 如果任何字段与上一行匹配,我们可以跳到下一行。

这可以通过以下方式完成:

$ awk '{for(i=1;i<=NF;++i) if (FS t FS ~ FS $i FS) {t=$0; next}; t=$0}1'
320 512
241 333 514
240 326 512
240
100 112
240 326 512

使其起作用的诀窍是确保该行以字段分隔符开始和结束。如果我们要进行测试t ~ $i,则可以将数字25与数字255相匹配。但是,通过确保所有数字都被字段分隔符隔开,我们可以进行测试FS t FS ~ FS $i FS

注意:如果您不想打印第一行,请用1替换最后的(FNR>1)

答案 5 :(得分:0)

提供更新后的输入:

$ awk '$0 !~ p; {gsub(/ /,"|"); p="(^| )("$0")( |$)"}' file
241 333 514
240 326 5120
240
100 112
240 326 5120

上面的代码只是将读取的前一行转换为(^| )(320|5120)( |$)之类的正则表达式,然后进行正则比较以查看当前行是否匹配它,并在当前行与修改后的前一行不匹配时打印当前行。如果您的字段包含RE元字符,而您的字段显然不是全数字,那么这种方法只会导致错误匹配