exe c .Wait () с измененным Stdin ждет бесконечно - PullRequest
0 голосов
/ 11 марта 2020

Я сталкиваюсь со странным поведением с exe c .Wait () с измененным Stdin. Я просто изменяю Stdin, чтобы иметь возможность дублировать его содержимое, подсчитать объем данных ... но здесь проблема не в этом.

Я сделал эту урезанную программу просто для демонстрации странного поведения:

  • с измененным Stdin, cmd.Wait() ждет бесконечно ... пока я не нажму "enter" или "^ C"
  • с неизмененным Stdin (раскомментируйте строку cmd.Stdin = os.Stdin ), программа обрабатывается до конца без нареканий.
  • когда я запускаю эту программу (с измененным Stdin) с Delve (dlv debug), программа обрабатывает до конца без нареканий!
  • I также добавил time.Sleep 30 секунд между cmd.Start() и cmd.Wait(), а затем подключил программу к Delve (dlv attach PID). Когда я ввожу continue, cmd.Wait() ждет бесконечно ... пока я не нажму "enter" или "^ C"

Я проверил эти поведения с go1.11 и go1.12

package main

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

type Splitter struct {
    f  *os.File
    fd int
}

func NewSplitter(f *os.File) *Splitter {
    return &Splitter{f, int(f.Fd())}
}

func (s *Splitter) Close() error {
    return s.f.Close()
}

func (s *Splitter) Read(p []byte) (int, error) {
    return s.f.Read(p)
}

func (s *Splitter) Write(p []byte) (int, error) {
    return s.f.Write(p)
}

func main() {
    var cmd *exec.Cmd
    cmd = exec.Command("cat", "foobarfile")
    cmd.Stdin = NewSplitter(os.Stdin)
    //cmd.Stdin = os.Stdin
    cmd.Stdout = NewSplitter(os.Stdout)
    cmd.Stderr = NewSplitter(os.Stderr)
    cmd.Start()
    cmd.Wait()
    fmt.Println("done")
}

Есть что-то, что я делаю не так?

Спасибо за вашу помощь.

Ответы [ 2 ]

2 голосов
/ 11 марта 2020

Вы заменяете дескрипторы файла процесса, которые обычно *os.File, другими типами Go. Для того чтобы stdin действовал как поток, пакет os/exec должен запустить программу, чтобы скопировать данные между io.Reader и процессом. Это задокументировано в пакете os/exec:

// Otherwise, during the execution of the command a separate
// goroutine reads from Stdin and delivers that data to the command
// over a pipe. In this case, Wait does not complete until the goroutine
// stops copying, either because it has reached the end of Stdin
// (EOF or a read error) or because writing to the pipe returned an error.

Если вы посмотрите на трассировку стека из вашей программы, вы увидите, что она ожидает завершения выполнения io-подпрограмм в Wait():

goroutine 1 [chan receive]:
os/exec.(*Cmd).Wait(0xc000076000, 0x0, 0x0)
    /usr/local/go/src/os/exec/exec.go:510 +0x125
main.main()

Поскольку вы теперь управляете потоком данных, вы можете закрыть его при необходимости. Если Stdin здесь не нужен, не назначайте его вообще. Если он будет использоваться, то вы должны Close() вернуть его Wait().

Другой вариант - убедиться, что вы , используя *os.File, что Самый простой способ - использовать методы StdinPipe, StdoutPipe и StderrPipe, которые в свою очередь используют os.Pipe() , Этот способ гарантирует, что процесс имеет дело только с *os.File, а не с другими Go типами.

0 голосов
/ 11 марта 2020

Эта программа дублирует содержимое, как вы просили. Вы также можете попробовать прокомментированную часть. И комментарии говорят сами за себя, я надеюсь, что это объясняет ваш запрос.

package main

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

func main() {
    // Execute cat command w/ arguments
    // cmd := exec.Command("cat", "hello.txt")

    // Execute cat command w/o arguments
    cmd := exec.Command("cat")

    // Attach STDOUT stream
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        log.Println(err)
    }

    // Attach STDIN stream
    stdin, err := cmd.StdinPipe()
    if err != nil {
        log.Println(err)
    }

    // Attach STDERR stream
    stderr, err := cmd.StderrPipe()
    if err != nil {
        log.Println(err)
    }

    // Spawn go-routine to copy os's stdin to command's stdin
    go io.Copy(stdin, os.Stdin)

    // Spawn go-routine to copy command's stdout to os's stdout
    go io.Copy(os.Stdout, stdout)

    // Spawn go-routine to copy command's stderr to os's stderr
    go io.Copy(os.Stderr, stderr)

    // Run() under the hood calls Start() and Wait()
    cmd.Run()

    // Note: The PIPES above will be closed automatically after Wait sees the command exit.
    // A caller need only call Close to force the pipe to close sooner.
    log.Println("Command complete")
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...