如何使用 Linux cmd 丢弃基于两个文件的公共列的行?

How to discard row based on a common column of two files using Linux cmd?

file1.csv

col1,col2,col3
a,b,c
b,v,n
x,u,v
t,m,m

file2.csv

col1,col2,col3
p,m,n
a,z,i

col1 在两个文件中充当主键。如果 file2.csvcol1 的任何值出现在 file1.csv 中,则此行将从 file1.csv.

中丢弃

输出文件:

col1,col2,col3
b,v,n
x,u,v
t,m,m

注意:我对基于 Unix 的解决方案很感兴趣。请使用sortuniqjoin等提供解决方案

在 Unix 上有一个 join 命令几乎完全符合您的要求:

join -v1 -t, \
  <(tail +2 file1.txt | sort -k1 -t,) \
  <(tail +2 file2.txt | sort -k1 -t,)

对于您提供的示例文件,这是其输出:

b,v,n
t,m,m
x,u,v

命令分解

  • join -v1 -t,
    • -v1:显示第一个文件中无法通过连接列与第二个文件中的行配对的行(用于连接的默认列为 1,但可以通过 -1 覆盖和 -2 个选项)
    • -t,: 使用逗号作为 field/column 分隔符
  • <(tail +2 file1.txt | sort -k1 -t,)
    • <( … )join 命令需要文件名作为参数,因此我们使用 process substitution 从嵌套命令的输出创建此类临时文件
    • tail +2 file1.txt: 跳过header行
    • sort -k1 -t,: join 命令需要排序的文件
      • -t,: 使用逗号作为 field/column 分隔符
      • -k1: 按第一个字段排序

使用 R 编程语言:

> needle <- read.csv("/path/to/file2.csv", stringsAsFactors=FALSE)
> needle
  col1 col2 col3
1    p    m    n
2    a    z    i
> 
> haystack <- read.csv("/path/to/file1.csv", stringsAsFactors=FALSE)
> haystack
  col1 col2 col3
1    a    b    c
2    b    v    n
3    x    u    v
4    t    m    m
> 
> haystack[!(haystack$col1 %in% needle$col1), ]
  col1 col2 col3
2    b    v    n
3    x    u    v
4    t    m    m
> 

上面的代码应该是不言自明的。对于 R 中的二维数据,索引是用逗号分隔的下标完成的。例如,对于数据框 csv_data[i,j]i-indexing_value 表示行,而 j-indexing_value 表示列。

[R 中的索引从数字 1 开始,而不是 0。此外,根据 R 安装的年龄,将参数 stringsAsFactors=FALSE 包含到每个 read.csv() 函数调用中可能是多余的。

最后一行给出了所需的结果,您可以从字面上理解代码的意思是:"for the haystack dataframe find and return rows where haystack$col1 不包含(即不 %in%needle$col1 值"。

https://www.r-project.org/
https://cran.r-project.org/index.html

使用 Raku(以前称为 Perl_6)

    ~$ raku -e ' \
    my @needle   = "/path/to/file2.csv".IO.lines.skip(1).map: *.split(","); \
    my @haystack = "/path/to/file1.csv".IO.lines.skip(1).map: *.split(","); \
    my @x = ([Z] @needle).[0]; my @y = @haystack.map(*.[0,3...*]); \
    .join(",").put for @haystack[@y.grep({!/@x/}, :k)]; ' file

示例输出:

b,v,n
x,u,v
t,m,m

以上是使用 Raku 编程语言的解决方案,Raku 编程语言是 Perl 编程语言家族的一员。

简而言之,文件被读取 line-wise,skip-ping 第一个 (header) 行,map 用于 split,”(逗号)上的每一行。每个 csv 文件都存储为一个 @-sigiled Raku 数组。

在 object 创建的两行代码之后,每个数组都被提炼成一列值。 @needle 数组被提炼为 @x,而 @haystack object 被提炼为 @y。为了突出 Raku 的灵活性 (TIMTOWDI),显示了两种不同的提取所需列的方法:

  • @x 使用 [Z] "zip" 运算符提取每行的元素 这样每行的第一个元素(即第一列)是 提取。
  • @y 使用数字索引序列 [0,3...*] 来提取 所需的元素。 Raku 足够聪明,可以找出剩下的 序列值 [0,3,6,9].

最后,Raku 的 grep 函数用于搜索 @x@y 之间的匹配项。 :k(键)“副词”参数告诉 Raku return 匹配的数字索引位置,或者在这种情况下--non-matches,因为 grep 使用 !{…} 布尔块否定 returned 匹配值。

ADDENDUM: 正如@EdMorton 在评论中指出的那样,上面的代码不输出列名(file1.csv 的第一行)。这很容易通过添加以下行来解决(最好将其设为行 before my @needle =... .):

"/path/to/file1.csv".IO.lines[0].put;

https://raku.org

在每个 Unix 机器上的任何 shell 中使用任何 awk:

$ awk -F, '
    FNR==1 { if (NR==1) print; next }
    NR==FNR { a[]; next }
    !( in a)
' file2.csv file1.csv
col1,col2,col3
b,v,n
x,u,v
t,m,m