How do I write to stdin (returned from exec_command) in paramiko?

Mark C. picture Mark C. · Feb 1, 2018 · Viewed 7.6k times · Source

I am trying to write to a custom program's stdin with paramiko. Here is a minimal (non-)working example:

~/stdin_to_file.py:

#! /usr/bin/python

import time, sys
f = open('/home/me/LOG','w')
while True:
    sys.stdin.flush()
    data = sys.stdin.read()
    f.write(data+'\n\n')
    f.flush()
    time.sleep(0.01)

Then I do these commands in IPython:

import paramiko
s = paramiko.client.SSHClient 
s.load_system_host_keys()
s.connect('myserver')
stdin, stdout, stderr = s.exec_command('/home/me/stdin_to_file.py')
stdin.write('Hello!')
stdin.flush()

Unfortunately, nothing then appears in ~/LOG. However, if I do

$ ~/stdin_to_file.py < some_other_file

The contents of some_other_file appear in ~/LOG.

Can anyone suggest where I've gone wrong? It seems like I'm doing the logical thing. None of these work either:

stdin.channel.send('hi')
using the get_pty parameter
sending the output of cat - to stdin_to_file.py

Answer

pynexj picture pynexj · Feb 1, 2018

sys.stdin.read() will keep reading until EOF so in your paramiko script you need to close the stdin (returned from exec_command()). But how?

1. stdin.close() would not work.

According to Paramiko's doc (v1.16):

Warning: To correctly emulate the file object created from a socket’s makefile() method, a Channel and its ChannelFile should be able to be closed or garbage-collected independently. Currently, closing the ChannelFile does nothing but flush the buffer.

2. stdin.channel.close() also has problem.

Since stdin, stdout and stderr all share one single channel, stdin.channel.close() will also close stdout and stderr which is not expected.

3. stdin.channel.shutdown_write()

The correct solution is to use stdin.channel.shutdown_write() which disallows writing to the channel but still allows reading from the channel so stdout.read() and stderr.read() would still work.


See following example to see the difference between stdin.channel.close() and stdin.channel.shutdown_write().

[STEP 101] # cat foo.py
import paramiko, sys, time

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy() )
ssh.connect(hostname='127.0.0.1', username='root', password='password')
cmd = "sh -c 'read v; sleep 1; echo $v'"
stdin, stdout, stderr = ssh.exec_command(cmd)
if sys.argv[1] == 'close':
    stdin.write('hello world\n')
    stdin.flush()
    stdin.channel.close()
elif sys.argv[1] == 'shutdown_write':
    stdin.channel.send('hello world\n')
    stdin.channel.shutdown_write()
else:
    raise Exception()
sys.stdout.write(stdout.read() )
[STEP 102] # python foo.py close           # It outputs nothing.
[STEP 103] # python foo.py shutdown_write  # This works fine.
hello world
[STEP 104] #