【问题标题】:How to replace nth field in a csv file with mapped value from another file?如何用另一个文件中的映射值替换 csv 文件中的第 n 个字段?
【发布时间】:2013-11-16 02:48:41
【问题描述】:

我有一个格式如下的 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 脚本内的哈希表中,然后使用哈希映射替换?

【问题讨论】:

    标签: bash csv sed awk


    【解决方案1】:

    尝试:

    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 文件并将数据保存在一个关联数组中,该数组与 csv 文件中的字段 234 进行比较。结果产生:

    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】:

    一种纯 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
    

    【讨论】:

    • 您的方法有效发现 Bur awk 在单个命令中效果很好(查看 Birei 的答案)
    • @Ashish 我知道!但这个问题会有一套全面的答案:awkBashPerl。这种方式很好。 :).
    【解决方案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;
    }
    

    【讨论】:

      【解决方案4】:

      另一个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
      

      【讨论】:

        猜你喜欢
        • 2021-08-08
        • 2015-12-26
        • 1970-01-01
        • 2012-09-06
        • 1970-01-01
        • 1970-01-01
        • 2015-07-13
        • 2019-10-27
        相关资源
        最近更新 更多