You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

fyne.Terminal调用RunWithConnection连接外部命令失败,求远程适配方案

问题:Fyne终端模拟器使用RunWithConnection连接exec.Command时显示空白

直接使用term.RunLocalShell()可以正常运行,但手动将os/exec命令的stdin/stdout连接到term.RunWithConnection(in, out)时,终端显示空白且无报错。

我发现terminal.RunLocalShell会调用内部的terminal.open()方法,该方法使用github.com/creack/pty创建伪终端(PTY),但terminal.pty是私有字段无法外部访问。我的实际需求是运行远程shell命令,通过TCP连接的管道将远程进程的stdin/stdout映射到本地终端模拟器。

最小复现代码:

package main

import (
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/container"
    "fyne.io/fyne/v2/layout"
    "github.com/fyne-io/terminal"
    "log/slog"
    "os"
    "os/exec"
    "sync"
)

func main() {
    err := runMain()
    if err != nil {
        slog.Error(err.Error())
        os.Exit(1)
    }
}

func runMain() error {
    wg := sync.WaitGroup{}
    wg.Add(1)

    term := terminal.New()
    go func() {
        var err error

        // 此代码可正常运行
        // err = term.RunLocalShell()

        // 此代码无法运行,终端显示空白...
        cmd := exec.Command("/usr/bin/bash")
        si, err := cmd.StdinPipe()
        if err != nil {
            slog.Error(err.Error())
            os.Exit(1)
        }
        so, err := cmd.StdoutPipe()
        if err != nil {
            slog.Error(err.Error())
            os.Exit(1)
        }
        err = cmd.Start()
        if err != nil {
            slog.Error(err.Error())
            os.Exit(1)
        }
        slog.Info("started", "pid", cmd.Process.Pid)
        err = term.RunWithConnection(si, so)

        if err != nil {
            slog.Error(err.Error())
        }

        wg.Done()
    }()

    a := app.New()
    go func() {
        wg.Wait()
        a.Quit()
    }()
    w := a.NewWindow("Test")
    w.SetContent(container.New(layout.NewStackLayout(), term))
    w.ShowAndRun()
    return nil
}

解决方案

问题核心是:bash这类交互式shell需要**伪终端(PTY)**环境才能正常工作,直接用StdinPipe/StdoutPipe创建的管道是普通管道,不具备PTY的终端特性(比如控制字符处理、窗口大小通知、ANSI转义序列支持等),所以bash不会输出提示符,终端也就显示空白。

要解决这个问题,你需要自己手动创建PTY并关联到exec命令,然后将PTY的读写端传给RunWithConnection。具体步骤如下:

  1. 引入github.com/creack/pty包(和Fyne终端内部用的是同一个)
  2. 创建PTY并将命令的标准输入/输出/错误都绑定到PTY
  3. 将PTY的主设备端作为RunWithConnection的输入输出
  4. 同步终端窗口大小到PTY(可选但必要,否则命令输出会乱换行)

修改后的代码示例:

package main

import (
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/container"
    "fyne.io/fyne/v2/layout"
    "github.com/creack/pty"
    "github.com/fyne-io/terminal"
    "log/slog"
    "os"
    "os/exec"
    "sync"
)

func main() {
    err := runMain()
    if err != nil {
        slog.Error(err.Error())
        os.Exit(1)
    }
}

func runMain() error {
    wg := sync.WaitGroup{}
    wg.Add(1)

    term := terminal.New()
    go func() {
        defer wg.Done()

        cmd := exec.Command("/usr/bin/bash")
        // 创建PTY并绑定到命令
        ptmx, err := pty.Start(cmd)
        if err != nil {
            slog.Error("failed to start cmd with pty", "err", err)
            os.Exit(1)
        }
        defer ptmx.Close()

        slog.Info("started", "pid", cmd.Process.Pid)

        // 同步终端窗口大小到PTY
        go func() {
            prevSize := term.Size()
            for size := range term.SizeChan() {
                if size != prevSize {
                    pty.Setsize(ptmx, &pty.Winsize{
                        Rows: uint16(size.Height),
                        Cols: uint16(size.Width),
                    })
                    prevSize = size
                }
            }
        }()

        // 将PTY的主设备端传入RunWithConnection
        err = term.RunWithConnection(ptmx, ptmx)
        if err != nil {
            slog.Error("terminal run failed", "err", err)
        }

        // 等待命令结束
        if err := cmd.Wait(); err != nil {
            slog.Info("cmd exited", "err", err)
        }
    }()

    a := app.New()
    go func() {
        wg.Wait()
        a.Quit()
    }()
    w := a.NewWindow("Test")
    w.SetContent(container.New(layout.NewStackLayout(), term))
    w.ShowAndRun()
    return nil
}

关键说明

  • pty.Start(cmd)会自动将cmd的stdin/stdout/stderr绑定到PTY的从设备端,同时返回PTY的主设备端ptmx,我们只需要操作这个主设备端即可和命令交互。
  • 窗口大小同步是必须的:当用户调整终端窗口时,需要将新的大小通知给PTY,否则bash等命令的输出会按照初始大小换行,导致显示错乱。
  • 对于远程shell场景,你只需要将远程TCP连接的读写端替换掉示例中的ptmx即可——确保远程端也是通过PTY启动的shell,否则同样会遇到交互问题。

内容的提问来源于stack exchange,提问作者nagylzs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 22:17:04