import 'dart:async'; import 'dart:convert'; import 'dart:io'; /// 外部命令执行失败(非零退出/超时/无法启动)。 class CommandException implements Exception { CommandException(this.message); final String message; @override String toString() => message; } /// 运行外部命令(lp/lpoptions/lpstat)并返回标准输出。 /// /// 一律用参数列表传参不经过 shell,杜绝命令注入; /// 强制 LC_ALL=C 英文输出(macOS 会把 lpstat/lp 本地化,中文系统队列名与状态词粘连无法按空格解析)。 Future runCommand( String executable, List arguments, { int timeoutMs = 15000, bool tolerateExit = false, }) async { final Process process; try { process = await Process.start( executable, arguments, environment: const {'LC_ALL': 'C', 'LANG': 'C'}, ); } on ProcessException catch (e) { throw CommandException('无法启动 $executable:${e.message}'); } // stdout/stderr 立即挂上消费 Future,避免管道缓冲区写满导致死锁 final stdoutFuture = process.stdout.transform(utf8.decoder).join(); final stderrFuture = process.stderr.transform(utf8.decoder).join(); final int exitCode; try { exitCode = await process.exitCode.timeout(Duration(milliseconds: timeoutMs)); } on TimeoutException { process.kill(); throw CommandException('$executable 执行超时'); } final out = await stdoutFuture; if (exitCode != 0 && !tolerateExit) { final err = await stderrFuture; throw CommandException('$executable 返回 $exitCode:${err.trim()}'); } return out; }