【问题标题】:How to execute interactive CLI command in golang?如何在 golang 中执行交互式 CLI 命令?
【发布时间】:2019-06-22 10:30:11
【问题描述】:

我正在尝试执行一个要求多个输入的命令,例如,如果您尝试将文件从本地设备复制到我们使用的远程设备scp test.txt user@domain:~/,那么它会要求我们输入密码。我想要的是编写一个 go 代码,其中我在代码本身中提供密码,例如pass:='Secret Password'。同样,我有 CLI 命令,它会询问我们一些事情,例如 IP、名称等,所以我需要编写一个代码,我只需在代码本身中声明所有值,并且当我运行代码时它不会询问任何内容只需从代码中获取所有输入并运行 CLI 命令,以防将文件复制到远程它不应该在我运行我的 go 二进制文件时询问我密码它应该直接将我的文件复制到远程决定。

func main() {
    cmd := exec.Command("scp", "text.txt", "user@domain:~/")        
    stdin, err := cmd.StdinPipe()
    if err = cmd.Start(); err != nil {
        log.Fatalf("failed to start command: %s", err)
    }
    io.WriteString(stdin, "password\n")
    if err = cmd.Wait(); err != nil {
    log.Fatalf("command failed: %s", err)
    }
}

如果我使用此代码,它会卡在 user@domain 的密码上:

并且没有文件被复制到远程设备。

【问题讨论】:

  • 尝试将密码指定为 URL 的一部分,如 user:password@domain:~/,对于其他命令,请尝试将它们指定为参数。
  • 以上命令只是交互式命令的例子。我有 CLI 会问几个问题,例如 Enter Your NameWhat is your IP
  • 您可能需要生成一个 PTY 并将子进程附加到它。

标签: go command-line-interface


【解决方案1】:

解决此问题的一种方法是使用命令行标志:

package main

import (
    "flag"
    "fmt"
    "math"
)

func main() {
    var (
        name = flag.String("name", "John", "Enter your name.")
        ip   = flag.Int("ip", 12345, "What is your ip?")
    )
    flag.Parse()
    fmt.Println("name:", *name)
    fmt.Println("ip:", *ip)
}

现在您可以使用nameip 标志运行程序:

go run main.go -name="some random name" -ip=12345678910`
some random name
ip: 12345678910

这个channel 是一个很好的资源——他曾经为 Go 团队工作,并制作了大量关于使用该语言开发命令行程序的视频。祝你好运!

【讨论】:

    【解决方案2】:

    我在尝试通过 golang os/exec 运行 linux make menuconfig 时遇到了这个问题。

    要完成您想要实现的目标,请尝试将 cmd.Stdin 设置为 os.Stdin。这是一个工作示例:

    package main
    
    import (
        "fmt"
        "os"
        "os/exec"
    )
    
    type cmdWithEnv struct {
        pwd     string
        command string
        cmdArgs []string
        envs    []string
    }
    
    func runCommand(s cmdWithEnv) error {
        cmd := exec.Command(s.command, s.cmdArgs...)
        if len(s.pwd) != 0 {
            cmd.Dir = s.pwd
        }
    
        env := os.Environ()
        env = append(env, s.envs...)
        cmd.Env = env
    
        fmt.Printf("%v\n", cmd)
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
        cmd.Stdin = os.Stdin // setting this allowed me to interact with ncurses interface from `make menuconfig`
    
        err := cmd.Start()
        if err != nil {
            return err
        }
    
        if err := cmd.Wait(); err != nil {
            return err
        }
        return nil
    }
    
    func buildPackage() {
        makeKernelConfig := cmdWithEnv{
            pwd:     "linux",
            command: "make",
            cmdArgs: []string{"-j12", "menuconfig"},
            envs:    []string{"CROSS_COMPILE=ccache arm-linux-gnueabihf-", "ARCH=arm"},
        }
    
        runCommand(makeKernelConfig)
    }
    
    func main() {
        buildPackage()
    }
    

    【讨论】:

      【解决方案3】:

      解决方案 1

      你可以用printf命令绕过这个

      cmd := "printf 'John Doe\nNew York\n35' | myInteractiveCmd"
      out, err := exec.Command("bash", "-c", cmd).Output()
      

      解决方案 2

      您可以使用io.Pipe()Pipe creates a synchronous in-memory pipe,您可以将答案写入io.Writer,您的cmd 将从io.Reader 读取。

      r, w := io.Pipe()
      cmd := exec.Command("myInteractiveCmd")
      cmd.Stdin = r
      go func() {
          fmt.Fprintf(w, "John Doe\n")
          fmt.Fprintf(w, "New York\n")
          fmt.Fprintf(w, "35\n")
          w.Close()
      }()
      cmd.Start()
      cmd.Wait()
      

      测试信息 为了测试这一点,我编写了 cmd,它询问姓名、城市、年龄并将结果写入文件。

      reader := bufio.NewReader(os.Stdin)
      
      fmt.Print("Name: ")
      name, _ := reader.ReadString('\n')
      name = strings.Trim(name, "\n")
      ...
      

      【讨论】:

        猜你喜欢
        • 2012-02-20
        • 1970-01-01
        • 1970-01-01
        • 2019-05-25
        • 2016-10-07
        • 2016-06-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多