【问题标题】:Using AWK to merge two files based on multiple columns使用AWK基于多列合并两个文件
【发布时间】:2015-11-07 14:40:17
【问题描述】:

我有两个 CSV 文件,带有 ;(分号) 作为分隔符,我 需要基于三列合并 每个文件使用 AWK。关键列 不是连续的。想法是得到 文件 B 中的两列并打印它们 在文件 A 中的所有其他列之后。

文件 A(密钥在 A1、A3 和 A5 中):

A1;A2;A3;A4;A5
K1;D1;K2;D2;K3
K4;D3;K5;D4;K6
K7;D5;K8;D6;K9
K1;D7;K2;D8;K3

文件 B(B1、B2、B4 中的键):

B1;B2;B3;B4;B5
K1;K2;D9;K3;D0
K4;K5;DA;K6;DB
KA;KB;DC;KC;DD

会产生:

A1;A2;A3;A4;A5;;
K1;D1;K2;D2;K3;D9;D0
K4;D3;K5;D4;K6;DA;DB
K7;D5;K8;D6;K9;;
K1;D7;K2;D8;K3;D9;D0

我在 SO(例如 How to merge two files based on the first three columns using awkHow to merge two files using AWK?)和其他地方找到了几个示例,但我无法将它们转换为我的需求,因为它们没有被记录得那么好以至于 AWK像我这样的 n00b 会真正理解它们的工作原理。

我得到的最接近的是:

awk -F \; -v OFS=\; 'FNR==NR{c[$1]=$3 FS $5;next}{ print $0, c[$1]}' B A

但它仍然从输出的第 1 行和第 4 行中遗漏了一个分号或一列:

A1;A2;A3;A4;A5;
K1;D1;K2;D2;K3;D9;D0
K4;D3;K5;D4;K6;DA;DB
K7;D5;K8;D6;K9;
K1;D7;K2;D8;K3;D9;D0

如何说明要用于比较的列?显然现在它只使用第一列进行比较。

【问题讨论】:

  • 您能否更具体地说明输入、所需输出、应如何比较(以及哪些)列?文件 A 中的哪些列必须匹配文件 B 中的哪些列,然后应该在输出中打印哪些列?
  • 对不起,如果不清楚,但文件 A 和 B 应该合并比较列 (A1, A3, A5) 到 (B1, B2, B4)。输出什么,所有列都应该从 A 输出,而“非键”列(B3 和 B5)从 B 输出。你是对的,那个输出示例对此有点不清楚。我尝试在其中使用粗体,但它在代码块中不起作用。
  • 文件是否排序有问题?
  • 想了想,即创建一个从 A1,A3,A5 到 B1,B2,B4 不带分隔符的虚拟比较字段,并根据这些进行连接。
  • 您的示例代码仅使用 $1 作为键,而不是其他 2 个字段。

标签: csv awk


【解决方案1】:
join -j1 -a1 -t';' <(cut -d';' -f 1,3,5 A | sed -e 's/;//g' | paste -d';' - A | sort ) <(cut -d';' -f 1,2,4 B | sed -e 's/;//g' | paste -d';' - B | sort ) | cut -d';' -f2,3,4,5,6,9,11

#the commands on new lines for readability only
#join command, print all of file A, even if unmatching
join -j1 -a1 -t';'
#input from file A
<(cut -d';' -f 1,3,5 A | sed -e 's/;//g' | paste -d';' - A | sort )
#input from file B
<(cut -d';' -f 1,2,4 B | sed -e 's/;//g' | paste -d';' - B | sort )
#selecting the columns
| cut -d';' -f2,3,4,5,6,9,11

在每种情况下:

1) 从文件AB 的所需列创建一个虚拟字段

2) 然后使用paste 将每个伪文件创建为dummy comparison field; rest of file

3) sortjoin 的可用性输出

4) 在虚拟字段的基础上使用join

5) cut 匹配的所需列 join 产生

【讨论】:

  • 它排除了所需输出中的第 1 行和第 4 行。
  • 哦,我跳过了你也想要A 的无与伦比的行。已更新,请参阅 join 命令中的 -a1
  • 请注意,它不会为空字段打印 ;; 或将单个 ; 作为最后一个字符 - 我希望这不太相关
【解决方案2】:

不确定我是否正确理解了要求,但这给出了给定输入的预期输出:

awk -F \; -v OFS=\; 'FNR==NR{c[$1]=$3 FS $5;next}{ print $0, $1 in c ? c[$1] : ";"}' B A
A1;A2;A3;A4;A5;;
K1;D1;K2;D2;K3;D9;D0
K4;D3;K5;D4;K6;DA;DB
K7;D5;K8;D6;K9;;
K1;D7;K2;D8;K3;D9;D0

从问题中的代码中,我将打印语句从

print $0, c[$1]

print $0, $1 in c ? c[$1] : ";"

【讨论】:

    【解决方案3】:

    这将在不匹配的行上打印没有额外的;。你必须先提供B文件。

     awk 'BEGIN {
              OFS=FS=";"
          } 
    
          FNR==NR {
              key[$1 FS $2 FS $4]=$3 OFS $5
          } 
    
          FNR!=NR {
              c=$1 FS $3 FS $5; 
              if(c in key) 
                   print $0,key[c]; 
              else 
                   print
          }'  fileB fileA
    

    如果您需要额外的分隔符,请将最后一个 print 更改为 print $0 OFS OFS

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-16
      • 1970-01-01
      • 2013-03-28
      • 1970-01-01
      • 1970-01-01
      • 2018-07-15
      • 2019-06-14
      • 2020-07-07
      相关资源
      最近更新 更多