Process and Job Management

Foreground, background, and job control

long_task.sh          # runs in foreground — shell blocks until it exits
long_task.sh &        # runs in background — shell prompt returns immediately
jobs                  # list background jobs for this shell session
fg %1                 # bring job 1 back to the foreground
bg %1                 # resume a stopped job 1 in the background

Ctrl+Z doesn't kill a foreground process — it sends SIGTSTP, suspending it (paused, not terminated), moving it into the background job list in a stopped state; bg resumes it there, fg brings it back to the foreground. This is genuinely different from Ctrl+C (SIGINT), which the process can catch and act on (or which terminates it if uncaught) — suspension via SIGTSTP doesn't give the process a choice or a chance to run cleanup code, it's paused by the kernel directly.

Signals from the shell's perspective

graph TD ctrlc["Ctrl+C"] --> sigint["SIGINT -> foreground process group"] ctrlz["Ctrl+Z"] --> sigtstp["SIGTSTP -> foreground process group<br/>(suspend, not terminate)"] closeterm["Terminal closes<br/>(SSH disconnect, terminal window closed)"] --> sighup["SIGHUP -> every process<br/>attached to that terminal"]

This is the practical, terminal-driven view of signals already covered conceptually — Ctrl+C/Ctrl+Z are just keyboard shortcuts for sending specific signals to whatever's running in the foreground. SIGHUP ("hang up") is the one that surprises people running a long process over SSH: closing the terminal or losing the SSH connection sends SIGHUP to every process still attached to that session, and the default action for SIGHUP is termination — a long-running script left running in a plain foreground shell over SSH dies the moment the connection drops, even though nothing explicitly told it to stop.

Surviving terminal disconnection: nohup, disown, and real alternatives

nohup long_task.sh &      # ignore SIGHUP specifically, keep running after logout
disown %1                 # remove an already-backgrounded job from the shell's
                           # job table, so the shell exiting doesn't send it SIGHUP

nohup runs a command with SIGHUP explicitly set to be ignored — the process survives the terminal closing. disown does something related but distinct: it removes a job the shell is already tracking from that shell's job table, so when the shell itself exits, it no longer considers that job "its" to signal at all. For anything that should be a genuinely durable background service (not just surviving one terminal session) rather than a one-off long task, a proper service manager (systemd) — with defined restart policies, log capture, and start-on-boot behavior — is the more robust tool than relying on nohup/disown tricks at the shell level.

Inspecting and killing processes

ps aux | grep myapp     # list processes, filter by name
top                     # live view of CPU/memory usage per process
kill -TERM <pid>        # ask nicely (SIGTERM, the default signal for `kill`)
kill -9 <pid>           # SIGKILL — immediate, uncatchable, no cleanup
pkill -f "myapp"        # kill by matching the command line, not just a known PID

kill (without -9) sends SIGTERM by default — a request to terminate that a well-behaved process can catch and use to clean up (closing files, finishing in-flight work) before exiting, exactly the graceful-shutdown pattern already covered. kill -9 (SIGKILL) skips all of that — the kernel terminates the process immediately with no opportunity for it to run any cleanup code at all, which is why it's a last resort (a process truly stuck ignoring SIGTERM) rather than a routine way to stop something.

Common pitfall

Using kill -9 as the default/first instinct to stop a stuck process, rather than trying SIGTERM first, means any in-flight work that process was doing — a half-written file, an open database transaction, a partially-flushed buffer — is abandoned mid-operation with zero chance to clean up, potentially leaving corrupted state behind that a graceful shutdown would have avoided. Reserve -9 for the specific case where SIGTERM was already tried and the process is confirmed to be ignoring or unable to respond to it, not as a routine substitute for it.