1

I want to use the subprocess module to control some processes spawned via ssh.

By searching and testing I found that this works:

import subprocess
import os
import time

node = 'guest@localhost'
my_cmd = ['sleep','1000']
devnull = open(os.devnull, 'wb')
cmd = ['ssh', '-t', '-t', node] + my_cmd
p = subprocess.Popen(cmd, stderr=devnull, stdout=devnull)


while True:
    time.sleep(1)
    print 'Normal output'

The -t -t option I provide allows me to terminate the remote process instead of just the ssh command. But this, also scrambles my program output as newlines are no longer effective making it a long and hard to read string.

How can I make ssh not affecting the formatting of the python program?

Sample output:

guest:~$ python2 test.py 
Normal output
             Normal output
                          Normal output
                                       Normal output
                                                    Normal output
                                                                 Normal output
                                                                              Normal output
(First ctrl-c)
Normal output
Normal output
Normal output
(Second ctrl-c)
^CTraceback (most recent call last):
  File "test.py", line 13, in <module>
    time.sleep(1)
KeyboardInterrupt
Cœur
  • 37,241
  • 25
  • 195
  • 267
Mini Fridge
  • 919
  • 12
  • 24
  • 1
    Can you provide sample output? It's unclear what you mean about it "scrambling". – SuperBiasedMan Jun 29 '16 at 11:30
  • stackoverflow will allign everything. The output will not allign to the start of the terminal after changing line. The messages are printing correctly though. – Mini Fridge Jun 29 '16 at 11:37
  • also if I ctrl-c and get back to my terminal, by pressing enter the command line is not printing in new line but next to my previous line. Something to do with -t -t option in ssh. – Mini Fridge Jun 29 '16 at 11:39
  • @SuperBiasedMan provided some output with an edit. – Mini Fridge Jun 29 '16 at 11:47
  • Tthe -t -t option forces ssh to use a pty on peer. That means that you can get terminal control characters. But where does your *long and hard to read string* comes from? Do you capture it in a Python variable? – Serge Ballesta Jun 29 '16 at 12:07
  • @SergeBallesta "But where does your long and hard to read string comes from?". I am not sure what you are asking. Is -t -t the right way to go, anyway? – Mini Fridge Jun 29 '16 at 12:09
  • `-t -t` is useful when you need to use full screen terminal programs like vi that insist on being run on a true terminal. My question is: is there a variable in your Python code that contains the output you showed? – Serge Ballesta Jun 29 '16 at 12:18
  • @SergeBallesta No, the output in the question produced is produced by standard python print statements, and raised exceptions by my main python program, not by the subprocesses. The subprocess output is suppresed to /dev/null. – Mini Fridge Jun 29 '16 at 12:24
  • Your code works for me. There must be something else in your "main python program" which is causing this. You'll have to include more of your code. – Aya Jun 29 '16 at 13:37
  • The command `sleep 1000` (even it last a looong time) does not require a tty. Would removing the `-t -t` be enough to suppress the *scrambling*? – Serge Ballesta Jun 29 '16 at 13:43
  • @Aya I edited with a testable script that does what i say provided you've setup ssh-key for not asking a password. – Mini Fridge Jun 29 '16 at 13:49
  • @SergeBallesta If -t -t is not present and I kill the application, a ps aux | grep sleep will show the sleep command is still running. I want to be able to kill the sleep process from python. If -t -t is not present the output is printed properly. – Mini Fridge Jun 29 '16 at 13:51
  • @MiniFridge Ah. I can reproduce it now. – Aya Jun 29 '16 at 14:08
  • Well. I've found *a* solution, but it may not be appropriate depending on what you want to do with the remote process, which I presume *isn't* going to be `sleep` in practice. What's the actual use-case? – Aya Jun 29 '16 at 14:47
  • @Aya It starts up a Java service. Ideally I'd like to pass the same signal, to the Java when I SIGKILL, or SIGTERM the service. – Mini Fridge Jun 29 '16 at 15:03
  • Probably moot now, since you have a fairly complete answer, but if you have no need to send input, I was going to suggest redirecting `stdin` from `/dev/null` in the call to `subprocess.Popen()` which worked for me. – Aya Jun 29 '16 at 15:13
  • @Aya Cool, I suppose this will solve the same problem. Thanks for your input! – Mini Fridge Jun 29 '16 at 16:02

1 Answers1

2

Ok, the output is now clear. I do not exactly know why, but the command ssh -t -t puts the local terminal in raw mode. It makes sense anyway, because it is intended to allow you to directly use curses programs (such as vi) on the remote, and in that case, no conversion should be done, not even the simple \n -> \r\n that allows a simple new line to leave the cursor on first column. But I could not find a reference on this in ssh documentation.

It (-t -t) allows you to kill the remote process because the raw mode let the Ctrl + C to be sent to the remote instead of being processed by the local tty driver.

IMHO, this is design smell, because you only use a side effect of the pty allocation to pass a Ctrl + C to the remote and you suffer for another side effect which is the raw mode on local system. You should rather process the standard input (stdinput = subprocess.PIPE) and explicitely send a chr(3) when you input a special character on local keyboard, or install a signal handler for SIG-INT that does it.

Alternatively, as a workaround, you can simply use something like os.system("stty opost -igncr") (or better its subprocess equivalent) after starting the remote command to reset the local terminal in an acceptable mode.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Thank you for your answer. The ctrl-c was an example really I will kill or terminate the process, via the subprocess module (p.kill() or p.terminate()) so I don't care about forwarding signals to the subprocess. stdin=subprocess.PIPE did the trick. – Mini Fridge Jun 29 '16 at 15:08
  • If he doesn't allocate a tty on the remote system, then sending Ctrl-C won't have any particular effect. The tty driver converts the character into the INTR signal. – Kenster Jun 29 '16 at 15:11