Bash:使用变量作为数组名称

时间:2011-11-08 03:05:34

标签: bash

我正在解析一个日志文件,并为每个用户创建具有行号和最后一个字段的关联数组(登录的总时间)。日志文件的行如下所示:

jww3321   pts/2        cpe-76-180-64-22 Mon Oct 18 23:29 - 00:27  (00:58)
jpd8635   pts/1        cpe-74-67-131-24 Mon Oct 18 23:22 - 03:49  (04:26)

如果第一个字段(jww3321)是数组名称,并且数组中的第一个条目将是(1,00:58),则下一个将是(2,(下次用户))。为了获得正确的密钥,我需要保存列表的长度,并在将下一个值添加到用户数组时添加一个。到目前为止我的代码看起来像这样:

cat lastinfo.txt | while read line
do
    uname=`echo "$line" | awk '{print $1;}'`
    count=`echo "${#$uname[@]}"`
    echo "$count"
done

我尝试过使用间接引用,但我遇到了这个错误:

l8t1: line 7: ${#$uname[@]}: bad substitution

有什么建议吗?

4 个答案:

答案 0 :(得分:4)

我不确定我是否理解你正在尝试做什么,特别是“关联”部分(我无法看到使用关联数组的位置),但是这段代码做了我理解你想要的东西做:

#!/bin/bash
while IFS=" " read user time; do
    eval "item=\${#$user[@]} ; $user[\$item]=\(\$((\$item + 1)),$time\)"
    [[ "${arraynames[@]}" =~ $user ]] || arraynames[${#arraynames[@]}]=$user
done< <(sed -r 's/^ *([[:alnum:]]*) .*\((.*)\)$/\1 \2/')

for arrayname in ${arraynames[@]}; do
    eval "array=(\${$arrayname[@]})"
    echo "$arrayname has ${#array[@]} entries:"
    for item in ${!array[@]}; do
        echo "$arrayname[$item] = ${array[$item]}"
    done
    echo
done

它从stdin读取。我用这样的示例文件测试了它:

    jww3321   pts/2        cpe-76-180-64-22 Mon Oct 18 23:29 - 00:27  (00:58)
    jpd8635   pts/1        cpe-74-67-131-24 Mon Oct 18 23:22 - 03:49  (04:26)
    jww3321   pts/2        cpe-76-180-64-22 Mon Oct 18 23:29 - 00:27  (01:58)
    jpd8635   pts/1        cpe-74-67-131-24 Mon Oct 18 23:22 - 03:49  (05:26)

输出:

    jww3321 has 2 entries:
    jww3321[0] = (1,00:58)
    jww3321[1] = (2,01:58)

    jpd8635 has 2 entries:
    jpd8635[0] = (1,04:26)
    jpd8635[1] = (2,05:26)

请注意,仅使用标准的整数索引数组。在Bash中,截至目前,左侧的间接数组引用总是涉及使用eval(uuuuuuhhhhh,幽灵般的声音),在右侧,您可以使用${!}离开替换和命令替换$()

使用eval的经验法则:在eval时逃避您想要展开的内容,并且在 eval时间之前不要逃避您想要展开的。如果您对最终被评估的内容有疑问,请复制该行并更改eval echo

编辑:回答sarnold的评论,这是一种没有评估的方法:

#!/bin/bash
while IFS=" " read user time; do
    array=$user[@] array=( ${!array} ) item=${#array[@]}
    read $user[$item] <<< "\($(($item + 1)),$time\)"
    [[ "${arraynames[@]}" =~ $user ]] || arraynames[${#arraynames[@]}]=$user
done< <(sed -r 's/^ *([[:alnum:]]*) .*\((.*)\)$/\1 \2/')

for arrayname in ${arraynames[@]}; do
    array=$arrayname[@] array=( ${!array} )
    echo "$arrayname has ${#array[@]} entries:"
    for item in ${!array[@]}; do
        echo "$arrayname[$item] = ${array[$item]}"
    done
    echo
done

答案 1 :(得分:2)

您不是在创建关联数组。该错误与${#$uname[@]}的语法有关:删除第二个美元符号。

答案 2 :(得分:2)

bash内,您可以使用eval

eval count=`echo "$\{#$uname[@]\}"`

RESP。

eval count="$\{#$uname[@]\}"

答案 3 :(得分:1)

我喜欢bash(1),我认为这对于“小”任务来说是公平的。我经常对在狭小空间里完成多少工作印象深刻。但我认为其他语言可以提供更友好的数据结构。十年前,我会在不考虑两次的情况下使用perl(1),但我已经开始不喜欢将哈希存储为其他哈希值的语法。 Python也很简单,但我现在比Python更了解Ruby,所以这里有类似于你正在做的事情:

#!/usr/bin/ruby -w

users = Hash.new() do |hash, key|
    hash[key] = Array.new()
end

lineno = 0

while(line = DATA.gets) do
    lineno+=1
    username, _ptr, _loc, _dow, _mon, _date, _in, _min, _out, time =
        line.split()
    u = users[username]
    minutes = 60 * Integer(time[1..2]) + Integer(time[4..5])
    u << [lineno, minutes]
end

users.each() do |user, list| 
    total = list.inject(0) { |sum, entry| sum + entry[1] }
    puts "#{user} was on #{list.length} times for a total of #{total} minutes"
end

__END__
jww3321   pts/2        cpe-76-180-64-22 Mon Oct 18 23:29 - 00:27  (00:58)
jpd8635   pts/1        cpe-74-67-131-24 Mon Oct 18 23:22 - 03:49  (04:26)
jww3321   pts/2        cpe-76-180-64-22 Mon Oct 18 23:29 - 00:27  (00:58)
jpd8635   pts/1        cpe-74-67-131-24 Mon Oct 18 23:22 - 03:49  (04:26)
jww3321   pts/2        cpe-76-180-64-22 Mon Oct 18 23:29 - 00:27  (00:58)
jpd8635   pts/1        cpe-74-67-131-24 Mon Oct 18 23:22 - 03:49  (04:26)

__END__(以及相应的DATA)只是为了使其成为独立的 例。如果您选择使用此功能,请将DATA替换为STDIN和 删除__END__及其后的所有内容。

由于我主要是在C中思考,这可能不是最惯用的Ruby 例如,但它确实演示了散列(关联数组)如何具有 每个键的数组(遗憾的是它比它复杂得多) be),显示如何附加到数组(u << ...),显示一些简单 数学,显示对哈希(users.each() do ...)的一些简单迭代,甚至使用一些higher order functionslist.inject(0) { .. }) 计算sum。是的,总和可以用更常见的方式计算 循环构造,但有一些关于“做这个”的选择 对该列表的所有元素进行操作“使其成为一个简单的构造 选择。

当然,我不知道你真正做了什么 last(1)命令,但ruby(1)似乎比。bash(1)更容易 相应的{{1}}脚本将是。 (我希望看到它,在 结束,仅仅是为了我自己的教育。)