Video Coming Soon...

Created by Zed A. Shaw Updated 2026-01-15 05:25:56

42: nohup

I really liked learning how to implement nohup in this course. nohup is a tool that does two extremely useful things:

  1. 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.
  2. 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

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 codeView Source file go-coreutils/nohup/main.go Only

package 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.

Previous Lesson Next Lesson

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.