null

Pexpect, терминалы и more ненависти

CLI - действительно богатый возможностями автоматизации интерфейс. Однако некоторые утилиты (как например ftp) к сожалению имеют интерактивный интерфейс. К счастью, человечество придумало библиотеку expect и в частности, pexpect (для Python), которую можно использовать например так:

import pexpect

child = pexpect.spawn ('ftp ftp.openbsd.org')
child.expect ('Name .*: ')
child.sendline ('anonymous')
child.expect ('Password:')
child.sendline ('noah@example.com')
child.expect ('ftp> ')
...


Она использует эмулятор терминала pty (тот же самый механизм используется например xterm или другим графическим терминалом)
   
Напишем простейший пример использования pexpect, вызывающий сначала bash, а уже в нем команду ls -1 (да, я знаю про subprocess, это всего лишь пример)

import pexpect
import sys
import re
from StringIO import StringIO

bash = pexpect.spawn('bash --norc')     # --norc нужна, чтобы не читался bashrc

# Читаем приглашение
try:
    prompt = bash.read_nonblocking(size=128, timeout=10)
except pexpect.TIMEOUT:
    pass

prompt = prompt.strip()
print 'Prompt:', prompt

bash.sendline('ls -1')

# re.escape чтобы символы $ и т.п. в приглашении
# не воспринимались как спец-символы регулярного выражения
bash.expect(re.escape(prompt))

# Читаем вывод ls
ls_out = bash.before

for line in StringIO(ls_out).readlines():
    print 'Read:', line.strip()



Вывод нашей программы несколько неожиданный:

  Solaris   Linux

Prompt: bash-3.00#
Read: ls -1
Read: pexpect_patched.py
Read: pexpect.py
Read: pexpect.pyc
Read: pexptest1.py

Prompt: bash-4.2$
Read: ls -1
Read: pexpect_patched.py
Read: pexpect.py
Read: pexpect.pyc
Read: pexptest1.py


Как видим, что мы вводили в bash - то и прочитали. Чтобы избежать этого, можно использовать метод setecho, сбрасывающий флаг терминала ECHO (он заставляет терминал выводить символы, которые вводятся пользователем) - того же самого эффекта можно добиться, используя команду stty -echo. Добавим в наш скрипт строчку bash.setecho(False). Результаты становятся интереснее:
 

  Solaris   Linux

Prompt: bash-3.00#
Traceback (most recent call last):
  File "pexptest2.py", line 16, in ?
    bash.setecho(False)
  File "/root/pexpect/pexpect.py", line 760, in setecho
    attr = termios.tcgetattr(self.child_fd)
termios.error: (22, 'Invalid argument')

Linux:
Prompt: bash-4.2$
Read: ls -1
Read: pexpect_patched.py
Read: pexpect.py
Read: pexpect.pyc
Read: pexptest1.py
Read: pexptest2.py

Проблема в Solaris 10 связана особенностью реализации pexpect - при открытии пары терминалов она сохраняет fd мастер-терминала (того, который /dev/ptmx) а не дочернего - /dev/pts/X, а в Solaris опции терминала поддерживаются только дочерним (на самом деле, для этого нужно подгрузить STREAMS-модули ptem и ldterm, но это уже делает CPython).

Проблема же в Linux - с тем, что bash восстанавливает настройки терминала после в момент ввода команды функцией rl_deprep_terminal - save_tty_settings. Поэтому имеет смысл ввести параметр echoing в конструкторе pexpect.spawn, устанавливающий свойства терминала перед выполнением execve. Я написал измененную версию pexpect, исправляющие эти проблемы:

import pexpect_patched as pexpect

...

bash = pexpect.spawn('bash --norc', echoing=False)


    

  Solaris   Linux

Prompt: bash-3.00#
Read: ls -1
Read: pexpect_patched.py
Read: pexpect_patched.pyc
Read: pexpect.py
Read: pexpect.pyc
Read: pexptest1.py
Read: pexptest2.py

Prompt: bash-4.2$
Read: pexpect_patched.py
Read: pexpect_patched.pyc
Read: pexpect.py
Read: pexpect.pyc
Read: pexptest1.py
Read: pexptest2.py

Как видим в Solaris 10 проблема не решилась, что связано с тем, что в bash 3.0 termios-терминалы не поддерживаются (только BSD). В Solaris 11 используется bash-4.2, который поддерживает оба типа терминалов.

Ложка дегтя

К сожалению изначальная задача так решена и не была, так как MDB в Solaris вообще не учитывает атрибуты терминалов, что хорошо видно в коде:  http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/cmd/mdb/common/mdb/mdb_termio.c Поэтому как в той сказке про золотую рыбку, я остался у разбитого корыта, то есть со стандартным модулем subprocess


pexpect_patched.py

К списку статей

 

Интересуюсь по большей части системным анализом программного обеспечения: поиском багов и анализом неисправностей, а также системным программированием (и не оставляю надежд запилить свою операционку, хотя нехватка времени сказывается :) ). Программированием увлекаюсь с 12 лет, но так уж получилось, что стал я инженером.

Основная сфера моей деятельности связана с поддержкой Solaris и оборудования Sun/Oracle, хотя в последнее время к ним прибавились технологии виртуализации (линейка Citrix Xen) и всякое разное от IBM - от xSeries до Power. Учусь на кафедре Вычислительной Техники НИУ ИТМО.

See you...out there!

http://www.facebook.com/profile.php?id=100001947776045
https://twitter.com/AnnoyingBugs

Ничего не найдено. n is 0