You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python检索macOS当前运行的终端会话内容?

问题

我正在开发一个Python项目,需要从Python脚本中检查任意运行中的终端会话内是否存在指定关键词。需求是当有终端会话(比如应用输出)echo出“E1M3 - Toxin Refinery”时,脚本打印“Very real”。我尝试了以下脚本:

import psutil
import re
import time
import sys

def get_terminal_sessions():
    terminal_sessions = []
    for process in psutil.process_iter(['pid', 'name', 'username', 'terminal']):
        try:
            if process.info['name'] == 'bash' or process.info['name'] == 'zsh':
                terminal_sessions.append(process)
        except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
            pass
    return terminal_sessions

def print_terminal_sessions(terminal_sessions):
    print("Terminal Sessions:")
    for session in terminal_sessions:
        print(f"PID: {session.pid} | User: {session.info['username']} | Terminal: {session.info['terminal']}")

def monitor_terminal_sessions():
    while True:
        terminal_sessions = get_terminal_sessions()
        for session in terminal_sessions:
            try:
                # Get terminal information
                terminal_path = session.info['terminal']
                with open(terminal_path, "r") as terminal:
                    lines = terminal.readlines()
                    for line in lines:
                        if re.search(r"E1M3 - Toxin Refinery", line):
                            print("VERY REAL")
                            sys.exit()
            except Exception as e:
                print(f"Error: {e}")
                pass
        time.sleep(1)

if __name__ == "__main__":
    monitor_terminal_sessions()
问题分析

原脚本存在几个关键问题:

  • 直接读取终端设备文件(如/dev/pts/0)无法获取历史输出,这类字符设备的读取逻辑和普通文件不同,readlines()不会返回之前的输出内容。
  • 仅监控bash/zsh主进程不够,实际输出关键词的通常是这些shell启动的子进程(比如echo命令、其他应用)。
  • 每次循环重复读取终端设备,容易触发权限问题,且无法实时跟踪新输出。
修改后的解决方案

以下是调整后的脚本,通过跟踪终端关联的所有进程并监控其输出实现需求:

import psutil
import re
import time
import sys
import threading
from typing import List

def get_terminal_processes() -> List[psutil.Process]:
    """获取所有关联终端的进程(含shell及其子进程)"""
    terminal_procs = []
    # 先筛选所有shell进程
    shells = []
    for proc in psutil.process_iter(['pid', 'name', 'terminal']):
        try:
            if proc.info['name'] in ('bash', 'zsh', 'sh') and proc.info['terminal'] is not None:
                shells.append(proc)
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            pass
    
    # 获取每个shell的递归子进程,包含shell本身
    for shell in shells:
        try:
            children = shell.children(recursive=True)
            terminal_procs.extend(children)
            terminal_procs.append(shell)
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            pass
    
    # 去重,避免重复处理同一进程
    return list({p.pid: p for p in terminal_procs}.values())

def monitor_single_process(proc: psutil.Process):
    """监控单个进程的标准输出"""
    try:
        # 遍历进程的打开文件,找到输出相关的文件描述符
        for fd in proc.open_files():
            if fd.path in ('/dev/stdout', '/dev/tty') or proc.info['terminal'] in fd.path:
                with open(fd.fd, 'r', encoding='utf-8', errors='ignore') as f:
                    # 跳到文件末尾,只监控新输出
                    f.seek(0, 2)
                    while True:
                        line = f.readline()
                        if line and re.search(r"E1M3 - Toxin Refinery", line):
                            print("Very real")
                            sys.exit()
                        time.sleep(0.1)
    except (psutil.NoSuchProcess, psutil.AccessDenied, PermissionError, OSError):
        # 忽略无权限或已结束的进程
        pass

def monitor_all_terminals():
    """持续监控所有终端关联进程的输出"""
    monitored_pids = set()
    while True:
        current_procs = get_terminal_processes()
        # 为新进程启动监控线程
        for proc in current_procs:
            if proc.pid not in monitored_pids:
                monitored_pids.add(proc.pid)
                threading.Thread(target=monitor_single_process, args=(proc,), daemon=True).start()
        # 清理已结束的进程ID
        monitored_pids = {pid for pid in monitored_pids if psutil.pid_exists(pid)}
        time.sleep(1)

if __name__ == "__main__":
    monitor_all_terminals()
注意事项
  • 脚本需要足够权限运行(如sudo),否则可能无法访问其他用户的进程或终端文件。
  • 若进程输出被重定向到文件而非终端,此方法无法捕获该输出。
  • 使用守护线程监控单个进程输出,避免主循环被阻塞。

内容的提问来源于stack exchange,提问作者Divided

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 06:55:12