5

I need to execute multiple shell commands in a ssh session using the subprocess module. I am able to execute one command at a time with:

   subprocess.Popen(["ssh",  "-o UserKnownHostsFile=/dev/null", "-o StrictHostKeyChecking=no", "%s" % <HOST>, <command>])

But is there a way to execute multiple shell commands with subprocess in a ssh session? If possible, I don't want to use packages.

Thank you very much!

GuiGWR
  • 125
  • 1
  • 20
  • For just multiple commands this might be an overkill, but you could write out to a shell script, scp it over, run it remotely and collect output with return codes. Again this only makes sense for more complex logic and larger number of commands. For few commands, just chain them with "&&", ";", "||" or whatever else makes sence – Sergey Jun 30 '18 at 05:27
  • 1
    @Sergey The ':' and '&&' options are both outlined in my answer, as well as using stdin combined with `sh -s` remotely to do what I think is a nicer version of the `scp` and run approach. – Matthew Story Jun 30 '18 at 05:32

1 Answers1

5

Strictly speaking you are only executing one command with Popen no matter how many commands you execute on the remote server. That command is ssh.

To have multiple commands executed on the remote server just pass them in your command string seperated by ;s:

commands = ["echo 'hi'",  "echo 'another command'"]
subprocess.Popen([
    "ssh",
    "-o UserKnownHostsFile=/dev/null",
    "-o StrictHostKeyChecking=no",
    ";".join(commands)
])

You could alternatively join commands on && if you wanted each command to only execute if the previous command had succeeded.

If you have many commands and you are concerned that you might exceed the command line limits, you could execute sh (or bash) with the -s option on the remote server, which will execute commands one-by-one as you send them:

p = subprocess.Popen([
    "ssh",
    "-o UserKnownHostsFile=/dev/null",
    "-o StrictHostKeyChecking=no",
    "sh -s",
], stdin=subprocess.PIPE)

for command in commands:
    p.stdin.write(command)
    p.stdin.write("\n")
    p.flush()

p.communicate()

Note that in Python3 you will need to encode the command to a byte string (command.encode("utf8")) before writing it to the stdin of the subprocess.

I feel like this is overkill for most simple situations though where the initial suggest is simplest.

Matthew Story
  • 3,573
  • 15
  • 26
  • Didn't know about the `-s` option with `sh`, since I have a huge amount of commands to execute and for a safety approach, I'll try the second method you propose. Thank you! – GuiGWR Jun 30 '18 at 12:31
  • Not sure the `sh -s` is necessary; I tried this without, and it still seems to work. – Huw Walters Jan 18 '20 at 14:49