11

我试图让用户使用 raw_input() 在控制台输入命令,这很好用。问题是我有后台线程偶尔会在屏幕上输出日志信息,当他们这样做时,它们会弄乱输入提示(因为输出会转到当前光标所在的位置)。

这是一个小的 Python 程序,说明了我的意思。

#!/usr/bin/env python
import threading
import time

def message_loop():
    while True:
        time.sleep(1)
        print "Hello World"

thread = threading.Thread(target = message_loop)
thread.start()

while True:
    input = raw_input("Prompt> ")
    print "You typed", input

这是我运行它时的示例:

Prompt> Hello World
Hello World
Hello World
Hello World
test
You typed test
Prompt> Hello World
Hello World
Hello World
hellHello World
o
You typed hello
Prompt> Hello World
Hello World
Hello World
Hello World

我想要的是提示与线程的输出一起移动。像这样:

Hello World
Hello World
Prompt> test
You typed test
Hello World
Hello World
Hello World
Hello World
Hello World
Prompt> hello
You typed hello
Hello World
Hello World
Hello World
Hello World
Prompt> 

关于如何在不诉诸丑陋黑客的情况下实现这一目标的任何想法?:)

4

4 回答 4

24

我最近遇到了这个问题,想把这个解决方案留在这里以备将来参考。这些解决方案从终端清除待处理的 raw_input(readline)文本,打印新文本,然后将 raw_input 缓冲区中的内容重新打印到终端。

第一个程序非常简单,但只有在只有一行文本等待 raw_input 时才能正常工作:

#!/usr/bin/python

import time,readline,thread,sys

def noisy_thread():
    while True:
        time.sleep(3)
        sys.stdout.write('\r'+' '*(len(readline.get_line_buffer())+2)+'\r')
        print 'Interrupting text!'
        sys.stdout.write('> ' + readline.get_line_buffer())
        sys.stdout.flush()

thread.start_new_thread(noisy_thread, ())
while True:
    s = raw_input('> ')

输出:

$ ./threads_input.py
Interrupting text!
Interrupting text!
Interrupting text!
> WELL, PRINCE, Genoa and Lucca are now no more than private estates of the Bo
Interrupting text!
> WELL, PRINCE, Genoa and Lucca are now no more than private estates of the Bo
naparte family. No, I warn you, that if you do not tell me we are at war,

第二个正确处理 2 个或更多缓冲行,但具有更多(标准)模块依赖项,并且需要一点终端黑客:

#!/usr/bin/python

import time,readline,thread
import sys,struct,fcntl,termios

def blank_current_readline():
    # Next line said to be reasonably portable for various Unixes
    (rows,cols) = struct.unpack('hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,'1234'))

    text_len = len(readline.get_line_buffer())+2

    # ANSI escape sequences (All VT100 except ESC[0G)
    sys.stdout.write('\x1b[2K')                         # Clear current line
    sys.stdout.write('\x1b[1A\x1b[2K'*(text_len/cols))  # Move cursor up and clear line
    sys.stdout.write('\x1b[0G')                         # Move to start of line


def noisy_thread():
    while True:
        time.sleep(3)
        blank_current_readline()
        print 'Interrupting text!'
        sys.stdout.write('> ' + readline.get_line_buffer())
        sys.stdout.flush()          # Needed or text doesn't show until a key is pressed


if __name__ == '__main__':
    thread.start_new_thread(noisy_thread, ())
    while True:
        s = raw_input('> ')

输出。以前的 readline 行已正确清除:

$ ./threads_input2.py
Interrupting text!
Interrupting text!
Interrupting text!
Interrupting text!
> WELL, PRINCE, Genoa and Lucca are now no more than private estates of the Bo
naparte family. No, I warn you, that if you do not tell me we are at war,

有用的资源:

如何在 Python 中获取 Linux 控制台窗口的宽度

apt like column output - python 库 (此代码示例显示如何获取 Unix 或 Windows 的终端宽度)

http://en.wikipedia.org/wiki/ANSI_escape_code

于 2011-01-11T01:22:25.453 回答
3

我认为您需要一些可以让您从终端窗口动态打印/删除/覆盖文本的东西,例如 UNIXwatchtop命令的工作方式。

我认为在您的情况下,您会打印“Prompt>”,但是当您获得“Hello World”时,您会用“Hello World”覆盖“Prompt>”,然后在下面的行上打印“Prompt>”。我不认为你可以通过定期输出打印到终端来做到这一点。

你可以使用 Python 的curses库做你想做的事。我从未使用过它,所以我无法告诉您如何解决您的问题(或者该模块是否能够解决您的问题),但我认为值得一看。搜索“python curses tutorial”提供了一个似乎很有帮助的PDF 教程文档。

于 2010-01-17T20:16:46.770 回答
1

您需要从单个线程更新标准输出,而不是从多个线程...否则您无法控制交错 i/o。

您将要创建一个用于输出写入的线程。

您可以在线程中使用队列并让所有其他线程将其输出日志记录信息写入其中。然后从该队列中读取并在适当的时间连同您的提示消息一起写入标准输出。

于 2010-01-17T21:17:15.347 回答
0

我不认为这是可能的。无论如何,这应该如何表现?在用户按下 Enter 之前什么都没有显示?如果是这样,输出只会在用户发出命令(或系统期望的任何命令)时出现,这听起来并不理想。

我认为你的线程应该输出到另一个文件。

于 2010-01-17T20:01:22.827 回答