使用python子进程伪造从终端运行cmd

时间:2012-11-06 02:35:58

标签: python subprocess

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

source login.sh
oupload [options]

login.sh只设置了几个env变量,然后是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的主例程,它就会导致核心转储与线程错误。

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

我正试图远离使用pexpect,主要是因为我们必须使用供应商提供的python 2.4,因为它们提供了预编译的库,我试图将脚本的分发尽可能减少占用空间。 - 如果必须的话,我必须这样做,但我宁愿不使用它(我真的不知道它是否适用于这种情况)。

对我还能尝试的其他任何想法都会非常感激。

更新

所以我通过进一步深入字节码并弄清楚编译命令中缺少的内容来解决这个问题。

然而,这提出了两个问题 -

  1. 供应商代码在调用时,在完成后退出
  2. 供应商代码写入stdout,我需要存储和操作(它包含上传的pkg的ID)。我不能只重定向stdout,因为供应商代码仍在询问用户名/密码。
  3. 通过将代码包装在try / except子句中,

    1很容易解决。

    通过执行类似于https://stackoverflow.com/a/616672/677373

    的操作解决了

    2

    我使用cStringIO而不是日志文件。我还必须实现一个假的'flush'方法,因为似乎供应商代码正在调用它并且抱怨我为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:子句中执行'pass'的唯一原因是except子句总是被调用。 'rc'实际上是命令的返回代码,所以我可能会为非零情况添加处理。

2 个答案:

答案 0 :(得分:0)

  

我试图查找可能预加载stdin缓冲区的方法

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

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

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

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',当然。)

答案 1 :(得分:0)

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

我相信J.F. Sebastian的回答可能会更好地解决最初的问题,所以我建议人们寻找类似问题的答案,看看使用pty模块的路径。