Python脚本和Linux shell之间的交互

4

我有一个需要通过命令行与用户交互,并记录任何输出的Python脚本。

我目前有以下代码:

# lots of code

popen = subprocess.Popen(
    args,
    shell=True,
    stdin=sys.stdin,
    stdout=sys.stdout,
    stderr=sys.stdout,
    executable='/bin/bash')

popen.communicate()

# more code

这个命令会执行一个shell命令(例如:adduser newuser02),就像在终端输入一样,包括交互行为。这很好。

现在,我想从Python脚本中记录屏幕上出现的所有内容。但是我似乎无法使此部分起作用。

我尝试过使用subprocess.PIPE的各种方式,但这通常会搞乱交互方式,例如不输出提示字符串。

我还尝试了各种直接改变sys.stdout行为的方法,但由于子进程直接写入到sys.stdout.fileno(),所以这都没有成功。


顺便提一下,你使用shell=True的原因是什么?特别是当你要运行的东西本身就是/bin/bash的时候?你基本上是告诉Python启动一个bash的副本来告诉它启动另一个bash的副本(可能是这样,因为你没有显示整个代码),来告诉它启动一个adduser的副本。你可能甚至不需要一个shell,更何况两个了。 - abarnert
@abarnert:executable不会启动一个新的bash进程:它告诉subprocess模块使用其值,而不是/bin/sh,如果shell=Trueargs可能是一些带有bash-isms的shell命令,例如'use_tty &>/dev/null'。据我所知,意图是:script -c '/bin/bash -c "$args"' log。尽管正如我的答案所示,您甚至不需要额外的bash进程来运行此处的命令。 - jfs
2个回答

3

Popen可能不适合交互式程序,因为存在缓冲问题某些程序直接从终端读写,例如检索密码。参见为什么不能只使用管道(popen())?

如果您想模拟{{link4:script实用程序}},则可以使用{{link5:pty.spawn()}},请参见从Python子进程复制终端输出的代码示例记录语法错误和未捕获的异常以供Python子进程使用,并将它们打印到终端上

#!/usr/bin/env python
import os
import pty
import sys

with open('log', 'ab') as file:
    def read(fd):
        data = os.read(fd, 1024)
        file.write(data)
        file.flush()
        return data

    pty.spawn([sys.executable, "test.py"], read)

或者您可以使用 pexpect 来获得更多的灵活性:

import sys
import pexpect # $ pip install pexpect

with open('log', 'ab') as fout:
    p = pexpect.spawn("python test.py")
    p.logfile = fout # or .logfile_read
    p.interact()

如果您的子进程不缓冲其输出(或者它不影响交互性),并且将其输出打印到其stdout或stderr,则可以尝试使用subprocess
#!/usr/bin/env python
import sys
from subprocess import Popen, PIPE, STDOUT

with open('log','ab') as file:
    p = Popen([sys.executable, '-u', 'test.py'],
              stdout=PIPE, stderr=STDOUT,
              close_fds=True,
              bufsize=0)
    for c in iter(lambda: p.stdout.read(1), ''):
        for f in [sys.stdout, file]:
            f.write(c)
            f.flush()
    p.stdout.close()
    rc = p.wait()

要分别读取stdout/stderr,您可以使用Python subprocess get children's output to file and terminal?中的teed_call()


0

这应该可以工作

import subprocess
f = open('file.txt','w')
cmd = ['echo','hello','world']
subprocess.call(cmd, stdout=f)

1
这不会在控制台中显示输出。也无法与需要用户输入的命令一起使用,例如“adduser”。 - HetMes
你的问题表明你想把输出记录到文件中。你是希望同时在文件和控制台中记录吗? - rogue-one
是的,这个想法是在 Python 脚本结束时,我拥有一个完整的日志记录,包括脚本本身明确生成的任何日志记录。 - HetMes
1
@user480441:adduser 可能需要直接从终端输入。否则,由 call() 启动的子进程应该继承其父进程的 stdin,即您应该能够提供输入,例如尝试 subprocess.call('python') - jfs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接