summaryrefslogtreecommitdiff
path: root/doc/overview.rst
blob: 139ba36555151ca4ccb1c7a12e203e46c1471e8e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
API Overview
============

Pexpect can be used for automating interactive applications such as ssh, ftp,
mencoder, passwd, etc. The Pexpect interface was designed to be easy to use.

Here is an example of Pexpect in action::

    # This connects to the openbsd ftp site and
    # downloads the recursive directory listing.
    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> ')
    child.sendline('lcd /tmp')
    child.expect('ftp> ')
    child.sendline('cd pub/OpenBSD')
    child.expect('ftp> ')
    child.sendline('get README')
    child.expect('ftp> ')
    child.sendline('bye')

Obviously you could write an ftp client using Python's own :mod:`ftplib` module,
but this is just a demonstration. You can use this technique with any application.
This is especially handy if you are writing automated test tools.

There are two important methods in Pexpect -- :meth:`~pexpect.spawn.expect` and
:meth:`~pexpect.spawn.send` (or :meth:`~pexpect.spawn.sendline` which is
like :meth:`~pexpect.spawn.send` with a linefeed). The :meth:`~pexpect.spawn.expect`
method waits for the child application to return a given string. The string you
specify is a regular expression, so you can match complicated patterns. The
:meth:`~pexpect.spawn.send` method writes a string to the child application.
From the child's point of view it looks just like someone typed the text from a
terminal. After each call to :meth:`~pexpect.spawn.expect` the ``before`` and ``after``
properties will be set to the text printed by child application. The ``before``
property will contain all text up to the expected string pattern. The ``after``
string will contain the text that was matched by the expected pattern.
The match property is set to the `re match object <http://docs.python.org/3/library/re#match-objects>`_.

An example of Pexpect in action may make things more clear. This example uses
ftp to login to the OpenBSD site; list files in a directory; and then pass
interactive control of the ftp session to the human user::

    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> ')
    child.sendline ('ls /pub/OpenBSD/')
    child.expect ('ftp> ')
    print child.before   # Print the result of the ls command.
    child.interact()     # Give control of the child to the user.

Special EOF and TIMEOUT patterns
--------------------------------

There are two special patterns to match the End Of File (:class:`~pexpect.EOF`)
or a Timeout condition (:class:`~pexpect.TIMEOUT`). You can pass these
patterns to :meth:`~pexpect.spawn.expect`. These patterns are not regular
expressions. Use them like predefined constants.

If the child has died and you have read all the child's output then ordinarily
:meth:`~pexpect.spawn.expect` will raise an :class:`~pexpect.EOF` exception.
You can read everything up to the EOF without generating an exception by using
the EOF pattern expect. In this case everything the child has output will be
available in the ``before`` property.

The pattern given to :meth:`~pexpect.spawn.expect` may be a regular expression
or it may also be a list of regular expressions. This allows you to match
multiple optional responses. The :meth:`~pexpect.spawn.expect` method returns
the index of the pattern that was matched. For example, say you wanted to login
to a server. After entering a password you could get various responses from the
server -- your password could be rejected; or you could be allowed in and asked
for your terminal type; or you could be let right in and given a command prompt.
The following code fragment gives an example of this::

    child.expect('password:')
    child.sendline(my_secret_password)
    # We expect any of these three patterns...
    i = child.expect (['Permission denied', 'Terminal type', '[#\$] '])
    if i==0:
        print('Permission denied on host. Can\'t login')
        child.kill(0)
    elif i==1:
        print('Login OK... need to send terminal type.')
        child.sendline('vt100')
        child.expect('[#\$] ')
    elif i==2:
        print('Login OK.')
        print('Shell command prompt', child.after)

If nothing matches an expected pattern then :meth:`~pexpect.spawn.expect` will
eventually raise a :class:`~pexpect.TIMEOUT` exception. The default time is 30
seconds, but you can change this by passing a timeout argument to
:meth:`~pexpect.spawn.expect`::

    # Wait no more than 2 minutes (120 seconds) for password prompt.
    child.expect('password:', timeout=120)

Find the end of line -- CR/LF conventions
-----------------------------------------

Pexpect matches regular expressions a little differently than what you might be
used to.

The :regexp:`$` pattern for end of line match is useless. The :regexp:`$`
matches the end of string, but Pexpect reads from the child one character at a
time, so each character looks like the end of a line. Pexpect can't do a
look-ahead into the child's output stream. In general you would have this
situation when using regular expressions with any stream.

.. note::

  Pexpect does have an internal buffer, so reads are faster than one character
  at a time, but from the user's perspective the regex patterns test happens
  one character at a time.

The best way to match the end of a line is to look for the newline: ``"\r\n"``
(CR/LF). Yes, that does appear to be DOS-style. It may surprise some UNIX people
to learn that terminal TTY device drivers (dumb, vt100, ANSI, xterm, etc.) all
use the CR/LF combination to signify the end of line. Pexpect uses a Pseudo-TTY
device to talk to the child application, so when the child app prints ``"\n"``
you actually see ``"\r\n"``.

UNIX uses just linefeeds to end lines of text, but not when it comes to TTY
devices! TTY devices are more like the Windows world. Each line of text ends
with a CR/LF combination. When you intercept data from a UNIX command from a
TTY device you will find that the TTY device outputs a CR/LF combination. A
UNIX command may only write a linefeed (``\n``), but the TTY device driver
converts it to CR/LF. This means that your terminal will see lines end with
CR/LF (hex ``0D 0A``). Since Pexpect emulates a terminal, to match ends of
lines you have to expect the CR/LF combination::

    child.expect('\r\n')

If you just need to skip past a new line then ``expect('\n')`` by itself will
work, but if you are expecting a specific pattern before the end of line then
you need to explicitly look for the ``\r``. For example the following expects a
word at the end of a line::

    child.expect('\w+\r\n')

But the following would both fail::

    child.expect('\w+\n')

And as explained before, trying to use :regexp:`$` to match the end of line
would not work either::

    child.expect ('\w+$')

So if you need to explicitly look for the END OF LINE, you want to look for the
CR/LF combination -- not just the LF and not the $ pattern.

This problem is not limited to Pexpect. This problem happens any time you try
to perform a regular expression match on a stream. Regular expressions need to
look ahead. With a stream it is hard to look ahead because the process
generating the stream may not be finished. There is no way to know if the
process has paused momentarily or is finished and waiting for you. Pexpect must
implicitly always do a NON greedy match (minimal) at the end of a input.

Pexpect compiles all regular expressions with the :data:`re.DOTALL` flag.
With the :data:`~re.DOTALL` flag, a ``"."`` will match a newline.

Beware of + and * at the end of patterns
----------------------------------------

Remember that any time you try to match a pattern that needs look-ahead that
you will always get a minimal match (non greedy). For example, the following
will always return just one character::

    child.expect ('.+')

This example will match successfully, but will always return no characters::

    child.expect ('.*')

Generally any star * expression will match as little as possible.

One thing you can do is to try to force a non-ambiguous character at the end of
your :regexp:`\\d+` pattern. Expect that character to delimit the string. For
example, you might try making the end of your pattern be :regexp:`\\D+` instead
of :regexp:`\\D*`. Number digits alone would not satisfy the :regexp:`(\\d+)\\D+`
pattern. You would need some numbers and at least one non-number at the end.


Debugging
---------

If you get the string value of a :class:`pexpect.spawn` object you will get lots
of useful debugging information. For debugging it's very useful to use the
following pattern::

    try:
        i = child.expect ([pattern1, pattern2, pattern3, etc])
    except:
        print("Exception was thrown")
        print("debug information:")
        print(str(child))

It is also useful to log the child's input and out to a file or the screen. The
following will turn on logging and send output to stdout (the screen)::

    child = pexpect.spawn(foo)
    child.logfile = sys.stdout

Exceptions
----------

:class:`~pexpect.EOF`

Note that two flavors of EOF Exception may be thrown. They are virtually
identical except for the message string. For practical purposes you should have
no need to distinguish between them, but they do give a little extra information
about what type of platform you are running. The two messages are:

- "End Of File (EOF) in read(). Exception style platform."
- "End Of File (EOF) in read(). Empty string style platform."

Some UNIX platforms will throw an exception when you try to read from a file
descriptor in the EOF state. Other UNIX platforms instead quietly return an
empty string to indicate that the EOF state has been reached.

If you wish to read up to the end of the child's output without generating an
:class:`~pexpect.EOF` exception then use the ``expect(pexpect.EOF)`` method.

:class:`~pexpect.TIMEOUT`

The :meth:`~pexpect.spawn.expect` and :meth:`~pexpect.spawn.read` methods will
also timeout if the child does not generate any output for a given amount of
time. If this happens they will raise a :class:`~pexpect.TIMEOUT` exception.
You can have these method ignore a timeout and block indefinitely by passing 
``None`` for the timeout parameter::

    child.expect(pexpect.EOF, timeout=None)