Python subprocess通信冻结

7
我有以下的 Python 代码,它会卡住:
cmd = ["ssh", "-tt", "-vvv"] + self.common_args
cmd += [self.host]
cmd += ["cat > %s" % (out_path)]
p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate(in_string)

这段代码旨在通过ssh将一个字符串(in_string)保存到远程文件中。

文件成功保存,但随后程序卡住了。如果我使用

cmd += ["echo"] instead of
cmd += ["cat > %s" % (out_path)]

这个进程没有挂起,所以我很确定我对通信方式认为进程已退出的理解有误。

你知道我应该如何编写命令,使得“cat > file”不会使通信挂起吗?


1
我认为这个帖子可以帮助你,它是一个使用SSH写入远程文件的示例。 - Kobi K
有点偏题,但与其使用SSH进程来完成这个任务,你考虑过类似SSHFS的解决方案吗?这意味着你只需要担心如何写入文件,而不必维护所有的东西。 - Andrew Walker
2个回答

1

-tt选项分配tty,防止子进程在.communicate()关闭p.stdin时退出(EOF被忽略)。这个命令是有效的:

import pipes
from subprocess import Popen, PIPE

cmd = ["ssh", self.host, "cat > " + pipes.quote(out_path)] # no '-tt'
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate(in_string)

你可以使用 paramiko,这是一个纯Python的ssh库,通过ssh将数据写入远程文件:
#!/usr/bin/env python
import os
import posixpath
import sys
from contextlib import closing

from paramiko import SSHConfig, SSHClient

hostname, out_path, in_string = sys.argv[1:] # get from command-line 

# load parameters to setup ssh connection
config = SSHConfig()
with open(os.path.expanduser('~/.ssh/config')) as config_file:
    config.parse(config_file)
d = config.lookup(hostname)

# connect
with closing(SSHClient()) as ssh:
    ssh.load_system_host_keys()
    ssh.connect(d['hostname'], username=d.get('user'))
    with closing(ssh.open_sftp()) as sftp:
        makedirs_exists_ok(sftp, posixpath.dirname(out_path))
        with sftp.open(out_path, 'wb') as remote_file:
            remote_file.write(in_string)

makedirs_exists_ok()函数模仿os.makedirs()函数:

from functools import partial
from stat import S_ISDIR

def isdir(ftp, path):
    try:
        return S_ISDIR(ftp.stat(path).st_mode)
    except EnvironmentError:
        return None

def makedirs_exists_ok(ftp, path):
    def exists_ok(mkdir, name):
        """Don't raise an error if name is already a directory."""
        try:
            mkdir(name)
        except EnvironmentError:
            if not isdir(ftp, name):
                raise

    # from os.makedirs()
    head, tail = posixpath.split(path)
    if not tail:
        assert path.endswith(posixpath.sep)
        head, tail = posixpath.split(head)

    if head and tail and not isdir(ftp, head):
        exists_ok(partial(makedirs_exists_ok, ftp), head)  # recursive call

    # do create directory
    assert isdir(ftp, head)
    exists_ok(ftp.mkdir, path)

但是为什么使用-tt + echo会起作用?你的解释似乎意味着它也不应该与echo一起工作!? - Jerome WAGNER
@JeromeWAGNER: echo 不期望从 stdin 输入。尝试任何程序,直到所有输入(stdin)都被消耗完毕才退出。尽管我也没有完全理解它。可能是在关闭伪终端文件描述符链的某个地方出现了 bug。这是与平台有关的,例如,这里有一段使用 pty 的代码(我想这就是 ssh 启动远程子进程的方式(我可能完全错了))。 - jfs

0

在编程中,cat命令挂起是有道理的,它正在等待一个EOF。我尝试在字符串中发送一个EOF,但无法使其正常工作。在研究这个问题时,我发现了一个流畅使用SSH来执行类似于你的cat示例的命令行任务的优秀模块。它可能不完全符合您的用例需求,但确实能够解决您的问题。

安装fabric

pip install fabric

在一个名为 fabfile.py 的文件中放置

from fabric.api import run

def write_file(in_string, path):
    run('echo {} > {}'.format(in_string,path))

然后在命令提示符中运行以下命令:

fab -H username@host write_file:in_string=test,path=/path/to/file

谢谢您的建议,但我正在尝试修补一个现有的项目(ansible),而对fabric的依赖将不被接受。 - Jerome WAGNER
欢迎。但是,我想它可能不适合您的需求。Kobi K的答案对您有用吗? - William Denman
是的,Kobi K的答案似乎有效,但我不理解“communicate”的问题。我发现删除“-tt”就可以让它正常工作;这对我来说是个谜。 - Jerome WAGNER
在需要从Python运行ssh时带有-tt选项的情况下,比如需要从stdin获取输入的交互式会话,甚至需要sudo密码的远程脚本,通过os.system()执行ssh脚本将解决问题。相关帖子:https://dev59.com/3HA65IYBdhLWcg3wrQl4 - Snidhi Sofpro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接