使用 Python 通过 ssh 在远程主机上执行本地 shell 函数

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

我的.profile定义了一个函数

myps () {
        ps -aef|egrep "a|b"|egrep -v "c\-"
}

我想从我的 python 脚本中执行它

import subprocess
subprocess.call("ssh user@box \"$(typeset -f); myps\"", shell=True)

找回错误

bash: -c: line 0: syntax error near unexpected token `;'
bash: -c: line 0: `; myps'

转义;结果

bash: ;: command not found

原始命令没有正确解释 myps 之前的 ;。使用 sh -c 可以解决这个问题,但是......(请参阅下面的 Charles Duffy 评论)。

使用 single/double 引号的组合有时会使语法更易于阅读并且不易出错。考虑到这一点,运行 命令的安全方法(假设 .profile 中的函数实际上可以在 subprocess.Popen 对象启动的 shell 中访问):

subprocess.call('ssh user@box "$(typeset -f); myps"', shell=True),  

另一种(不太安全)的方法是对 subshell 命令使用 sh -c

subprocess.call('ssh user@box "sh -c $(echo typeset -f); myps"', shell=True) 
# myps is treated as a command

这似乎返回了相同的结果:

subprocess.call('ssh user@box "sh -c typeset -f; myps"', shell=True) 

肯定有其他方法可以完成这些类型的任务,但是,这可能会让您了解原始命令的问题所在。

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

这里有一些相关的项目:

  • 需要在本地调用定义此函数的点文件 ,然后 您 运行 typeset -f 通过网络转储函数的定义.默认情况下,非交互式 shell 不会 运行 大多数点文件(任何由 ENV 环境变量指定的都是例外)。

    在给定的示例中,这是由脚本中的 . ~/profile 命令提供的。

  • shell需要支持typeset,所以必须是bashksh,而不是sh (默认由 script=True 使用),可能由 ashdash 提供,缺少此功能。

    在给定的示例中,这是通过将 ['ksh', '-c'] 传递给 argv 数组的前两个参数来实现的。

  • typeset 需要在本地是 运行,所以它不能位于第一个 script=True 以外的 argv 位置。 (举个例子:subprocess.Popen(['''printf '%s\n' "$@"''', 'This is just literal data!', '$(touch /tmp/this-is-not-executed)'], shell=True) 仅将 printf '%s\n' "$@" 计算为 shell 脚本;This is just literal data!$(touch /tmp/this-is-not-executed) 作为文字数据传递,因此没有名为 /tmp/this-is-not-executed 已创建)。

    在给定的示例中,这是由 not using script=True 提出的。

  • 显式调用 ksh -s(或 bash -s,视情况而定)确保 shell 评估您的函数定义与 shell 您 编写了 那些函数,而不是将它们传递给 sh -c,否则会发生。

    在给定的示例中,这是由脚本中的 ssh user@box ksh -s 提供的。

我最终使用了这个。

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()