我有一个 awk 语句,它读取 YAML 文件并输出特定值。我需要在一个循环中循环这个 awk,在循环中从值列表中读取一个键值并将该键传递给 awk。
YAML 文件具有以下结构:
abc:
NAME: Bob
OCCUPATION: Technician
def:
NAME: Jane
OCCUPATION: Engineer
假设我想获取abc
OCCUPATION
的键值TECHNICIAN
,通过谷歌搜索我设法构建了一个 awk 语句来给出我想要的
> awk 'BEGIN{OFS=""} /^[^ ]/{ f=/^abc:/; next } f{ if (sub(/:$/,"")) abc=$2; else print abc,$1 $2}' test.yml| grep "OCCUPATION:" | cut -d':' -f2
Technician
但是,如果我使用此循环,将 -v 选项传递给 awk 似乎不会给出任何结果:
items="abc,def"
for item in $(echo $items | sed "s/,/ /g");
do
echo $item;
awk -v name="$item" 'BEGIN{OFS=""} /^[^ ]/{ f=/^\name:/; next } f{ if (sub(/:$/,"")) name=$2; else print name,$1 $2}' test.yml| grep "OCCUPATION:" | cut -d':' -f2;
done
我只得到了我设置的调试回声
abc
def
我哪里错了?我认为变量应该在 awk 中正确解释?
编辑:根据 Steeldrivers 的评论,我对输入进行了一些更改
items="abc,def"
for item in $(echo $items | sed "s/,/ /g");
do
echo $item;
awk -v name="$item" 'BEGIN{OFS=""} /^[^ ]/{ f=name; next } f{ if (sub(/:$/,"")) name=$2; else print name,$1 $2}' test.yml| grep "OCCUPATION:" | cut -d':' -f2;
done
但是现在我得到了OCCUPATION
打印的所有值:
abc
Technician
Engineer
def
Technician
Engineer
我尝试使用该~
运算符,但我认为我没有正确使用它,因为它给了我错误,所以我决定直接解析该值,但这会产生重复:/
答案1
当使用 YAML、JSON 或 XML 等结构化文本时,您确实应该使用“理解”结构的解析器。有几种特定的命令行工具可用于各种结构化文本(例如,xmlstarlet
用于 xml、jq
用于 json 和yqfor yaml),大多数编程/脚本语言都有用于解析和处理结构化文本的库。
以下是如何使用 perl 核心 YAML 模块在 perl 中执行此操作:
(这需要 perl >= 5.14 的版本,此时 YAML 模块被包含为核心模块发行版的标准部分。perl 5.14 于 2013 年发布。对于早期版本的 perl,您可以使用 安装 YAML cpan
)。
#!/usr/bin/perl
use strict;
use YAML qw(LoadFile);
my $file = shift; # first arg is the input filename
my $data = LoadFile($file); # load the yaml data into a hashref variable
# loop over the remaining args (i.e. the keys)
foreach my $item (@ARGV) {
print "$item\n";
print $$data{$item}{'OCCUPATION'}, "\n";
}
将其另存为,例如,yaml.pl
并使其可执行chmod +x yaml.pl
。
如果您的 yaml 数据保存在名为 的文件中input.yaml
,您可以像这样运行它:
$ ./yaml.pl input.yaml abc def
abc
Technician
def
Engineer
与 awk 或 sed 一样,这也可以压缩成一段难以理解的一行代码:
$ perl -MYAML=LoadFile -E '$data=LoadFile(shift);foreach (@ARGV) {say $_;say $$data{$_}{"OCCUPATION"}}' input.yaml abc def
abc
Technician
def
Engineer
perl 还可以自动为您分割参数。例如,如果将foreach
循环更改为:
foreach my $item (split /\s*,\s*/,join(",",@ARGV)) {
你可以运行它:
$ ./yaml.pl input.yaml abc def
或者
$ ./yaml.pl input.yaml "abc,def"
或任意组合(假设使用 ghi 和 jkl 键):
$ ./yaml.pl input.yaml "abc,def" ghi jkl
答案2
使用yq
(jq
来自的包装器https://kislyuk.github.io/yq/)在命令行(或脚本中)解析 YAML:
$ yq -r '.abc.OCCUPATION' file.yml
Technician
在 shell 循环中给出它abc
:def
$ for thing in abc def; do yq -r --arg node "$thing" '$node,.[$node].OCCUPATION' file.yml; done
abc
Technician
def
Engineer
或者,对于制表符分隔的列:
$ for thing in abc def; do yq -r --arg node "$thing" '[$node,.[$node].OCCUPATION] | @tsv' file.yml; done
abc Technician
def Engineer
也就是说,调用yq
with--arg
后跟yq
要设置的变量名称,然后是要设置的值。然后在表达式中使用该变量yq
。这有效同样地在jq
。
没有 shell 循环,而是从顶级键中获取值:
$ yq -r 'foreach keys[] as $node (.;.;[$node,.[$node].OCCUPATION]|@tsv)' file.yml
abc Technician
def Engineer
还有一些其他工具yq
都可以进行 YAML 解析。如果你在 Ubuntu 上安装yq
,snap
你会从一个叫 Mike Farah 的人那里得到一个版本。它的工作方式不同,我倾向于使用它来转换为 JSON,然后将数据通过管道传输到jq
:
$ yq -j e file.yml | jq -r '.abc.OCCUPATION'
Technician
$ for thing in abc def; do yq -j e file.yml | jq -r --arg node "$thing" '$node,.[$node].OCCUPATION'; done
abc
Technician
def
Engineer
或者,对于制表符分隔的列:
$ for thing in abc def; do yq -j e file.yml | jq -r --arg node "$thing" '[$node,.[$node].OCCUPATION] | @tsv'; done
abc Technician
def Engineer
答案3
当你有适当的文本处理工具时,你不需要 shell 循环来处理简单的文本,例如awk;下面我们使用GNUawk为此,我们可以定义多字符 RS 和 RT,它是对当前匹配 RS 的反向引用:
$ awk -v RS='(^|\n)[a-z]+:\n' 'rt ~ /^abc:\n$/ { print $NF; exit } { rt=RT }' infile
Technician
严格检查报告值是否真实“职业”key 并从变量传递键/标头而不是对它们进行硬编码,您可以这样做:
$ awk -v hdr='abc' -v key='OCCUPATION' -v RS='(^|\n)[a-z]+:\n' -F'\n' \
'rt ~ ("^" hdr ":\n") {
for(i=1; i<=NF; i++)
if(match($i, "^\\s*" key ":\\s*" )) { print substr($i, RSTART+RLENGTH); exit }
}
{ rt=RT }' infile
Technician
答案4
还使用awk
:
awk -F'[[:space:]]+' '$1 == "" {if (s == "abc:" && $2 == "OCCUPATION:") print $3; next} {s=$1}' file
Technician
如果职业是“网络技术员”或任何包含空格的职业,则此操作将会失败。因此,为了防止这种情况:
awk -F'[[:space:]]+' '$1 == "" {if (s == "abc:" && $2 == "OCCUPATION:") { sub(/[^:]*:[[:space:]]*/,""); print }; next} {s=$1}' file
Technician
埃德·莫顿的解决方案{ sub(/[^:]*:[[:space:]]*/,""); print }
在print $3
这里也有效。