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
Bash: Use a variable as an array name
提问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。任何时候您对最终评估的内容有疑问时,请复制该行并更改eval为echo.
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)都只是为了让这个独立的例子。如果您选择使用它,请替换DATA为STDIN并删除__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)脚本更容易。(我想看到它,最后,只是为了我自己的教育。)

