最近总在用 fabric,踩了一些坑, 分享一个实用函数

2019-04-30 18:27:28 +08:00
 firejoke

最近一直在做的一个东西, 很多地方需要用到 shell 命令,
同样的命令既要在本地运行, 也要放到远程,并获取远程的输出结果
如果每个地方都要判断是在本地还是远程然后再去分别实例不同的对象,
所以写了一个函数, 尽量减少一下重复代码

def cmd_method(host, port=22, user=None, pwd=None):
    """
    执行远程或本地命令
    r = r"(?i)y\|n|\[y/d/n\]|\[y/n\]|y/n"
    watcher = Responder(pattern=r, response='y\n')
    Responder 对象可以通过正则匹配对 stdout 做分析, 如果匹配到了,就向 stdin 写入 response
    完成命令的反复交互
    :param host: 主机 ip, 本地可以用 'localhost' or '127.0.0.1'
    :param port: ssh 的端口
    :param user: 登陆远程主机的用户
    :param pwd: 远程登陆的密码
    :return: 正常输出和错误输出: (stdout, stderr)
    """

    from fabric import Connection
    from invoke import run, Responder
    from paramiko import AuthenticationException
    from paramiko.ssh_exception import NoValidConnectionsError, SSHException

    def local(command, interactive):
        watcher = Responder(
                pattern=interactive['pattern'],
                response=interactive['response']) if interactive else None
        res = run(command, watchers=[watcher], warn=True, hide=True)
        res = res.stdout.encode('utf8'), res.stderr.encode('utf8')
        return res

    def remote(command, interactive):
        if not ip_check(host):
            return 'host ip error'
        with Connection(host=host, port=port, user=user,
                        connect_kwargs={'password': pwd},
                        connect_timeout=10) as c:
            watcher = Responder(
                    pattern=interactive['pattern'],
                    response=interactive['response']) if interactive else []
            res = c.run(command, watchers=[watcher], warn=True, hide=True)
            res = res.stdout.encode('utf8'), res.stderr.encode('utf8')
            return res
    if host and host in ('localhost' or '127.0.0.1'):
        return local
    elif host and port and user and pwd:
        with Connection(host=host, port=port, user=user,
                        connect_kwargs={'password': pwd},
                        connect_timeout=10) as c:
            try:
                # 测试参数可用
                c.run('hostname', hide=True)
            except (AuthenticationException, NoValidConnectionsError,
                    SSHException) as e:
                return e.__str__()
        return remote
    else:
        return 'args error'

因为 fabric 的 Connection 的 run 方法也是继承自 invoke, 所以参数作用基本都是一样的
我最常用的是 warn 和 hide 还有 watchers
warn 默认为 False, 默认情况下会因为 shell 命令的错误输出而抛错, 也就是直接抛出 stderr
如果设为 True, 就会将 shell 命令的错误输出写到 Result 对象的 stderr 内

hide 也是默认为 False, 默认情况下将远程的输出信息在当前命令行输出, 为 True 时, 则不会, 但不论是什么, 都不会影响 Result 对象的 stdout 和 stderr 结果, 还可以只隐藏 stdout 或 stderr

watchers 参数, 传入的是一个包含诺干 Responder 实例的列表
当需要运行交互式的命令时, 可以用 Responder 对象来匹配输出, 并写入输入, 做自动化部署时很实用

还有一个 pty 参数, 这个参数, 默认是设为 True, 找文档时候发现很多人都是设为 True, 但在我踩过很多坑后,
我发现当设为 True 时, 有时标准输出(stdout)和错误输出(stderr)会混乱, 不方便后面的逻辑判断, 所以最好别动

还有 out_stream 和 err_stream, 可以将输出导到一个 write 模式打开的类 file 对象, 方便做记录

2017 次点击
所在节点    Python
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/560201

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX