go exec对于不同的shell命令的不同行为

时间:2019-05-03 02:18:06

标签: go cmd interactive-shell

我正在尝试对console 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)

有什么区别?我该如何使第二个工作? (无需关闭命令)

P.S 当我直接打印到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)

1 个答案:

答案 0 :(得分:1)

cypher-shell的输入不是(strong)交互式终端时,它希望读取 entire 输入并将其作为单个脚本执行。 “全部输入”表示“直到EOF为止的所有信息”。这对于REPL程序来说很典型:例如,python的行为也是如此。

因此,直到您stdin.Close(),您的Cypher代码才开始执行。您的cmd.Stdout = os.Stdout示例似乎可以工作,因为stdin在您的Go程序退出时被隐式关闭,并且只有 then 然后cypher-shell执行您的代码并打印到stdout,仍连接到您的终端。

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

但是,如果所有其他方法均失败,则可以通过使cypher-shell认为其标准输入is a terminal来解决此问题。这称为“ pty”,您可以使用github.com/kr/pty在Go中进行操作。 要抓住的地方是,这还会使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值。