Присваивать переменной массива 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]=...
- ХОРОШО: