Video Coming Soon...

Created by Zed A. Shaw Updated 2025-11-14 00:08:54

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