【问题标题】:pre-populate associative array keys in awk?在awk中预填充关联数组键?
【发布时间】:2011-09-29 04:46:55
【问题描述】:

我编写了一个 munin 插件,它使用 slurm 的 sacct 来监控 HPC 集群上的作业状态。我用 sh + awk 编写了它(而不是我通常选择的工具 perl)。

脚本有效,但我花了很长时间才弄清楚如何预填充可能状态的关联数组(一些/大多数可能不存在于 sacct 输出中,我希望它们默认为零)。 Google 帮不上什么忙,我能想到的最好办法是在字符串上使用 split 来生成一个临时数组,然后我对其进行迭代。

我想出了这个:

BEGIN {
    num = split("cancelled completed completing failed nodefail pending running suspended timeout",statenames," ");
    for (i=1;i<=num;i++) {
        states[statenames[i]] = 0
    }
  }

这可行,但与我在 perl 中的做法相比显得笨拙,如下所示:

foreach (qw(cancelled completed completing failed nodefail pending running suspended timeout)) {
    $states{$_} = 0;
}

或者这个

%states = map {$_ => 0} qw(cancelled completed completing failed nodefail pending running suspended timeout);

我的问题是:在 awk 中有没有一种类似于 perl 版本的方法?

[编辑]

澄清一下,这是我通过管道传输到 awk 的 sacct 输出示例。请注意,此输出中的唯一状态是 RUNNING、COMPLETED 和 CANCELED - 其他状态不存在(因为它们今天没有发生),但无论如何我都希望它们出现在我的脚本输出中(以 munin 可用的形式为“ statename.value 0")。

# sacct -X -P -o 'state' -n
RUNNING
RUNNING
RUNNING
RUNNING
COMPLETED
RUNNING
COMPLETED
RUNNING
COMPLETED
COMPLETED
CANCELLED by 1000
COMPLETED

[再次编辑]

这是我的 munin 插件的示例输出:

# ./slurm-sacct
suspended.value 0
pending.value 0
nodefail.value 0
failed.value 0
running.value 6
completing.value 0
completed.value 5
timeout.value 0
cancelled.value 1

脚本运行并执行我想要的操作,我只是想知道是否有更好的方法来初始化关联数组。

【问题讨论】:

  • 你说得对。 awk 不提供直接用它的索引初始化数组的方法。这是 awk 和 perl 之间的哲学差异 - awk 故意保持其语言简洁,因此没有直接的方法来做与其他语言结构无关的事情,而 perl 为您提供了令人眼花缭乱的无数语言结构来简单地做事情(但不一定简明扼要)尽可能。 @DavidZaslavsky 为您提供了正确的 awk 答案,但我建议您考虑一个修改,我现在将发布一个单独的答案,因为它不适合评论。

标签: awk associative-array


【解决方案1】:

您可能根本不需要这样做。 awk 中的变量是动态的,这意味着它们在第一次使用时会自动初始化(分配给或访问),这也适用于数组元素。

如果在数字上下文中访问变量,则将其初始化为 0,否则将初始化为空字符串。 (至少 gawk 会这样做,虽然我不确定它是否依赖于实现)所以如果你正在做一些事情,比如计算每个状态下的工作数量,整个程序就像这样简单

{ states[$1]++ }
END {
     for (state in states) print state, states[state]
}

每次执行表达式states[$1]++时,都会检查states[$1]是否存在,如果不存在则将其初始化为0。


编辑:根据您的评论,我猜您想为每个可能的状态打印一行,无论该状态是否有任何工作。在这种情况下,您需要包含所有可能的状态名称,并且没有像 Perl 中那样的快捷表示法。据我所知,您已经找到的东西几乎是干净的。 (Awk 在设计时并没有考虑到这种用法)

我建议如下:

{ states[$1]++ }
END {
     split("cancelled completed completing failed nodefail pending running suspended timeout",statenames," ");
     for (state in statenames) print state, states[state]+0
}

【讨论】:

  • 是的,这都是真的,但正如我所说,并非所有状态都会出现在每次执行 sacct 中 - 我希望它们默认为零,这就是我想预填充 assoc 的原因.具有所有可能状态名称的数组键。例如通常,将出现的唯一状态是“正在运行”、“已完成”、“失败”和“已取消”。除非数组的其他可能状态(关联数组的键)为零值,否则它们不会出现在输出中。至少,不是没有很多特殊情况的 if/then/else 代码——用所有可能的键名初始化 assoc 数组要简单得多。
  • POSIX awk 还将变量初始化为 0 或空字符串。我认为它实际上可以追溯到原始的 awk。
  • 好的,听起来我的 BEGIN 和你的 END 块和 awk 一样好。我的问题的主要原因是我很惊讶谷歌没有提出任何直接相关的东西,所以我认为这要么是我错过的非常明显的事情,要么只是我对 awk 应用了一种 perlish 心态。
  • 无论如何,我会给它一天左右的时间,以防其他人想出一些好的东西,但我想我最终会接受这个答案,因为你的“......它得到......”评论。不过,我更喜欢我的方法,因为它作为初始化/设置的东西对我来说更具概念意义,因此它属于 BEGIN 块。无论如何,谢谢。
【解决方案2】:

也许 Craig 可以使用:

print "Timeout states ",states[timeout],".";

这个:

print "Timeout states ",int(states[timeout]),".";

在我的情况下,如果 awk 输入中没有超时状态,第一次打印会给出:

超时状态。

虽然第二个会给出:

超时状态为 0。

【讨论】:

  • 无需调用 int(),只需使用 +0 或 printf "%d"。
【解决方案3】:

我认为 awk 中更自然的方法是拥有一个单独的密钥文件。考虑一个文件keys.txt,每行一个键。然后你可以这样做:

printf "key1\nkey2\nkey2\nkey5" | 
  awk '
    FILENAME == "keys.txt" {
      counts[$0] = 0
      next
    }

    {
      counts[$0]++
    }

    END {
      for (key in counts) {
        print key, counts[key]
      }
    }' keys.txt -

keys.txt 中有五个键,这会产生:

key1 1
key2 2
key3 0
key4 0
key5 1

虽然这里的键是按顺序显示的,但这只是偶然的,不应依赖。

对于特定示例,您也可以完全跳过关联数组。相反,您可以使用 awk 最低限度地处理这些行并使用 sort | uniq -c 将计数制成表格。可以对密钥文件使用join 来确保所有密钥的存在。

【讨论】:

  • 哈希是必要的,因为它是始终获取输出中列出的所有作业状态的“最干净”/最简单的方法,即使它们并不总是出现在输入中。这就是为什么我没有用 sort|uniq|xargs|sed 做一些简单的事情。而且我没有使用 perl,因为多年来我没有使用 awk,而只是简单地从列表文本流中提取字段,并认为这是练习 awk 的好机会。
  • 另外,我不能说我喜欢外部文件的想法。它向脚本添加了外部依赖,并且没有做任何事情来消除我想出的“笨拙”(IMO,让它变得更糟)。
【解决方案4】:

awk 比 Perl 有点笨拙(我会说“不那么简洁”)。

您可以这样写(类似于@Michael 的回答):

pipeline of data |
awk '
  NR == FNR {statenames[$1]=0; next}
  { usual processing }
  END { usual output }
' <(printf "%s\n" cancelled completed completing failed nodefail pending running suspended timeout) -

【讨论】:

    【解决方案5】:

    @DavidZaslavsky 的答案的一个调整可能是按照您在 split() 行上指定的顺序打印状态。那将是:

    { states[tolower($1)]++ }
    END {
         n = split("cancelled completed completing failed nodefail pending running suspended timeout",statenames)
         for (i=1; i<=n; i++) {
             state = statenames[i]
             print state, states[state]+0
         }
    }
    

    我还将输入转换为小写,使其与您的硬编码值匹配,去掉了 split() 的不必要的第三个参数和随后的空语句(尾随分号)。

    如果您想在输入中找到不在硬编码集中的状态名称,您可以将其调整为:

    { states[tolower($1)]++ }
    END {
         n = split("cancelled completed completing failed nodefail pending running suspended timeout",statenames)
         for (i=1; i<=n; i++) {
             state = statenames[i]
             print state, states[state]+0
             delete states[state]
         }
         for (state in states) {
             print "WARNING: found new state name %s\n",state | "cat>&2"
             print state, states[state]+0
         }
    }
    

    【讨论】:

      猜你喜欢
      • 2017-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-26
      • 2020-12-27
      • 2011-03-08
      • 2010-10-01
      相关资源
      最近更新 更多