在awk中预先填充关联数组键?

时间:2011-09-29 04:46:56

标签: awk associative-array

我写过一个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和CANCELLED - 其他状态不存在(因为它们今天没有出现),但我仍然希望它们在我的脚本输出中(以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

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

5 个答案:

答案 0 :(得分:5)

您可能根本不需要这样做。 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
}

答案 1 :(得分:4)

也许克雷格可以使用而不是:

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

这样:

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

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

  

超时状态。

虽然第二个会给出:

  

超时状态0。

答案 2 :(得分:1)

我认为在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对密钥文件可以确保所有密钥的存在。

答案 3 :(得分:0)

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) -

答案 4 :(得分:0)

@ 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
     }
}

我还将输入转换为小写,因此它匹配您的硬编码值,摆脱了不必要的3rd arg到split()和随后的null语句(尾随分号)。

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

{ 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
     }
}