问题
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 -f
to dump the function's definition over the wire. By default, a noninteractive shell does not run the majority of dotfiles (any specified by theENV
environment variable is an exception).In the given example, this is served by the
. ~/profile
command within the script.The shell needs to be one supporting
typeset
, so it has to bebash
orksh
, notsh
(as used byscript=True
by default), which may be provided byash
ordash
, lacking this feature.In the given example, this is served by passing
['ksh', '-c']
is the first two arguments to the argv array.typeset
needs 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-executed
is 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 -s
inside 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