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