将变量传递给 AWK 在循环内不起作用

将变量传递给 AWK 在循环内不起作用

我有一个 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

使用yqjq来自的包装器https://kislyuk.github.io/yq/)在命令行(或脚本中)解析 YAML:

$ yq -r '.abc.OCCUPATION' file.yml
Technician

在 shell 循环中给出它abcdef

$ 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

也就是说,调用yqwith--arg后跟yq要设置的变量名称,然后是要设置的值。然后在表达式中使用该变量yq。这有效同样地jq

没有 shell 循环,而是从顶级键中获取值:

$ yq -r 'foreach keys[] as $node (.;.;[$node,.[$node].OCCUPATION]|@tsv)' file.yml
abc     Technician
def     Engineer

还有一些其他工具yq都可以进行 YAML 解析。如果你在 Ubuntu 上安装yqsnap你会从一个叫 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 循环来处理简单的文本,例如;下面我们使用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这里也有效。

相关内容