问题
My .profile defines a function
myps () {
ps -aef|egrep "a|b"|egrep -v "c\-"
}
I'd like to execute it from my python script
import subprocess
subprocess.call("ssh user@box \"$(typeset -f); myps\"", shell=True)
Getting an error back
bash: -c: line 0: syntax error near unexpected token `;'
bash: -c: line 0: `; myps'
Escaping ; results in
bash: ;: command not found
回答1:
The original command was not interpreting the ; before myps properly. Using sh -c fixes that, but... ( please see Charles Duffy comments below ).
Using a combination of single/double quotes sometimes makes the syntax easier to read and less prone to mistakes. With that in mind, a safe way to run the command ( provided the functions in .profile are actually accessible in the shell started by the subprocess.Popen object ):
subprocess.call('ssh user@box "$(typeset -f); myps"', shell=True),
An alternative ( less safe ) method would be to use sh -c for the subshell command:
subprocess.call('ssh user@box "sh -c $(echo typeset -f); myps"', shell=True)
# myps is treated as a command
This seemingly returned the same result:
subprocess.call('ssh user@box "sh -c typeset -f; myps"', shell=True)
There are definitely alternative methods for accomplishing these type of tasks, however, this might give you an idea of what the issue was with the original command.
回答2:
script='''
. ~/.profile # load local function definitions so typeset -f can emit them
ssh user@box ksh -s <<EOF
$(typeset -f)
myps
EOF
'''
import subprocess
subprocess.call(['ksh', '-c', script]) # no shell=True
There are a few pertinent items here:
The dotfile defining this function needs to be locally invoked before you run
typeset -fto dump the function's definition over the wire. By default, a noninteractive shell does not run the majority of dotfiles (any specified by theENVenvironment variable is an exception).In the given example, this is served by the
. ~/profilecommand within the script.The shell needs to be one supporting
typeset, so it has to bebashorksh, notsh(as used byscript=Trueby default), which may be provided byashordash, lacking this feature.In the given example, this is served by passing
['ksh', '-c']is the first two arguments to the argv array.typesetneeds to be run locally, so it can't be in an argv position other than the first withscript=True. (To provide an example:subprocess.Popen(['''printf '%s\n' "$@"''', 'This is just literal data!', '$(touch /tmp/this-is-not-executed)'], shell=True)evaluates onlyprintf '%s\n' "$@"as a shell script;This is just literal data!and$(touch /tmp/this-is-not-executed)are passed as literal data, so no file named/tmp/this-is-not-executedis created).In the given example, this is mooted by not using
script=True.Explicitly invoking
ksh -s(orbash -s, as appropriate) ensures that the shell evaluating your function definitions matches the shell you wrote those functions against, rather than passing them tosh -c, as would happen otherwise.In the given example, this is served by
ssh user@box ksh -sinside the script.
回答3:
I ended up using this.
import subprocess
import sys
import re
HOST = "user@" + box
COMMAND = 'my long command with many many flags in single quotes'
ssh = subprocess.Popen(["ssh", "%s" % HOST, COMMAND],
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
result = ssh.stdout.readlines()
来源:https://stackoverflow.com/questions/38729374/executing-a-local-shell-function-on-a-remote-host-over-ssh-using-python