Присваивать переменной массива bash косвенно, по динамически создаваемому имени переменной

Скрипт 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 column == open; затем; заполнить массив этого столбца 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: строка 41: arr8[83]=10.0.0.83: команда не найдена

Вот мой новый код для этого примера:

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

1 ответ

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

не работает так, как вы ожидаете - вы не можете назначить переменные оболочки косвенно - через выражение, которое расширяется до имени переменной - таким образом.

Ваша попытка обойти eval также имеет недостатки - см. ниже.


ТЛ; др

Если вы используете Bash 4.3 или выше:

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

Баш 4.2 или ранее:

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

Предостережение: это будет работать в вашей конкретной ситуации, но может слегка провалиться в других; читайте подробности, в том числе более надежную, но громоздкую альтернативу, основанную на read,

eval - решение на основе, упомянутое shellter в удаленном комментарии, является проблематичным не только по соображениям безопасности (как они упоминали), но и потому, что оно может быть довольно сложным в отношении цитирования; для полноты вот 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, Поскольку этот вопрос касается переменных массива, этот подход не обсуждается далее.

  • [самый надежный, но громоздкий]: использовать read:
IFS=$'\n' read -r -d '' "arr$varEnvCol"[index] <<<"$(echo $line | awk -F, '{print $1}')"
  • IFS=$'\n' гарантирует, что начальные и конечные пробелы в каждой строке ввода остаются без изменений.
  • -r мешает интерпретации \ символы. на входе.
  • -d '' гарантирует, что вводится ВСЕ, даже многострочные.
    • Обратите внимание, однако, что любой трейлинг \n символы. раздели
    • Если вас интересует только первая строка ввода, опустите -d ''
  • "arr$varEnvCol"[index] расширяется до переменной - элемента массива, в данном случае - для присвоения; обратите внимание, что ссылаясь на переменную index внутри индекса не требуется $ префикс, потому что индексы оцениваются в арифметическом контексте, где префикс является необязательным.
  • <<< - так называемая здесь строка - отправляет свой аргумент stdin, где read берет свой вклад от.

  • [самое простое, но может сломаться]: использовать declare:

declare "arr$varEnvCol"[index]="$(echo $line | awk -F, '{print $1}')"
  • (Это немного нелогично, в этом declare предназначен для объявления, а не изменения переменной, но он работает в bash 3.x и 4.x с ограничениями, указанными ниже.)
  • Работает нормально вне функции - был ли массив явно объявлен с declare или нет.
  • Предостережение: ВНУТРИ функции, работает только с ЛОКАЛЬНЫМИ переменными - вы не можете ссылаться на глобальные переменные оболочки (переменные, объявленные вне функции) изнутри функции таким образом. Попытка сделать это неизменно создает ЛОКАЛЬНУЮ переменную, ЗАКРЫВАЮЩУЮ переменную глобальной оболочки.

  • [небезопасно и сложно]: использовать eval:

eval "arr$varEnvCol[index]"='$(echo $line | awk -F, '\''{print $1}'\'')'
  • ПРЕДУПРЕЖДЕНИЕ: использовать только eval если вы полностью контролируете содержимое оцениваемой строки; eval выполнит любую команду, содержащуюся в строке, с потенциально нежелательными результатами.
  • Понимание того, какие ссылки на переменные / подстановки команд расширяются, когда нетривиально - самый безопасный подход - задержать расширение, чтобы они происходили, когда eval выполняется, а не немедленное расширение, которое происходит при передаче аргументов eval,
  • Для успешного выполнения оператора присваивания переменной RHS (правая сторона) должен в конечном итоге вычислить один токен - либо без кавычек без пробелов, либо в кавычках (необязательно с пробелами).
  • В приведенном выше примере используются одинарные кавычки для задержки расширения; таким образом, передаваемая строка не должна содержать одинарные кавычки напрямую и поэтому разбивается на несколько частей с литералом ' символы. сращены как \',
  • Также обратите внимание, что LHS (левая сторона) оператора присваивания передается eval должна быть строка в двойных кавычках - использование строки без кавычек с выборочными кавычками $ не сработает, любопытно
    • ХОРОШО: eval "arr$varEnvCol[index]"=...
    • FAILS: eval arr\$varEnvCol[index]=...
Другие вопросы по тегам