[Tutor] run local script on a remote machine

Cameron Simpson cs at zip.com.au
Thu Oct 27 03:22:21 EDT 2016


On 26Oct2016 10:44, Alex Kleider <akleider at sonic.net> wrote:
[... snipped experiment.py and the demo call.sh script ...]
>3:
>#!/usr/bin/env python3
>#
># file: call.py
>
>import os
>import shlex
>import subprocess
>
>script = "/home/alex/Py/BackUp/Sandbox/Scripted/experiment.py"

This is fine.

>if os.path.isfile(script):
>    print("File exists on local machine.")
>else:
>    print("No such file.")

This is fine.

>command = (
>"ssh -p22 alex at 10.10.10.10 python3 -u - one two three < {}"
>    .format(script))
>ret = subprocess.call(shlex.split(command))

This is not fine.

There are a few things wrong here.

First, as a maytter of practice you should _never_ construct command strings as 
you are doing here: embedding an arbitrary filename directly in a string that 
will be evaluated by a command parser. This applies equally to shell commands 
such as yours or to SQL or anything else where you're preparing text to be 
parsed. Supposing your filename has shell punctuation in it, even the lowly 
space character? The outcome would not be what you intended. You can see where 
I'm going here I'm sure. Read more here (SQL specific, though the principle is 
general):

  http://bobby-tables.com/

The second problem, and the one causing your immediate issue, is the use of 
shlex.split. This is _not_ a full shell parser. It is a convenience function 
that recognises common shell quoting syntax to be used when writing little 
minilanguages of your own - it gets you a preimplemented quoting parser you can 
then let your users access to mark off strings.

Your command looks like this, roughly:

  ssh -p22 ..blah blah blah... < /path/to/script.py

All shlex.split is going to do with this is to break it into separate strings:

    ssh
    -p22
    ..blah
    blah
    blah...
    <
    /path/to/script.py

They're just strings. No redirection recognition or implementation is happening 
here. Then when you call subprocess.call:

    ret = subprocess.call(shlex.split(command))

you're passing a list of those strings as the first argument. subprocess.call 
and its partner Popen have two modes for their first argument: if it is a 
string then it will be passed to the system shell, otherwise it is executed 
directly without being handled by a shell. Effectively the first form takes:

    subprocess.call(some_string)

and runs is as:

    subprocess.call(['/bin/sh', '-c', some_string])

You're using the second form, and that is by far the better thing to do, so 
good there. _However_, you're effectively invoking ssh with no redirections; 
instead your passing the strings '<' and '/path/to/script.py' as arguments to 
ssh.

What you really want to do is this (untested):

  with open(script) as scfp:
    ret = subprocess.call(['ssh', '-p22', 'alex at 10.10.10.10',
                           'python3', '-u', '-',
                           'one', 'two', 'three'],
                          stdin=scfp)

which arranges to attach an open file reading from your script to the ssh 
subprocess you are invoking. Note that it does _not_ pass the script pathname 
itself as an argument to ssh. Neither does your shell script.

Now for the confusing bit: what _was_ your program really doing? Well, ssh 
concatenates its command arguments:

  python3 -u - one two three

together and passes them to the far end, which hands them to the shell! So 
effectively, like the string form of subprocess.call, ssh itself effectively 
invokes:

  sh -c 'python3 -u - one two three'

at the far end. So you're going to need shell quotes in that string if you ever 
pass something slightly tricky, or something arbitrary. But you're not yet.

_However_, the original form of you program was passing _these_ strings as 
command arguments to ssh:

    python3 -u - one two three < /home/alex/Py/BackUp/Sandbox/Scripted/experiment.py

so ssh is invoking this:

    /bin/sh -c 'python3 -u - one two three < /home/alex/Py/BackUp/Sandbox/Scripted/experiment.py'

at the far end. (Those are all on one line, BTW, in case your email reader 
folds things up.)

So you might think: but then the shell _will_ see the "<" redirection! But of 
course that is the shell on the remote machine, and your script isn't on that 
machine, so the shell emits the error message you saw.

Hoping this clarifies what's going on and how to go forward.

Please feel free to ask any questions that occur.

Cheers,
Cameron Simpson <cs at zip.com.au>

Hofstadter's Law: It always takes longer than you expect, even when you take
into account Hofstadter's Law.
- Douglas Hosfstadter, Godel, Escher, Bach: an Eternal Golden Braid


More information about the Tutor mailing list