running multiple commands at once + running commands with no output

260 views
Skip to first unread message

Nicholas Chammas

unread,
Mar 25, 2015, 7:11:34 PM3/25/15
to asyncss...@googlegroups.com

Following the streaming API example, I tried a few things and am not sure why they are not working.

Check out this script:

import asyncio
import asyncssh

@asyncio.coroutine
def run_client():
    conn, client = yield from asyncssh.create_connection(host='localhost', client_factory=None)

    stdin, stdout, stderr = yield from conn.open_session('ls')

    output = yield from stdout.read()
    print(output, end='')

    print("status: ", stdout.channel.get_exit_status())
    print("signal: ", stdout.channel.get_exit_signal())

    stdin, stdout, stderr = yield from conn.open_session('ls; date')
    output = yield from stdout.read()
    print(output, end='')  # only shows output from the ls

    print("status: ", stdout.channel.get_exit_status())
    print("signal: ", stdout.channel.get_exit_signal())

    stdin, stdout, stderr = yield from conn.open_session()  # assuming I am invoking a shell here, since there is no input command
    stdin.write("false\n")
    output = yield from stdout.read()  # hangs, presumably because there is no output?
    print(output, end='')

    print("status: ", stdout.channel.get_exit_status())  # I expect a 1 here
    print("signal: ", stdout.channel.get_exit_signal())

    conn.close()

try:
    asyncio.get_event_loop().run_until_complete(run_client())
except (OSError, asyncssh.Error) as exc:
    sys.exit('SSH connection failed: ' + str(exc))

My first command, ls, works fine by itself. However I can’t seem to combine commands with a semicolon, like ls; date, as only the output from the first command shows.

And running a command like false (which exits with a 1 and doesn’t print output) also seems to be problematic.

What’s the correct way to approach these tasks?

Ron Frederick

unread,
Mar 25, 2015, 11:54:00 PM3/25/15
to Nicholas Chammas, asyncss...@googlegroups.com
Hi Nicholas,

On your first question about the “ls; date” example, I think you have uncovered a bug. The documented behavior of read() with no length is to only return when EOF or a signal is received, collecting up all of the output until that point. However, the current implementation allows for “partial reads” in this case and it really shouldn’t. Partial reads should only be allowed when a byte count greater than 0 is passed in.

As a workaround for now, you can call readexactly(-1) on the stream and it will behave as read() should have. The fix also looks pretty simple, though. Here’s a patch you could apply:

diff --git a/asyncssh/stream.py b/asyncssh/stream.py
index 90f0587..41abaa6 100644
--- a/asyncssh/stream.py
+++ b/asyncssh/stream.py
@@ -304,7 +304,7 @@ class SSHStreamSession:
             if self._recv_buf_len < self._limit:
                 self._chan.resume_reading()

 

-            if n == 0 or (data and not exact) or self._eof_received:
+            if n == 0 or (n > 0 and data and not exact) or self._eof_received:
                 break

 

After a bit more testing, I’ll roll this into my version on Github.

Note that you can only count on getting all of the output in a single read() in a case where you are running a single command which exits. This same approach won’t work for an interactive shell, and that gets into the other question you had about the “false” command.

There are two things going on there. The first is what you already figured out, which is that if you call read and the command generates no output, you’re going to block. So, it is running the command but you can’t tell. You could confirm this by changing the command to something that generates output, but even then your read() will block once the above patch is in place, as you’d be waiting for EOF or a signal before the read() returns and the shell is still running so that won’t ever happen, You’d have to do a read() with a positive byte count to get it to return early, but then there would be no guarantee that you got all of the output which was generated (even if the length you specified was larger than what you are expecting). Basically, as soon as any output is available, a read() with a positive byte count will return it. You’d have to use readexactly() with a positive byte count if you didn’t want that, but then you’d need to know the exact number of bytes to read.

The other thing going on here is regarding the exit status. The thing to understand about that is that AsyncSSH is only going to be able to see the exit status of the shell which is started, not the exit status of individual commands within that shell. If you want to get exit status for each command, you need to create a separate SSH session for each command that ends when each command exits.

One way to see the exit status of the individual commands would be to ask the shell to print out that status after it is done running a command. If you made that output unique enough, you could keep reading the output a piece at a time until you saw that, and that would tell you the command had completed and what its status was. For instance, try something like:

    stdin, stdout, stderr = yield from conn.open_session()
    stdin.write("false; echo 'STATUS:' $status\n")
    output = yield from stdout.readline()

If there was other output and you didn’t know exactly how much of it to expect, the readline() would need to be in a loop of course, and you’d have to make sure none of the output looked like your final “STATUS” line, but hopefully this gives you some idea what’s going on here.

--
Visit the AsyncSSH home page at http://asyncssh.timeheart.net
---
You received this message because you are subscribed to the Google Groups "asyncssh-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to asyncssh-user...@googlegroups.com.
To post to this group, send email to asyncss...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/asyncssh-users/2648f98f-22b5-47d2-ad66-0dd9ec6ae814%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
-- 
Ron Frederick



Nicholas Chammas

unread,
Mar 26, 2015, 11:19:01 AM3/26/15
to asyncss...@googlegroups.com, nicholas...@gmail.com

I’ll wait for the patch regarding multiple commands on one line. Thanks for getting to the root of the issue.

As for the shell behavior, makes sense. If I try:

stdin, stdout, stderr = yield from conn.open_session(‘false’)
output = yield from stdout.read()
print(output, end=’’)

I get the expected output, no hanging.

Thank you for thoroughly addressing my questions.

Nick

Ron Frederick

unread,
Mar 27, 2015, 10:51:42 PM3/27/15
to Nicholas Chammas, asyncss...@googlegroups.com
On Mar 26, 2015, at 11:19 AM, Nicholas Chammas <nicholas...@gmail.com> wrote:
I’ll wait for the patch regarding multiple commands on one line. Thanks for getting to the root of the issue.

This change is now checked into github, in both the master and develop branches.

As for the shell behavior, makes sense. If I try:

stdin, stdout, stderr = yield from conn.open_session(‘false’)
output = yield from stdout.read()
print(output, end=’’)

I get the expected output, no hanging.

Thank you for thoroughly addressing my questions.


My pleasure!
-- 
Ron Frederick



Reply all
Reply to author
Forward
0 new messages