如何用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
相关产品推荐
相关产品推荐

