Различное поведение go exec для разных команд оболочки

Я пытаюсь использовать разные команды оболочки для консольного приложения, и по какой-то причине поведение для следующих интерактивных оболочек отличается.

Этот код выводит результат запроса 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)

person silent-box    schedule 03.05.2019    source источник
comment
Если работает второй вариант, то cypher-shell печатает в stderr, а не в stdout. Назначение cmd.Stdout отменяется вызовом StdinPipe.   -  person Peter    schedule 03.05.2019
comment
@Peter Я пытался удалить cmd.Stderr = os.Stderr, но он все равно печатает результат во втором варианте.   -  person silent-box    schedule 03.05.2019
comment
Я перепутал StdinPipe со StdoutPipe. Не обращайте внимания на мой предыдущий комментарий.   -  person Peter    schedule 03.05.2019


Ответы (1)


Когда ввод для 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 в производственном коде.

person Vasiliy Faronov    schedule 03.05.2019
comment
Работает как часы! Большое спасибо за ответ и заметки в сторону. Конечно, я пропускаю обработку ошибок в примерах, чтобы они были короткими. - person silent-box; 05.05.2019