redirected Channels closed after interactive run

8 views
Skip to first unread message

Armin Poschmann

unread,
May 3, 2021, 4:40:15 AM5/3/21
to asyncssh-users
Hi all, hope I am right here. 
I am trying to create a loop for an interactive ssh command with asyncssh. This works as i need it for one host, but after first run all redirect channels are closed. See code below. 
Is there any way to get around that?

----------------
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import asyncio, asyncssh, sys

async def run_client(host,cmd):
    async with asyncssh.connect(host) as conn:
        await conn.run(cmd, term_type='xterm-256color', 
            stdin=sys.stdin,
            stdout=sys.stdout, 
            stderr=sys.stderr
        )

cmd = 'bc'
hosts = 2 * [ 'localhost' ]
        
for host in hosts:
    try:
        print("--- Run on:",host) 
        asyncio.get_event_loop().run_until_complete(run_client(host,cmd))
    except (OSError, asyncssh.Error) as exc:
        sys.exit('SSH connection failed: ' + str(exc))
 

Ron Frederick

unread,
May 3, 2021, 10:16:56 AM5/3/21
to Armin Poschmann, asyncssh-users
Do you need stdin redirected here, or do you just need stdout and stderr? Trying to redirect stdin to multiple commands could be tricky, as the input would already be consumed by the first command, leaving nothing for later commands to read.

If you only need stdout and stderr, are you expecting a large amount of output such that you really need to be incrementally outputting it to the main stdout/stderr as you do, or would it be ok to buffer up that output until each command finishes? If the amount of output is small, the easiest thing to do would be to call conn.run() without redirection and then reference result.stdout as shown at https://asyncssh.readthedocs.io/en/latest/#running-multiple-clients. It only shows outputting stdout, but if you add a terminal type to the run() all, that will cause all of the output to both stdout and stderr to be merged together, which may be good enough for what you need here.

If there’s a lot of output and you don’t want to buffer it until the command completes, you could do something like:

import asyncio, asyncssh

async def run_client(host, command):
    try:
        async with asyncssh.connect(host) as conn:
            async with conn.create_process(command) as proc:
                print(f'{host}: CONNECTION OPEN')

                async for line in proc.stdout:
                    print(f'{host}: {line}', end='')

                print(f'{host}: CONNECTION CLOSED')
    except (OSError, asyncssh.Error) as exc:
        print(f'{host}: CONNECTION FAILED: {exc}')

async def run_multiple_clients():
    # Put your lists of hosts here
    hosts = ['localhost', '0.0.0.1', '0.0.0.2']

    tasks = (run_client(host, 'tail -f /var/log/system.log') for host in hosts)
    await asyncio.gather(*tasks)

asyncio.get_event_loop().run_until_complete(run_multiple_clients())

This has the advanced of adding a prefix to every line of output with what host the output came from, which may be useful to you. If you set a terminal type in the conn.run() call, you can get the remote SSH server to merge both stdout and stderr into a single stream, so the relative order of that output will be preserved, if that’s important.

If you need to keep stdout and stderr separate and you don’t want to buffer the output, this may be possible, but it does raise questions about how you’ll be able to know where to boundaries are between output coming from the different hosts. Your example adds some output to stdout between commands, but doesn’t put anything out to stderr that would let you know where the boundary is there.

It may be possible to do what you’re trying to do using redirection if you set “send_eof=False” on the conn.run() call, but I haven’t specifically tried that. The main downside here is that you can’t modify the output in any way as it passes through, which can make it difficult to identify which host it came from.
-- 
Ron Frederick
ro...@timeheart.net



Reply all
Reply to author
Forward
0 new messages