代理执行命令时,不丢失 TTY 的标准输出/标准错误。

huangapple go评论81阅读模式
英文:

Proxy exec.Cmd Stdout / Stderr without losing TTY

问题

我有以下代码,执行任意的 shell 命令并将 stdoutstderr 导向终端:

c := exec.Command("/bin/sh", "-c", cmd)
c.Stdin = os.Stdin
c.Stdout = os.Stdout
c.Stderr = os.Stderr

然而,我需要在打印之前处理输出,所以我用一个代理的 io.Writer 接口进行了封装:

type ProxyWriter struct {
    file *os.File
}

func NewProxyWriter(file *os.File) *ProxyWriter {
    return &ProxyWriter{
        file: file,
    }
}

func (w *ProxyWriter) Write(p []byte) (int, error) {
    // ... 先对字节进行处理
    fmt.Fprintf(w.file, "%s", string(p))
    return len(p), nil
}

所以现在的原始代码变成了:

c := exec.Command("/bin/sh", "-c", cmd)
c.Stdin = os.Stdin
c.Stdout = NewProxyWriter(os.Stdout)
c.Stderr = NewProxyWriter(os.Stderr)

这个方法大部分情况下都有效,但是 stdoutstderr 不再被视为 TTY。之前的样式或颜色输出不再生效。

我确认了这不是 ProxyWriter 导致格式混乱的问题,通过将命令设置为以下内容进行输出,可以正确显示带颜色的文本:

c := exec.Command("echo", "3[0;31mTEST3[0m")

一个更明确的测试是:

c := exec.Command("/bin/sh", "-c", "if [ -t 1 ] ; then echo \"terminal\"; else echo \"not a terminal\"; fi")

输出结果为:

not a terminal

有没有办法在不失去 TTY 状态的情况下封装命令的 stdout / stderr?

英文:

I've got the following code that executes an arbitrary shell command and pipes the stdout and stderr to the terminal.:

c := exec.Command("/bin/sh", "-c", cmd)
c.Stdin = os.Stdin
c.Stdout = os.Stdout
c.Stderr = os.Stderr

However, I need to process the output before I print it, so I've wrapped it with a proxy io.Writer interface:

type ProxyWriter struct {
	file   *os.File
}

func NewProxyWriter(file *os.File) *ProxyWriter {
	return &ProxyWriter{
		file: file,
	}
}

func (w *ProxyWriter) Write(p []byte) (int, error) {
    // ... do something with bytes first
    fmt.Fprintf(w.file, "%s", string(p))
	return len(p), nil
}

So the original code is now:

c := exec.Command("/bin/sh", "-c", cmd)
c.Stdin = os.Stdin
c.Stdout = NewProxyWriter(os.Stdout)
c.Stderr = NewProxyWriter(os.Stderr)

This works for the most part, however, stdout and stderr no longer seem to qualify as a TTY. Any previously styled or colored output is no longer styled or colored.

I've confirmed that this is not an issue of my ProxyWriter simply messing with the formatting by setting the command to the following, which outputs the colored text correctly.

c := exec.Command("echo", "3[0;31mTEST3[0m")

A more explicit test is:

c := exec.Command("/bin/sh", "-c", "if [ -t 1 ] ; then echo \"terminal\"; else echo \"not a terminal\"; fi")

Which outputs:

not a terminal

Is there anyway I can wrap the commands stdout / stderr without losing the TTY status?

答案1

得分: 1

将以下代码段翻译为中文:

func (w *ProxyWriter) Write(p []byte) (int, error) {
    // ... do something with bytes first
    fmt.Fprintf(w.file, "%s", string(p))
    return len(p), nil
}

翻译为:

func (w *ProxyWriter) Write(p []byte) (int, error) {
    // ... 首先对字节进行一些操作
    return w.Write(p)
}

fmt.Fprintf 有一些逻辑来避免终端断行。

英文:

Replace

func (w *ProxyWriter) Write(p []byte) (int, error) {
    // ... do something with bytes first
    fmt.Fprintf(w.file, "%s", string(p))
    return len(p), nil
}

To

func (w *ProxyWriter) Write(p []byte) (int, error) {
    return w.Write(p)
}

fmt.Fprintf have some logic to avoid terminal breaking.

答案2

得分: 0

你需要一个完整的 tty 实现,以使人工的标准输出符合终端的要求。mattn 建议的库似乎是一个不错的选择。

然而,根据你尝试保留颜色的命令,可能还有另一个简单得多的选项。大多数带有颜色输出的 GNU 实用程序,如 lsgrepdmesg,都有一个 --color 标志,可以覆盖 TTY 检测。

例如:

$ ls --color=always

$ ls -lsh | grep --color=always drwx

我经常使用这种方法将带有颜色的 dmesg 输出通过 less 进行分页:

$ sudo dmesg --color=always | less -R

你也可以相反地禁用颜色输出:

ls --color=never
英文:

You will need a full tty implementation to make an artificial stdout qualify as a terminal. The library that mattn suggested seems like a good candidate.

However, another much simpler option may be available depending on the command you are trying to keep colorization for. Most GNU utilities with color output such as ls, grep, and dmesg have a --color flag which can override the TTY detection.

For example:

$ ls --color=always

$ ls -lsh | grep --color=always drwx

I use this frequently to pipe colored dmesg output through less so that I can paginate it:

$ sudo dmesg --color=always | less -R

You can also do the opposite and disable color output:

ls --color=never

huangapple
  • 本文由 发表于 2017年7月4日 09:40:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/44895757.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定