【问题标题】:Using AWK to merge two files based on multiple conditions使用AWK根据多个条件合并两个文件
【发布时间】:2019-06-14 08:55:47
【问题描述】:

我知道这个问题已经被问过好几次了。这是一个例子:

Using AWK to merge two files based on multiple columns

如果发生以下匹配,我的目标是打印出 file_b 的第 2、4、5 和 7 列以及 file_a 的第 17 和 18 列: file_a.csv 的第 2、6 和 7 列分别与 file_b.csv 的第 2、4 和 5 列匹配。

但无论我如何尝试,我都无法让它适用于我的情况。这是我的两个文件:

file_a.csv

col2, col6, col7, col17, col18
a, b, c, 145, 88
e, f, g, 101, 96
x, y, z, 243, 222

file_b.csv

col2, col4, col5, col7
a, b, c, 4.5
e, f, g, 6.3
x, k, l, 12.9

输出应如下所示:

col2, col4, col5, col7, col17, col18
a, b, c, 4.5, 145, 88
e, f, g, 6.3, 101, 96

我试过了:

awk -F, -v RS='\r\n' 'NR==FNR{key[$2 FS $6 FS $7]=$17 FS $18;next} {if($2 FS $4 FS $5 in key); print $2 FS $4 FS $5 FS $7 FS key[$2 FS $6 FS $7]}' file_a.csv file_b.csv > out.csv

目前我得到的输出是:

col2, col4, col5, col7,
a, b, c, 4.5,
e, f, g, 6.3,

换句话说,file_a 中的 col17 和 col18 没有显示出来。

昨天我问了一个相关问题,我遇到了换行问题。这得到了回答和解决,但现在我认为这个问题与检查 if 条件有关。

更新: 我正在共享指向实际数据的截断副本的链接。这些文件和实际文件之间的唯一区别是真实文件有数百万行。这些只有 10 个。

file_a.csv

file_b.csv

【问题讨论】:

  • 您的 Input_file(s) 中是否需要 \r 字符?如果没有,请先使用tr -d '\r' < Input_file > temp && mv temp Input_file 摆脱它们。还打印awk '{print $17,$18}' Input_file 看看你得到的输出是期望的,让我们知道吗?
  • 感谢@RavinderSingh13。请参阅下面我对 Tiw 回答的评论。

标签: csv awk merge text-processing


【解决方案1】:

请试试这个(GNU sed):

awk 'BEGIN{RS="\r\n";FS=OFS=",";SUBSEP=FS}NR==FNR{arr[$2,$6,$7]=$17 FS $18;next} {if(arr[$2,$4,$5]) print $2,$4,$5,$7,arr[$2,$4,$5]}'

这是 BEGIN 块开始的时间。OFS 也开始了。
当我们打印多个由同一事物分隔的字段时,我们可以设置OFS,并在要打印的事物之间简单地用逗号隔开。

当您为数组中的键赋值时,无需检查key in arr
默认情况下,当arr[somekey] 之前未分配时,它是empty/"",它在awk 中计算为false(在标量上下文中为0),并且一个非空字符串计算为@ 987654331@(awk 中没有字面上的 truefalse)。
(你使用了错误的 array 名称,$2,$6,$7 是数组 arr 中的键。使用 key 作为数组名称会令人困惑。)

你可以像这样测试一些简单的概念:

awk 'BEGIN{print arr["newkey"]}'

您不需要输入文件来执行BEGIN 块。

此外,有时您可以使用引号,以避免混淆和潜在问题。

更新: 您的文件实际上以 \n 结尾,如果您不能确定行尾是什么,请使用:

awk 'BEGIN{RS="\r\n|\n|\r";FS=OFS=",";SUBSEP=FS}NR==FNR{arr[$2,$6,$7]=$17 FS $18;next} {if(arr[$2,$4,$5]) print $2,$4,$5,$7,arr[$2,$4,$5]}' file_a.csv file_b.csv

或者这个(这个会忽略空行):

awk 'BEGIN{RS="[\r\n]+";FS=OFS=",";SUBSEP=FS}NR==FNR{arr[$2,$6,$7]=$17 FS $18;next} {if(arr[$2,$4,$5]) print $2,$4,$5,$7,arr[$2,$4,$5]}' file_a.csv file_b.csv

另外,最好先转换以避免这种情况,方法是:

sed -i 's/\r//' files

或者你可以使用dos2unix命令:

dos2unix file

这是一个方便的命令行工具,只做上面的事情。
如果您的系统中还没有它,您可以安装它。
转换后,一般情况下不需要分配RS

【讨论】:

  • 不工作。现在我得到一个空文件作为输出。我确实在代码末尾添加了文件名:file_a.csv file_b.csv > out.csv
  • 我现在添加了真实数据。请看一看。
  • 现在完美运行。但我对 \r 和 \n 感到困惑。根据上面 RavinderSingh13 的评论,我尝试删除“CR”和“LF”(如 Notepad++ 所示)。我设法删除了 CR,但没有删除 LF。我是否纠正了 CR=/n 和 LF=/r? /n = 换行符吗?那么 /r 是什么?
  • CR=\r, LF=\n。不同的行尾。常规:Windows:\r\n,linux:\n,macOS:\r(我不确定这个)。今天不同的字符做同样的事情。 (在过去,回车意味着移动到行首,返回意味着移动到下一行。)@ahmed_m
  • @ahmed_m [] 表示其中的任何字符(或任何其他以 ^ 开头的字符)。 + 表示 one or more occurances。搜索Regex了解更多信息。
【解决方案2】:
$ awk 'BEGIN      {RS="\r\n"; FS=OFS=","}
       NR==FNR    {a[$2,$6,$7]=$17 OFS $18; next} 
  ($2,$4,$5) in a {print $2,$4,$5,$7,a[$2,$4,$5]}' file1 file2 > output

您的主要问题是,在数组查找中,您应该使用的索引是第二个文件键,而不是第一个文件键。 if条件后面的分号也是错误的。剩下的只是化妆品。

不确定是否要终止输出 \r\n,如果是,请同时设置 ORS=RS,否则它只是换行符。

【讨论】:

    【解决方案3】:

    既然你提到文件很大,你可以试试 Perl,如果可以的话。

    假定文件具有“\r”。

    $ cat file_a.csv
    col2, col6, col7, col17, col18
    a, b, c, 145, 88
    e, f, g, 101, 96
    x, y, z, 243, 222
    $ cat file_b.csv
    col2, col4, col5, col7
    a, b, c, 4.5
    e, f, g, 6.3
    x, k, l, 12.9
    $ perl -F, -lane 'BEGIN { %kv=map{chomp;chop;@a=split(",");"$a[0],$a[1],$a[2]"=>"$a[3]"} qx(cat file_b.csv) } if($.>1){ $x="$F[0],$F[1],$F[2]";chomp($F[-1]);print "$x,$kv{$x}",join(",",@F[-2,-1]) if $kv{$x} } ' file_a.csv
    a, b, c, 4.5 145, 88
    e, f, g, 6.3 101, 96
    $
    

    【讨论】:

      猜你喜欢
      • 2023-03-11
      • 1970-01-01
      • 2015-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-30
      • 1970-01-01
      相关资源
      最近更新 更多