0

I need to ssh to a remote machine and then execute a few cmds using python 3+.

Based on this answer https://stackoverflow.com/a/57439663/2175783 I tried

cmds = "cmd1; ./script.sh"
output, errors = subprocess.Popen(f'ssh user@{ip} {cmds}', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()

where script.sh is a bash script.

But only cmd1 seems to execute (I dont see output from script.sh only output from cmd1)

Anything obviously wrong?

user2175783
  • 1,291
  • 1
  • 12
  • 28

4 Answers4

2

Your problem is that ./script.sh is executed locally, not remotely, try this (notice the single quotes inside double quotes of cmds):

python << EOF
import subprocess
cmds = "'echo Hello; hostname'"
output, errors = subprocess.Popen(f'ssh user@{ip} {cmds}', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
print(output)
EOF
Philippe
  • 20,025
  • 2
  • 23
  • 32
  • That doesnt seem to make a difference. I think the issue is that the first cmd sets an environment [sets some env vars, enables a python virtual env, etc] in which the script (the second command) then should run. – user2175783 Jul 26 '23 at 01:54
  • Then there must be other errors, Add `print(errors)` at the end of above python script, and see what's wrong. – Philippe Jul 26 '23 at 02:45
  • A command can only set the environment for children, so once the first command finishes, everything is the same as before – Diego Torres Milano Jul 26 '23 at 03:04
1

You should use fabric and do something like

with Connection(host=ip, user=user) as c:
   c.run('command1')
   c.run('command2')
Diego Torres Milano
  • 65,697
  • 9
  • 111
  • 134
1

At least part of the problem is that the local shell (invoked by shell=True) is processing the command before it's sent to the remote system, so the ; in the command string is treated as a command delimiter on the local system (and the part after ; executes locally rather than being sent to the remote system). If the actual command is more complex, it may be doing other unwanted parsing (e.g. replacing $variable in the command with the local value of that variable).

At least in the example in the question, the local shell isn't doing anything useful, so one solution is to use shell=True. There is one other change, though: rather than passing the command as a single string, you need to pass it as a list of words, like ["ssh", f"user@{ip}", cmds]:

output, errors = subprocess.Popen(["ssh", f"user@{ip}", cmds], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()

Avoiding expanding $variable references locally might solve the environment variable problems you're having; if not, you'd have to explain how you're defining and using the variables in question.

Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
1

If your first cmd (cmd1) creates a shell in which the second script should run, I have found the following approach works

  1. Create a second wrapper script called wrapper_script.sh containing both cmds
cmd1<<EOF
/path/to/script.sh
EOF
  1. Run the wrapper script
import subprocess
cmds = "'wrapper_script.sh'"
output, errors = subprocess.Popen(f'ssh user@{ip} {cmds}', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
print(output)
JennyToy
  • 588
  • 4
  • 19