Getting Started with Job Control - jobs, fg, bg, and Ctrl+Z

Getting Started with Job Control - jobs, fg, bg, and Ctrl+Z

What You'll Learn

  • Use Ctrl+Z to pause a running command and reclaim your terminal
  • Swap freely between foreground and background with fg / bg
  • List current jobs with jobs and target individual jobs with %n
  • Run a command in the background from the start by appending &
  • Get past common gotchas like "I lost my work" or "this won't go away"

Target Audience: Anyone who's been editing a file in vim or running a long command and wanted to "just check one thing" in the same terminal — and accidentally killed everything with Ctrl+C.

Introduction: The Day Lina Killed Her Vim Session

Lina: Senpai, listen! I was editing a long config file in vim, and I needed to run ls in another terminal. I didn't know how to open a new one, so I just hit Ctrl+C — and it killed the whole vim session. I lost every unsaved change.
Linny-senpai: Ouch. The key combo you actually wanted exists, though. It's Ctrl+Z (Control-Z).
Lina: Z, not C?
Linny-senpai: Right. Ctrl+C kills the running command, but Ctrl+Z pauses it and sets it aside. Your terminal comes back so you can run other commands, and when you're done you call fg to return to the paused work.
Lina: That sounds like magic!
Linny-senpai: It's called "job control" — a basic feature every POSIX shell (bash, zsh) provides. Today you only need four things: Ctrl+Z (pause), bg (resume in background), fg (return to foreground), jobs (list).

The Practical Pattern

  • Want your terminal back? → Ctrl+Zbg (keeps running in the background)
  • Want to pause and leave it alone? → Ctrl+Z by itself (stays paused)
  • Want to come back to it? → fg
  • Forgot what's running? → jobs

Assumed Environment

  • bash / zsh (job control is a standard POSIX shell feature)
  • Does not work in dash or non-interactive shells (/bin/sh -c)
  • All keystroke examples assume an interactive terminal session

1. "Job" vs. "Process" — What's the Difference?

Lina: What exactly is a "job"? Is it different from a "process"?
Linny-senpai: Same thing, different lens. A process is what the OS sees: each running program. A job is what your shell sees: one command you typed, as a single unit.
Lina: So a job is shell-side, a process is OS-side?
Linny-senpai: Exactly. If you type ls | grep .txt | sort, the OS creates three processes, but the shell treats it as one job. When you hit Ctrl+Z, the shell pauses the whole job — all three processes — at once.

Job Number vs. PID

Type Example Assigned by Used for
Job number %1 The shell fg %1, kill %1, etc.
PID 12345 The kernel kill 12345, ps output

Memory trick: Job numbers always take a leading %. It's fg %1 (not fg 1), kill %1 (not kill 1). The latter — kill 1 — sends the kill signal to PID 1 (init), which is a dangerous command if you have permission.

2. Ctrl+Z: Pause Right Now

Linny-senpai: This is the single most important keystroke. Open vim, tail -f, python interactive — anything that holds your terminal — then press Ctrl+Z.
Lina: Won't it freeze? Can I really bring it back?
Linny-senpai: You can. "Paused" is not "killed." Think of it as putting the program in the freezer. fg thaws it out and resumes as if nothing happened.

Try It

$ sleep 100

sleep 100 does nothing for 100 seconds. Your terminal is locked the whole time.

Now press Ctrl+Z.

^Z
[1]+  Stopped                 sleep 100
$

If you see [1]+ Stopped, it worked. Notice the $ prompt is back — the terminal is yours again.

Reading [1]+

  • [1] = job number 1
  • + = the most recently touched job (the default target for fg / bg)
  • Stopped = paused (consuming no CPU)

You Can Now Run Other Commands

$ ls
$ pwd
$ echo "free to do other things"

sleep stays frozen in the background while you do whatever you want. That's the magic of Ctrl+Z.

3. fg: Bring Back / bg: Resume Behind

Lina: How do I un-pause a stopped job?
Linny-senpai: Two choices. fg = bring it back to the front and resume. bg = resume in the background while you keep using the terminal. The first puts your attention back on the job; the second keeps you free for other work.

fg: Back to Foreground

Let's bring back the sleep 100 from before.

$ fg
sleep 100

sleep continues from where it left off, and the terminal is locked again. Wait it out, hit Ctrl+Z again, or Ctrl+C to kill it.

bg: Resume in Background

If you want it running but don't want to wait for it:

$ sleep 100
^Z
[1]+  Stopped                 sleep 100
$ bg
[1]+ sleep 100 &
$

It moves from Stopped to & (running in background). sleep keeps ticking while you do other things.

Not all commands work with bg: Interactive full-screen apps like vim can't "wait for input in the background," so bg-ing them just sends them back to a stopped state (Stopped (tty input)). For vim, the right rhythm is Ctrl+Z to pause → fg to return — don't try to bg it.

4. Append &: Background From the Start

Linny-senpai: If you know up front "this needs to run in the background," append & when you launch it.
Lina: So instead of Ctrl+Z → bg in two steps, do it in one?
Linny-senpai: Right, same end result. Ctrl+Z → bg is "I started it and changed my mind," & is "send it to the back from the start."

Using &

$ sleep 100 &
[1] 12345
$

[1] is the job number, 12345 is the PID. Prompt returns immediately.

$ jobs
[1]+  Running                 sleep 100 &

Status is Running, not Stopped.

Real Example: Background Log Collection

$ tail -f /var/log/syslog > mylog.txt &
[1] 23456
$ # do other things while it runs
$ vim config.txt

Run tail -f in the background while editing a file with vim. Classic parallel workflow.

With & alone, output bleeds into your terminal: A backgrounded command's stdout still prints to your terminal by default. If it's noisy, redirect with > file 2>&1 to send output to a file (or > /dev/null 2>&1 to discard).

5. jobs: See What's Running

Lina: With multiple jobs running, I'll lose track of what's where.
Linny-senpai: One command fixes that: jobs. Lists every job your current shell is managing.

Basic Usage: jobs

$ sleep 100 &
[1] 12345
$ sleep 200 &
[2] 12346
$ vim notes.txt
# Ctrl+Z to pause
$ jobs
[1]   Running                 sleep 100 &
[2]-  Running                 sleep 200 &
[3]+  Stopped                 vim notes.txt

Three jobs visible.

  • + = most recent (default fg / bg target — vim here)
  • - = previous one (sleep 200)

Target a Specific Job: %n

$ fg %1     # bring sleep 100 to the front
$ bg %3     # send vim to background (it stays paused)
$ kill %2   # terminate sleep 200

Don't forget the %

kill 1 is the command that sends a kill signal to PID 1 (init). A regular user is blocked by permissions, but root can take down the whole system. Make % muscle memory whenever you reference jobs.

Useful jobs Options

Option Effect
jobs -l Also show PIDs
jobs -p PIDs only (handy for scripting)
jobs -r Only running jobs
jobs -s Only stopped jobs

6. What Happens When You Close the Terminal?

Lina: If I left background jobs running and closed the terminal, what happens?
Linny-senpai: They usually die. When the shell exits, it sends SIGHUP (hangup) to its child jobs by default.
Lina: So a long job I left in the background can just disappear when I close the terminal?
Linny-senpai: Yes. That's what nohup and disown are for. And for the most robust solution, tmux.

nohup: Ignore SIGHUP From the Start

$ nohup ./long_script.sh > output.log 2>&1 &
[1] 34567

Prefix with nohup and the job ignores SIGHUP when the shell exits. Log out and it keeps running.

disown: Detach From Shell After Launch

$ ./long_script.sh &
[1] 45678
$ disown %1
$ exit   # closing the terminal won't kill %1

If you started a job with & and then realized you want it to survive logout, disown is your tool.

tmux is more bulletproof

nohup / disown only keep the job alive past disconnect — but you lose the screen output. With tmux the whole session is saved on the server, and you can later tmux attach to come back to the live output. For long jobs where you want to see what happened, tmux wins. → Getting Started with tmux

7. Common Pitfalls

Lina: Senpai, what mistakes do beginners typically make?
Linny-senpai: Four. These cover most of the first-month landmines.

Pitfall 1: Mixing Up Ctrl+C and Ctrl+Z

Symptom: You meant to pause but the job died.

Cause: Confusing Ctrl+C (SIGINT — terminates) with Ctrl+Z (SIGTSTP — pauses).

Fix: Remember "C = Cancel, Z = Zzz (sleep)." Ctrl+Z is non-destructive — your vim work is safe.

Pitfall 2: Typing kill 1 Instead of kill %1

Symptom: kill 1 says "Operation not permitted" (or worse, as root, the system staggers).

Cause: You confused job number with PID and forgot the %. kill 1 sends a termination signal to PID 1 (init / systemd).

Fix: Always prefix job references with %. When in doubt, run jobs -l first to see the PID — or use kill %% to target the most recent job.

Pitfall 3: Background Output Crashing Your Prompt

Symptom: Output from a backgrounded command splatters into the middle of whatever you're typing.

Cause: Stdout and stderr still go to the terminal in the background.

Fix: Redirect with > file 2>&1 & (to a log file) or > /dev/null 2>&1 & (to discard).

Pitfall 4: vim Won't Run After bg

Symptom: You did Ctrl+Z then bg on vim, but jobs shows Stopped (tty input) forever.

Cause: vim needs to read from the terminal. In the background it can't, so the shell auto-pauses it.

Fix: For interactive full-screen apps (vim, top, nano, etc.), stick to Ctrl+Z to pause → fg to resume. Never bg them.

8. Templates: Job Control Without Accidents

Copy-paste: Run a command while editing in vim

# 1. Editing in vim
$ vim config.txt
# Ctrl+Z to pause

# 2. Terminal is back — do other things
$ ls /etc/
$ grep "error" /var/log/syslog

# 3. Resume vim
$ fg

The point is to bounce back and forth without closing vim. Build the rhythm: Ctrl+Z → side task → fg.

Copy-paste: Run a long job in the background

# Send output to a file so it doesn't trash your terminal
$ ./long_script.sh > output.log 2>&1 &
[1] 12345

# Check progress
$ jobs
$ tail -f output.log

# Terminate if needed
$ kill %1

Forgetting > output.log 2>&1 is the most common mistake. Keep them paired.

Copy-paste: Survive logout

# nohup from launch
$ nohup ./long_script.sh > output.log 2>&1 &
[1] 12345

# Or disown after the fact
$ ./long_script.sh > output.log 2>&1 &
[1] 12345
$ disown %1

# The most robust approach — tmux preserves the whole screen (recommended)
$ tmux
$ ./long_script.sh
# Ctrl+b → d to detach, tmux attach later to come back

Mini Challenges

Linny-senpai: Knowledge sticks when your fingers move. Try these three.

Challenge 1: Ctrl+Z ↔ fg Round Trip

$ sleep 30
# About 5 seconds in, press Ctrl+Z

$ jobs
# → Should show [1]+ Stopped sleep 30

$ ls    # try another command

$ fg    # return to sleep, finish out the remaining time

Challenge 2: Three Parallel Jobs

$ sleep 100 &
$ sleep 200 &
$ sleep 300 &

$ jobs    # → all three [1] [2] [3] should appear

$ kill %2 # kill just the middle one

$ jobs    # → [2] should now be gone

Challenge 3: Background With File Output

$ (for i in 1 2 3 4 5; do echo "step $i"; sleep 1; done) > result.log 2>&1 &

$ jobs    # Running
$ cat result.log   # output accumulates in the file

# When the job finishes, you'll see something like:
# [1]+  Done    ( for i in 1 2 3 4 5; ...) > result.log 2>&1
Lina: Got it! The safety of Ctrl+Z is huge — I'll never kill vim with Ctrl+C again.
Linny-senpai: That's the experience of "learning job control." Every day in the shell gets a little smoother from here.

Today's Three-Line Summary

  • Ctrl+Z pauses and frees the terminal, fg brings it back, bg keeps it running behind — never accidentally kill vim with Ctrl+C again
  • Always reference jobs with % (kill %1kill 1) — getting PID and job number mixed up can take down init
  • For background long-runners, make & + > file 2>&1 a habit, and reach for nohup / disown / tmux when you need to survive logout

Next Reading