fg Command in Linux: Complete Guide to Foreground Process Management and Parameters

fg command in Linux and it perimeters

fg command in Linux and it perimeters

Job control is one of those shell features that’s easy to ignore for years and then suddenly becomes indispensable the moment you accidentally start a long-running process in the wrong terminal, or need to pause something mid-task without killing it. fg — along with its close companions bg, jobs, and Ctrl+Z — is the core of that workflow. Here’s my complete rundown of how it actually works.

What is the fg Command?

fg brings a background or suspended job back into the foreground of your current shell session, giving it control of the terminal again. It’s a shell builtin, not a standalone binary — meaning it’s implemented directly inside your shell (bash, zsh, etc.) rather than existing as a separate executable on disk. That’s exactly why you won’t find /bin/fg or /usr/bin/fg anywhere; job control has to live inside the shell because only the shell tracks which processes belong to which job.

type fg

Output:

fg is a shell builtin

Basic Syntax

fg [JOB_SPEC]

If no job is specified, fg resumes the most recently backgrounded or suspended job (the one marked with a + in jobs output).

Understanding Job Control Basics

Before fg makes sense, it helps to walk through the whole flow:

  1. Suspend a running foreground process with Ctrl+Z. This sends SIGTSTP, pausing the process without terminating it.
  2. List current jobs with jobs, which shows every background/suspended job tied to your current shell session.
  3. Resume a job in the background with bg, letting it keep running without holding the terminal hostage.
  4. Bring a job back to the foreground with fg, restoring full terminal control (and blocking your shell prompt) until it finishes or you suspend it again.

A Full Walkthrough

$ sleep 300
^Z
[1]+  Stopped                 sleep 300

At this point, sleep 300 is paused, not running. I check what jobs exist:

$ jobs
[1]+  Stopped                 sleep 300

I can resume it in the background so it keeps running while I get my terminal back:

$ bg %1
[1]+ sleep 300 &

Now it’s running again, just not attached to my terminal. If I later want to bring it back to the foreground (say, to watch its output directly, or because I need to press Ctrl+C to actually kill it):

$ fg %1
sleep 300

My shell prompt is now blocked again until sleep 300 finishes or I suspend/interrupt it.

Job Specification Syntax

fg accepts several ways to identify which job you mean:

Job SpecMeaning
fgThe current job (marked + in jobs output)
fg %1Job number 1
fg %+The current job (explicit form of the default)
fg %-The previous job (marked - in jobs output)
fg %stringThe job whose command starts with string
fg %?stringThe job whose command contains string anywhere

Examples

fg %2          # bring job 2 to the foreground
fg %ping       # bring the job whose command starts with "ping" to the foreground
fg %?backup    # bring the job whose command contains "backup" anywhere in it

Reading jobs Output

$ jobs -l
[1]-  12345 Stopped                 vim notes.txt
[2]+  12401 Running                 rsync -av /data /backup &

Practical Real-World Examples

Suspending an editor to quickly check something, then returning

$ vim important-config.conf
# ... editing ...
^Z
[1]+  Stopped                 vim important-config.conf
$ ls -la /etc/
# ... check something ...
$ fg
vim important-config.conf
# back exactly where I left off

This is genuinely one of the most useful habits in daily terminal work — no need to save, quit, and reopen just to peek at something else.

Accidentally started a long process in the foreground without &

$ tar -czf huge-backup.tar.gz /var/log
^Z
[1]+  Stopped                 tar -czf huge-backup.tar.gz /var/log
$ bg
[1]+ tar -czf huge-backup.tar.gz /var/log &

I do this constantly — start something, realize halfway through it’s going to take a while, and move it to the background instead of waiting it out or killing and restarting it with & from the start.

Managing multiple background jobs at once

$ jobs
[1]   Running                 ./process-data.sh &
[2]-  Running                 ./generate-report.sh &
[3]+  Stopped                 nano draft.txt

$ fg %3
nano draft.txt

How fg Works Internally

Job control in Linux/Unix shells is built on top of process groups and sessions, kernel-level concepts. Every job the shell launches gets its own process group ID (PGID). The terminal itself keeps track of exactly one “foreground process group” at any given time — only processes in that group are allowed to read from the terminal or receive terminal-generated signals like Ctrl+C (SIGINT) and Ctrl+Z (SIGTSTP) directly.

When you run fg, the shell does two key things:

  1. Calls tcsetpgrp() to tell the kernel “this job’s process group is now the foreground process group of this terminal.”
  2. If the job was previously stopped, sends it SIGCONT to resume execution.

The shell then waits (blocks) until that job either exits or is suspended again, at which point control returns to the interactive shell prompt.

This is exactly why fg has to be a shell builtin rather than an external program — an external program couldn’t directly manipulate its parent shell’s terminal control or job table. It has to be handled inside the process that’s actually managing the terminal session.

fg vs bg vs jobs — Quick Comparison

CommandPurpose
fg [%job]Bring a job to the foreground, giving it terminal control and blocking the shell prompt
bg [%job]Resume a stopped job in the background, without taking terminal control
jobsList all jobs tracked by the current shell session
Ctrl+ZSuspend the current foreground job (sends SIGTSTP)
Ctrl+CInterrupt/terminate the current foreground job (sends SIGINT)
disownRemove a job from the shell’s job table so it survives the shell exiting, without killing it
nohup command &Launch a command immune to hangup signals from the start, so it survives terminal/session closure

Real-World Shell Scripting Considerations

fg is fundamentally an interactive feature — it’s rarely useful inside a non-interactive script, because job control behaves differently (and is often disabled by default) in scripts versus interactive shells. If you need script-level control over background processes, the more common and portable approach is tracking PIDs directly:

#!/bin/bash
long_running_task &
TASK_PID=$!

echo "Started background task with PID $TASK_PID"
wait $TASK_PID
echo "Task finished with exit code $?"

This achieves the practical goal fg/bg serve interactively — running something in the background and later waiting on it — but in a way that works reliably inside non-interactive scripts.

Troubleshooting Common Issues

“fg: no current job” — There’s nothing suspended or backgrounded in this shell session. Run jobs first to confirm what’s actually available.

“fg: job not found” — The job number or string you specified doesn’t match anything currently tracked. Job numbers reset and get reused as jobs complete, so double-check with jobs before assuming a number is still valid.

Job disappears when you close the terminal — By default, background jobs receive SIGHUP when their controlling terminal closes, which typically kills them. Use nohup, disown, or a terminal multiplexer like tmux/screen to keep a process running independent of the terminal session.

fg seems to do nothing / process was already running — If the job was already running in the background (not stopped), fg still works correctly — it simply attaches terminal control to it without needing to send SIGCONT, since the process was never actually paused.

Best Practices

Security Implications

Job control itself carries minimal direct security risk, but there’s a practical consideration worth knowing: jobs backgrounded in a shell session are tied to that session and, by default, to that user — they can’t be brought to the foreground or controlled by a different user’s shell, since job control state lives entirely within a single shell process’s memory. This isolation is enforced by normal Unix process ownership rules, the same as any other process-control operation.

Compatibility Across Distributions and Shells

fg, bg, and jobs are POSIX-standard shell builtins and behave essentially identically across bash, zsh, ksh, and dash (though dash, commonly used as /bin/sh on Debian/Ubuntu, has more limited interactive job control since it’s designed primarily as a fast, non-interactive script interpreter). This is entirely a shell-level feature rather than a distribution-level one — the exact same commands and behavior apply whether you’re on Ubuntu, RHEL, Arch, or macOS’s default zsh, as long as you’re in an interactive shell session with job control enabled.

fg in the Context of SSH Sessions

A scenario I run into constantly: I’m SSH’d into a remote server, background a long-running task, disconnect (intentionally or due to a flaky connection), and later reconnect wanting to check on it. The critical thing to understand is that a plain background job (command &) is still tied to the shell session that spawned it — if that shell exits (including due to an SSH disconnect closing the session), the job typically receives SIGHUP and dies, regardless of whether it was in the foreground or background at the time.

fg and bg alone don’t solve this — they only manage foreground/background state within an active session, not survival across sessions. For genuine persistence across disconnects, I reach for one of:

# nohup: explicitly ignore the hangup signal
nohup ./long-task.sh &

# disown: remove the job from the shell's job table so it's no longer tied to the shell exiting
./long-task.sh &
disown

# A terminal multiplexer: the most robust option, letting you fully detach and reattach later
tmux new -s work
./long-task.sh
# Ctrl+B, D to detach; tmux attach -t work to reattach later, with fg/bg working normally inside

I default to tmux for anything I genuinely care about surviving a disconnect, precisely because it preserves full interactive job control (fg, bg, Ctrl+Z) exactly as if I’d never left, rather than just keeping a single background process alive with nohup.

A Note on zsh and Other Shells

Everything covered here applies essentially identically in zsh, which is the default shell on modern macOS and increasingly common on Linux desktops. The core fg/bg/jobs/Ctrl+Z workflow is part of the POSIX shell specification, and virtually every interactive shell you’ll encounter — bash, zsh, ksh, tcsh — implements it the same way from a user’s perspective, even though the underlying implementation details differ between shells. One small zsh-specific convenience: zsh supports fg with a bare percent sign shortcut on its own line in some configurations, but the %1, %+, %-, %string syntax documented above works identically across all of them.

Common Mistakes With Job Control

The single most common mistake I see (and have made myself) is forgetting that Ctrl+Z suspends a process — it does not kill it, and it does not put it in the background automatically. A process left suspended (not backgrounded with bg) is completely halted, doing nothing at all, silently — no CPU, no I/O, no progress — until you either fg it back or explicitly bg it. I’ve absolutely left a database migration suspended for twenty minutes before, wondering why “it’s taking so long,” before realizing I’d hit Ctrl+Z out of habit and never resumed it.

Summary

fg is a small piece of a much bigger job-control system built on process groups and terminal session management at the kernel level — but in daily use, it boils down to one simple, genuinely useful habit: suspend with Ctrl+Z, check with jobs, and resume with fg or bg depending on whether you want it back in front of you or quietly running behind the scenes. It’s one of those features that feels unnecessary until the day it saves you from losing an in-progress vim session or restarting a half-finished long-running command.

References

Exit mobile version