与远程程序集成
Fabric 的核心业务 run
和 sudo
都支持将本地的输入发送至远程,其表现形式和 ssh
基本一致。例如,有时候会遇到需要密码的情况(比如 dump 数据库,或者修改用户密码时),程序会提供近乎直接的交互环境。
然而,由于 ssh
本身的限制,Fabric 对于该功能的实现并不能保证直观。这篇文档将详细地讨论这些问题。
注解
不熟悉 Unix stdout、stderr 管道和终端设备的读者,可能需要先访问 Wikipedia Unix 管道 以及 伪终端 页面了解相关知识。
合并 stdout 和 stderr
首先摆在我们面前的是 stdout 和 stderr 流问题,以及他们为什么要根据需求分开或者合并。
缓冲
Fabric 0.9.x 包括更早版本,以及 Python 本身,都是通过将缓冲区一行行地输出:直到遇到换行符才会将这一整行输出给用户。在大多数情况下这都能正常工作,但是在这样需要输出半行的情况就很难处理。
注解
行级输出缓冲可能导致程序无理由地挂起或冻结,比如输出没有后继行的文本、等待用户输入及确认。
新版本的 Fabric 基于字节缓冲输入和输出,这样就可以支持输入提示。同时还方便了和利用了 “curses” 库的复杂程序或者会重新绘制屏幕的程序(比如 top
)集成。
交叉输出流
不幸的是,(像很多其它程序那样)同时打印 stderr 和 stdout 将会导致每次只能输出两者的一个字节,最终的结果互相交叉,混乱地混合在一起。这时如果使用行级输出,虽然仍然是一个严重的问题,但会比另一者好得多。
为了解决这个问题,Fabric 在 SSH 层通过配置,在更低的层面合并两条输出流,保证输出能够更加自然一些。这项设置对应 Fabric 环境变量以及关键字参数 combine_stderr,其默认值是 True
。
得益于这项默认设置,才保证输出的正确,但是这会导致 run
/sudo`返回值的 `
.stderr`` 属性为空,因为全部被合并进了 stdout。
反过来,如果用户要求清晰的 Python 级 stderr 输出流,而不在乎用户(或者其它处理命令输出的 stdout 和 stderr 的事物)将要面对混乱的输出,可以根据需要选择将其设置为 False
。
伪终端
处理提示交互的另一个大问题在于输出用户自身的输入。
重播(echo)
一般的终端程序或者 bona fide 文本终端(例如没有 GUI 的 Unix 系统)将会提供一个被称为 tty 或者 pty(即 pseudo-terminal,伪终端)的程序,它将会(通过 stdout)自动重播用户所输入的全部文本,如果没有的就太难使用了。终端也可以选择关闭重播,比如请求用户安全密码时。
不过,如果是没有 tty 或者 pty 的程序(比如 cron 定时任务),在这种情况下,程序所接收到的数据都不会被重播回来,这是为了程序能够在没有人类监控的情况下也能完成任务,也是 老版本 Fabric 的默认行为。
Fabric 的实现方法
不幸的是,使用 Fabric 执行命令的上下文环境,没有用于重复用户输入的 pty,Fabric 只能自实现输出,对于很多程序来说,这已经足够了,但是在请求密码时会有安全问题。
本着对安全的重视和最小惊讶原则(目前为止,用户的体验都如同时在模拟终端中操作一样),Fabric 1.0 以及以上版本默认强制启用 pty,Fabric 简单地由远程端决定是重播还是隐藏输入,而不是自己实现重播。
注解
为了支持普通输出行为,使用 pty 还意味着附属在终端设备上时的行为会有所差异。例如,对于彩色化终端输出但不处理后台输出的程序,这时将会输出彩色输出。在检查 run
和 sudo
的输出时需要保持警惕!
如果想要关闭 pty 行为,可以使用命令行参数 --no-pty
和环境变量 always_use_pty。
两者结合
最后需要提到的是,时刻记住伪终端的使用十分依赖 stdout 和 stderr 的组合——就像 combine_stderr 的设置那样。这是因为终端设备会将 stdout 和 stderr 发送向同一个地方——用户屏幕——因此要将它们区分开来是不可能做到的。
然而,在 Fabric 级,这两组设置互相独立,并且可以通过多种方式组合使用。默认情况下,两者的值都为 True
,其它组合的效果如下:
run("cmd", pty=False, combine_stderr=True)
:Fabric 会自己处理所有 stdin,包括密码以及潜在的改变cmd
行为。 当cmd
在 pty 中执行很不方便,而且不必关心密码输入时会很有效。run("cmd", pty=False, combine_stderr=False)
:两项设置都为False
时,Fabric 会使用 stdin 而不会生成一个 pty——这对于大多数稍微复杂的命令来说,很可能会导致意料之外的行为,但是这是唯一能够直接接入远程 stderr 流的方法,所以在某些情况下也会游泳有用。run("cmd", pty=True, combine_stderr=False)
: 合法,但并没有什么不同,因为pty=True
会导致输出流合并,在需要避免combine_stderr
的某些特殊边界情况下(目前没有已知用例)可能会有用。