pty.spawn() and friends

Chris Gonnerman chris.gonnerman at newcenturycomputers.net
Sat Dec 29 12:39:42 EST 2001


----- Original Message ----- 
From: "James T. Dennis" <jadestar at idiom.com>


>  I've been trying to learn more Python and I've been sticking with 
>  the somewhat obscure kinds of things that I've routinely needed to
>  do as a system administrator.  I came across the pty module as a
>  primitive alternative to using expect and it seems like I should
>  be able to so simple work with it.
> 
>  So naturally I tried to implement the simplext expect script
>  (autopasswd) as a Python script using the pty.spawn() function.
> 
>  This doesn't work as I'd expect.

I about pulled a cranial muscle understanding the pty.spawn() code;
evidently it runs a subprocess which communicates via callbacks.
If you give no callback arguments, it does indeed behave much
like os.system().

>  ... well it certainly baffles the programmer that tries to use it.

No kidding.  Ouch.
 
>  My expectation was that this would spawn the program in question
>  ("/usr/bin/passwd" for my tests) and allow me to print to its 
>  input and read from its output; under programmatic control.  I 
>  would thus have to cross-wire the inputs and outputs of the child 
>  process with some sort of handle, object, or file descriptor on 
>  my (parent) process.

To do this, you need to roll a wrapper function using pty.fork().

>  However it seem like pty.spawn() doesn't do any cross-wiring.

It does, if you use the callbacks.

>  It seems to simply start a sub-process that interacts with my 
>  terminal, basically like the os.system() function.  (The 
>  difference is that pty.spawn() takes a tuple of arguments while 

Huh?  Three arguments does not a tuple make (at least from the 
caller's point-of-view.

>  os.system() takes a  string and passes it to a shell for parsing 
>  and execution).
> 
>  That seems pretty lame.  It's like the author of the pty module
>  didn't know what the author of the os module was doing and 
>  provided a gratuitously similar function with no actual relevance 
>  to psuedo-ttys and no additional functionality.  In so doing, 
>  they've raised (my) expectations and failed to satisfy them.

Explained above.

>  What pty.spawn() *should* do (to meet with reasonable expectations
>  that it would have something to do with spawning subprocesses 
>  *UNDER THE CONTROL OF A psudo-tty*) is return a file-like object 
>  to which one can write (commands or "keystrokes") and from which 
>  one could read (responses, prompts, potentially screen updates).  

Also explained above.

I'm not real sure why it was done this way; the callback method
is not my first choice.  It might interact well with GUI code, 
though.

>  Ideally this would come with some convenience methods, and 
>  members/constants (possibly inherited from a common module with 
>  curses, and/or tty, to allow one to send keystrokes by their 
>  symbol names (rather than having to manually fuss with escape 
>  sequences) and receive data as structures of character/attribute 
>  data).  (Imagine being able to run a child ncurses monitoring 
>  process and automatically programmatically respond to a specific 
>  bit of text "turning red" or "going on the blink").

Sounds good.  When will you post the code?  :-) :-)  [humor,ar,ar]

>  None of this would be nearly so irritating if USAGE EXAMPLES had 
>  been provide to explain what the programmer should expect of the 
>  program.

Ditto here!

>  Naturally I had to fuss with it for some time before figure out 
>  that it really was a lame and redundant function rather than my 
>  own lack of skill.  The fact that the reference docs give a couple 
>  of optional arguments: [,master_read[,stdin_read]] which are 
>  supposed to be:
> 
> functions which read from a file-descriptor
> 
>  ... complicates it further.  What kind of function could I write 
>  that "reads from a file-descriptor" but which takes no arguments 
>  telling it *which* file-descriptor! 

CALLBACKS.  You implement a function like so:

def my_read_callback(fd, nbytes):
    # your code here.

and the pty.spawn() function calls it, assuming you do this:

    pty.spawn(my_argv, my_read_callback, my_std_read_callback)

(for instance).

>  At first I thought this was trying to say to call pty.spawn() with
>  one argument to act sort of like os.system(), or two arguments
>  (a tuple and a function invocation) to spawn a process under the
>  control of that function.  (Envisioning something where *that 
>  function's* stdin and stdout where wired up to the spawned 
>  process' stdout and stdin respectively).  (If that was the case 
>  I'd expect that the optional third argument would be another 
>  controlling function for handling the child process' stderr; 
>  communications between the two would be "merely a matter of 
>  programming").

Hopefully my explanation makes this clearer.  Handling stderr 
*would* be nice...  (or does handling stdout also handle stderr
with pty's?  I haven't tried it.)

>  Ugh!  Between this and the equally frustrating Python curses docs
>  I'm pretty grumpy.

curses is called that for a reason.  ;-)

If you aren't a curses programmer at the C level it is certainly 
hard to understand.






More information about the Python-list mailing list