3

In python, I am trying to connect thru ssh and play multiple commands one by one. This code works fine and output is printed out to my screen:

cmd = ['ssh', '-t', '-t', 'user@host']
p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
p.stdin.write('pwd\n')
p.stdin.write('ls -l\n') 
p.stdin.write('exit\n')   
p.stdin.close()

My problem is when I try to grab each response in a string. I have tried this but the read function is blocking:

cmd = ['ssh', '-t', '-t', 'user@host']
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
p.stdin.write('pwd\n')
st1 = p.stdout.read()
p.stdin.write('ls -l\n')    
st2 = p.stdout.read()
p.stdin.close()

3 Answers 3

2

I agree with Alp that it's probably easier to have a library to do the connection logic for you. pexpect is one way to go. The below is an example with paramiko. http://docs.paramiko.org/en/1.13/

import paramiko

host = 'myhost'
port, user, password = '22', 'myuser', 'mypass'
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.load_system_host_keys()
client.connect(host, port, user, password, timeout=10)

command = 'ls -l'
stdin, stdout, stderr = client.exec_command(command)
errors = stderr.read()
output = stdout.read()
client.close()
Sign up to request clarification or add additional context in comments.

Comments

1

The read() call is blocking because, when called with no argument, read() will read from the stream in question until it encounters EOF.

If your use case is as simple as your example code, a cheap workaround is to defer reading from p.stdout until after you close the connection:

cmd = ['ssh', '-t', '-t', 'deploy@pdb0']
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
p.stdin.write('pwd\n')
p.stdin.write('ls -l\n')
p.stdin.write('exit\n')    
p.stdin.close()
outstr = p.stdout.read()

You'll then have to parse outstr to separate the output of the different comamnds. (Looking for occurrences of the remote shell prompt is probably the most straightforward way to do that.)

If you need to read the complete output of one command before sending another, you have several problems. First this can block:

p.stdin.write('pwd\n')
st1 = p.stdout.read()

because the command you write to p.stdin might be buffered. You need to flush the command before looking for output:

p.stdin.write('pwd\n')
p.stdin.flush()
st1 = p.stdout.read()

The read() call will still block, though. What you want to do is call read() with a specified buffer size and read the output in chunks until you encounter the remote shell prompt again. But even then you'll still need to use select to check the status of p.stdout to make sure you don't block.

There's a library called pexpect that implements that logic for you. It'll be much easier to use that (or, even better, pxssh, which specializes pexpect for use over ssh connections), as getting everything right is rather hairy, and different OSes behave somewhat differently in edge cases. (Take a look at pexpect.spawn.read_nonblocking() for an example of how messy it can be.)

Even cleaner, though, would be to use paramiko, which provides a higher level abstraction to doing things over ssh connections. In particular, look at the example usage of the paramiko.client.SSHClient class.

Comments

1

Thanks for both of you for your answers. To keep it simple I have updated my code with:

def getAnswer(p, cmnd):
    # send my command
    if len(cmnd) > 0:
        p.stdin.write(cmnd + '\n')
        p.stdin.flush()
    # get reply -- until prompt received
    st = ""
    while True:
        char = p.stdout.read(1)
        st += char
        if char == '>':
            return st

cmd = ['ssh', '-t', '-t', 'user@host']
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
#discard welcome message
getAnswer(p, '')
st1 = getAnswer(p, 'pwd')
st2 = getAnswer(p, 'ls -l')
...
p.stdin.write('exit\n')
p.stdin.flush()
p.stdin.close()
p.stdout.close()

This is not perfect but works fine. To detect a prompt I am simply waiting for a '>' this could be improved by first sending a 'echo $PS1' and build a regexp accordingly.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.