返回Python中的连续终端日志

2024-05-13 07:13:48 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在创建一个小应用程序,我可以插入我的android手机的IP地址,然后只需按一个按钮。我通过ADB进行无线连接。这是可行的,现在我想在一个可滚动的、不可编辑的文本字段中显示adb logcat的日志。但是在这里我遇到了一个问题,我试图捕获logcat命令的输出,就像这样p = os.popen("adb logcat"),然后像这样打印它。这只会使我的应用程序(tkinter)冻结,我猜这与打印永远不会结束这一事实有关。有人知道如何显示logcat结果吗

功能代码

def logcatcommand():
    p = os.popen("adb logcat")
    print(p.read())

按钮:

button2 = tk.Button(text="Open logcat",width=25, height=3, command=logcatcommand)
button2.grid(row=5, column=0)

你们对我如何实时显示这些信息也有想法吗?我想我必须使用这样的代码:

result = scrolledtext.ScrolledText(window, wrap = tk.WORD, width = 40, height = 10, font = ("Times New Roman", 15))
result.grid(column = 1, row=0)
result.insert(tk.INSERT, output)
result.configure(state ='disabled') 

其中输出是从终端检索到的实时数据

采样线logcat:

09-10 14:10:28.479   971  3009 I WifiHAL : event received NL80211_CMD_VENDOR, vendor_id = 0x1374, subcmd = 0xd
09-10 14:10:34.779  1526  4567 I BatteryStatsService: In wakeup_callback: resumed from suspend
09-10 14:10:35.321  1526  4567 I BatteryStatsService: In wakeup_callback: suspend aborted

编辑: 多亏了Javier Gonzalez,我能够使用以下方式打印日志:

def logcatcommand():
    popen = subprocess.Popen(args="adb logcat", shell=True, stdout=subprocess.PIPE)
    return iter(popen.stdout.readline, b"")


def logcatresult():
    for line in logcatcommand():
        print(line)

但是,当尝试将值设置为变量或只是执行其他操作(如尝试按下另一个按钮)时,我唯一看到的是MacOSX中的彩虹纺车

格里茨


Tags: 代码应用程序编辑osdefresultwidth按钮
1条回答
网友
1楼 · 发布于 2024-05-13 07:13:48

我相信这是以一种稍微不同的方式提出的:Python: How to read stdout of subprocess in a nonblocking way

在您的代码中,我实际使用的是子流程模块,而不是os.popen

在我第一次遇到这个问题时,我使用了我在上面链接的问题的答案中提到的select模块。更现代的方法是使用asyncio。这里的问题是使用asyncio的方法多种多样,您可以在SO中找到这些方法。由于asyncio在不同的版本中发生了显著的变化,所以它们有所不同

编辑#1

潜在的问题是主线程阻塞。现在,在logcatresult函数中有一个永远不会结束的循环,因此它永远不会返回。Tk循环永远无法恢复控制。这就是它冻结的原因

一般来说,一个人可以做几件事:

  1. 定期进行非阻塞呼叫。 换句话说:主循环需要定期调用非阻塞函数,该函数返回任何输出(如果有)
  2. 将任何阻塞代码移动到单独的线程
  3. 使用asyncio(我不会给你举一个这样的例子)

#1:在不阻塞的情况下读取日志的想法符合#1您需要实现定期调用的逻辑

#2:如果将读取循环移动到线程,则可以释放主线程来执行其他任何操作并做出响应。然后让logcatresult线程填充滚动文本。在这种情况下,你甚至不需要我上面提到的东西

以下是可能适合您的简化版本:

from tkinter import *
from tkinter import messagebox
import threading
import subprocess

THREAD = None
PROCESS = None


def read_log(process):
    for line in iter(process.stdout.readline, b""):
        print(line.decode(), end=''),


def read_log_thread():
    """ Button-Event-Handler starting the log reading. """
    global THREAD
    global PROCESS
    PROCESS = subprocess.Popen(["adb logcat"], shell=False, stdout=subprocess.PIPE)
    THREAD = threading.Thread(target=read_log, args=(PROCESS,))
    THREAD.start()


def hello():
    messagebox.showinfo(message='Hello.')


def main():
    root = Tk()
    Button(master=root, text='Start reading', command=read_log_thread).pack()
    buttonX = Button(master=root, text='Hello', command=hello).pack()
    root.mainloop()
    if PROCESS and PROCESS.poll():
        PROCESS.terminate()
    THREAD.join()

if __name__ == '__main__':
    main()

要记住的事情:

  • 这个实现只是一个指南。它将有一些问题(下面提到)
  • 我会尽量不使用globals
  • 您需要确保在最后(所有子进程结束、线程结束等)正确清理所有内容
  • 没有什么可以阻止您按两次按钮,创建多个(泄漏的)线程和进程,因为变量是全局的,不进行任何检查
  • 还要注意,我使用了shell=False,因此我可以在最后使用PROCESS.terminate()。否则,在进程结束之前,您的程序将不会退出

相关问题 更多 >