考虑以下三个文件,第一行包含标题:
文件1:
id name in1
1 jon 1
2 sue 1
file2的:
id name in2
2 sue 1
3 bob 1
file3的:
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等中以方便的方式实现。编辑:为简单起见,您可以假设这三个文件已经被排序。
答案 0 :(得分:3)
这与Bash script to find matching rows from multiple CSV files中解决的问题非常相似。它不完全相同,但非常相似。 (如此相似,我只需删除三个sort
命令,稍微更改三个sed
命令,更改文件名,将“缺失”值从no
更改为{{1} },并将最终0
中的替换从逗号更改为空格。)
sed
命令join
(通常sed
,但数据已经充分排序)是所需的主要工具。假设sort
未出现在原始数据中。要记录文件中是否存在行,我们需要文件中的:
字段(它几乎就在那里);如果没有匹配,我们会1
提供join
。每个非标题行末尾的0
需要变为1
,标题中的最后一个字段也需要以:1
开头。然后,使用:
的{{3}},我们可以写:
bash
$ 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
输出是连接列(0),然后是两个文件中的第二列(-o 0,1.2,2.2
)。第二个连接在输入中有3列,因此它指定1
。参数-o 0,1.2,1.3,2.2
本身就意味着“读取标准输入”。 -
表示法是“进程替换”,其中为join命令提供了文件名(通常为<(...)
),它包含括号内的命令输出。然后输出再次通过/dev/fd/NN
过滤,用空格替换冒号,产生所需的输出。
与期望输出的唯一区别在于sed
之后3 bob
的排序;在您所需的输出中反向命令它们的基础并不是特别清楚。如果它至关重要,可以设计一种方法来不同地解析订单(例如3 adam
,除了在数据之后对标签行进行排序;如果需要,还有解决方法)。
答案 1 :(得分:3)
GNU代码awk:
{
if ($1=="id") { v[i++]=$3; next }
b[$1,$2]=$1" "$2
c[i-1,$1" "$2]=$3
}
END {
printf ("id name")
for (x in v) printf (" %s", v[x]); printf ("\n")
for (y in b) {
printf ("%s", b[y])
for (z in v) if (c[z,b[y]]==0) {printf (" 0")} else printf (" %s", c[z,b[y]])
printf ("\n")
}
}
$cat file? id name in1 1 jon 1 2 sue 1 id name in2 2 sue 1 3 bob 1 id name in3 2 sue 1 3 adam 1 $awk -f prog.awk file? id name in1 in2 in3 3 bob 0 1 0 3 adam 0 0 1 1 jon 1 0 0 2 sue 1 1 1
答案 2 :(得分:2)
此awk
脚本可以执行您想要的操作:
$1=="id"&&$2=="name"{
ins[$3]= 1;
lastin = $3;
}
$1!="id"||$2!="name" {
ids[$1] = 1;
names[$2] = 1;
a[$1,$2,lastin]= $3
used[$1,$2] = 1;
}
END {
printf "id name"
for (i in ins) {
printf " %s", i
}
printf "\n"
for (id in ids) {
for (name in names) {
if (used[id,name]) {
printf "%s %s", id, name
for (i in ins) {
printf " %d", a[id,name,i]
}
printf "\n"
}
}
}
}
假设您的文件名为list1
,list2
等,并且awk文件为script.awk
,您可以像这样运行
$ cat list* | awk -f script.awk
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
我确信这是一个更简单,更简单的方法,但这是我在凌晨1:30可以提出的所有内容:)
答案 3 :(得分:0)
https://code.google.com/p/ea-utils/source/browse/trunk/clipper/xjoin
TODO:更好地处理标题,处理流式输入。
Usage: xjoin [options] [:]<operator> <f1> <f2> [...*]
Joins file 1 and file 2 by the first column, suitable
for arbitratily large files (disk-based sort).
Operator is one of:
# Pasted ops, combines rows:
in[ner] return rows in common
le[ft] return rows in common, left joined
ri[ght] return rows in common, right joined
ou[ter] return all rows, outer joined
# Exclusive (not pasted) ops, only return rows from 1 file:
ex[clude] return only those rows with nothing in common (see -f)
xl[eft] return left file rows that are not in right file
xr[ight] return right file rows that are not in left file
Common options:
-1,-2=N per file, column number to join on (def 1)
-k=N set the key column to N (for both files)
-d STR column delimiter (def tab)
-q STR quote char (def none)
-h [N] files have headers (optionally, N is the file number)
-u [N] files may contain duplicate entries, only output first match
-s [N] files are already sorted, don't sort first
-n numeric sort key columns
-p prefix headers with filename/
-f prefix rows with the input file name (op:ex only)