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

use*_*718 3 arrays variables bash 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,
Run Code Online (Sandbox Code Playgroud)

当我读取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
Run Code Online (Sandbox Code Playgroud)

UPDATE

我也尝试使用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
Run Code Online (Sandbox Code Playgroud)

mkl*_*nt0 13

arr$varEnvCol[$index]="$(...)"
Run Code Online (Sandbox Code Playgroud)

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

您试图解决eval的问题也存在缺陷 - 见下文.


TL;博士

如果你使用bash 4.3或以上:

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

bash 4.2或更早版本:

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

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

@shellter在删除注释中eval提到的基于解决方案的问题不仅出于安全原因(如他们所提到的那样),而且因为它在引用方面会变得非常棘手; 为了完整性,这是基于解决方案:eval

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

请参阅下面的说明.


间接分配给bash数组变量:

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

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

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

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

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

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

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

  • [最简单,但可能会破裂]:使用declare:

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