【问题标题】:awk full join 2 files on single columnawk 在单列上完全连接 2 个文件
【发布时间】:2015-06-28 04:50:56
【问题描述】:

我有 2 个 CSV 文件,我想使用 AWK 将它们合并在一起。

file1.csv:

A1,B1,C1
"apple",1,2
"orange",2,3
"pear",5,4

file2.csv:

A2,D2,E2,F2
"apple",1,3,4
"peach",2,3,3
"pear",5,4,2
"mango",6,5,1

这是我想要的输出:

A1,B1,C1,A2,D2,E2,F2
"apple",1,2,"apple",1,3,4
"orange",2,3,NULL,NULL,NULL,NULL
"pear",5,4,"pear",5,4,2
NULL,NULL,NULL,"peach",2,3,3
NULL,NULL,NULL,"mango",6,5,1

我想对文件 1 和文件 2 进行完全连接,其中 A1=A2。 File2 的行数比 file1 多。对于没有匹配列值的记录,将插入 NULL 值。

【问题讨论】:

  • 是的,但我不确定如何

标签: linux bash shell unix awk


【解决方案1】:

为了简单起见,可以使用标准的join 实用程序。

注意:连接需要排序的输入,因此解决方案必须首先对输入进行排序

样本加入

tail -n +2 file1.csv | sort -k 1 1>file3.csv;
tail -n +2 file2.csv | sort -k 1 1>file4.csv;
paste -d, file1.csv file2.csv | head -n 1 1>output.txt;
join -a 1 -a 2 -t , -e NULL -1 1 -2 1 \
     -o 1.1,1.2,1.3,2.1,2.2,2.3,2.4 \
     file3.csv file4.csv 1>>output.txt;

输出

A1,B1,C1,A2,D2,E2,F2
"apple",1,2,"apple",1,3,4
NULL,NULL,NULL,"mango",6,5,1
"orange",2,3,NULL,NULL,NULL,NULL
NULL,NULL,NULL,"peach",2,3,3
"pear",5,4,"pear",5,4,2

【讨论】:

  • -a 是什么意思?
  • -a 1 标志告诉 join 保留第一次输入的所有字段(-a 2 类似)。这是除了-e NULL 之外还需要获得所需的输出
  • 为了simplicity的名字,它运行了3个命令,创建了2个临时文件,改变了原来的输出顺序。
  • @anubhava 正确 - 尽管对某些人来说这个解决方案更直观且更易于维护(如果正在使用中)。取决于 OP 之后是什么
  • 如何同时保留标题?
【解决方案2】:

你可以使用这个awk

awk -F, 'FNR==1{if (NR==1)print "A1,B1,C1,A2,D2,E2,F2";next} 
         FNR==NR{a[$1]=$0;next}
         {print $0 FS (($1 in a)? a[$1]:"NULL,NULL,NULL,NULL"); delete a[$1]}
         END{for (i in a) print "NULL,NULL,NULL," a[i]}' file2.csv file1.csv
A1,B1,C1,A2,D2,E2,F2
"apple",1,2,"apple",1,3,4
"orange",2,3,NULL,NULL,NULL,NULL
"pear",5,4,"pear",5,4,2
NULL,NULL,NULL,"mango",6,5,1
NULL,NULL,NULL,"peach",2,3,3

【讨论】:

  • 你能解释一下这部分是什么意思吗?:FNR==NR{a[$1]=$0;next} 以及为什么最后有一个删除delete a[$1]
  • FNR==NR 表示为第一个输入文件执行下一个块,即file2.csva[$1]=$0 填充数组a,键为$1(第一个字段),值为$0 或第一个文件的整行。 delete a[$1] 从数组 a 中删除已处理的键,以便可以在 END 块中打印剩余的行,即 mango 和来自 file2.csv 的 peach 记录。
  • 谢谢。运行后它给了我> 输入。有什么遗漏吗?
  • 另外,如果您由于输入文件不同而得到不同的输出,那么请提供这些有问题的文件,以便我可以调查并调整答案(如果需要)。
  • 谢谢,我的新输入文件 1 有 4 列,输入文件 2 有 8 列。我认为唯一需要改变的是在第二行打印更多的 col 名称和 4 个空值以生成 8,在第三行再打印 1 个以生成 4 个空值。
【解决方案3】:

试试这个:

awk -F',' 'BEGIN{flag=2}NR==FNR{if(flag==2){head=$0;--flag;}else{a[$1]=$0}}
NR>FNR{if(flag==1){print head","$0;flag=0}else{if(a[$1]){print a[$1],$0;delete a[$1]}
else{print "NULL,NULL,NULL,"$0}}}END{for(i in a){if(a[i]){print a[i]",NULL,NULL,NULL,NULL"}}}' 
file1.csv file2.csv

输出:

A1,B1,C1,A2,D2,E2,F2
"apple",1,2 "apple",1,3,4
NULL,NULL,NULL,"peach",2,3,3
"pear",5,4 "pear",5,4,2
NULL,NULL,NULL,"mango",6,5,1
"orange",2,3,NULL,NULL,NULL,NULL

【讨论】:

  • 这行得通!但是,当我在较大尺寸的文件上使用它时,它们在某些列上被错误地连接起来。所以我必须在每个打印语句之前加上 OFS="," 并且它得到了解决。
  • @jxn 没错。使用 OFS="," 会更好、更健壮。
【解决方案4】:

为了完整起见,bash 脚本也可以处理具有一些限制的工作。以下示例适用于该示例,但需要在每个输入文件中的同一行上匹配行。如果 csv 文件不是这种情况,则需要对文件进行简单的预排序。

这更像是一个练习而不是一个竞争者,但最终以与其他一些解决方案相同或更好的顺序满足条件。如果您有任何问题,请告诉我:

#!/bin/bash

declare -i l1=0   # lines in file 1
declare -i l2=0   # lines in file 2
declare -i a1s=0  # array 1 stride
declare -i a2s=0  # array 2 stride

while read -r line; do              ## fill array from file1
    a1+=( $(tr ',' ' ' <<<$line) )
    ((l1++))
done <"$1"

while read -r line; do              ## fill array from file2

    a2+=( $(tr ',' ' ' <<<$line) )
    ((l2++))

done <"$2"

a1s=$((${#a1[@]}/l1))   ## stride of array 1
a2s=$((${#a2[@]}/l2))   ## stride of array 2

[ $l1 -lt $l2 ] && lim=$l1 || lim=$l2   ## which has more rows?

for ((i = 0; i < lim; i++)); do         ## for common rows
    if [ $i -eq 0 -o ${a1[$((i*a1s))]} = ${a2[$((i*a2s))]} ]; then
        for ((j = 0; j < a1s; j++)); do
            [ $j -eq 0 ] && printf "%s" ${a1[$((i*a1s+j))]} || printf ",%s" ${a1[$((i*a1s+j))]}
        done
        for ((j = 0; j < a2s; j++)); do printf ",%s" ${a2[$((i*a2s+j))]}; done
        printf "\n"
    else
        for ((j = 0; j < a1s; j++)); do
            [ $j -eq 0 ] && printf "%s" ${a1[$((i*a1s+j))]} || printf ",%s" ${a1[$((i*a1s+j))]}
        done
        for ((j = 0; j < a2s; j++)); do printf ",NULL"; done
        printf "\n"
        for ((j = 0; j < a1s; j++)); do
            [ $j -eq 0 ] && printf "NULL" || printf ",NULL"
        done
        for ((j = 0; j < a2s; j++)); do printf ",%s" ${a2[$((i*a2s+j))]}; done
        printf "\n"
    fi
done

if [ $l1 -lt $l2 ]; then    ## for excess rows (longest row-wise)
    last=$l2
    for ((i = lim; i < last; i++)); do
        for ((j = 0; j < a1s; j++)); do
            [ $j -eq 0 ] && printf "NULL" || printf ",NULL"
        done
        for ((j = 0; j < a2s; j++)); do printf ",%s" ${a2[$((i*a2s+j))]}; done
        printf "\n"
    done
else
    last=$l1
    for ((i = lim; i < last; i++)); do
        for ((j = 0; j < a1s; j++)); do
            [ $j -eq 0 ] && printf "%s" ${a1[$((i*a1s+j))]} || printf ",%s" ${a1[$((i*a1s+j))]}
        done
        for ((j = 0; j < a2s; j++)); do printf ",NULL"; done
        printf "\n"
    done
fi

exit 0

输出

$ bash ../read2redir.sh A1.txt A2.txt
A1,B1,C1,A2,D2,E2,F2
"apple",1,2,"apple",1,3,4
"orange",2,3,NULL,NULL,NULL,NULL
NULL,NULL,NULL,"peach",2,3,3
"pear",5,4,"pear",5,4,2
NULL,NULL,NULL,"mango",6,5,1

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-29
    • 2014-03-01
    • 2014-05-29
    • 2019-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多