Вопрос или проблема
У меня есть массив, $HOSTS
, который содержит набор имен хостов.
У меня есть таблица базы данных, в которой есть набор IP-адресов. Я хочу пройти по каждому IP-адресу в этой таблице и отправить команду (hostname -s
) на этот адрес, а затем добавить вывод в $HOSTS
. У удаленных серверов не настроена PKI, и скрипт должен работать автоматически без человеческого вмешательства. Это закрытая сеть, а пароль root хранится в виде переменных в скрипте ($SERVERPASSWORD
). И прежде чем кто-нибудь скажет это, я понимаю, что хранить пароль root в такой переменной — не лучшая идея, но в данном случае важнее отсутствие человеческого вмешательства, чем защита пароля root.
Вот соответствующая часть моего текущего (очищенного) скрипта:
for IP in $(sql query that returns a list of IP addresses);do
HOSTS=($(expect -c 'spawn ssh root@$IP "hostname -s"; expect "assword:";send ""$SERVERPASSWORD"\r";interact' ${HOSTS[@]}))
done
Результатом этого является сбой с сообщением:
Can’t read “IP”: no such variable while executing “spawn ssh root@$IP
“hostname -s””
Я могу поставить echo $IP
вместо этого, и это работает, как ожидалось, так что я знаю, что переменная IP заполняется в цикле.
После некоторых поисков в Google я понял, что expect
должен иметь свою собственную переменную и не может использовать переменные Bash, но все решения, которые я нашел, относились к настоящим скриптам Expect, а не к использованию expect внутри Bash-скрипта. Я пытался изменить свою команду на expect set IP $IP -c 'spawn ....'
, но это, похоже, не сработало.
Что я могу сделать, чтобы expect
считал значение $IP
в этом цикле For?
Избавьтесь от привычки использовать ИМЕНА ПЕРЕМЕННЫХ В ВЕРХНЕМ РЕГИСТРЕ, оставьте их зарезервированными для оболочки. Однажды вы напишете PATH=something
, а затем зададитесь вопросом, почему ваш скрипт не работает.
Также вы можете использовать array+=( "new element" )
для добавления в массив.
Ниже я расширил однострочное решение для читаемости.
Это всего лишь проблема кавычек в оболочке. Есть несколько способов ее решить:
-
использовать двойные кавычки для тела expect. Это значит, что вам придется по-другому обрабатывать кавычки expect: вы можете экранировать все двойные кавычки (неудобно) или использовать фигурные скобки, которые являются механизмом одинарных кавычек Tcl:
hosts+=( "$( expect -c " spawn ssh root@$ip {hostname -s} expect {assword:} send {$serverPassword}; send \\r expect eof " )" )
-
передавать переменные через окружение. Здесь я использую цитируемый heredoc, чтобы вам не пришлось беспокоиться о кавычках:
hosts+=( "$( ip="$ip" pw="$serverPassword" expect <<<'END_EXPECT' spawn ssh root@$env(ip) "hostname -s" expect "assword:" send "$env(serverPassword)\r" expect eof END_EXPECT )" )
Ответ или решение
Чтобы настроить и использовать переменные в однострочной команде Expect в Bash-скрипте эффективно, необходимо учитывать некоторые особенности работы между оболочкой Bash и языком Expect. В данном случае вы пытаетесь передать переменную $IP
и $SERVERPASSWORD
в блок кода Expect. Давайте разберёмся, как это сделать корректно.
Проблема
Ошибка, с которой вы столкнулись, связана с тем, что Expect не может напрямую интерпретировать переменные, определённые в Bash. Это происходит потому, что кусок кода Expect выполняется в отдельной среде. Чтобы передать значение переменной, используйте один из следующих подходов:
Решения
1. Использование двойных кавычек
Одним из простейших решений является использование двойных кавычек для обёртывания тела команды Expect. Однако в этом случае вам придётся позаботиться о правильном экранировании переменных внутри Expect, используя фигурные скобки. Вот как это можно сделать:
HOSTS+=("$(
expect -c "
spawn ssh root@$IP {hostname -s}
expect {assword:}
send \"${SERVERPASSWORD}\\r\"
expect eof
"
)")
Здесь корректно передаются значения переменных $IP
и $SERVERPASSWORD
в тело Expect-кода. Обратите внимание на экранирование внутри строки.
2. Использование окружения
Другим подходом является передача переменных посредством окружения. Вы можете использовать heredoc для упрощения этой стратегии:
HOSTS+=("$(
ip="$IP" pw="$SERVERPASSWORD" expect <<<'END_EXPECT'
spawn ssh root@$ip "hostname -s"
expect "assword:"
send "$pw\r"
expect eof
END_EXPECT
)")
В этом случае переменные ip
и pw
передаются в среду выполнения Expect, и вы можете легко к ним обращаться. Это значительно упрощает управление кавычками и экранированием.
Завершение
Когда вы используете один из этих методов, ваш скрипт будет обрабатывать IP-адреса и запрашивать имя хоста с эдекватным выводом. Таким образом, вы сможете беспрепятственно обновлять массив $HOSTS
, не сталкиваясь с ошибками.
Не забудьте, что использование переменных, содержащих секретные данные, таких как пароли, может подвергать систему риску. Подумайте о возможных методах защиты этой информации в будущем, даже в условиях, когда автоматизация важнее безопасности.