我们有一个供应商提供的 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,因为他们提供了预编译库,并且我试图将脚本的分布保持在尽可能小的足迹 - 如果我必须,我必须,但我宁愿不使用它(老实说,我也不知道它是否适用于这种情况)。
任何关于我可以尝试的其他想法将不胜感激。
更新
因此,我通过深入研究字节码并找出编译命令中缺少的内容来解决这个问题。
然而,这带来了两个问题——
- 供应商代码在调用时正在执行退出
- 供应商代码正在写入标准输出,我需要对其进行存储和操作(它包含上传的 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”实际上是命令的返回码,所以我可能会添加对非零情况的处理。