考虑以下三个标题位于第一行的文件:
文件1:
id name in1
1 jon 1
2 sue 1
文件2:
id name in2
2 sue 1
3 bob 1
文件3:
id name in3
2 sue 1
3 adam 1
我想合并这些文件以获得以下输出,merged_files:
id name in1 in2 in3
1 jon 1 0 0
2 sue 1 1 1
3 bob 0 1 0
3 adam 0 0 1
这个请求有几个我没有发现在 grep/sed/awk/join 等中以方便的方式实现的特殊功能。编辑:为简单起见,您可以假设这三个文件已经排序。
最佳答案
这与 Bash script to find matching rows from multiple CSV files 中解决的问题非常相似.它不完全相同,但非常相似。 (非常相似,我只需要删除三个 sort
命令,稍微更改三个 sed
命令,更改文件名,将“缺失”值从 no
更改为 0
,并更改替换最后 sed
从逗号到空格。)
join
命令 sed
(通常也是 sort
,但数据已经充分排序)是所需的主要工具。假设:
没有出现在原始数据中。要记录文件中一行的存在,我们需要 1
文件中的字段(几乎就在那里);我们会有 join
供应0
当没有比赛时。 1
在每个非标题行的末尾需要变成 :1
, 并且标题中的最后一个字段也需要以 :
开头.然后,使用 bash
的 process substitution ,我们可以写:
$ sed 's/[ ]\([^ ]*\)$/:\1/' file1 |
> join -t: -a 1 -a 2 -e 0 -o 0,1.2,2.2 - <(sed 's/[ ]\([^ ]*\)$/:\1/' file2) |
> join -t: -a 1 -a 2 -e 0 -o 0,1.2,1.3,2.2 - <(sed 's/[ ]\([^ ]*\)$/:\1/' file3) |
> sed 's/:/ /g'
id name in1 in2 in3
1 jon 1 0 0
2 sue 1 1 1
3 adam 0 0 1
3 bob 0 1 0
$
sed
命令(三次)添加 :
在文件每一行的最后一个字段之前。连接几乎是对称的。 -t:
指定字段分隔符为冒号; -a 1
和 -a 2
意味着当文件中没有匹配项时,该行仍将包含在输出中; -e 0
意味着如果文件中没有匹配项,则 0
在输出中生成;和 -o
选项指定输出列。对于第一次加入,-o 0,1.2,2.2
输出是连接列 (0),然后是两个文件中的第二列(1
)。第二个连接在输入中有 3 列,因此它指定 -o 0,1.2,1.3,2.2
.参数-
就其本身而言意味着“读取标准输入”。 <(...)
表示法是“进程替换”,其中文件名(通常为 /dev/fd/NN
)提供给连接命令,它包含括号内命令的输出。然后通过 sed
过滤输出再次用空格替换冒号,产生所需的输出。
与所需输出的唯一区别是 3 bob
的排序在 3 adam
之后;您在所需输出中反向订购它们的依据不是特别清楚。如果它很重要,可以设计一种方法来以不同方式解决顺序(例如 sort -k1,1 -k3,5
,除了在数据之后对标签行进行排序;如有必要,有解决方法)。
关于bash:清理三个文件的外部连接,保留文件成员资格,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17507765/