假设我有以下制表符分隔的文件data.tsv
:
1 a 3
2 b 2
3 a 10
2 c 2
2 a 8
现在,我想交换第 1 列中的值 - 比如说,将它们从 ID 转换为名称 - 通过使用以下命令map.tsv
:
1 foo
2 bar
3 baz
结果应该是:
foo a 3
bar b 2
baz a 10
bar c 2
bar a 8
我知道对于一可以使用一些 AWK 技巧进行映射,但我希望能够对多个列使用多个映射,并进行如下简单的调用:
$ my_map_command 1:map.tsv < data.tsv
有没有一个命令可以做类似的事情?
答案1
csvkit
csvjoin
包中的命令csvkit可用于实现类似的行为:
$ csvjoin -tH -c 1,1 data.tsv map.tsv 2> /dev/null
a,b,c,b2
1,a,3,foo
2,b,2,bar
3,a,10,baz
2,c,2,bar
2,a,8,bar
csvcut
重新排序/删除列很简单,可以从同一个包中完成。
核心工具
也可以使用该标准join(1)
,但它需要对数据进行排序(如果未排序,还需要对地图进行排序):
$ join -j1 -t ' ' <(sort -k1 data.tsv) map.tsv
1 a 3 foo
2 a 8 bar
2 b 2 bar
2 c 2 bar
3 a 10 baz
这两种情况一次只能执行一个映射,因此多个映射需要通过管道传输到进一步的调用。
答案2
将其放入名为的文件中my_map_command
:
#!/usr/bin/awk -f
FNR==NR{map[$1]=$2}
FNR!=NR{
printf "%s%s",map[$1],OFS
for (i=2; i<=NF; i++) printf "%s%s",$i,OFS
printf "\n"
}
然后运行:
chmod u+x my_map_command
像这样调用你的脚本:
./my_map_command map.tsv data.tsv
多个地图文件:
./my_map_command <(cat map1.tsv map2.tsv) data.tsv
答案3
在文件参数之间分配一个变量:
awk '!data{map[$1]=$2; next} $1 in map{$1=map[$1]} 1' map1 map2... data=1 data
- 将值读入
map
数组,直到给出标志为止 - 给出标志值(
data=1
参数)后,将数据中的字段交换为映射值
awk '
!data {
map[field,$1]=$2
maps[field]
next
}
{
for (i in maps)
if ((i,$i) in map)
$i=map[i,$i]
}
1' field=1 map1 field=2 map2 data=1 data
- 字段 1 in
data
映射到值 frommap1
- 字段 2 in
data
映射到值 frommap2
答案4
或者只是使用您可能已经知道的语言来查询表格数据:sql;
语法很简单:q "<SQL Query>"
,或者q -t "…"
如果您的文件是 tsv,或者-d …
其他分隔符。您的查询将是:
SELECT m.c2, d.c2, d.c3
FROM data.tsv AS d INNER JOIN map.tsv AS m
ON d.c1==m.c1
q - text as data
(包装好的 到处)是一个巧妙的工具,可以应用SQL任何表格、分隔数据。即使 STDIN 也可以工作,只需指定“-”作为文件名,考虑JOIN - as m
用您输入的内容替换 map.tsv 。
不错的“奖励” q
:您可以使用 SQL ORDER
,GROUP
以及进行计算!展示这一切:
q 'SELECT m.c2 AS I, d.c2, d.c3, ROUND(d.c3*0.4, 2) as b
FROM data.tsv AS d INNER JOIN - AS m
ON d.c1==m.c1
ORDER BY I' < map.tsv
bar b 2 0.8
bar c 2 0.8
⋮
注意:名称“q”完全无法被谷歌搜索,因此在搜索时添加“text-as-data”或“harelba”(作者)。