1

我们有一个供应商提供的 python 工具(它是字节编译的,我们没有源代码)。因此,我们也被锁定在使用供应商提供的 python 2.4。util的方法是:

source login.sh
oupload [options]

login.sh 只是设置了一些环境变量,然后是 2 个别名:

odownload () {
${PYTHON_CMD} ${OCLIPATH}/ocli/commands/word_download_command.pyc "$@"
}
oupload () {
${PYTHON_CMD} ${OCLIPATH}/ocli/commands/word_upload_command.pyc "$@"
}

现在,当我按照他们的方式运行时 - 工作正常。它将提示输入用户名和密码,然后执行此操作。

我正在尝试围绕该工具创建一个包装器,以便在它运行后执行一些额外的步骤,并为该实用程序提供一些合理的默认值。我遇到的问题是,在我的一生中,我无法弄清楚如何使用子流程来成功地做到这一点。似乎意识到原始命令不是直接从终端运行并退出。

我创建了一个 '/usr/local/bin/oupload' 并从原始 login.sh 复制。唯一的区别是我实际上运行了命令,而不是最后做一个别名。

然后,在我的 python 脚本中,我尝试运行我的新 shell 脚本:

if os.path.exists(options.zipfile):
    try:
        cmd = string.join(cmdargs,' ')
        p1 = Popen(cmd, shell=True, stdin=PIPE)

但我得到:

Enter Opsware Username: Traceback (most recent call last):
File "./command.py", line 31, in main
File "./controller.py", line 51, in handle
File "./controllers/word_upload_controller.py", line 81, in _handle
File "./controller.py", line 66, in _determineNew
File "./lib/util.py", line 83, in determineNew
File "./lib/util.py", line 112, in getAuth
Empty Username not legal

Unknown Error Encountered                                              

SUMMARY:
Name: Empty Username not legal
Description: None

因此,似乎发送了额外的回车(我尝试删除所有选项,但没有帮助)。

如果我不设置 stdin=PIPE,我会得到:

Enter Opsware Username: Traceback (most recent call last):
File "./command.py", line 31, in main
File "./controller.py", line 51, in handle
File "./controllers/word_upload_controller.py", line 81, in _handle
File "./controller.py", line 66, in _determineNew
File "./lib/util.py", line 83, in determineNew
File "./lib/util.py", line 109, in getAuth
IOError: [Errno 5] Input/output error

Unknown Error Encountered                                              

我已经尝试过使用 p1.communicate、p1.stdin.write() 以及 shell=False 和 shell=True 的其他变体,但我没有运气试图弄清楚如何正确发送用户名和密码. 作为最后一个结果,我尝试查看他们提供的实用程序的字节码 - 它没有帮助 - 一旦我使用正确的参数调用 util 的主例程,它最终导致核心转储 w/线程错误。

最后的想法 - 该实用程序似乎不想“等待”任何输入。从 shell 运行时,它会在“用户名”提示符处暂停。当通过 python 的 popen 运行时,假设没有给出密码,它只是通过并结束。我试图查找可能预加载标准输入缓冲区的方法 - 认为如果它可用,该进程可能会从中读取,但无法确定这是否可能。

我试图远离使用 pexpect,主要是因为我们必须使用供应商提供的 python 2.4,因为他们提供了预编译库,并且我试图将脚本的分布保持在尽可能小的足迹 - 如果我必须,我必须,但我宁愿不使用它(老实说,我也不知道它是否适用于这种情况)。

任何关于我可以尝试的其他想法将不胜感激。

更新

因此,我通过深入研究字节码并找出编译命令中缺少的内容来解决这个问题。

然而,这带来了两个问题——

  1. 供应商代码在调用时正在执行退出
  2. 供应商代码正在写入标准输出,我需要对其进行存储和操作(它包含上传的 pkg 的 ID)。我不能只重定向标准输出,因为供应商代码仍在询问用户名/密码。

1 通过将代码包装在 try/except 子句中很容易解决。

2 通过执行类似的操作来解决:https ://stackoverflow.com/a/616672/677373

我使用 cStringIO 而不是日志文件。我还必须实现一个假的“刷新”方法,因为似乎供应商代码正在调用它并抱怨我为 stdout 提供的新 obj 没有提供它 - 代码最终看起来像:

class Logger(object):
    def __init__(self):
        self.terminal = sys.stdout
        self.log = StringIO()

    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)

    def flush(self):
        self.terminal.flush()
        self.log.flush()

if os.path.exists(options.zipfile):
    try:
        os.environ['OCLI_CODESET'] = 'ISO-8859-1'
        backup = sys.stdout
        sys.stdout = output = Logger()
        # UploadCommand was the command found in the bytecode
        upload = UploadCommand()
        try:
            upload.main(cmdargs)
        except Exception, rc:
            pass
        sys.stdout = backup
        # now do some fancy stuff with output from output.log

我应该注意,我在 except: 子句中简单地“通过”的唯一原因是始终调用 except 子句。“rc”实际上是命令的返回码,所以我可能会添加对非零情况的处理。

4

2 回答 2

0

我试图查找可能预加载标准输入缓冲区的方法

您是否想创建一个命名的 fifo,用用户名/密码信息填充它,然后以读取模式重新打开它并将其传递给 popen(如 中popen(..., stdin=myfilledbuffer))?

您也可以只创建一个普通的临时文件,将数据写入其中,然后以读取模式重新打开它,再次将重新打开的句柄作为标准输入传递。(这是我个人会避免做的事情,因为将用户名/密码写入临时文件通常很糟糕。OTOH 比 FIFO 更容易测试)

至于根本原因:我怀疑有问题的软件是通过非阻塞方法从标准输入读取的。不知道为什么在连接到终端时会起作用。

AAAANYWAY:根本不需要直接通过 Popen 使用管道,对吧?我有点笑这个的hackishness,但我敢打赌它会为你工作:

# you don't actually seem to need popen here IMO -- call() does better for this application.
statuscode = call('echo "%s\n%s\n" | oupload %s' % (username, password, options) , shell=True)

测试status = call('echo "foo\nbar\nbar\nbaz" |wc -l', shell = True)(输出是'4',自然。)

于 2012-11-06T07:51:46.723 回答
0

最初的问题是通过避免问题而不使用终端来解决的,而是导入由 shell 脚本调用的 python 代码并使用它。

但是,我相信 JF Sebastian 的回答可能会更好地满足最初的要求,因此我建议寻找类似问题答案的人们查看使用 pty 模块的路径。

于 2012-11-07T22:29:32.627 回答