golang exec Output()卡住了,但是Run()没问题

when I execute an go exec command, it stucks, I don't know why?
the Go code:

func main() {
    cmd := exec.Command("/bin/bash", "test.sh")
    _, err := cmd.Output()
    //err := cmd.Run()
    if err != nil {
            fmt.Println(err)
    } else {
            fmt.Println("out")
    }
}

as show in the code, if use Run(), it's OK.

the test.sh:

#!/bin/bash
./sleep.sh &

it call another shell script, run sleep.sh in background

the sleep.sh:

#!/bin/bash
while true
do
  echo hello >> test.txt
  sleep 30
done

it never quit, always run in the background.

you can see actually it will not output anything to stdout, but Output() will Stuck, Run() can just fine. the source code of Go show Output() will call Run(). so why?

some GDB info when the program stuck:

(gdb) info goroutines
  1 waiting  runtime.gopark
  2 waiting  runtime.gopark
  3 waiting  runtime.gopark
  4 waiting  runtime.gopark
* 5 syscall  syscall.Syscall

(gdb) goroutine 5 bt
#0  syscall.Syscall () at /usr/local/go/src/syscall/asm_linux_amd64.s:19
#1  0x00000000004acf2f in syscall.read (fd=4, p= []uint8 = {...}, n=4254696, err=...) at /usr/local/go/src/syscall/zsyscall_linux_amd64.go:783
#2  0x00000000004ac73d in syscall.Read (fd=4, p= []uint8 = {...}, n=859530501352, err=...) at /usr/local/go/src/syscall/syscall_unix.go:160
#3  0x0000000000487703 in os.(*File).read (f=0xc82002c030, b= []uint8 = {...}, n=859530919936, err=...) at /usr/local/go/src/os/file_unix.go:211
#4  0x0000000000484f3a in os.(*File).Read (f=0xc82002c030, b= []uint8 = {...}, n=0, err=...) at /usr/local/go/src/os/file.go:95
#5  0x00000000004a5c6f in bytes.(*Buffer).ReadFrom (b=0xc8200140e0, r=..., n=0, err=...) at /usr/local/go/src/bytes/buffer.go:173
#6  0x0000000000482160 in io.copyBuffer (dst=..., src=..., buf= []uint8, written=0, err=...) at /usr/local/go/src/io/io.go:375
#7  0x0000000000481fa4 in io.Copy (dst=..., src=..., written=0, err=...) at /usr/local/go/src/io/io.go:351
#8  0x000000000047262b in os/exec.(*Cmd).writerDescriptor.func1 (~r0=...) at /usr/local/go/src/os/exec/exec.go:232
#9  0x00000000004726cd in os/exec.(*Cmd).Start.func1 (c=0xc820088000, fn={void (error *)} 0xc820029fb8) at /usr/local/go/src/os/exec/exec.go:340
#10 0x0000000000456241 in runtime.goexit () at /usr/local/go/src/runtime/asm_amd64.s:1721
#11 0x000000c820088000 in ?? ()
#12 0x000000c82000c140 in ?? ()
#13 0x0000000000000000 in ?? ()

What you are seeing is normal behaviour, but it's a little unusual. What happens is your program forks /bin/bash, which forks test.sh, which forks sleep.sh. Now you are waiting on Output, which means the stdout from the command you executed, /bin/bash, which has forked test.sh, then returned.

So, why does this behave differently when you just call cmd.Run ? Because your program forks /bin/bash, which then forks test.sh, and returns. Your program is not waiting to read the output of the program, it's just looking for the exit status of /bin/bash, which returns immediately.

Here are some exercises:

  1. Modify your code sample to use cmd.StdoutPipe
  2. Observe the changes in behaviour between waiting on cmd.StdoutPipe().Read() to return io.EOF (ioutils.ReadAll will be useful here) and another goroutine waiting on cmd.Run(). Be sure not to let your main goroutine exit til the other one reading from the pipe has exited.