比较文件中的两行,如果模式匹配则删除这些行

比较文件中的两行,如果模式匹配则删除这些行

我有一个这样的文件。

12345 X678GHR 0 ADD
23445 HGT6787 1 ADD
12345 X678GHR 0 REM
67894 OIY5678 0 ADD
12345 OIY5678 0 ADD
12345 X678GHR 1 ADD

我必须比较文件中的行以删除稍后添加和删除的行。所以输出应该是这样的:

23445 HGT6787 1 ADD
67894 OIY5678 0 ADD
12345 OIY5678 0 ADD
12345 X678GHR 1 ADD

清除了文件中后来添加和删除的记录。

更新:我还必须确保删除记录之间的第 2 列和第 3 列也匹配。在我的原始文件中,分隔符不是空格。这是一个闭括号“)”

请帮忙。我对 UNIX 很陌生

答案1

如果不需要保证条目的顺序,那么给出

$ cat file
12345)X678GHR)0)ADD
23445)HGT6787)1)ADD
12345)X678GHR)0)REM
67894)OIY5678)0)ADD
12345)OIY5678)0)ADD
12345)X678GHR)1)ADD

以下 awk

$ awk -F ')' '
    $NF == "ADD" {lines[$1 FS $2 FS $3] = $0} 
    $NF == "REM" {delete lines[$1 FS $2 FS $3]} 
    END {for(i in lines) print lines[i]}
' file
12345)X678GHR)1)ADD
67894)OIY5678)0)ADD
23445)HGT6787)1)ADD
12345)OIY5678)0)ADD

如果您确实需要保留顺序,则可以通过对文件进行两次传递来实现:

$ awk -F ')' '
    NR == FNR {if($NF == "REM") rem[$1 FS $2 FS $3]; next} 
    !($1 FS $2 FS $3 in rem)
' file file
23445)HGT6787)1)ADD
67894)OIY5678)0)ADD
12345)OIY5678)0)ADD
12345)X678GHR)1)ADD

答案2

如果保留输入行的顺序很重要,那么单个关联数组是不够的(因为关联数组在大多数语言中本质上是无序的,包括 awk 和 perl),因此您需要两个数组。

  1. 具有数字索引的数组,其中包含输入行的文本
  2. 一个关联数组,包含与哈希匹配的第一个数组的行号。

这在 Perl 中比在 awk 中容易得多,所以我将使用它。我将用于@lines第一个数组和%keys第二个数组。

@F是使用该选项时自动创建的包含自动分割字段的数组的名称-F- 类似于 awk 的 $1、$2、$3 等,不同之处在于它是 $F[0]、$F[1]、$F[2 ] 等,$F[-1]是 @F 的最后一个元素,大致相当于 awk 的$NF.请注意,perl 数组从 0 开始,而不是从 1 开始。

perl -F'\)' -l -e '
  $key = join(")",@F[0..$#F-1]);
  if ($F[-1] eq "ADD") {
    $lines[$.] = $_;      # $. is the line number of the current file
    $keys{$key} = $.;
  } elsif ($F[-1] eq "REM") {
    delete($lines[$keys{$key}]);
    delete($keys{$key});
  }

  if (eof) {
    foreach $l (@lines) { print $l if $l; };
    @lines = ();
    %keys = ();
  };' inputfile

输出:

23445)HGT6787)1)ADD 
67894)OIY5678)0)ADD
12345)OIY5678)0)ADD
12345)X678GHR)1)ADD

请注意,此 Perl 版本适用于输入中的任意数量的字段,未硬编码使用字段 1-3 作为数据,使用字段 4 作为“ADD”或“REM”指令。相反,它使用除数据的最后一个字段和指令的最后一个字段之外的所有字段。这也可以使用 awk 来完成,但是您需要编写一个join()函数或至少一个简单的循环来连接除最后一个字段之外的所有字段。

该 perl 版本还能够通过检测每个文件的末尾(使用该eof()函数)来处理多个输入文件,并在打印尚未删除的行后清除两个数组。即它重置每个输入文件末尾的所有内容。我本来可以END {}像 @steeldriver 的 awk 答案一样使用一个块,但这似乎很合适,因为你永远不知道什么时候你会想要以稍微不同的方式重新使用脚本......而且这总是一个好主意问问自己“如果呢?”以及“这怎么会失败呢?”输入问题。

答案3

给定

$ cat input
12345)X678GHR)0)ADD
23445)HGT6787)1)ADD
12345)X678GHR)0)REM
67894)OIY5678)0)ADD
12345)OIY5678)0)ADD
12345)X678GHR)1)ADD

... 然后

$ tac input | perl -n -l -e \
        's/REM$// ? $hit{"${_}ADD"}=1 : print unless delete $hit{$_}' \
    | tac

... 产生

23445)HGT6787)1)ADD
67894)OIY5678)0)ADD
12345)OIY5678)0)ADD
12345)X678GHR)1)ADD

解释:

  1. tac命令将文件从最后一行重新排序到第一行;这使得 REM 行出现在匹配的 ADD 行之前。另一个tac用于最后恢复原始顺序。
  2. perl 命令开关可以组合为-nle,但为了清楚起见,将它们分开:
    • -n= 不自动打印每一行
    • -l= 从每个输入行末尾去除换行符,并将其添加回任何print命令
    • -e= 指定(“输入”)Perl 脚本的一行
  3. Perl 代码做了很多事情:
    • 运算?:符是 if/then/else:condition?expr1:expr2- 如果条件为 true,则运行 expr1;否则运行 expr2
    • 如果我们可以成功地从行尾删除“REM”(因此最终得到类似“12345)X678GHR)0)”的内容,那么我们将在其中附加一个“ADD”并将其粘贴到“命中列表”中”
    • 否则(意味着我们有一个 ADD),我们将打印该记录,除非它位于“命中列表”上;无论它是否存在,我们都会从“命中列表”中删除该记录

“命中列表”是一个通俗术语,指的是要杀死/消除的事物。在此 Perl 代码中,它不是作为列表实现,而是作为(无序)哈希(又名关联数组)的键实现,以便我们可以进行快速查找。与每个键相关的值并不重要;这里我们使用数字 1。(我们可以使用除 0 或 undef 之外的任何值。)

假设:

  1. 您正在比较整行(所有列)除了最后的 ADD/REM。
  2. 不打印 REM 行是可以接受的(即使没有相应的 ADD 行)。
  3. 不会发生嵌套取消。例如,ADD/ADD/REM/REM(其中第 3 行取消第 2 行且第 4 行取消第 1 行)不会发生。

相关内容