我想合并多个两列制表符分隔的文件,格式如下:
a
A 5
C 4
D 2
b
A 2
B 5
C 3
c
B 4
C 4
D 2
放入以下格式的单个表中:
a b c
A 5 2 0
B 0 5 4
C 4 3 4
D 2 0 2
答案1
join
是可以使用的工具,但它的选项有点令人讨厌:
join -t $'\t' -a1 -a2 -o 0,1.2,2.2 file1 file2 |
join -t $'\t' -a1 -a2 -o 0,1.2,1.3,2.2 - file3 |
sed 's/\t\(\t\|$\)/\t0\1/g'
a b c
A 5 2 0
B 0 5 4
C 4 3 4
D 2 0 2
我首先使用了该-e
选项,但这导致了标题行出现问题。
答案2
您本质上是想创建一个二维值数组。每行的第一列对应于钥匙,取自每个输入文件中每行的第一个制表符分隔字段。以下每一列对应一个单独的输入文件。
awk 'BEGIN {
RS = "(\r\n|\n\r|\r|\n)"
FS = " *\t *"
SUBSEP = ":"
}
FNR==1 {
++file
}
NF>=2 {
if ($1 in keynum)
key = keynum[$1]
else {
key = ++keys
keynum[$1] = key
keystr[key] = $1
}
value[key,file] = $2
}
END {
files = file
for (key = 1; key <= keys; key++) {
printf "%s", keystr[key]
for (file = 1; file <= files; file++)
printf "\t%s", value[key,file]
printf "\n"
}
}' INPUT1 INPUT2 ... INPUTN
该BEGIN
规则将记录分隔符设置为任何类型的换行符,以便每一行都是一个单独的记录。它还将字段分隔符设置为制表符,包括其周围的任何空格。
在 awk 中,所有数组都是关联的,并且基本上是一维的。通过连接索引(SUBSEP
中间有一个)来支持多维数组。这里,我们使用:
作为分隔符,因为使用的索引是正整数。 (如果您愿意,您可以使用许多其他字符;例如 tab \t
。)
该FNR==1
规则在每个输入文件的第一行触发。我们增加变量file
,使其1
适用于第一个输入文件、2
第二个输入文件,依此类推。
NF>=2
对于至少具有两个字段的所有记录都会触发该规则。在本例中,这意味着每行都有一个制表符。第一个字段是钥匙,第二个字段价值。
该变量key
是一个正整数,指的是唯一的键字符串。 (1 指第一个唯一的钥匙在所有输入文件中看到,2 到第二个,依此类推。)
关联数组keynum
将键字符串映射到键数字(key
,正整数)。这keystr
是逆映射,将键数字映射到键字符串。
在NF>=2
规则中,如果第一个字段已经是已知键,则查找其编号。否则,第一个字段将作为新的唯一键字符串添加。然后,第二个字段被保存到value
数组中。
END
处理完所有输入文件后将触发该规则。该value
数组是一个逻辑上的二维数组,包含我们想要的字段。
外循环key
按照第一次看到的顺序循环遍历所有看到的唯一键。外循环的每次迭代都会产生一个输出行。
内部循环file
按照输入文件的列出顺序循环遍历每个输入文件。每次迭代都会在当前行输出中生成一个附加列。每个输出行包含的列比指定的输入文件的数量正好多一列。 (请注意,如果未指定输入文件,awk 将从标准输入读取,并且将其视为一个输入文件。)
这绝对不是实现这一目标的最简单方法,但我喜欢这个,因为它很强大(接受在 Unix、Linux、旧 Mac、新 Mac、Windows 中创建的输入文件——基本上在任何使用 ASCII 兼容字符集的地方;此外,如果某些输入文件只有所有已知键的子集,也不会感到困惑),相对容易理解、维护和适应类似的情况。
如果您想将上述内容作为脚本运行,请将以下内容另存为例如paste.awk
:
#!/usr/bin/awk -f
BEGIN {
RS = "(\r\n|\n\r|\r|\n)"
FS = " *\t *"
SUBSEP = ":"
}
FNR==1 {
++file
}
NF>=2 {
if ($1 in keynum)
key = keynum[$1]
else {
key = ++keys
keynum[$1] = key
keystr[key] = $1
}
printf "key = %s, file = %s, value = %s\n", key, file, $2 >/dev/stderr
value[key,file] = $2
}
END {
files = file
for (key = 1; key <= keys; key++) {
printf "%s", keystr[key]
for (file = 1; file <= files; file++)
printf "\t%s", value[key,file]
printf "\n"
}
}
如果你有input1
包含
a
A 5
C 4
D 2
并input2
含有
b
A 2
B 5
C 3
并input3
含有
c
B 4
C 4
D 2
但每行的第二个字符是Tab;即使用例如创建
printf ' \ta\nA\t5\nC\t4\nD\t2\n' > input1
printf ' \tb\nA\t2\nB\t5\nC\t3\n' > input2
printf ' \tc\nB\t4\nC\t4\nD\t2\n' > input3
或者,如果您将上面的文本复制并粘贴到文件中,请运行sed -e 's|^\(.\) *|\1\t|' -i input1 input2 input3
以修复它们;然后,运行
paste.awk input1 input2 input3
输出
a b c
A 5 2
C 4 3 4
D 2 2
B 5 4
只不过上面的连续空格实际上是tabs。您会看到,该网站上的软件将制表符转换为空格。
编辑添加:如果您想对缺失的条目使用某些预定义值,请将END
规则修改为
END {
files = file
for (key = 1; key <= keys; key++) {
printf "%s", keystr[key]
for (file = 1; file <= files; file++)
if ((key SUBSEP file) in value)
printf "\t%s", value[key,file]
else
printf "\t%s", blank
printf "\n"
}
}
并设置变量blank
以反映您想要的值。 (您可以使用 命令行设置它,或者修改 awk 代码并在规则中的某处或规则的开头./paste.awk -v blank=0 input1 input2 input3
设置值。)BEGIN
END
答案3
这是 GNU awk 版本。首先,我找到所有键值,因此我可以用零填充空值:
keys=$(cut -d $'\t' -f1 file{1,2,3} | sort -u | paste -sd,)
gawk -F'\t' -v keys="$keys" '
BEGIN {
n = split(keys,k,/,/)
for (i=1; i<=n; i++) values[k[i]] = k[i]
}
{v[$1] = $2}
ENDFILE {
for (key in values)
values[key] = values[key] FS (v[key] ? v[key] : 0)
delete v
}
END {
for (key in values) print values[key]
}
' file1 file2 file3 | sort -t $'\t' -k 1,1