Reputation: 97
When I use Go's exec.Command{}
to execute some bash script that contains nohup, it will hang forever.
I don't know what are the differences between ping and ifconfig. I tried to redirect the stdin (< /dev/null
), the stdout(> /dev/null
) and the stderr(2> /dev/null
), and their combination, some of them work some don't.
When I use sh
to execute the script, it just ends up immediately.
The Go code:
package main
import (
"fmt"
"os/exec"
)
func main() {
cmd := exec.Command("sh", "a.sh")
out, err := cmd.Output() // Or cmd.CombinedOutput()
fmt.Println(string(out), err)
}
The Bash script (a.sh):
#!/bin/bash
# hangs
#nohup ping localhost &
# dot not hang
nohup ifconfig &
Upvotes: 1
Views: 1834
Reputation: 488163
(Converting comments, with glitches fixed, to answer)
The use of nohup
here is mostly a red herring. The real problem is that ping
never finishes. However, nohup
has some extra weirdness, which you can see if you run, from an interactive terminal, these two sets of commands:
$ nohup echo foo
nohup: ignoring input and appending output to 'nohup.out'
$ cat nohup.out
foo
$
vs:
$ nohup echo foo </dev/null 2>&1 | cat
foo
$
Note how the first one printed a weird message, and then the output foo
went to a file; the second did not, and then the output foo
showed up on the regular output stream. This is because POSIX says that nohup
should do these redirections if appropriate.1 When run with exec.Cmd
and cmd.Output
, the redirections are not performed.
At the OS level, on a Linux- or other Unix-like system, the exec
code creates an OS pipe
object by which the invoked command can send output back to the Go runtime. (There may be a separate pipe for its stderr output, or the two may both be directed to a single pipe, depending on how you run the command; see https://golang.org/src/os/exec/exec.go#L280.) This pipe winds up being passed to ping
, so that ping
can keep writing output there as long as it likes.
The shell itself exits, because the command nohup ping localhost &
is backgrounded. However, ping
still has write access to the pipe object, so the Go runtime continues calling the OS read
code until the pipe is closed—which is never. If the pipe were ever closed, the Go runtime would receive EOF and call the wait
system call to collect the shell's exit status, but that never happens.
Redirecting ping
's output, such that the shell itself has the only write access to the pipe, should result in the pipe being closed as soon as the shell itself exits.
(Some shells may have a builtin nohup
that may behave weirdly, especially in the presence of redirection. This is true of some particularly ancient shells.)
1See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/nohup.html for complete details. The Linux variant redirects stdin as well as stdout and stderr, if the input is a terminal, and if the output and stderr are terminals. The FreeBSD variant redirects only stdout and/or stderr. The "is a terminal" test is based on the C language isatty
function, which does the same thing as https://godoc.org/golang.org/x/crypto/ssh/terminal#IsTerminal.
Upvotes: 3