Разное поведение go exec для разных команд оболочки - PullRequest
2 голосов
/ 03 мая 2019

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

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

В чем разница?Как я могу заставить второй работать?(без закрытия команды)

PS 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)

1 Ответ

1 голос
/ 03 мая 2019

Когда вход для cypher-shell является , а не (интерактивным) терминалом, он ожидает прочитать весь ввод и выполнить его как единый сценарий. «Весь ввод» означает «все до конца». Это типично для программ 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 значения в производственном коде.

...