Вопрос или проблема
У меня есть expect-скрипт, который предоставляет IP-адрес:
#!/bin/expect -f
set nodename [lindex $argv 0]
spawn virsh console $nodename
expect "Escape character is"
send "\n"
expect "localhost login: " {
send "root\n"
expect "Password: "
send "cloud123\n"
}
expect "~]#" {
send "\n"
send "ifconfig | grep 192.168.1. | awk \'{print \$2}\'"
send "\n"
expect '(\d+\.\d+\.\d+\.\d+)'
send "logout"
}
Я хочу, чтобы скрипт возвращал этот IP-адрес. Я вызываю этот expect-скрипт из shell-скрипта следующим образом:
#!/bin/bash
ip=$(expect GetIP.exp nodetwo)
echo $ip
Как мне сделать так, чтобы мой expect-скрипт возвращал вывод в shell-скрипт?
Включение и отключение логирования не помогло – вероятно, из-за временных задержек.
Следующий expect-скрипт сработал для меня, когда я вызывал его так:
# ip=$(./virsh-expect nodetwo | tr -d '\r' | grep '^192.168')
# echo $ip
192.168.122.99
Важный аспект заключается в том, что символы возврата каретки удаляются из вывода с помощью tr
, а IP-адрес находится в shell-скрипте с помощью grep. Expect-скрипт даже не пытается “ожидать” его, он просто ожидает следующую командную строку root и завершает работу.
Я также сделал expect-скрипт более универсальным – он просто ожидает “login:” вместо “localhost login:” (что может не сработать на любой ВМ, у которой есть имя хоста… а у большинства будет), и ищет простую “# ” для командной строки root.
#!/usr/bin/expect -f
set nodename [lindex $argv 0]
spawn virsh console $nodename
expect "Escape character is"
send "\n\n"
expect "login: " {
send "root\n"
expect "Password: "
send "cloud123\n"
}
expect "# " {
send "ifconfig | awk \'/192\.168/ {print \$2}\'"
send "\n"
expect "# "
send "exit"
}
Альтернативным решением является использование не expect
, а Expect.pm
в Perl или pexpect
в Python. Эти библиотеки работают практически так же, как expect
, но упрощают извлечение данных из expect-сессии.
Я все еще считаю, что есть гораздо лучшие способы получить IP-адрес виртуальной машины, чем использование expect
на virsh console
.
Вот способ, который извлекает MAC-адрес из virsh dumpxml
и затем ищет соответствующую запись dnsmasq-dhcp в /var/log/daemon.log
с помощью awk
:
# mac=$(virsh dumpxml nodetwo | sed -n -e "/mac address/ s/.*'\([^']*\)'.*/\1/ p" | tail -1)
# ip=$(awk "/dnsmasq.*DHCPACK.*$mac/ {print \$7}" /var/log/daemon.log | tail -1)
# echo $ip
192.168.122.99
На моей системе ВМ имеет два сетевых интерфейса, и меня интересует только последний для этой цели, поэтому я использую tail -1
на строке virsh dumpxml
. tail -1
на строке awk
обеспечивает, чтобы мы получили только последнюю выдачу dhcp для этого MAC-адреса.
(на самом деле, я тестировал с ВМ, которая на моей системе называется ‘sid’, а не ‘nodetwo’, но я отредактировал вывод, а также пароль в expect-скрипте, чтобы он соответствовал вашему вопросу. Также IP-адреса, которые я использую для своих ВМ, имеют формат 192.168.122.x вместо 192.168.1.x – но это тривиальная разница в деталях)
Обновление
Мне пришлось воспользоваться этим скриптом вчера ночью, чтобы найти IP-адрес свободbsd-виртуальной машины, которую я создал. Я использую ISC dhcpd
вместо dnsmasq
, поэтому формат логирования слегка отличается. Эта версия работает для обоих dnsmasq
и dhcpd
:
$ cat find-vm-ip-by-name.sh
#!/bin/bash
mac=$(virsh dumpxml "$1" | sed -n -e "/mac address/ s/.*'\([^']*\)'.*/\1/ p" | tail -1)
ip=$(awk "/dnsmasq.*DHCPACK.*$mac/ {print \$7} ; /dhcpd.*DHCPACK.*$mac/ {print \$8}" /var/log/daemon.log | tail -1)
echo $ip
Вы можете попробовать следующий способ:
send_user $ip
“send_user” выводит данные, которые отправляются на stdout. Это используется для отправки сообщений на экран при выполнении скрипта. Отлично подходит для обратной связи с пользователем, баннеров и для генерации сообщений об ошибках.
Полезный шаблон для получения только одного вывода stdout
Этот шаблон, похоже, хорошо работает для меня. Пример, демонстрирующий это с помощью printf
в Bash
:
#!/usr/bin/expect -f
log_user 0
spawn bash
send "PS1='>'\r"
expect -re {>$}
send "printf 'ab\\ncd\\n'\r"
expect -re "\n(.*?)>$"
puts -nonewline $expect_out(1,string)
send "exit\r"
expect eof
который выдает точно:
ab
cd
как и ожидалось, что является ожидаемым результатом моего тестового запроса в Bash:
printf 'ab\ncd\n'
Эта тестовая команда также демонстрирует, что вывод с несколькими строками работает нормально.
Тестировано на expect 5.45.4, Bash 5.2.32, Ubuntu 24.10
Ответ или решение
Получение выходных данных от expect-скрипта в переменную Bash скрипта — это задача, которая требует определенного понимания работы взаимодействия между двумя скриптовыми системами. Рассмотрим, как можно реализовать данный процесс, используя предоставленный контекст.
Теория
Expect — это инструмент автоматизации интерактивных приложений, таких как командные оболочки и консоли, где требуется сессия с ожиданием ввода. Типичное использование включает автоматизацию ввода паролей и других взаимодействий с пользователем. В нашем случае, скрипт на Expect взаимодействует с консолью виртуальной машины через virsh console
, ожидая и отправляя команду, чтобы получить IP-адрес.
Bash-сценарии позволяют выполнять команды в командной строке Unix и имеют популярные средства для обработки переменных и выходных данных команд. Проблема, которую мы пытаемся решить, заключается в том, чтобы Expect-скрипт передавал свои данные, такие как IP-адрес, обратно в Bash-сценарий.
Пример
В представленном примере было показано несколько способов решения:
-
Модификация Expect-скрипта. Измените его так, чтобы логически выделить вывод информации. Можно использовать
send_user
. -
Фильтрация вывода в Bash-сценарии. Из Expect-скрипта можно просто направлять весь вывод в stdout, а затем обрабатывать его в Bash, используя средства фильтрации (например,
tr
,grep
). -
Используя другие инструменты. Рассматривались альтернативы Expect, такие как
Expect.pm
в Perl илиpexpect
в Python, которые обеспечивают более гибкое извлечение данных.
Пример изменения Expect-скрипта для прямого вывода IP-адреса:
#!/usr/bin/expect -f
set nodename [lindex $argv 0]
spawn virsh console $nodename
expect "Escape character is"
send "\n\n"
expect "login: " {
send "root\n"
expect "Password: "
send "cloud123\n"
}
expect "# " {
send "ifconfig | awk '/192\\.168/ {print \$2}'\n"
expect -re "([\d.]+)"
send_user "$expect_out(1,string)\n"
send "exit\n"
}
Применение
Теперь посмотрим, как это применяется в реальных сценариях. Простейший способ — это перенаправление вывода в переменную в Bash-сценарии и постобработка:
#!/bin/bash
ip=$(./GetIP.exp nodetwo | tr -d '\r' | grep '^192.168')
echo $ip
- Здесь используется
tr -d '\r'
для удаления символов возврата каретки (\r
), которые могут появляться. - Затем мы используем
grep
для фильтрации нужных строк.
Дополнительные решения
Зачастую задачу извлечения IP-адреса из виртуальной машины можно решить более эффективно без использования консольного взаимодействия:
-
Использование virt-dumpxml: Можно извлечь MAC-адрес VM и использовать его для поиска соотв. IP в логах DHCP.
-
Использование сетевого мониторинга: Прямое отслеживание сетевых пакетов может дать полезную информацию о сетевых изменениях, включая назначение IP.
Заключение
Использование Expect для извлечения данных полезно, когда у вас отсутствуют альтернативные способы взаимодействия с системами. Однако важно помнить, что этот подход может быть нестабильным, если интерфейс консольного взаимодействия меняется. Надежные решения зачастую зависят от стабильного API или анализов логов, которые более предсказуемы в долгосрочной перспективе. Выбор инструмента всегда должен зависеть от конкретных требований задачи и доступных ресурсов среды исполнения.