Я пытаюсь использовать разные команды оболочки для консольного приложения, и по какой-то причине поведение для следующих интерактивных оболочек отличается.
Этот код выводит результат запроса 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 ничего не печатает:
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.
Neo4J отлично работает, когда я печатаю напрямую в os.Stdout:
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)
@Peter Я пытался удалить cmd.Stderr = os.Stderr, но он все равно печатает результат во втором варианте.
Я перепутал StdinPipe со StdoutPipe. Не обращайте внимания на мой предыдущий комментарий.

Когда вводом для cypher-shell является нет (интерактивный) терминал, он ожидает прочитать ввод весь и выполнить его как единый скрипт. «Весь ввод» означает «все до EOF». Это типично для REPL-программ: например, python ведет себя так же.
Таким образом, ваш код Cypher даже не начнет выполняться, пока вы не stdin.Close(). Ваш cmd.Stdout = os.Stdout пример работает, потому что stdin неявно закрывается при выходе из вашей программы Go, и только тогдаcypher-shell выполняет ваш код и печатает на стандартный вывод, который все еще подключен к вашему терминалу.
Вероятно, вам следует структурировать свой процесс по-другому. Например, нельзя ли запускать новый cypher-shell для каждого запроса?
Однако, если ничего не помогает, вы можете обойти это, обманув cypher-shell, заставив его думать, что это стандартный ввод терминал. Это называется «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, потому что вы не используете никаких функций оболочки. Вы можете избежать накладных расходов на дополнительный процесс оболочки, запустив cypher-shell напрямую:
cmd := exec.Command("cypher-shell", "-u", "neo4j", "-p", "121314", "--format", "plain")
Сторона 2: Не отбрасывать возвращенные error значения в производственном коде.
Работает как часы! Большое спасибо за ответ и заметки в сторону. Конечно, я пропускаю обработку ошибок в примерах, чтобы они были короткими.
Если работает второй вариант, то cypher-shell печатает в stderr, а не в stdout. Назначение cmd.Stdout отменяется вызовом StdinPipe.