Video Coming Soon...
42: nohup
I really liked learning how to implement nohup in this course. nohup is a tool that does two extremely useful things:
- Capture all, and I mean all of the output, no matter how stupid the programmer of the other tool has become. Got a command line tool that insists on sending useful information to the error output?
nohup. - Keeps a command running even if your network connection or terminal crashes...on Unix. On Windows this maybe works? Maybe could work? I don't know.
The Challenge
To implement nohup you'll need one little package from the internet named "github.com/mattn/go-isatty". This package has two functions that will tell you if os.Stdout is attached to a terminal or not:
fd := os.Stdout.Fd()
is_atty := isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
You'll also need to ignore the syscall.SIGHUP signal so that nohup keeps running even if the terminal closes:
signal.Ignore(syscall.SIGHUP)
I'm giving you this code because this is deeep Unix lore nobody remembers. I didn't even remember it and I've been using Unix for decades. Now you're on your own.
The documentation for nohup is here:
Requirements
See the list of requirements
- isatty -- https://github.com/mattn/go-isatty
- os -- https://pkg.go.dev/os
- os/exec -- https://pkg.go.dev/os/exec
- flag -- https://pkg.go.dev/flag
- log -- https://pkg.go.dev/log
- io -- https://pkg.go.dev/io
- os/signal -- https://pkg.go.dev/os/signal
- syscall -- https://pkg.go.dev/syscall
Spoilers
Try your best to figure this one out but it may be a little weird if you've never used Unix.
See my first version code
View Source file go-coreutils/nohup/main.go Onlypackage main
import (
"os"
"os/exec"
"github.com/mattn/go-isatty"
"flag"
"log"
"io"
"os/signal"
"syscall"
)
func Exec(prog string, args []string, target_out io.Writer) {
cmd := exec.Command(prog, args...)
if cmd.Err != nil { log.Fatal(cmd.Err) }
in, err := cmd.StdinPipe()
if err != nil { log.Fatal(err) }
in.Close()
stdout, err := cmd.StdoutPipe()
if err != nil { log.Fatal(err) }
stderr, err := cmd.StderrPipe()
if err != nil { log.Fatal(err) }
output := io.MultiReader(stdout, stderr)
err = cmd.Start()
if err != nil { log.Fatal(err) }
_, err = io.Copy(target_out, output)
if err != nil { log.Fatal(err) }
}
func OpenOutput() io.Writer {
fd := os.Stdout.Fd()
is_atty := isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
if is_atty {
// create nohup
out_file, err := os.OpenFile("nohup.out", os.O_RDWR | os.O_CREATE | os.O_TRUNC, 0644)
if err != nil { log.Fatal(err) }
return out_file
} else {
return os.Stdout
}
}
func main() {
flag.Parse()
args := flag.Args()
if flag.NArg() == 0 {
log.Fatal("USAGE: nohup cmd [args]")
}
output := OpenOutput()
Exec(args[0], args[1:], output)
signal.Ignore(syscall.SIGHUP)
}
Testing It
If you want to push your learning further then you can try to implement an automated test for this. I actually need to learn how to test utilities like this with Go, so for now just consider this an extra challenge for later until I learn how to teach it.
Register for Learn Go the Hard Way
Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.