Bash:使用变量作为数组名

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8045474/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 01:02:06  来源:igfitidea点击:

Bash: Use a variable as an array name

bash

提问by user1034850

I am parsing a log file and creating associative arrays for each user with the line number and the last field (total time logged in). The lines of the log file look like this:

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

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)

Where the first field (jww3321) will be the array name and the first entry in the array will be (1,00:58), the next will be (2,(the next time for user)). In order to obtain the proper keys I need to save the the length of list and add one to it when I add the next value to a user array. My code so far looks like this:

其中第一个字段 (jww3321) 将是数组名称,数组中的第一个条目将是 (1,00:58),下一个将是 (2,(用户的下一次))。为了获得正确的键,我需要保存列表的长度,并在将下一个值添加到用户数组时添加一个。到目前为止,我的代码如下所示:

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

I have tried using indirect references but I am stuck with this error:

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

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

Any suggestions?

有什么建议?

回答by ata

I'm not sure if I understood correctly what you are trying to do, specifically the "associative" part (I can't see where an associative array is used), but this code does what I UNDERSTAND that you want to do:

我不确定我是否正确理解了您要执行的操作,特别是“关联”部分(我看不到关联数组的使用位置),但是此代码执行了我理解的您想要执行的操作:

#!/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:]]*) .*\((.*)\)$/ /')

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

It reads from stdin. I've tested it with an example file like this:

它从标准输入读取。我已经用这样的示例文件对其进行了测试:

    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)

Output:

输出:

    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)

Note that only standard integer-indexed arrays are used. In Bash, as of now, indirect array references in the left side alwaysinvolve using eval(uuuuuuhhhh, ghostly sound), in the right side you can get away with ${!}substitution and command substitution $().

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

Rule of thumb with eval: escape what you want to be expanded at evaltime, and don't escape what you want to be expanded beforeevaltime. Any time you're in doubt about what ends up being eval'd, make a copy of the line and change evalfor echo.

eval 的经验法则:及时转义要扩展的内容eval,不要在时间之前转义要扩展的内容eval。任何时候您对最终评估的内容有疑问时,请复制该行并更改evalecho.

edit:to answer sarnold's comment, a way to do this without eval:

编辑:要回答 sarnold 的评论,这是一种无需 eval 即可执行此操作的方法:

#!/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:]]*) .*\((.*)\)$/ /')

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

回答by choroba

You are not creating associative arrays. The error is related to the syntax of ${#$uname[@]}: delete the second dollar sign.

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

回答by bmk

Within bashyou could use eval:

bash你可以使用eval

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

resp.

分别

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

回答by sarnold

I like bash(1), I think it's fair enough for "small" tasks. I'm often impressed with how much work it can get done in small spaces. But I think other languages can provide friendlier datastructures. A decade ago, I would have used perl(1)for this without thinking twice but I've grown to dislike the syntax of storing hashes as values in other hashes. Python would be pretty easy too, but I know Ruby better than Python at this point, so here's something similar to what you're working on:

我喜欢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)

The __END__(and corresponding DATA) are just to make this a self-contained example. If you choose to use this, replace DATAwith STDINand delete __END__and everything following it.

__END__(和相应的DATA)都只是为了让这个独立的例子。如果您选择使用它,请替换DATASTDIN并删除__END__其后的所有内容。

Since I mostlythink in C, this might not by the most idiomatic Ruby example, but it does demonstrate how a hash (associative array) can have an array for each key (which is sadly more complicated than it could be), shows how to append to the array (u << ...), shows some simple mathematics, shows some simple iteration over the hash (users.each() do ...), and even uses some higher order functions(list.inject(0) { .. }) to calculate a sum. Yes, the sum could be calculated with a more-usual looping construct, but there's something about the elegence of "do this operation on all elements of this list" that makes it an easy construct to choose.

由于我主要使用 C 语言,这可能不是最惯用的 Ruby 示例,但它确实演示了哈希(关联数组)如何为每个键创建一个数组(可悲的是,这比它可能更复杂),展示了如何附加到数组 ( u << ...),展示一些简单的数学运算,展示对哈希 ( users.each() do ...) 的一些简单迭代,甚至使用一些高阶函数( list.inject(0) { .. }) 来计算 a sum。是的,总和可以用更常见的循环构造来计算,但是有一些关于“对这个列表的所有元素执行这个操作”的优雅使它成为一个容易选择的构造。

Of course, I don't know what you're reallydoing with the data from the last(1)command, but this ruby(1)seems easier than the corresponding bash(1)script would be. (I'd like to see it, in the end, just for my own education.)

当然,我不知道您对命令中的数据真正做了什么last(1),但这ruby(1)似乎比相应的bash(1)脚本更容易。(我想看到它,最后,只是为了我自己的教育。)