使用python子流程从终端模拟运行cmd

使用python子流程从终端模拟运行cmd,python,subprocess,Python,Subprocess,我们有一个供应商提供的python工具(即字节编译,我们没有源代码)。因此,我们也被锁定在使用供应商提供的Python2.4中。使用util的方法是: source login.sh oupload [options] login.sh只设置了几个环境变量,然后设置了两个别名: odownload () { ${PYTHON_CMD} ${OCLIPATH}/ocli/commands/word_download_command.pyc "$@" } oupload () { ${PYTHON

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

source login.sh
oupload [options]
login.sh只设置了几个环境变量,然后设置了两个别名:

odownload () {
${PYTHON_CMD} ${OCLIPATH}/ocli/commands/word_download_command.pyc "$@"
}
oupload () {
${PYTHON_CMD} ${OCLIPATH}/ocli/commands/word_upload_command.pyc "$@"
}
现在,当我按他们的方式运行时,效果很好。它将提示输入用户名和密码,然后执行该操作

我试图在工具运行后创建一个包装器来执行一些额外的步骤,并为该工具提供一些合理的默认值。我遇到的问题是,就我的一生而言,我无法弄清楚如何使用子流程成功地完成这项工作。它似乎意识到原来的命令不是直接从终端运行的,而是BAIL

我创建了一个“/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运行时,它会在“Username”提示符处暂停。当运行python的popen时,假设没有给出密码,它只会快速运行并结束。我试着寻找可能预加载stdin缓冲区的方法——我想如果它可用的话,进程可能会从中读取,但我无法确定这是否可行

我试图避免使用pexpect,主要是因为我们必须使用供应商提供的Python2.4,因为它们提供了预编译库,并且我试图将脚本的分发保持在尽可能小的范围内-如果我必须,我必须,但我宁愿不使用它(老实说,我也不知道它在这种情况下是否有效)

如果您能想到我还能尝试什么,我将不胜感激

更新

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

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

  • 调用供应商代码时,该代码在完成时正在退出
  • 供应商代码正在写入stdout,我需要对其进行存储和操作(它包含上传的pkg的ID)。我不能只重定向stdout,因为供应商代码仍在请求用户名/密码
  • 通过将代码包装在try/except子句中,1很容易得到解决

    通过执行类似于以下内容的操作来解决问题:

    我没有使用日志文件,而是使用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”实际上是命令的返回代码,因此我可能会添加非零情况的处理

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

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

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

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

    aaanyway:根本不需要通过Popen直接使用管道,对吗?我有点嘲笑这一点,但我敢打赌它会对你有用:

    # 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')进行测试。

    通过避免问题,不使用终端,而是导入shell脚本调用的python代码并使用它,原始问题得到了解决


    但是,我相信J.F.Sebastian的答案对于最初提出的问题可能会更有效,因此我建议寻找类似问题答案的人查看使用pty模块的路径。

    尝试了使用shell管道和tempfile的调用方法,两者都不起作用(但都会引发不同的错误)。尝试执行命名管道,这导致了一些奇怪的行为-脚本现在似乎在用户名提示下“暂停”,但是,任何击键都会导致返回shell,但进程仍在后台运行。另外,有趣的是,oupload进程将其父pid显示为init,而不是python包装器ev尽管这两个模块仍在运行。如果您找到了一个可接受的答案,那么您可以将其作为答案发布并接受。要伪造终端,如果您不想使用pexpect模块,您可以尝试使用stdlib中的
    pty
    module,(在您的案例中设置stdin/stdout和写入/读取循环)嗯,我的“答案”并没有真正回答这个问题——即如何伪造终端。我绕过了这个问题,没有为此烦恼。我相信使用“pty”可能会奏效,所以