b1.0
This commit is contained in:
@@ -0,0 +1,452 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user