常见问题

一般项目问题

为什么 Invoke 从 Fabric 项目中分离出来?

Fabric(1.x 和更早)是一个混合项目,实现了两个功能集: 任务执行(组织任务功能,通过 CLI 执行它们,以及本地 shell 命令)和高水平的 SSH 行动(组织服务器/主机,远程 shell 命令,以及文件传输)。

对于需要这两套功能的用例,这种安排效果很好。然而,随着时间的推移,人们发现许多用户只需要一个或另一个,只在本地使用的用户对沉重的SSH/密码安装要求感到不满,而专注于远程的用户则对混合代码库造成的API限制感到挣扎。

在规划 Fabric 2.x 时,将 “local” 功能集作为一个独立的库是有意义的,而将 SSH 组件设计成上面的一个独立层似乎是合理的。因此,Invoke 的创建是为了专门关注本地和抽象的问题,让 Fabric 2.x 只关注服务器和网络命令

Fabric 2 利用了 Invoke 的 API 的许多部分,并允许(但不要求!)使用 Invoke 的 CLI 功能,允许多种用例(构建工具、高级 SSH 库、混合构建/编排工具)共存,而不会对彼此产生负面影响。

定义/执行任务

我的任务的第一个参数没有显示在 --help 中!

如果你忘记为你的任务定义一个初始上下文参数,这个问题就会冒出来。

例如,你能发现这个任务文件样本中的问题吗?

from invoke import task

@task
def build(c, where, clean=False):
    pass

@task
def clean(what):
    pass

当用 inv --listinv --help 检查这个任务文件时,并没有引起明显的错误。然而,clean 忘记了为上下文预留第一个参数 – 所以 Invoke 把 what 当作上下文参数!这意味着它没有显示在帮助中。这意味着它不会出现在帮助输出或其他命令行解析阶段”

命令行说我的任务的第一个参数是无效的!

参见 我的任务的第一个参数没有显示在 --help 中! - 这可能是同一个问题。

运行本地shell命令(run

为什么我的命令在 Invoke 下的表现与手动运行的表现不同?

99% 的情况下,在你的 run 调用中加入 pty=True 会使事情如你所期望的那样运行。请继续阅读为什么会这样(以及为什么 pty=True 不是默认的)。

命令行程序经常根据控制终端是否存在而改变行为;一个常见的例子是使用或不使用彩色输出。当你的输出接收者是终端上的人时,你可能想使用颜色,调整行长以匹配终端宽度,等等。

相反,当你的输出被发送到另一个程序(shell 管道、CI 服务器、文件等)时,颜色转义代码和其他终端特有的行为会导致不必要的垃圾。

Invoke 的用例跨越了上述两种情况 – 有时你只想直接显示数据,有时你只想把它抓成一个字符串;往往你想两者兼得。正因为如此,对于伪终端的使用,没有任何默认行为 – 一些大块的用例无论如何都会带来不便。

对于不关心的用例,没有伪终端的直接调用更快、更干净,所以它是默认的。

调用 Python 或 Python 脚本在运行结束时打印出所有的输出!

备注

这通常只是 Python 3 下的一个问题。

症状很容易发现–你正在运行一个需要几秒钟或更长时间才能执行的命令,它通常会在执行过程中打印出几行文字,但通过 run 开始时似乎什么都没有发生,一旦执行完毕,所有的输出都会打印出来。

这通常是由于Python–你所调用的Python内部可执行程序,而不是Invoke运行的那个–对其输出流进行了不必要的缓冲。当它认为它是以非交互式方式被调用时,它就会这样做。

修复方法是通过说 pty=True 来强制 Invoke 在伪终端中运行命令(例如,run("python foo", pty=True))。

另外,由于 Invoke 和内部命令都是 Python,你可以尝试在使用 Invoke 的代码中直接加载内部 Python 模块,并调用其命令行存根使用的任何方法–而不是使用 run。这通常也有其他好处。

为什么我有时会看到 err: stdin: is not a tty

参见 为什么我的命令在 Invoke 下的表现与手动运行的表现不同? – 同样的根本原因(默认缺乏 PTY)可能是怎么回事。在某些情况下(例如通过 Fabric 库),它的发生是因为 shell 的登录文件调用了需要 PTY 的程序(例如 biffmesg),所以如果实际的前台命令似乎没有问题,请确保在那里查看。

在我运行命令后,所有的东西都默默地退出了!

仔细检查命令的退出代码!默认情况下,在 run 调用结束时收到非零的退出代码将导致 Invoke 停止执行并以相同的代码退出。一些程序(pylint、Nagios 检查脚本等)使用退出代码来表示非致命状态,这可能会造成混淆。

这里的解决方案是在你的 run 调用中添加 warn=True,这将禁用自动退出行为。然后你可以手工检查结果的 .exited 属性,以确定它是否真的成功了。

自动回复功能对我的密码提示不起作用!

有些程序将密码提示或其他输出直接写到本地终端(操作系统级的TTY设备),绕过了通常的 stdout/stderr 流。例如,这正是 stdlib getpass 模块 所做的,如果你正在调用一个恰好用 Python 编写的程序的话。

当这种情况发生时,我们无能为力,因为我们所能看到的只是子进程的常规输出流。值得庆幸的是,解决方案通常很简单:只要在你的 run 调用中加入 pty=True。强制使用一个明确的伪终端,通常会诱使这类程序向 stderr 写入提示信息

当我运行命令时,我得到 IOError: Inappropriate ioctl for device

这个错误通常意味着你的项目或其依赖的某些代码用一个实际上没有连接到终端的对象(sys.stdin, sys.stdoutsys.stderr)替换了一个进程流,但它假装自己是终端。例如,测试运行程序或构建系统经常这样做”

99% 的时候,这只对 stdin 弹出,在这种情况下,你可以通过在 run 中指定 in_stream=False 来解决(注意:False**不是 None !)

Gory 细节

从技术上讲,发生的事情是交给 Invoke 的命令执行器的对象,例如 run('command', in_stream=xxx) (或 out_stream 或等;这些都默认为上面列出的 sys 成员)实现了 fileno 方法,但没有返回真正终端文件描述符的 ID。以这种方式破坏合同是导致 Invoke 做操作系统不喜欢的事情的原因。

我们一直在努力使这个检测更加智能;如果升级到最新版本的 Invoke 还不能解决你的问题,请提交一份错误报告,包括关于 sys.stdin/stdout/stderr 的值和类型的细节。希望我们能找到另一个可以使用的启发式方法!