通过动态构造的变量名间接分配给bash数组变量

时间:2014-05-23 01:57:12

标签: arrays bash variables eval indirection

Bash脚本用于从csv创建具有未知列的多个数组。

我正在尝试编写一个脚本来比较具有相似列的两个csv文件。我需要它来从其他csv找到匹配列并比较任何差异。踢球者是我希望脚本是动态的,允许输入任意数量的列,它仍然能够运行。我以为我有一个很好的计划来解决这个问题,但事实证明我遇到了语法错误。这是我需要比较的csv示例。

IP address, Notes,  Nmap-SSH,   Nmap-SMTP, Nmap-HTTP, Nmap-HTTPS,
10.0.0.1,   ,       open,       closed,     open,     open,
10.0.0.2,   ,       closed,     open,       closed,   closed,

当我读取csv文件时,我打算查找“IF列==打开;然后;使用IP地址填充此列的数组”这将在此方案中为我提供4个列表,其中包含正在侦听的IP说港口。然后,我可以将其与我的安全设备配置进行比较,以确保它已正确配置。最后,对于我来说,这就是我认为可以完成创建数组以供我稍后搜索的内容。但是当我尝试在数组名称中使用变量时,我遇到了麻烦。我的语法可以纠正,还是只有更好的方法来做这种事情?

#!/bin/bash
#
#
# This script compares config_cleaned_<ip>.txt output against ext_web_env.csv and outputs the differences
#
#
# Read from ext_web_env.csv file and create Array
#
        FILENAME=./tmp/ext_web_env.csv
#
        index=0
#
        while read line
          do
# How many columns are in the .csv?
        varEnvCol=$(echo $line | awk -F, '{print NF}')
            echo "columns = $varEnvCol"

# While loop to create array for each column

                while [ $varEnvCol != 2 ]
                  do
# Checks to see if port is open; if so then add IP address to array
                   varPortCon=$(echo $line | awk -F, -v i=$varEnvCol '{print $i}')
                        if [ $varPortCon = "open" ]
                          then
                                arr$varEnvCol[$index]="$(echo $line | awk -F, '{print $1}')"
# I get this error message "line29 : arr8[194]=10.0.0.194: command not found"
                        fi
           echo "arrEnv$varEnvCol is: ${arr$varEnvCol[@]}"
# Another error but not as important since I am using this to debug "line31: arr$varEnvCol is: ${arr$varEnvCol[@]}: bad substitution"
                   varEnvCol=$(($varEnvCol - 1))
                done
                index=$(($index + 1 ))
          done < $FILENAME

更新

我也尝试使用eval命令,因为所有数据都将由其他脚本填充。

但收到此错误消息:

./ compare.sh:line 41:arr8 [83] = 10.0.0.83:找不到命令

以下是此示例的新代码:

 if [[ $varPortCon = *'open'* ]]
   then
    eval arr\$varEnvCol[$index]=$(echo $line | awk -F, '{print $1}')
 fi

1 个答案:

答案 0 :(得分:13)

arr$varEnvCol[$index]="$(...)"

无法按照您期望的方式工作 - 您无法通过扩展的表达式间接地分配到shell变量变量名 - 这样

您尝试使用eval的解决方法也存在缺陷 - 请参阅下文。


TL;博士

如果您使用bash 4.3或更高版本:

declare -n targetArray="arr$varEnvCol"
targetArray[index]=$(echo $line | awk -F, '{print $1}')

bash 4.2或更早版本:

declare "arr$varEnvCol"[index]="$(echo $line | awk -F, '{print $1}')"

警告:这将适用于您的特定情况,但可能会在其他情况下巧妙地失败;请继续阅读详细信息,包括基于 read更强大但更繁琐的替代方案。

@shellter在自已删除的评论中提到的基于eval的解决方案不仅出于安全原因(如他们所提到的)而存在问题,而且因为它可能会非常棘手< EM>引用;为了完整性,这是基于eval的解决方案:

eval "arr$varEnvCol[index]"='$(echo $line | awk -F, '\''{print $1}'\'')'

见下文的解释。


间接分配给bash数组变量

bash 4.3+:使用declare -n有效地创建另一个变量的别名('nameref')

这是目前最好的选择,如果有的话:

declare -n targetArray="arr$varEnvCol"
targetArray[index]=$(echo $line | awk -F, '{print $1}')

declare -n有效地允许您通过其他名称引用变量(无论该变量是否为数组),并且为其创建别名的名称可以是一个表达式(一个扩展的字符串),如图所示。

bash 4.2-有几个选项,每个选项都有权衡

注意:使用 非数组变量时,最佳方法是使用printf -v 。由于这个问题是关于 array 变量的,因此不再进一步讨论这种方法。

  • [最强大,但很麻烦]:使用read
IFS=$'\n' read -r -d '' "arr$varEnvCol"[index] <<<"$(echo $line | awk -F, '{print $1}')"
  • IFS=$'\n'确保每个输入行中的前导和尾随空格保持不变。
  • -r阻止对\字符的解释。在输入中。
  • -d ''可确保捕获所有输入,甚至是多行
    • 但请注意,任何尾随\n字符。被剥夺
    • 如果您仅对第一个输入行感兴趣,请忽略-d ''
  • "arr$varEnvCol"[index]扩展为变量 - 数组元素,在本例中为 assign to;请注意,在数组下标中引用变量index不需要$前缀,因为下标是在算术上下文中计算的,其中前缀为可选
  • <<< - 一个所谓的 here-string - 将其参数发送到stdin,其中read从中获取输入。

  • [最简单,但可能会中断]:使用declare

declare "arr$varEnvCol"[index]="$(echo $line | awk -F, '{print $1}')"
  • (这有点违反直觉,因为declare意味着声明,而不是修改变量,但它适用于bash 3.x和4.x,具有下面提到的约束。)
  • 在函数外部工作正常 - 数组是否使用declare显式声明。
  • 警告:INSIDE一个函数,只适用于LOCAL变量 - 你不能引用shell-global变量(在函数外声明的变量)在函数内部。尝试这样做总是会创建一个LOCAL变量ECLIPSING shell-global变量。

  • [不安全和棘手]:使用eval

eval "arr$varEnvCol[index]"='$(echo $line | awk -F, '\''{print $1}'\'')'
  • CAVEAT:如果您完全控制正在评估的字符串的内容,则仅使用eval; eval将执行包含在字符串中的任何命令,但可能会产生不需要的结果。
  • 了解哪些变量引用/命令替换在非常重要时得到扩展 - 最安全的方法是延迟扩展,以便它们在eval执行时发生而不是参数传递给 eval时发生的立即扩展。
  • 要使变量赋值语句成功,RHS(右侧)必须最终评估为单个标记 - 不带引号不带空格或引用(可选择使用空格)。
  • 以上示例使用引号来延迟扩展;因此,传递的字符串不能直接包含单引号,因此分为多个部分,文字为'个字符。 拼接在\'
  • 另请注意,传递给eval的赋值语句的LHS(左侧)必须是双引号字符串 - 使用不带引号的字符串,并选择性引用{{奇怪的是,1}}无法运作:
    • 好的:$
    • 失败:eval "arr$varEnvCol[index]"=...