解析具有 key/value 对列的文件

parsing a file with a column of key/value pairs

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

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

我想打印第 2 列和与键关联的值 "sd" 预期输出应该是

cd 5
ij 23

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

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

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

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

但我无法同时打印其他列值

鉴于:

$ 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 }' /tmp/file.txt
cd
ij
sd

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

然后打印具有 'sd' 的字段,只需循环遍历字段:

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

awk 救援!

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

cd 5
ij 23

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

我知道您要求 awk,但这里是强制性的 sed one liner,它比 awk 示例短一点。在 peaks 提示之后,我在该行的不同部分添加了更多带有 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=([^;]+).*/ /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= 之后到 ;
  • 的值
  • 替换使用捕获的 </code>(第二列)和 <code>sd= 之后的值)来创建您想要的输出

这里有 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 捕获组将所需值与 </code>:</p> 隔离开来 <pre><code>$ gawk '/sd=/{print , gensub(/.*sd=([^;]*).*/,"\1","g",)}' pf.txt cd 5 ij 23 bb 15

或者,使用 non-gawk awk,您使用两次 sub 调用来删除所需值前后的部分:

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

只要你的数据中有 name/value 对,最好从该数据创建一个 name/value 数组,这样你就可以按名称引用这些值:

$ 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 , n2v["sd"] }

$ awk -f tst.awk file
cd 5
ij 23