【问题标题】:Different behavior of go exec for different shell commandsgo exec 对不同 shell 命令的不同行为
【发布时间】:2019-09-21 13:38:27
【问题描述】:

我正在尝试对控制台 go 应用程序使用不同的 shell 命令,但由于某种原因,以下交互式 shell 的行为有所不同。

此代码打印 mongoDB 查询的结果:

cmd := exec.Command("sh", "-c", "mongo --quiet --host=localhost blog")
stdout, _ := cmd.StdoutPipe()

stdin, _ := cmd.StdinPipe()
stdoutScanner := bufio.NewScanner(stdout)

go func() {
    for stdoutScanner.Scan() {
        println(stdoutScanner.Text())
    }
}()

cmd.Start()
io.WriteString(stdin, "db.getCollection('posts').find({status:'ACTIVE'}).itcount()\n")

//can't finish command, need to reuse it for other queries
//stdin.Close()
//cmd.Wait()

time.Sleep(2 * time.Second)

但 Neo4J shell 的相同代码不会打印任何内容:

cmd := exec.Command("sh", "-c", "cypher-shell -u neo4j -p 121314 --format plain")
stdout, _ := cmd.StdoutPipe()

stdin, _ := cmd.StdinPipe()
stdoutScanner := bufio.NewScanner(stdout)

go func() {
    for stdoutScanner.Scan() {
        println(stdoutScanner.Text())
    }
}()

cmd.Start()
io.WriteString(stdin, "match (n) return count(n);\n")

//can't finish the command, need to reuse it for other queries
//stdin.Close()
//cmd.Wait()
time.Sleep(2 * time.Second)

有什么区别?我怎样才能使第二个工作? (不关闭命令)

附言 当我直接打印到os.Stdout 时,Neo4J 工作正常:

cmd := exec.Command("sh", "-c", "cypher-shell -u neo4j -p 121314 --format plain")

cmd.Stdout = os.Stdout

stdin, _ := cmd.StdinPipe()

cmd.Start()
io.WriteString(stdin, "match (n) return count(n);\n")

//stdin.Close()
//cmd.Wait()
time.Sleep(2 * time.Second)

【问题讨论】:

  • 如果它适用于第二个变体,那么 cypher-shell 会打印到 stderr,而不是 stdout。 cmd.Stdout 分配通过调用 StdinPipe 被撤销。
  • @Peter 我试图删除cmd.Stderr = os.Stderr,但它仍然以第二个变体打印结果。
  • 我把 StdinPipe 误认为是 StdoutPipe。没关系我之前的评论。

标签: go cmd interactive-shell


【解决方案1】:

cypher-shell 的输入不是(交互式)终端时,它希望读取整个输入并将其作为单个脚本执行。 “整个输入”是指“直到 EOF 之前的所有内容”。这是 REPL 程序的典型情况:例如,python 的行为也是如此。

所以您的 Cypher 代码甚至在您 stdin.Close() 之前都不会开始执行。您的 cmd.Stdout = os.Stdout 示例似乎有效,因为当您的 Go 程序退出时,stdin 隐式关闭,并且只有 then cypher-shell 执行您的代码并打印到仍然连接到您的终端的标准输出.

您可能应该以不同的方式构建您的流程。例如,您不能为每个查询运行一个新的cypher-shell 吗?

然而,如果所有其他方法都失败了,您可以通过欺骗cypher-shell 使其认为其标准输入为is a terminal 来解决此问题。这称为“pty”,您可以在 Go 中使用 github.com/kr/pty 进行操作。 问题是,这也会使cypher-shell 打印提示并回显您的输入,如果您希望以编程方式处理输出,则必须检测并丢弃它们。

cmd := exec.Command("sh", "-c", "cypher-shell -u neo4j -p 121314 --format plain")
f, _ := pty.Start(cmd)
stdoutScanner := bufio.NewScanner(f)
cmd.Start()

// Give it some time to start, then read and discard the startup banner.
time.Sleep(2 * time.Second)
f.Read(make([]byte, 4096))

go func() {
    for stdoutScanner.Scan() {
        println(stdoutScanner.Text())
    }
}()

io.WriteString(f, "match (n) return count(n);\n")
time.Sleep(2 * time.Second)

io.WriteString(f, "match (n) return count(n) + 123;\n")
time.Sleep(2 * time.Second)

除 1: 在您的示例中,您不需要 sh -c,因为您没有使用 shell 的任何功能。您可以通过直接运行 cypher-shell 来避免额外的 shell 进程的开销:

cmd := exec.Command("cypher-shell", "-u", "neo4j", "-p", "121314", "--format", "plain")

旁白 2:不要丢弃生产代码中返回的 error 值。

【讨论】:

  • 它就像一个魅力!非常感谢您的回答和旁注。当然,为了简短起见,我跳过了示例中的错误处理。
猜你喜欢
  • 1970-01-01
  • 2014-08-08
  • 2015-04-12
  • 1970-01-01
  • 1970-01-01
  • 2011-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多