将local()的输出传递给Fabric中远程run()命令的stdin的最佳方法?

时间:2011-02-23 22:57:42

标签: python fabric

是否有一种简单的方法可以将输出从本地命令传输到远程命令(反之亦然)?

我一直只是通过管道传输文件,移动文件,然后阅读它...但似乎可以有一种更简单的方法。

对于更简单的情况,只需捕获输出并使用字符串插值即可:

ip = local('hostname -i')
run('Script was run from ip: %s' % ip)

但是当输出需要转义为在命令行上安全和/或需要来自stdin时,它有点棘手。

如果输出是bash-safe,那么像run('echo "%s" | mycmd' % ip)这样的东西会做我正在寻找的东西(我猜这意味着一个相同的问题就是“是否有一种简单的方法来bash-escape字符串? “),但似乎应该有一个”正确的方法“来提供远程标准输入。

编辑:

用long-ish输入来澄清简单字符串插入的许多潜在问题:经典的shell问题(例如,输出可能包含"; rm -rf /)但是(在我的情况下更实际地)输出可以包含引号(单引号和双引号)。

认为只是做run("echo '%s' | cmd" % output.replace("'", "'\\''")应该有效,但可能会有边缘情况错过。

正如我上面提到的,这似乎是织物可以通过直接将一个字符串发送到run()的stdin来为我更优雅地处理的事物类型(尽管我可能只是因为处理其他所有事情而被宠坏了)如此优雅:)

3 个答案:

答案 0 :(得分:0)

您可以发送remote stdin with fexpect, my fabric extension。这也发送一个文件,但隐藏在api后面。你仍然需要逃避。

答案 1 :(得分:0)

为了将(二进制)流发送到远程服务器,我已经完成了一次。

它有点hackish,因为它深入挖掘面料和paramiko的渠道,可能有未经测试的边缘情况,但它似乎主要是做这项工作

def remote_pipe(local_command, remote_command, buf_size=1024*1024):
    '''executes a local command and a remote command (with fabric), and
    sends the local's stdout to the remote's stdin'''
    local_p= subprocess.Popen(local_command, shell=True, stdout=subprocess.PIPE)
    channel= default_channel() #fabric function
    channel.set_combine_stderr(True)
    channel.settimeout(2)
    channel.exec_command( remote_command )
    try:
        read_bytes= local_p.stdout.read(buf_size)
        while read_bytes:
            channel.sendall(read_bytes)
            read_bytes= local_p.stdout.read(buf_size)
    except socket.error:
        local_p.kill()
        #fail to send data, let's see the return codes and received data...
    local_ret= local_p.wait()
    received= channel.recv(buf_size)
    channel.shutdown_write()
    channel.shutdown_read()
    remote_ret= channel.recv_exit_status()
    if local_ret!=0 or remote_ret!=0:
        raise Exception("remote_pipe failed. Local retcode: {0} Remote retcode: {1}  output: {2}".format(local_ret, remote_ret, received))

如果有人想要提供修改,这是btrfs-send-snapshot

的一部分

答案 2 :(得分:0)

这是@ goncalopp答案的略微改进版本:

def remote_pipe(local_command, remote_command, buffer_size=1024*1024, channel_timeout=60):
    '''executes a local command and a remote command (with fabric), and
sends the local's stdout to the remote's stdin'''
    local_process = Popen(local_command, shell=True, stdout=PIPE)
    channel = default_channel() # Fabric function
    channel.set_combine_stderr(True)
    channel.settimeout(channel_timeout)
    channel.exec_command(remote_command)
    try:
        bytes_to_send = local_process.stdout.read(buffer_size)
        while bytes_to_send:
            channel.sendall(bytes_to_send)
            bytes_to_send = local_process.stdout.read(buffer_size)
    except socket.error:
        # Failed to send data, let's see the return codes and received data...
        local_process.kill()
    local_returncode = local_process.wait()
    channel.shutdown_write()
    remote_output = ""
    try:
        bytes_received = channel.recv(buffer_size)
        while bytes_received:
            remote_output += bytes_received
            bytes_received = channel.recv(buffer_size)
    except socket.error:
        pass
    channel.shutdown_read()
    remote_returncode = channel.recv_exit_status()
    print(remote_output)
    if local_returncode != 0 or remote_returncode != 0:
        raise Exception("remote_pipe() failed, local return code: {0}, remote return code: {1}".format(local_returncode, remote_returncode, remote_output))

除了可读性之外,改进之处在于,如果远程命令输出少于buffer_size个字节,它不会因套接字超时而中止,并且它会输出远程命令的完整输出。