【问题标题】:Executing a local shell function on a remote host over ssh using Python使用 Python 通过 ssh 在远程主机上执行本地 shell 函数
【发布时间】:2016-08-02 19:55:58
【问题描述】:

我的.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

【问题讨论】:

  • 正常的 ssh 命令是什么样子的(例如ssh user@box ....)?
  • 感谢您的提问。普通命令看起来像ssh user@box "$(typeset -f); myps"。它工作得很好。
  • 你可以试试subprocess.call(["/usr/bin/ssh","user@box","$(typeset -f);myps"]) ?
  • @JoranBeasley,这实际上不起作用——只有第一个数组元素被解析为 shell 脚本内容,所以 $(typeset -f) 不会在本地运行(导出本地 shell 函数)。
  • @Jam, ...btw,这里的部分问题是您的 .profile 没有在非交互式 shell 中执行,因此实际上不会加载函数定义。

标签: python ksh


【解决方案1】:
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,所以它必须是bash 或ksh,而不是sh(默认情况下由script=True 使用),可以由ash 提供或dash,缺少此功能。

    在给定的示例中,这是通过将 ['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 的文件。

    在给定的示例中,这是由不使用script=True提出的。

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

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

【讨论】:

    【解决方案2】:

    我最终使用了这个。

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

    【讨论】:

    • 这根本不会在远程主机上运行本地 shell 函数。它在代码中的什么位置检索 shell 函数的定义?它在哪里将该功能传递给远程主机?这是很好的代码——它本质上没有任何问题——但它没有回答你明确提出的问题。
    【解决方案3】:

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

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

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

    另一种(不太安全)方法是使用sh -c 作为子shell 命令:

    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) 
    

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

    【讨论】:

    • ...所以,这三个建议确实每个都需要单独的评论。
    • 在sh -c echo $(typeset -f); myps中,第一个命令是sh -c echo,通过输出到typeset(如果有的话)生成额外的参数,当@时填写$0、$1等987654334@ 被调用——但是,因为你没有运行,比如说,sh -c 'echo "$0" "$@"',它们实际上被忽略了,因为那个脚本(只包含一个 echo 命令)实际上并没有读取它的参数列表。跨度>
    • @CharlesDuffy:当我运行命令时,排版显示了所有env 变量,以及它后面的命令(例如BASH_ARGV=([0]="myps")。所以也许我不理解某些方面?
    • 使用-f 参数,typeset 转储函数定义——这里的目标是确保远程shell 可以访问定义的函数。
    • subprocess.call('ssh user@box "$(typeset -f); myps"', shell=True), if OP 想要的函数实际上可以在 subprocess.Popen 对象启动的 shell 中访问。在我的回答中,我确保我在本地采购点文件,但如果本地 /bin/sh 由 bash 提供(而不是破折号等),并且 OP 在父进程 bash 实例中调用 export -f myps 之前这个脚本被调用,那么即使没有这种努力它也可能工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-12
    • 2015-11-20
    • 2011-05-09
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多