假设我有两个文件en.csv
和sp.csv
,每个文件都包含两个以逗号分隔的记录:
en.csv
:
1,dog,red,car
3,cat,white,boat
sp.csv
:
2,conejo,gris,tren
3,gato,blanco,bote
如果我执行
join -t, -a 1 -a 2 -e MISSING en.csv sp.csv
我得到的输出是:
1,dog,red,car
2,conejo,gris,tren
3,cat,white,boat,gato,blanco,bote
请注意,所有丢失的字段都已折叠。要获得“正确的”全外连接,我需要指定一种格式;从而
join -t, -a 1 -a 2 -e MISSING -o 0,1.2,1.3,1.4,2.2,2.3,2.4 en.csv sp.csv
产量
1,dog,red,car,MISSING,MISSING,MISSING
2,MISSING,MISSING,MISSING,conejo,gris,tren
3,cat,white,boat,gato,blanco,bote
这种生成完整外连接的方法的一个缺点是需要明确指定最终表的格式,这在程序化应用程序中可能并不容易(其中连接表的标识仅在运行时)。
最新版本的GNU join
通过支持特殊格式auto
消除了这一缺点。因此,使用join
这样的版本,上面的最后一个命令可以被更通用的
join -t, -a 1 -a 2 -e MISSING -o auto en.csv sp.csv
如何使用不支持join
选项的-o auto
版本实现同样的效果?
背景和详情
我有一个Unix shell(zsh)脚本,用于处理多个CSV平面文件,并通过广泛使用GNU join
的'-o auto'选项来实现。我需要修改此脚本,以便它可以在可用join
命令不支持-o auto
选项的环境中工作(如BSD join
以及旧版本的情况GNU join
)。
脚本中此选项的典型用法如下:
_reccut () {
cols="1,$1"
shift
in=$1
shift
if (( $# > 0 )); then
join -t, -a 1 -a 2 -e 'MISSING' -o auto \
<( cut -d, -f $cols $in | sort -t, -k1 ) \
<( _reccut "$@" )
else
cut -d, -f $cols $in | sort -t, -k1
fi
}
我展示这个例子来说明用显式格式替换-o auto
是很困难的,因为要包含在这种格式中的字段在运行时才会知道。
上面的函数_reccut
基本上从文件中提取列,并将结果表连接到第一列。要了解_reccut
的运作方式,想象一下,除了上面提到的文件,我们还有文件
de.csv
2,Kaninchen,Grau,Zug
1,Hund,Rot,Auto
然后,例如,要显示en.csv
的并排第3列,sp.csv
的第2列和第4列,以及de.csv的第3列,将运行:
% _reccut 3 en.csv 2,4 sp.csv 3 de.csv | cut -d, 2-
red,MISSING,MISSING,Rot
MISSING,conejo,tren,Grau
white,gato,bote,MISSING
答案 0 :(得分:2)
这是一个可能适用于您的数据的解决方案,也可能不适用于您的数据。它通过按行号对齐csv文件中的记录来解决问题,即记录2
最终在行2
上,记录3123
在行号3123
上等等。丢失的记录/行用MISSING
字段填充,因此输入文件将被修改为如下所示:
en.csv
:
1,dog,red,car
2,MISSING,MISSING,MISSING
3,cat,white,boat
de.csv
:
1,Hund,Rot,Auto
2,Kaninchen,Grau,Zug
3,MISSING,MISSING,MISSING
sp.csv
:
1,MISSING,MISSING,MISSING
2,conejo,gris,tren
3,gato,blanco,bote
从那里可以很容易地删除感兴趣的列,并使用paste
并排打印。
为实现这一目标,我们先对输入文件进行排序,然后应用一些愚蠢的awk
魔法:
join -o auto
的{{1}}字段相同)MISSING
字段,直到对齐再次正确MISSING
字段的更多行,直到达到最大值。 reccut.sh
:
#!/bin/bash
get_max_recnum()
{
awk -F, '{ if ($1 > max) { max = $1 } } END { print max }' "$@"
}
align_by_recnum()
{
sort -t, -k1 "$1" \
| awk -F, -v MAXREC="$2" '
NR==1 { for(x = 1; x < NF; x++) missing = missing ",MISSING" }
{
i = NR
if (NR < $1)
{
while (i < $1)
{
print i++ missing
}
NR+=i
}
}1
END { for(i++; i <= MAXREC; i++) { print i missing } }
'
}
_reccut()
{
local infiles=()
local args=( $@ )
for arg; do
infiles+=( "$2" )
shift 2
done
MAXREC="$(get_max_recnum "${infiles[@]}")" __reccut "${args[@]}"
}
__reccut()
{
local cols="$1"
local infile="$2"
shift 2
if (( $# > 0 )); then
paste -d, \
<(align_by_recnum "${infile}" "${MAXREC}" | cut -d, -f ${cols}) \
<(__reccut "$@")
else
align_by_recnum "${infile}" "${MAXREC}" | cut -d, -f ${cols}
fi
}
_reccut "$@"
$ ./reccut.sh 3 en.csv 2,4 sp.csv 3 de.csv
red,MISSING,MISSING,Rot
MISSING,conejo,tren,Grau
white,gato,bote,MISSING