Получить вывод из expect-скрипта в переменную

Вопрос или проблема

У меня есть 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'

Эта тестовая команда также демонстрирует, что вывод с несколькими строками работает нормально.

Связано: https://stackoverflow.com/questions/45210358/expect-output-only-stdout-of-the-command-and-nothing-else

Тестировано на expect 5.45.4, Bash 5.2.32, Ubuntu 24.10

Ответ или решение

Получение выходных данных от expect-скрипта в переменную Bash скрипта — это задача, которая требует определенного понимания работы взаимодействия между двумя скриптовыми системами. Рассмотрим, как можно реализовать данный процесс, используя предоставленный контекст.

Теория

Expect — это инструмент автоматизации интерактивных приложений, таких как командные оболочки и консоли, где требуется сессия с ожиданием ввода. Типичное использование включает автоматизацию ввода паролей и других взаимодействий с пользователем. В нашем случае, скрипт на Expect взаимодействует с консолью виртуальной машины через virsh console, ожидая и отправляя команду, чтобы получить IP-адрес.

Bash-сценарии позволяют выполнять команды в командной строке Unix и имеют популярные средства для обработки переменных и выходных данных команд. Проблема, которую мы пытаемся решить, заключается в том, чтобы Expect-скрипт передавал свои данные, такие как IP-адрес, обратно в Bash-сценарий.

Пример

В представленном примере было показано несколько способов решения:

  1. Модификация Expect-скрипта. Измените его так, чтобы логически выделить вывод информации. Можно использовать send_user.

  2. Фильтрация вывода в Bash-сценарии. Из Expect-скрипта можно просто направлять весь вывод в stdout, а затем обрабатывать его в Bash, используя средства фильтрации (например, tr, grep).

  3. Используя другие инструменты. Рассматривались альтернативы 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-адреса из виртуальной машины можно решить более эффективно без использования консольного взаимодействия:

  1. Использование virt-dumpxml: Можно извлечь MAC-адрес VM и использовать его для поиска соотв. IP в логах DHCP.

  2. Использование сетевого мониторинга: Прямое отслеживание сетевых пакетов может дать полезную информацию о сетевых изменениях, включая назначение IP.

Заключение

Использование Expect для извлечения данных полезно, когда у вас отсутствуют альтернативные способы взаимодействия с системами. Однако важно помнить, что этот подход может быть нестабильным, если интерфейс консольного взаимодействия меняется. Надежные решения зачастую зависят от стабильного API или анализов логов, которые более предсказуемы в долгосрочной перспективе. Выбор инструмента всегда должен зависеть от конкретных требований задачи и доступных ресурсов среды исполнения.

Оцените материал
Добавить комментарий

Капча загружается...