我有一个csv文件,格式如下:
23:56:00,5,1,7,99,100,101
23:56:30,5,1,7,98,199,191
23:57:00,6,1,6,99,99,98
23:57:30,5,2,6,97,99,199
...
以下格式的地图文件:
1:10
2:12
3:30
4:aa
5:16
6:11
7:bb
我要完成的是将第一个csv文件中第2,3和4列中的字段替换为它们在地图文件中映射到的值。
例如在上面的例子中,我想要的最终输出是:
23:56:00,16,10,bb,99,100,101
23:56:30,16,10,bb,98,199,191
23:57:00,11,10,11,99,99,98
23:57:30,16,12,11,97,99,199
最好的方法是什么?我试图找出一种使用awk / sed的方法,但我不知道如何访问awk中的多个文件,如果这是最好的方法。由于它是一个大文件,因此会有很多重复,因此我不认为每次检查映射是正确的方法。
有没有办法将地图存储到shell脚本中的哈希表中,然后使用哈希映射替换?
答案 0 :(得分:5)
尝试:
awk '
BEGIN { FS = OFS = "," }
FNR == NR {
split($0, f, /:/)
map[f[1]] = f[2]
next
}
{
for (i=2; i<=4; i++) {
if ($i in map) { $i = map[$i] }
}
}
{ print }
' mapfile csvfile
首先读取 map 文件并将数据保存在一个关联数组中,该数组与{{1}中的字段2
,3
和4
进行比较文件。结果产生:
csv
答案 1 :(得分:3)
一种纯粹的Bash可能性(Bash版本≥4):
将地图文件粘贴到关联数组中并处理csv文件:
#!/bin/bash
declare -A map=()
while IFS=: read -r k v; do
[[ -z "$k$v" ]] && continue # ignore empty lines
map[$k]=$v
done < mapfile.txt
IFS=,
while read -r -a ary; do
[[ -z "${ary[@]}" ]] && continue # ignore empty lines
ary[1]=${map[${ary[1]}]}
ary[2]=${map[${ary[2]}]}
ary[3]=${map[${ary[3]}]}
echo "${ary[*]}"
done < csvfile.txt
如果地图文件中的键是非负整数,则不需要关联数组,只需将行declare -A map=()
替换为map=()
。
由于Bash不是处理数据最快的,但它可能不是最有效的,但效果很好!
顺便说一句,没有任何错误检查,因此请确保将此脚本应用于格式良好的文件。
在您的示例中,这会产生:
23:56:00,16,10,bb,99,100,101
23:56:30,16,10,bb,98,199,191
23:57:00,11,10,11,99,99,98
23:57:30,16,12,11,97,99,199
答案 2 :(得分:3)
Perl解决方案。最近版本的bash中存在哈希,但在使用它时我更喜欢真正的编程语言。
#!/usr/bin/perl
use warnings;
use strict;
open my $MAP, '<', '1.map' or die $!;
my %map;
while (<$MAP>) {
chomp;
my ($key, $value) = split /:/;
$map{$key} = $value;
}
open my $CSV, '<', '1.csv' or die $!;
while (<$CSV>) {
my @fields = split /,/;
s/(.*)/$map{$1}/ for @fields[1, 2, 3];
print join ',' => @fields;
}
答案 3 :(得分:1)
另一个awk
awk -F",|:" 'FNR==NR {a[$1]=$2;next} {print $1":"$2":"$3,a[$4],a[$5],a[$6],$7,$8,$9}' OFS=, map csv
23:56:00,16,10,bb,99,100,101
23:56:30,16,10,bb,98,199,191
23:57:00,11,10,11,99,99,98
23:57:30,16,12,11,97,99,199