Files
SRCC/docs/fyne-go-command-tutorial.md
2026-06-22 11:33:18 +08:00

453 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Fyne + Go 命令执行与输出捕获教程
## 1. Fyne 快速入门
### 安装 Fyne
```bash
go get fyne.io/fyne/v2@latest
```
### 最小 Fyne 窗口
```go
package main
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Hello Fyne")
myWindow.SetContent(widget.NewLabel("Hello, World!"))
myWindow.Resize(fyne.NewSize(400, 300))
myWindow.ShowAndRun()
}
```
### 常用控件
| 控件 | 说明 |
|------|------|
| `widget.NewLabel(text)` | 文本标签 |
| `widget.NewButton(text, callback)` | 按钮 |
| `widget.NewEntry()` | 单行输入框 |
| `widget.NewMultiLineEntry()` | 多行输入框 |
| `widget.NewCheck(text, callback)` | 复选框 |
| `widget.NewSelect(options, callback)` | 下拉选择 |
| `widget.NewProgressBar()` | 进度条 |
### 布局容器
| 容器 | 说明 |
|------|------|
| `container.NewVBox(...)` | 垂直排列 |
| `container.NewHBox(...)` | 水平排列 |
| `container.NewBorder(top, bottom, left, right, center)` | 边框布局 |
| `container.NewGridWithColumns(cols, ...)` | 网格布局 |
---
## 2. Go 执行外部命令并获取输出
### 核心包: `os/exec`
Go 标准库 `os/exec` 包提供了执行外部命令的功能。
### 2.1 基础方法
#### 获取命令输出 (一次性)
```go
package main
import (
"fmt"
"os/exec"
)
func main() {
// cmd := exec.Command("cmd", "/c", "dir") // Windows
cmd := exec.Command("ls", "-l") // Linux/macOS
output, err := cmd.Output() // 返回 []byte
if err != nil {
fmt.Println("执行出错:", err)
return
}
fmt.Println(string(output))
}
```
#### 合并 stdout + stderr
```go
output, err := cmd.CombinedOutput() // stdout + stderr 合并
```
#### 分别获取 stdout 和 stderr
```go
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
fmt.Println("stdout:", stdout.String())
fmt.Println("stderr:", stderr.String())
```
### 2.2 实时读取输出 (逐行)
适用于长时间运行的命令或需要实时显示进度的场景。
```go
func runCommandWithLiveOutput(name string, args ...string) error {
cmd := exec.Command(name, args...)
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return err
}
if err := cmd.Start(); err != nil {
return err
}
// 并发读取 stdout
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
fmt.Println("[OUT]", scanner.Text())
}
}()
// 并发读取 stderr
go func() {
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
fmt.Println("[ERR]", scanner.Text())
}
}()
return cmd.Wait()
}
```
### 2.3 设置超时
```go
func runWithTimeout(timeout time.Duration, name string, args ...string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, name, args...)
output, err := cmd.Output()
if ctx.Err() == context.DeadlineExceeded {
return "", fmt.Errorf("命令执行超时 (%v)", timeout)
}
return string(output), err
}
```
### 2.4 设置工作目录和环境变量
```go
cmd := exec.Command("make", "build")
cmd.Dir = "/path/to/project" // 设置工作目录
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64") // 追加环境变量
```
### 2.5 Windows 特殊处理
Windows 下 `cmd` 命令需要 `/c` 前缀:
```go
// Windows
cmd := exec.Command("cmd", "/c", "dir")
// 或者使用 PowerShell
cmd := exec.Command("powershell", "-Command", "Get-ChildItem")
```
通用跨平台封装:
```go
func shellCommand(command string) *exec.Cmd {
if runtime.GOOS == "windows" {
return exec.Command("cmd", "/c", command)
}
return exec.Command("sh", "-c", command)
}
```
---
## 3. Fyne + 命令执行完整示例
### 3.1 简单的命令执行 GUI
```go
package main
import (
"bytes"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"os/exec"
"runtime"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("命令执行器")
myWindow.Resize(fyne.NewSize(600, 400))
cmdEntry := widget.NewEntry()
cmdEntry.SetPlaceHolder("输入命令,如: ping 127.0.0.1")
outputLabel := widget.NewMultiLineEntry()
outputLabel.Disable() // 只读
outputLabel.Wrapping = fyne.TextWrapWord
runBtn := widget.NewButton("执行", func() {
command := cmdEntry.Text
if command == "" {
return
}
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("cmd", "/c", command)
} else {
cmd = exec.Command("sh", "-c", command)
}
var out bytes.Buffer
var errOut bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &errOut
err := cmd.Run()
if err != nil {
outputLabel.SetText("错误: " + err.Error() + "\n" + errOut.String())
return
}
outputLabel.SetText(out.String())
})
content := container.NewVBox(
widget.NewLabel("输入要执行的命令:"),
cmdEntry,
runBtn,
widget.NewLabel("输出:"),
outputLabel,
)
myWindow.SetContent(content)
myWindow.ShowAndRun()
}
```
### 3.2 带实时输出的进阶版本
```go
package main
import (
"bufio"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"os/exec"
"runtime"
"sync"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("实时命令输出")
myWindow.Resize(fyne.NewSize(700, 500))
cmdEntry := widget.NewEntry()
cmdEntry.SetPlaceHolder("输入命令...")
output := widget.NewMultiLineEntry()
output.Disable()
output.Wrapping = fyne.TextWrapWord
var mu sync.Mutex
appendOutput := func(text string) {
mu.Lock()
defer mu.Unlock()
current := output.Text
if len(current) > 0 {
current += "\n"
}
output.SetText(current + text)
}
runBtn := widget.NewButton("执行", func() {
command := cmdEntry.Text
if command == "" {
return
}
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("cmd", "/c", command)
} else {
cmd = exec.Command("sh", "-c", command)
}
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()
cmd.Start()
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
appendOutput(scanner.Text())
}
}()
go func() {
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
appendOutput("[ERR] " + scanner.Text())
}
}()
cmd.Wait()
})
clearBtn := widget.NewButton("清空", func() {
output.SetText("")
})
content := container.NewBorder(
container.NewVBox(
widget.NewLabelWithStyle("命令执行器 (实时输出)", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
widget.NewSeparator(),
cmdEntry,
container.NewHBox(runBtn, clearBtn),
),
nil, nil, nil,
output,
)
myWindow.SetContent(content)
myWindow.ShowAndRun()
}
```
### 3.3 带进度条的长时间任务
```go
package main
import (
"context"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"os/exec"
"time"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("带超时的命令执行")
myWindow.Resize(fyne.NewSize(500, 300))
progress := widget.NewProgressBar()
statusLabel := widget.NewLabel("就绪")
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "ping", "-n", "5", "127.0.0.1") // Windows
// cmd := exec.CommandContext(ctx, "ping", "-c", "5", "127.0.0.1") // Linux/macOS
// 模拟进度更新
go func() {
for i := 0; i <= 100; i++ {
progress.SetValue(float64(i) / 100.0)
time.Sleep(100 * time.Millisecond)
}
}()
output, err := cmd.Output()
if ctx.Err() == context.DeadlineExceeded {
statusLabel.SetText("执行超时!")
return
}
if err != nil {
statusLabel.SetText("错误: " + err.Error())
return
}
statusLabel.SetText("完成! 输出: " + string(output))
}()
content := container.NewVBox(
widget.NewLabel("任务进度:"),
progress,
statusLabel,
)
myWindow.SetContent(content)
myWindow.ShowAndRun()
}
```
---
## 4. `os/exec` 关键方法对比
| 方法 | 用途 | 特点 |
|------|------|------|
| `cmd.Run()` | 执行命令并等待完成 | stdout/stderr 需手动设置 |
| `cmd.Output()` | 执行并返回 stdout | stderr 为空时出错 |
| `cmd.CombinedOutput()` | 执行并返回 stdout+stderr | stderr 合并到输出 |
| `cmd.Start()` + `cmd.Wait()` | 手动控制启停 | 可配合 Pipe 实现实时输出 |
| `cmd.StdoutPipe()` | 获取 stdout 管道 | 需在 Start 前调用 |
| `cmd.StderrPipe()` | 获取 stderr 管道 | 需在 Start 前调用 |
| `exec.CommandContext()` | 带 context 的命令 | 支持超时/取消 |
---
## 5. 常见问题
### Q: `cmd.Output()` 报 "exit status 1"
**A:** 命令执行失败且 stderr 有内容时,`Output()` 会直接报错。改用 `CombinedOutput()` 或手动设置 `cmd.Stdout` / `cmd.Stderr`
### Q: 如何执行 `cd` 命令?
**A:** `cd` 是 shell 内置命令,不能用 `exec.Command` 直接执行。应使用 `cmd.Dir` 设置工作目录。
### Q: Fyne 编译后文件太大?
**A:** 使用 `go build -ldflags="-s -w"` 压缩,或使用 `fyne package` 打包。
### Q: Fyne 不支持 Windows 7
**A:** Fyne v2.4+ 需要 Windows 10 1809+。
---
## 6. 参考资料
- [Fyne 官方文档](https://developer.fyne.io/)
- [Go os/exec 文档](https://pkg.go.dev/os/exec)
- [Fyne GitHub](https://github.com/fyne-io/fyne)