<think>我们计划创建一个GUI工具,该工具能够扫描指定文件夹中的脚本文件(特别是adb脚本),并生成同名按钮来执行这些脚本。
主要功能点:
1. 扫描指定文件夹(如当前目录下的某个子目录)内的所有脚本文件(例如 .bat, .sh 等可执行文件)
2. 为每个脚本文件在GUI界面中生成一个按钮,按钮名为脚本文件名(不含扩展名)
3. 点击按钮后执行对应的脚本
4. 显示执行日志(输出和错误)
我们将使用Python的Tkinter库来创建GUI,同时使用subprocess模块来执行脚本并捕获输出。
设计思路:
- 主窗口包含一个顶部框架(用于显示标题和配置按钮)和一个日志文本区域。
- 脚本按钮将动态生成在一个可滚动的区域中(例如使用Canvas和Frame组合,以便支持大量按钮)。
- 我们允许用户选择脚本目录(默认可以是当前目录下的'scripts'文件夹)。
步骤:
1. 创建主窗口和基本布局。
2. 实现扫描脚本目录的函数,获取脚本文件列表。
3. 根据脚本文件列表,动态创建按钮,并为每个按钮绑定执行对应脚本的函数。
4. 执行脚本时,使用子进程执行,并实时捕获输出显示在日志区域。
5. 考虑多线程,避免执行脚本时阻塞GUI。
注意:由于执行外部脚本可能需要时间,我们将使用线程来执行,避免界面卡死。
代码结构:
class ScriptRunnerApp:
def __init__(self, root):
# 初始化窗口
self.script_dir = "scripts" # 默认脚本目录
self.setup_ui(root)
def setup_ui(self, root):
# 创建界面组件
def load_scripts(self):
# 加载脚本文件,并为每个脚本创建按钮
def run_script(self, script_path):
# 执行脚本并输出到日志区域
我们将使用一个自定义的TextHandler类来重定向stdout和stderr到日志文本区域。
由于Tkinter不是线程安全的,我们在更新GUI时需要将操作放入主线程队列(使用`after`方法)。
具体实现如下:
</think>### 创建脚本调用GUI工具的实现方案
#### 核心思路与架构设计
我们将构建一个能够自动扫描文件夹中所有ADB脚本并为每个脚本创建同名执行按钮的GUI工具。核心组件包括:
```mermaid
graph TD
A[脚本扫描模块] --> B[动态按钮生成器]
B --> C[脚本执行引擎]
C --> D[日志输出系统]
D --> E[状态监控面板]
```
#### 完整实现代码(Python + Tkinter)
```python
import os
import tkinter as tk
from tkinter import ttk, scrolledtext
import subprocess
import threading
import platform
class AdbScriptRunner:
def __init__(self, root):
self.root = root
root.title("ADB脚本执行器 v1.0")
root.geometry("800x600")
# 创建主框架
main_frame = ttk.Frame(root, padding=15)
main_frame.pack(fill=tk.BOTH, expand=True)
# 创建控制面板
self.create_control_panel(main_frame)
# 创建脚本按钮区域
self.script_frame = ttk.LabelFrame(main_frame, text="可用脚本", padding=10)
self.script_frame.pack(fill=tk.BOTH, expand=True, pady=10)
# 创建日志区域
self.create_log_panel(main_frame)
# 初始加载脚本
self.load_scripts()
def create_control_panel(self, parent):
"""创建控制面板"""
control_frame = ttk.Frame(parent)
control_frame.pack(fill=tk.X, pady=(0, 10))
# 目录选择
ttk.Label(control_frame, text="脚本目录:").pack(side=tk.LEFT)
self.dir_var = tk.StringVar(value=os.getcwd())
dir_entry = ttk.Entry(control_frame, textvariable=self.dir_var, width=40)
dir_entry.pack(side=tk.LEFT, padx=5)
browse_btn = ttk.Button(control_frame, text="浏览...", command=self.select_directory)
browse_btn.pack(side=tk.LEFT)
refresh_btn = ttk.Button(control_frame, text="刷新脚本", command=self.load_scripts)
refresh_btn.pack(side=tk.RIGHT)
def create_log_panel(self, parent):
"""创建日志面板"""
log_frame = ttk.LabelFrame(parent, text="执行日志", padding=10)
log_frame.pack(fill=tk.BOTH, expand=True)
self.log_area = scrolledtext.ScrolledText(
log_frame,
wrap=tk.WORD,
font=("Consolas", 10)
)
self.log_area.pack(fill=tk.BOTH, expand=True)
self.log_area.tag_config("error", foreground="red")
self.log_area.tag_config("success", foreground="green")
def select_directory(self):
"""选择脚本目录"""
directory = tk.filedialog.askdirectory(initialdir=self.dir_var.get())
if directory:
self.dir_var.set(directory)
self.load_scripts()
def load_scripts(self):
"""加载文件夹中的脚本文件"""
# 清除现有按钮
for widget in self.script_frame.winfo_children():
widget.destroy()
script_dir = self.dir_var.get()
if not os.path.exists(script_dir):
self.log("⚠️ 目录不存在: " + script_dir, "error")
return
# 检测脚本文件
scripts = [f for f in os.listdir(script_dir)
if f.endswith(('.bat', '.sh', '.ps1'))]
if not scripts:
self.log("ℹ️ 目录中未找到脚本文件", "info")
return
# 创建脚本按钮
ttk.Label(self.script_frame, text="点击按钮执行脚本:").pack(anchor="w")
button_frame = ttk.Frame(self.script_frame)
button_frame.pack(fill=tk.X, pady=5)
for idx, script in enumerate(scripts):
btn = ttk.Button(
button_frame,
text=os.path.splitext(script)[0],
command=lambda s=script: self.execute_script(s)
)
btn.grid(row=idx//4, column=idx%4, padx=5, pady=5, sticky="w")
def execute_script(self, script_name):
"""执行选定的脚本"""
script_path = os.path.join(self.dir_var.get(), script_name)
self.log(f"▶ 开始执行: {script_name}")
# 根据平台选择执行方式
if platform.system() == "Windows":
command = ["cmd", "/c", script_path]
else:
command = ["bash", script_path]
# 使用线程防止GUI冻结
threading.Thread(
target=self.run_subprocess,
args=(command, script_name),
daemon=True
).start()
def run_subprocess(self, command, script_name):
"""运行子进程并捕获输出"""
try:
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
encoding="utf-8",
errors="replace"
)
# 实时捕获输出
while True:
output = process.stdout.readline()
if output == "" and process.poll() is not None:
break
if output:
self.log(output.strip())
# 检查退出状态
if process.returncode == 0:
self.log(f"✔ {script_name} 执行成功!", "success")
else:
self.log(f"✖ {script_name} 执行失败 (代码: {process.returncode})", "error")
except Exception as e:
self.log(f"⚠️ 执行错误: {str(e)}", "error")
def log(self, message, tag=None):
"""记录日志到文本框"""
self.log_area.configure(state="normal")
self.log_area.insert(tk.END, message + "\n", tag)
self.log_area.configure(state="disabled")
self.log_area.see(tk.END)
if __name__ == "__main__":
root = tk.Tk()
app = AdbScriptRunner(root)
root.mainloop()
```
#### 关键功能实现原理
1. **脚本自动扫描**:
```python
scripts = [f for f in os.listdir(script_dir)
if f.endswith(('.bat', '.sh', '.ps1'))]
```
使用文件扩展名过滤识别可执行脚本[^1]
2. **动态按钮生成**:
```python
for idx, script in enumerate(scripts):
btn = ttk.Button(..., command=lambda s=script: execute_script(s))
```
为每个脚本创建同名按钮并绑定执行函数
3. **跨平台执行支持**:
```python
if platform.system() == "Windows":
command = ["cmd", "/c", script_path]
else:
command = ["bash", script_path]
```
自动适配Windows/Linux/macOS系统[^1]
4. **实时输出捕获**:
```python
process = subprocess.Popen(..., stdout=subprocess.PIPE)
while True:
output = process.stdout.readline()
self.log(output.strip())
```
实时显示脚本执行输出流
#### 部署和使用指南
1. **准备脚本目录**:
```
/scripts
├─ connect_device.bat → 生成"connect_device"按钮
├─ screen_capture.sh → 生成"screen_capture"按钮
└─ install_apk.ps1 → 生成"install_apk"按钮
```
2. **运行流程**:
```mermaid
sequenceDiagram
用户->>+GUI: 选择脚本目录
GUI->>+文件系统: 扫描脚本文件
文件系统-->>-GUI: 返回脚本列表
GUI->>+界面: 动态生成按钮
用户->>+GUI: 点击脚本按钮
GUI->>+子进程: 执行对应脚本
子进程-->>-GUI: 实时输出日志
GUI->>+界面: 显示执行结果
```
3. **打包为可执行文件**:
```bash
pip install pyinstaller
pyinstaller --onefile --add-data "assets;assets" script_runner.py
```
#### 安全增强措施
1. **脚本执行沙箱**:
```python
# 在子进程中使用受限环境
process = subprocess.Popen(...,
env={"PATH": "/safe/path"},
shell=False # 防止shell注入
)
```
2. **签名验证**:
```python
import hashlib
def verify_script(script_path):
with open(script_path, "rb") as f:
return hashlib.sha256(f.read()).hexdigest() in ALLOWED_HASHES
```
3. **权限隔离**:
```python
if platform.system() != "Windows":
os.chmod(script_path, 0o755) # 设置适当权限
```