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 The Pro-Webdev Mega Bundle
Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.