By default, a command you run in a script executes in the foreground — the shell waits for it to finish before continuing. Appending & sends it to the background, allowing the script to continue immediately.
# Foreground: script waits here until sleep finishes
sleep 30
echo "30 seconds later..."
# Background: both commands start immediately
sleep 30 &
echo "This prints right away while sleep runs in background"| Command | Meaning |
|---|---|
command & |
Run command in the background |
jobs |
List current background jobs |
fg %1 |
Bring job #1 to the foreground |
bg %1 |
Resume suspended job #1 in the background |
Ctrl+Z |
Suspend foreground job |
$! |
PID of the last background command |
#!/usr/bin/env bash
# Parallel downloads
wget https://example.com/file1.zip &
PID1=$!
wget https://example.com/file2.zip &
PID2=$!
echo "Downloading file1 (PID: $PID1) and file2 (PID: $PID2)..."
# Wait for both to complete
wait $PID1 && echo "file1 downloaded."
wait $PID2 && echo "file2 downloaded."
echo "All downloads complete."for server in web1 web2 web3; do
ssh "$server" "sudo systemctl restart nginx" &
done
wait # Wait for ALL background jobs to complete
echo "All servers restarted."# Show all running processes
ps aux
ps -ef
# Find processes by name
pgrep nginx # Returns PIDs of all 'nginx' processes
pgrep -l nginx # PID + name
pgrep -a nginx # PID + full command line
pidof nginx # Similar, slightly different behavior
# Filter with grep
ps aux | grep "[n]ginx" # The [n] trick avoids matching the grep itself# Send a signal to a specific PID
kill 1234 # SIGTERM (15) — graceful shutdown (default)
kill -9 1234 # SIGKILL — force kill (cannot be caught)
kill -HUP 1234 # SIGHUP (1) — reload config (graceful restart)
# Kill by name
pkill nginx # Send SIGTERM to all 'nginx' processes
pkill -9 -u john # Kill all processes owned by user 'john'
killall -HUP nginx # Send SIGHUP to all processes named 'nginx'| Signal | Number | Meaning |
|---|---|---|
SIGHUP |
1 | Hangup / Reload configuration |
SIGINT |
2 | Keyboard interrupt (Ctrl+C) |
SIGTERM |
15 | Graceful termination (default kill) |
SIGKILL |
9 | Force kill — cannot be caught or ignored |
SIGUSR1/2 |
10/12 | User-defined signals |
trap lets your script intercept signals and run cleanup code before exiting. This is essential for production scripts.
#!/usr/bin/env bash
TMPFILE=$(mktemp)
LOCKFILE="/var/run/myscript.lock"
# Cleanup function
cleanup() {
echo "Script interrupted. Cleaning up..."
rm -f "$TMPFILE" "$LOCKFILE"
exit 1
}
# Run cleanup on these signals
trap cleanup SIGINT SIGTERM
# Create a lock file to prevent duplicate runs
if [[ -f "$LOCKFILE" ]]; then
echo "Error: Script is already running (PID: $(cat $LOCKFILE))"
exit 1
fi
echo $$ > "$LOCKFILE"
# --- Main script logic here ---
echo "Working... (PID: $$)"
echo "Press Ctrl+C to test cleanup."
sleep 60
# Normal cleanup at the end
rm -f "$TMPFILE" "$LOCKFILE"
echo "Done."The EXIT pseudo-signal is the most useful — it fires whenever the script exits, whether normally or due to an error:
#!/usr/bin/env bash
tmpdir=$(mktemp -d)
# This will ALWAYS run, no matter how the script exits
trap 'rm -rf "$tmpdir"' EXIT
# Now we can use $tmpdir freely — it's automatically cleaned up
cp important_files/* "$tmpdir/"
process_files "$tmpdir"| Tool | Purpose |
|---|---|
& and wait |
Run tasks in parallel; wait for completion |
$! |
Get the PID of the last background process |
ps aux |
View all running processes |
pgrep name |
Find PIDs by process name |
kill -SIGNAL pid |
Send a signal to a process |
pkill name |
Kill processes by name |
trap cmd SIGNAL |
Run code when a signal is received |