【问题标题】:parsing a file with a column of key/value pairs用一列键/值对解析文件
【发布时间】:2016-01-16 14:30:47
【问题描述】:

我正在尝试解析一个制表符分隔的文件,其中最后一列具有可变数量的键值对,以分号分隔。这是一个例子

ab cd ef as=2;sd=5;df=12.3
gh ij kl sd=23;df=55
mn op qr as=24;df=77

我想打印第二列以及与键“sd”关联的值 预期的输出应该是

cd 5
ij 23

我可以在 bash 中执行此操作吗?

这里的问题是键值列有可变的条目数,因此目标键在不同的行中会有不同的位置。

我可以像这样 grep 给定键的值

grep -o 'sd=[^;]*' file.txt 

但我不能同时打印其他列的值

【问题讨论】:

    标签: bash parsing awk key-value


    【解决方案1】:

    当您的数据中有名称/值对时,最好从该数据创建一个名称/值数组,以便您可以按名称引用值:

    $ cat tst.awk
    {
        delete n2v
        split($NF,tmp,/[;=]/)
        for (i=1;i in tmp;i+=2) {
            n2v[tmp[i]] = tmp[i+1]
        }
    }
    "sd" in n2v { print $2, n2v["sd"] }
    
    $ awk -f tst.awk file
    cd 5
    ij 23
    

    【讨论】:

    • /sd/ 放在顶部是否会更好,这样循环和拆分只有在存在“sd”时才会发生?
    • 这将为读取的每一行添加一个操作,因此虽然它会使没有sd 的行的处理速度更快一些,但它可能会使整体执行时间变慢,具体取决于多少行行确实包含sd。如果/当您想添加要打印的其他子字段时,它还会使代码在将来更难增强和维护。因此,与大多数提高性能的尝试一样,如果/当您发现自己有性能问题时,最好留待考虑。
    【解决方案2】:

    awk 来救援!

    $ awk -v k="sd=" '{n=split($NF,a,";");
                       for(i=1;i<=n;i++) 
                           if(a[i]~k) 
                              {sub(k,$2" ",a[i]);
                               print a[i]}}' file    
    
    cd 5
    ij 23
    

    如果您的密钥不是固定长度的,则将其锚定在左侧是一个更好的主意。 将a[i]~k 更改为a[i]~"^"k

    【讨论】:

    • 完美。谢谢
    • 警告:最初的要求似乎是密钥等于“sd”;如果这是要求,那么使用a[i]~k 可能会自找麻烦。
    • 我没有看到锚定(ifsub 都缺少它),如果 2 美元曾经包含 &amp;\&lt;digit&gt; 你会在你的输出。我不建议这样做,但 YMMV 取决于您的输入数据。
    【解决方案3】:

    我知道你要求 awk,但这里是强制性的 sed one liner,它比 awk 示例短一点。在峰值提示之后,我在行的不同部分添加了更多带有sd 的测试用例。

    cat kv.txt
    ab cd ef as=2;sd=5;df=12.3
    gh ij kl sd=23;df=55
    
    test1 sd in col2=true;df=55
    test2 sd_inFront spacer sd=2;other=5;
    test3 sd_inMiddle spacer other1=6;sd=3;other2=8
    test4 sd_atEnd spacer other1=7;sd=4;
    test5 sd_AtEndWO; spacer other1=8;sd=5
    
    test6 esd in col4=true;esd=6;
    test7 esd_inFront spacer esd=7;other=5;
    test8 esd_inMiddle spacer other1=6;esd=8;other2=8
    test9 esd_atEnd spacer other1=7;esd=9;
    test10 esd_AtEndWO; spacer other1=8;esd=10
    
    test11 sd_and_esd spacer other1=6;sd=11;other2;esd=4;other3=8
    test12 esd_and_sd spacer other1=6;esd=3;other2;sd=12;other3=8 
    
    cat kv.txt| sed -nr "/(.+\w){3} (.*;)?sd=/ {s/.* (.*) .* (.*;)?sd=([^;]+).*/\1 \3/g; p;}"
    cd 5
    ij 23
    sd_inFront 2
    sd_atEnd 4
    sd_AtEndWO; 5
    sd_and_esd 11
    esd_and_sd 12
    

    sed 命令由两部分组成:第一部分/(.+\w){3} (.*;)?sd=/ 匹配第四列中带有sd= 的行(作为第一个键或.*; 之后),并将大括号内的以下部分应用于该行。

    大括号内的第二部分由替换 (s) 和打印结果命令 (p) 组成。替换是这样的:

    • 四个.* 是您的列,第二列用括号捕获
    • (.*;)?sd=([^;]+) 捕获 sd= 之后直到 ; 的值
    • 替换使用捕获的\1(第二列)和\3sd= 之后的值)来创建您想要的输出

    【讨论】:

    • 警告:最初的要求似乎是密钥等于“sd”;如果这是要求,那么这个提议的解决方案就不可靠。
    【解决方案4】:

    这里是避免分裂和循环的 gawk/awk 解决方案。

    $ cat pf.txt
    ab cd ef as=2;sd=5;df=12.3
    gh ij kl sd=23;df=55
    aa bb cc as=24;df=77;sd=15
    mn op qr as=24;df=77
    

    使用 gawk,您可以使用 gensub 捕获组将所需值与 $4 隔离开来:

    $ gawk '/sd=/{print $2, gensub(/.*sd=([^;]*).*/,"\\1","g",$4)}' pf.txt
    cd 5
    ij 23
    bb 15
    

    或者,对于非 gawk awk,您使用两个 sub 调用来删除所需值之前和之后的部分:

    $ awk '/sd=/{ sub(/.*sd=/, "", $4); sub(/;.*/, "", $4); print $2, $4 }' pf.txt
    cd 5
    ij 23
    bb 15
    

    【讨论】:

    • 如果他的数据包含...sd=3;bsd=7... 之类的东西,那将会失败。您可以将 sd 的前面固定为,例如,.*[[:space:];]sd=(或在 gawk 中使用 \&lt;)来解决这个问题。
    【解决方案5】:

    给定:

    $ cat /tmp/file.txt
    ab  cd  ef  as=2;sd=5;df=12.3
    gh  ij  kl  sd=23;df=55
    mn  op  qr  as=24;df=77
    mn  sd  qr  as=24;df=77
    

    (这些是制表符,不是空格)

    您可以将awk 设置为选项卡或; 上的分隔字段,如下所示:

    $ awk -F "\t|;" '/sd/ {print $2}' /tmp/file.txt
    cd
    ij
    sd
    

    (我意识到不应该打印最后一个;请耐心等待)

    然后要打印具有“sd”的字段,只需遍历这些字段:

    $ awk -F "\t|;" '/sd/ { for (x=1;x<=NF;x++) if ($x~"^sd=") print $2 " " $(x) }' /tmp/file.txt
    cd sd=5
    ij sd=23
    

    然后,您可以在 = 上拆分该字段,更改 $x~"^sd=" 以获得完全匹配,并在 = 的任一侧打印拆分右侧的字段以获得您的精确输出:

    $ awk -F "\t|;" '/sd/ { for (x=1;x<=NF;x++) if ($x~"^sd=") { split($x, tmp, /=/); print $2 " " tmp[2]}}' /tmp/file.txt
    cd 5
    ij 23
    

    【讨论】:

    • 谢谢。这很棒
    • 警告:最初的要求似乎是密钥等于“sd”;如果这是要求,那么使用$x == "sd" 会更安全。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-12
    • 1970-01-01
    • 2017-02-14
    • 1970-01-01
    • 1970-01-01
    • 2014-06-18
    相关资源
    最近更新 更多