0

I created Python GUI that takes a list of commands as input and executes the list through a Telnet or SSH session. While opening a SSH session (using Paramiko in Python) I run commands on various devices using this code in a for loop:

                    stdin.write(command+"\n")
                    then = time.time()
                    timeout=10.0
                    while not stdout.channel.exit_status_ready():

                        if timeout is not None:
                            timeTest =time.time() - then
                            if timeout <= timeTest:
                                break 

                    # Only print data if there is data to read in the channel
                        if stdout.channel.recv_ready():

                            # Write data from stdout
                            temp=str(stdout.channel.recv(1024))
                            print temp
                            if (temp.endswith(delim)) or (temp.endswith("# ")) :
                                print "successful exit"
                                break

The code is designed to be used on modems that have BusyBox installed. Thus it's common for a user to enter a command to open busybox and run a sequence of commands in the BusyBox shell. As you can see in this line of code "if (temp.endswith(delim)) or (temp.endswith("# "))" the while loop is suppose to break when the command prompt of busybox is detected which is a "# " (this means the command has finished outputting).

The problem I'm having is that BusyBox isn't printing the command prompt to stdout or in the debug line "print temp". Why is this? The command outputs (an example is ls -l) are successfully printed to stdout but not the command prompt or busybox intro message: when a user enters busybox on these modems a introduction message is printed "BusyBox v1.17.2 (2014-10-02 10:50:35 PDT) built-in shell (ash) Enter 'help' for a list of built-in commands." which is also not printed to STDOUT. This is forcing the code to utilize the timeout for each command executed in busybox which is undesirable, i.e. it's slow and there could be command outputs that take longer than the desired timeout so it's best to look for the command prompt.

Is this issue due to the implementation of ash in BusyBox? Is there a way to receive the command prompt text?

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Evan
  • 65
  • 6

2 Answers2

0

See the entry on PS1 (the variable specifying the prompt to emit) in IEEE standard 1003.1 (emphasis added):

PS1

Each time an interactive shell is ready to read a command, the value of this variable shall be subjected to parameter expansion and written to standard error. The default value shall be "$ ". For users who have specific additional implementation-defined privileges, the default may be another, implementation-defined value. The shell shall replace each instance of the character '!' in PS1 with the history file number of the next command to be typed. Escaping the '!' with another '!' (that is, "!!" ) shall place the literal character '!' in the prompt. This volume of POSIX.1-2008 specifies the effects of the variable only for systems supporting the User Portability Utilities option.

Thus, any POSIX-compliant shell -- not just busybox ash, but ksh, dash, bash, and all the rest -- must write its prompt to stderr, not stdout.


Your simple fix, then, is to either read from stderr, or to use redirection to combine the streams (running exec 2>&1, for instance).

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • The $PS1 variable has no value upon entering a BusyBox ash shell on the modems, wouldn't you expect echo $PS1 to display "# ". Also I read everything from STDERR and the command prompt doesn't show up. – Evan Dec 09 '14 at 22:25
  • No, I wouldn't expect `echo "$PS1"` to necessarily display anything -- `PS1` is required to be honored if it exists, but it isn't required to exist. That busybox is using `#` rather than `$` as default when running as root violates POSIX, but is in line with long-standing conventions, and is thus likewise unsurprising. – Charles Duffy Dec 09 '14 at 22:30
  • As for still not seeing a prompt when you attempt to read stderr... well, you aren't providing a reproducer, so it's hard to say. Could be that you're not configuring the channel in such as a way to have a TTY assigned. Could be that you're blocking on a different read elsewhere. I could keep speculating for as long as I had patience to, and only have an even chance of guessing as to where your actual bug is -- but when running as an interactive shell with a TTY, ash quite certainly writes prompts to stderr rather than stdout. – Charles Duffy Dec 09 '14 at 22:32
  • Yes, I was just proving that it doesn't exist. Thus your answer no longer applies to my scenario. Thanks for the help though! – Evan Dec 09 '14 at 22:40
  • @Evan, the standard specifies a default. Thus, even if doesn't exist, a POSIX shell is required to behave as if it did _and use that default value_. – Charles Duffy Dec 09 '14 at 22:52
  • You may be on to something with the TTY assigning. I'm not sure if you're familiar with Paramiko but the command:stdin, stdout, stderr =ssh.exec_command("sh\n")" never actually executes in the SSH session. I'm forced to use stdin.write as shown in my sample code. I think the correct technique would be this sequence: transport = ssh.get_transport();channel=transport.open_session() ;stdin, stdout, stderr=channel.exec_command("sh\n"); but I get an object is not iterable on the last line. – Evan Dec 09 '14 at 23:03
  • `exec_command()` is generally used for noninteractive commands. If you want an interactive shell, you should be using `invoke_shell()` instead. – Charles Duffy Dec 09 '14 at 23:06
  • ...and if you're going to use `invoke_shell()`, I'd strongly suggest `get_pty()` first. – Charles Duffy Dec 09 '14 at 23:08
  • By the way, on some investigation, it looks like some versions of busybox *do* break POSIX with respect to their shell prompts. *sigh*. – Charles Duffy Dec 09 '14 at 23:08
  • It worked! Thanks a bunch Charles! Using get_pty() then invoke_shell() instead of exec_command() fixed the issue; the command prompt and BusyBox intro message are now displayed and I can adjust my code for desired results. – Evan Dec 09 '14 at 23:34
0

First: If you're trying to recognize shell prompts when invoking a shell programmatically, you're Doing It Wrong.

If you use exec_command() on a new channel (over the same transport) for each command you want to run, you'll get a separate stdout, stderr, etc. for each command; have the exit status for that command individually reported, and never need to guess whether a piece of output is from the command or from the outer shell.

Second: If you really want to do it that way (and, again, you shouldn't!), you should be using invoke_shell() after a get_pty() call, not exec_command('sh').

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441