0

I would like to run ssh with print of python. The followings are my test code.

import subprocess

# case1:
command_str = "\"print(\'test\')\""

# case 2:
# command_str = "\\\"print(\'test\')\\\""

ssh_command = ['ssh', 'USER_X@localhost', 'python', '-c']
ssh_command.append(command_str)
process = subprocess.run(ssh_command, stdout=subprocess.PIPE)
print(process.stdout)

case 1 and case 2 did not work. The outputs are followings,

case 1:

bash: -c: line 0: syntax error near unexpected token `('
bash: -c: line 0: `python -c print('test')'
b''

case 2:

bash: -c: line 0: syntax error near unexpected token `('
bash: -c: line 0: `python -c \"print('test')\"'
b''

Please let me know how it works.

2 Answers 2

2

It should work with

command_str = "'print(\"test\")'"

or equivalently

command_str = '\'print("test")\''

Explanation

The outermost quotes and the escaping are for the local Python. So in either case, the local Python string will be 'print("test")'.

There is no quoting or escaping required for the local shell, as subcommand.run(...) won't invoke it unless shell=True is passed.

Thus the single quotes within the python string are for the remote shell (presumably bash or other sh-compatible shell). The argument passed to the remote Python is thus print("test"). (And the double quotes in there are to signify the string literal to print to the remote python.)

Can we do without escaping (without \)?

As there are three levels involved (local Python, remote shell, remote Python), I don't think so.

Can we do with a single type of quotes?

Yes, with a bit more escaping. Let's build this from behind (or inside-out).

We want to print

test

This needs to be escaped for the remote Python (to form a string literal instead of an identifier):

"test"

Call this with the print() function:

print("test")

Quite familiar so far.

Now we want to pass this as an argument to python -c on a sh-like shell. To protect the ( and ) to be interpreted by that, we quote the whole thing. For the already present " not to terminate the quotation, we escape them:

"print(\"test\")"

You can try this in a terminal:

$> echo "print(\"test\")"
print("test")

Perfect!

Now we have to represent the whole thing in (the local) Python. We wrap another layer of quotes around it, have to escape the four(!) existing quotation marks as well as the two backslashes:

"\"print(\\\"test\\\")\""

(Done. This can also be used as command_str.)

Can we do with only single quotes (') and escaping?

I don't know, but at least not as easily. Why? Because, other than to Python, double and single quotes aren't interchangeable to sh and bash: Within single quotes, these shells assume a raw string without escaping until the closing ' occurs.

My brain hurts!

If literally, go see a doctor. If figuratively, yeah, mine too. And your code's future readers (including yourself) will probably feel the same, when they try to untangle that quoting-escaping-forest.

But there's a painless alternative in our beloved Python standard library!

import shlex

command_str = shlex.quote('print("test")')

This is much easier to understand. The inner quotes (double quotes here, but doesn't really matter: shlex.quote("print('test')") works just as fine) are for the remote Python. The outer quotes are obviously for the local Python. And all the quoting and escaping beyond that for the remote shell is taken care of by this utility function.

Sign up to request clarification or add additional context in comments.

1 Comment

:thank you for educational answer and the lovely solution, shlex.quote. I love it.
-1

The correct syntax for python 2 and 3 is:

python -c 'print("test")'

enter image description here

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.