Go 的分页输出

Paging output from Go

我正在尝试使用 $PAGER 或手动调用 moreless 从 golang 打印到 stdout 以允许用户轻松滚动浏览许多选项。我怎样才能做到这一点?

您可以使用 os/exec 程序包启动运行 less(或 $PAGER 中的任何内容)的进程,然后将字符串通过管道传输到其标准输入。以下对我有用:

func main() {
    // Could read $PAGER rather than hardcoding the path.
    cmd := exec.Command("/usr/bin/less")

    // Feed it with the string you want to display.
    cmd.Stdin = strings.NewReader("The text you want to show.")

    // This is crucial - otherwise it will write to a null device.
    cmd.Stdout = os.Stdout

    // Fork off a process and wait for it to terminate.
    err := cmd.Run()
    if err != nil {
        log.Fatal(err)
    }
}

这是一个有点幼稚的 cat 示例,它在设置时使用 $PAGER

package main

import (
        "io"
        "log"
        "os"
        "os/exec"
)

func main() {
        var out io.WriteCloser
        var cmd *exec.Cmd

        if len(os.Args) != 2 {
                log.Fatal("Wrong number of args: gcat <file>")
        }
        fileName := os.Args[1]
        file, err := os.Open(fileName)
        if err != nil {
                log.Fatal("Error opening file: ", err)
        }

        pager := os.Getenv("PAGER")

        if pager != "" {
                cmd = exec.Command(pager)

                var err error
                out, err = cmd.StdinPipe()
                if err != nil {
                        log.Fatal(err)
                }

                cmd.Stdout = os.Stdout

                if err := cmd.Start(); err != nil {
                        log.Fatal("Unable to start $PAGER: ", err)
                }

        } else {
                out = os.Stdout
        }

        _, err = io.Copy(out, file)
        if err != nil {
                log.Fatal(err)
        }

        file.Close()
        out.Close()

        if cmd != nil {
                if err := cmd.Wait(); err != nil {
                        log.Fatal("Error waiting for cmd: ", err)
                }
        }

}

我假设您已经将输出从您的程序打印到您想要捕获并发送到您的寻呼机的标准输出,您不想 重写 I/O 以使用其他响应所需的另一个输入流。

您可以创建一个 os.Pipe,其工作方式与 运行 一个带有“|less”的命令相同,方法是将一侧连接到寻呼机,另一侧连接到标准输出,如下所示:

// Create a pipe for a pager to use
r, w, err := os.Pipe()
if err != nil {
    panic("You probably want to fail more gracefully than this")
}

// Capture STDOUT for the Pager. Keep the old
// value so we can restore it later.
stdout := os.Stdout
os.Stdout = w

// Create the pager process to execute and attach
// the appropriate I/O streams.
pager := exec.Command("less")
pager.Stdin = r
pager.Stdout = stdout // the pager uses the original stdout, not the pipe
pager.Stderr = os.Stderr

// Defer a function that closes the pipe and invokes
// the pager, then restores os.Stdout after this function
// returns and we've finished capturing output.
//
// Note that it's very important that the pipe is closed,
// so that EOF is sent to the pager, otherwise weird things 
// will happen.
defer func() {
    // Close the pipe
    w.Close()
    // Run the pager
    if err := pager.Run(); err != nil {
        fmt.Fprintln(os.Stderr, err)
    }
    // restore stdout
    os.Stdout = stdout
}()

此版本为您想要分页的所有输出创建一个名为 pagerio.Writer(如果您愿意,可以将其分配给 os.Stdout)并正确关闭它并等待$PAGERmain() returns.


import (
    "fmt"
    "io"
    "log"
    "os"
    "os/exec"
)

var pager io.WriteCloser

func main() {
    var cmd *exec.Cmd
    cmd, pager = runPager()
    defer func() {
        pager.Close()
        cmd.Wait()
    }()
    fmt.Fprintln(pager, "Hello, 世界")
}

func runPager() (*exec.Cmd, io.WriteCloser) {
    pager := os.Getenv("PAGER")
    if pager == "" {
        pager = "more"
    }
    cmd := exec.Command(pager)
    out, err := cmd.StdinPipe()
    if err != nil {
        log.Fatal(err)
    }
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Start(); err != nil {
        log.Fatal(err)
    }
    return cmd, out
}