Различное поведение 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)

Если работает второй вариант, то cypher-shell печатает в stderr, а не в stdout. Назначение cmd.Stdout отменяется вызовом StdinPipe.

Peter 03.05.2019 07:12

@Peter Я пытался удалить cmd.Stderr = os.Stderr, но он все равно печатает результат во втором варианте.

silent-box 03.05.2019 15:02

Я перепутал StdinPipe со StdoutPipe. Не обращайте внимания на мой предыдущий комментарий.

Peter 03.05.2019 15:09
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
2
3
386
1
Перейти к ответу Данный вопрос помечен как решенный

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

Работает как часы! Большое спасибо за ответ и заметки в сторону. Конечно, я пропускаю обработку ошибок в примерах, чтобы они были короткими.

silent-box 05.05.2019 04:01

Другие вопросы по теме