Перенаправить конвейер stdout дочернего процесса в Go

105

Я пишу программу на Go, которая выполняет серверную программу (также Go). Теперь я хочу иметь стандартный вывод дочерней программы в окне терминала, в котором я запустил родительскую программу. Один из способов сделать это - использовать cmd.Output()функцию, но она выводит стандартный вывод только после выхода из процесса. (Это проблема, потому что эта серверная программа работает долгое время, и я хочу прочитать вывод журнала)

Переменная outимеет значение, type io.ReadCloserи я не знаю, что мне делать с ней для выполнения моей задачи, и я не могу найти в Интернете ничего полезного по этой теме.

func main() {
    cmd := exec.Command("/path/to/my/child/program")
    out, err := cmd.StdoutPipe()
    if err != nil {
        fmt.Println(err)
    }
    err = cmd.Start()
    if err != nil {
        fmt.Println(err)
    }
    //fmt.Println(out)
    cmd.Wait()
} 

Пояснение к коду: раскомментируйте Printlnфункцию, чтобы код компилировался, я знаю, что Println(out io.ReadCloser)это бессмысленная функция.
(выводит результат &{3 |0 <nil> 0}) Эти две строчки нужны только для компиляции кода.

mbert
источник
1
Ваша строка "exec" оператора импорта должна быть "os / exec".
evilspacepirate
спасибо за информацию, на самом деле это был только exec pre go1, теперь он в os. обновил его для go1
mbert
1
Я не думаю, что вам действительно нужно звонить io.Copyв подпрограммах go
rmonjo
Я не думаю, что вам нужно звонить cmd.Wait()или for{}зацикливаться ... почему они здесь?
weberc2 07
@ weberc2 для этого взгляда на ответ elimisteve. Цикл for не нужен, если вы просто хотите запустить программу один раз. Но если вы не вызовете cmd.Wait (), ваша main () может завершиться до завершения вызываемой программы, и вы не получите желаемого результата
mbert

Ответы:

207

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

Не нужно возиться с трубами или горутинами, это просто.

func main() {
    // Replace `ls` (and its arguments) with something more interesting
    cmd := exec.Command("ls", "-l")
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Run()
}
cmccabe
источник
4
Кроме того, если вы хотите, чтобы команда прослушивала ввод, вы можете просто установить, cmd.Stdin = os.Stdinтаким образом, как будто вы буквально выполнили эту команду из своей оболочки.
Nucleon
4
Для тех, кто хочет перенаправить logвместо stdout, есть ответ здесь
Рик Смит,
18

Я считаю , что если импортировать ioи osзаменить это:

//fmt.Println(out)

с этим:

go io.Copy(os.Stdout, out)

(см. документацию дляio.Copy и дляos.Stdout ), он будет делать то, что вы хотите. (Отказ от ответственности: не проверено.)

Кстати, вы, вероятно, захотите зафиксировать и стандартную ошибку, используя тот же подход, что и для стандартного вывода, но с помощью cmd.StderrPipeи os.Stderr.

руах
источник
2
@mbert: Я использовал достаточно других языков и достаточно читал о Go, чтобы иметь представление о том, какая функция, вероятно, будет существовать для этого и примерно в какой форме; тогда мне просто нужно было просмотреть соответствующие пакетные документы (найденные в Google), чтобы подтвердить, что моя догадка верна, и найти необходимые детали. Самыми сложными частями были (1) поиск того, что называется стандартным выводом ( os.Stdout) и (2) подтверждение предположения о том, что, если вы вообще не вызываете cmd.StdoutPipe(), стандартный вывод идет к стандартному выводу /dev/nullродительского процесса, а не к нему. .
ruakh
15

Для тех, кому это не нужно в цикле, но хотелось бы, чтобы вывод команды выводился на терминал без cmd.Wait()блокировки других операторов:

package main

import (
    "fmt"
    "io"
    "log"
    "os"
    "os/exec"
)

func checkError(err error) {
    if err != nil {
        log.Fatalf("Error: %s", err)
    }
}

func main() {
    // Replace `ls` (and its arguments) with something more interesting
    cmd := exec.Command("ls", "-l")

    // Create stdout, stderr streams of type io.Reader
    stdout, err := cmd.StdoutPipe()
    checkError(err)
    stderr, err := cmd.StderrPipe()
    checkError(err)

    // Start command
    err = cmd.Start()
    checkError(err)

    // Don't let main() exit before our command has finished running
    defer cmd.Wait()  // Doesn't block

    // Non-blockingly echo command output to terminal
    go io.Copy(os.Stdout, stdout)
    go io.Copy(os.Stderr, stderr)

    // I love Go's trivial concurrency :-D
    fmt.Printf("Do other stuff here! No need to wait.\n\n")
}
Elimisteve
источник
Незначительное к сведению: (Очевидно) вы можете пропустить результаты запущенных горутин, если ваше «делай что-нибудь здесь» выполняется быстрее, чем горутины. Завершение main () также приведет к завершению работы горутин. так что вы потенциально можете не в конечном итоге фактически выводить эхо в терминал, если вы не дожидаетесь завершения cmd.
galaktor