Executing a local shell function on a remote host over ssh using Python

自作多情 提交于 2020-01-13 19:36:12

问题


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 the ENV 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 be bash or ksh, not sh (as used by script=True by default), which may be provided by ash or dash, 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 with script=True. (To provide an example: subprocess.Popen(['''printf '%s\n' "$@"''', 'This is just literal data!', '$(touch /tmp/this-is-not-executed)'], shell=True) evaluates only printf '%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 (or bash -s, as appropriate) ensures that the shell evaluating your function definitions matches the shell you wrote those functions against, rather than passing them to sh -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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!