Talking with piped shell/ssh

pehr anderson pehr at pehr.net
Wed Sep 13 00:17:59 EDT 2000


Dear Hartmut, 

I just found some references for how to use popen2 to 
do more expect-like things.

Specificially, the way to wait for a variety of respones is:

f.read ((string, string, ...))
    * If a tuple of strings are passed, then read() reads up to and
        including the first matching string.

http://www.leo.org/cgi-bin/leo-peek.pl/pub/comp/general/programming/languages/script/python/contrib/expect-1.0.1.tar.gz?expect-1.0.1/README


Here is the entire file:


Expect Module for Python
------------------------

This is Python module that does most of what you would normally
need from Tcl's expect, but it works like a standard popen(1)
call but with BOTH reading AND writing.


FTP:
----

ftp://ftp.obsidian.co.za/pub/expect/


Example:
--------

    #!/usr/bin/python

    import sys
    from expect import *

    f = popen2 ("passwd " + sys.argv[1])
    print f.read ("word: ")
    f.write (sys.argv[2])
    print f.read ("word: ")
    f.write (sys.argv[2])
    print f.read ()

or

    #!/usr/bin/python

    import sys, time
    from expect import *

    f = popen2 ("passwd " + sys.argv[1], "p")
    print f.read (": ")
    time.sleep (0.1)
    f.write (sys.argv[2] + "\r\n")
    print f.read (":")
    time.sleep (0.1)
    f.write (sys.argv[2] + "\r\n")
    print f.read()


Classes:
--------

There are three classes defined by this module:
    popen - this is like a standard UNIX popen command
            the difference being that you can both read
            and write to the result file object. The stderr
            stream of the process is NOT redirected and
            will hence appear on the terminal exactly like
            with popen or back-quotes under sh.

    popen2 - like popen, but both stderr AND stdout of the
            process can be read through the same pipe. i.e.
            f.read() calls return ALL output of the process.

    popen3 - like popen2, but stderr and stdout are read
            through two separate functions: f.read()
            returns stdout of the process. f.read_error()
            returns stderr.


How it works:
-------------

This is a completely standalone implementation of popen. It does
not rely on the existing popen in any way.

The problem with reading and writing to a process is that it is
easy to write code that blocks indefinitely waiting for IO.

This library works by queueing reads when doing either a read or
a write call. Read operations are buffered so that incoming data
(the stderr and stdout of the process) are queued in
anticipation of any future read(). write() operations are
unbuffered.

This allows you to arbitrarily read to and write from a process
without worrying about an indefinite block. It is hence very
similar to Tcl's expect program.


Invocation:
-----------

f = popen (command, options)
f = popen2 (command, options)
f = popen3 (command, options)

command is a string. command is a shell program passed as <arg> to
    /bin/sh -c <arg>
options is a string. If options contains the character `b' then the
blocking is turned off. read() and write() will then not block waiting
for data. This is untested.

If options contains the character `p', then a pseudo tty is
opened for the process. The process will still inherit all
environment variables, hence for consistent behaviour, the
caller may want to explicitly set the TERM environment variable.
Note that many programs follow different behaviour depending on
whether they are attached to a tty or not.

In the passwd examples above, passwd is happy to read from a
pipe. If you use a terminal (`p') then you are required to send
a '\n' at the end of the line, and pause a small amount while
the getpass C library function switches into ICANON mode
(whatever that is) causing passwd to raw read separately from
the normal read queue.

All other letters passed to options are ignored.


Methods:
--------

f.read (integer)
    * Can take a normal integer to read a finite amount of data in the
        same way as usual file object's read does.

f.read ()
    * If no arguments are passed, then this reads as much data as is
        outputted by the process, blocking until it reads nothing
        (i.e. the process has exited).

f.read (string)
    * If a single string is passed, then read() reads up to and
        including the matching string.

f.read ((string, string, ...))
    * If a tuple of strings are passed, then read() reads up to and
        including the first matching string.

f.read_error ()
    * works just like read. Typically you would check for errors with
        read_error() after calling read().

f.close ()
    * closes all pipes and returns the exit code of the process. If
        the process has been killed, then this raises an error giving
        the terminating signal.

f.flush ()
    * does nothing

f.isatty ()
    * returns 0

f.readline ()
    * reads up to an including a newline character. takes no arguments

f.readlines ()
    * analogous to file objects readline(). takes no arguments

f.seek (offset, whence)
    * reads and discards offset amount of data. whence must be 1.
        returns nothing

f.tell ()
    * returns the total amount of data read from stdout (or in the case
        of popen2) from stdout and stderr.

f.writelines (list)
    * analogous to file objects writelines (). returns nothing.

f.pid ()
    * get the pid of the child process. Returns -1 if the child
        has died.

f.setblocking (block)
    * takes an integer. 1 turns blocking on, 0 turns blocking off.
        This overrides the options string.

f.command
    * a copy of the command.


Backquote notation:
-------------------

Backquotes work the same as in sh. It does this by binding the
the __repr__ method. Hence

    a=`popen ("echo hello")`

Works just like

    a=`echo hello`

under a sh or perl.


Intricacies:
------------


- To save invocation of /bin/sh, popen checks if the command
    contains any shell special characters i.e.  > ` ! < & $ \n ;
    ( ) { }  and then, failing this, tries to interpret the
    command-line by itself, following the exact conventions of
    bash. If you are not convinced, add in a ; at the end of
    your command to force use of /bin/sh.

- If you try to close a process that has stopped or still executing,
    close blocks waiting for the process to die.

- del f  is just like f.close, but sends the process the
    SIGTERM signal before blocking waiting for the child to die.
    Since a process can intercept the SIGTERM signal, its is
    still possible for Python to block at an arbitrary location.
    For instance:

        def func ():
            f = popen2 ("prog")
            time.sleep (0.1)

        func ()

    If prog were to ignore SIGTERM, then func would block until
    prog had exited because python dereferences  f  before
    returning from func.

    (The reason you need to sleep(0.1) is to give prog a chance
    to set its signal handlers.)

- f.p is the internal object that you would normally not be
    interested in. However `f.p` prints useful output like:

        f = popen2 ("echo 'Hi there!'")
        print `f.p`
        <ExpectObject running command "echo 'Hi there!'" as
                pid 10373 at 80b30c0>

- You can carry on reading from a process even after it has
    died and been close()'d. So long as there is still data
    in the queue. If there is no data, read just returns an
    empty string.

- Opening a pty currently works BSD style using /dev/pty??
    To support other kinds, I'll probably copy some of the
    rxvt code across.




hartmut Goebel wrote:
> 
> Hello,
> 
> I'm trying to spawn a shell (more exactly a ssh on a remote machine) and then
> want to talk with it. I've played around with popen2.Popen3() and managed to
> spawn the shell (which is not a problem anyway ;-).
> 
> I managed to read the input in some special cases but when trying to
> generalize them, I failed.
> 
> Is there any example source or snippet I can use?
> 
> Thanks for any pointer!
> 
> +++hartmut



More information about the Python-list mailing list