Skip to main content
Bash intermediate Lesson 17 of 22

Bash System Information

Use uname, df, du, ps, lsof, netstat/ss, and systemctl to inspect and monitor a Linux system from Bash scripts.

uname — System Identification

uname reports basic facts about the operating system and hardware. In scripts, its most important use is detecting the OS and CPU architecture so you can download the right binary or take the right code path for the current system.

uname -a        # all info: kernel name, hostname, release, version, machine type
uname -s        # kernel name: Linux
uname -r        # kernel release: 6.1.0-28-amd64
uname -m        # machine hardware: x86_64, aarch64
uname -n        # network hostname

# Detect OS type and branch accordingly
OS=$(uname -s)
case "$OS" in
  Linux)   echo "Running on Linux" ;;
  Darwin)  echo "Running on macOS" ;;
  *)       echo "Unknown OS: $OS" ;;
esac

# Detect CPU architecture for downloading the right binary
ARCH=$(uname -m)
case "$ARCH" in
  x86_64)  ARCH_LABEL="amd64" ;;
  aarch64) ARCH_LABEL="arm64" ;;
  armv7l)  ARCH_LABEL="armv7" ;;
  *)       echo "Unsupported arch: $ARCH" >&2; exit 1 ;;
esac

URL="https://releases.example.com/app-linux-${ARCH_LABEL}.tar.gz"
echo "Downloading: $URL"

df — Disk Free Space

df reports how much space is used and available on each mounted filesystem. In monitoring scripts, it is the primary tool for alerting on disks that are filling up before they cause outages.

df -h              # human-readable sizes (KB, MB, GB)
df -h /            # check a specific filesystem
df -i              # inode usage — a filesystem can be "full" on inodes even with free bytes
df -T              # include the filesystem type in the output

# In a script: check disk usage and alert if over a threshold
check_disk_space() {
  local mount="${1:-/}"
  local threshold="${2:-80}"

  local usage
  # awk extracts the percentage column and strips the % sign
  usage=$(df -h "$mount" | awk 'NR==2 {gsub(/%/,""); print $5}')

  if (( usage >= threshold )); then
    echo "ALERT: disk usage on $mount is ${usage}% (threshold: ${threshold}%)" >&2
    return 1
  fi
  echo "OK: disk usage on $mount is ${usage}%"
}

check_disk_space "/" 80
check_disk_space "/var" 90

du — Disk Usage

du measures how much space a directory or file actually consumes on disk. It is the right tool for finding what is consuming space when df shows a disk is nearly full.

du -sh /var/log           # summary of total directory size
du -sh /var/log/*         # size of each item inside a directory
du -h --max-depth=1 /var  # one level deep, human-readable sizes
du -sh * | sort -h        # sort by size, smallest first
du -sh * | sort -rh       # sort by size, largest first

# Find the top 10 largest directories under /var
du -h /var --max-depth=3 2>/dev/null | sort -rh | head -10

# Find large files that might be responsible for a full disk
find / -type f -size +500M 2>/dev/null | xargs du -sh | sort -rh

free — Memory Usage

free shows total, used, and available memory. In monitoring and alerting scripts it tells you whether the system is under memory pressure before that pressure causes OOM kills or swap thrashing.

free -h          # human-readable (KB, MB, GB)
free -m          # in megabytes

# In a script: check available memory and alert if below a threshold
check_memory() {
  local threshold_mb="${1:-500}"

  local available_mb
  # The 'available' column (field 7) is the most accurate measure of free memory
  available_mb=$(free -m | awk 'NR==2 {print $7}')

  if (( available_mb < threshold_mb )); then
    echo "WARN: only ${available_mb}MB available (threshold: ${threshold_mb}MB)" >&2
    return 1
  fi
  echo "OK: ${available_mb}MB memory available"
}

check_memory 500

ps — Process Status

ps lists running processes. It is the tool for finding what is running, who owns it, and how much CPU and memory it is consuming. In scripts, pgrep is usually more convenient than parsing ps output.

ps aux            # all processes, BSD format — shows CPU and memory %
ps -ef            # all processes, full format — shows PPID and full command
ps aux | grep nginx    # find nginx processes
ps -u www-data         # processes owned by a specific user

# Custom output columns — choose exactly what you need
ps -eo pid,ppid,user,%cpu,%mem,cmd --sort=-%cpu | head -20

# Find PIDs by name — cleaner than grepping ps output
pgrep nginx            # returns matching PIDs, one per line
pgrep -l nginx         # with process names
pgrep -a nginx         # with full command lines

# Monitor processes continuously without running top interactively
watch -n 2 'ps aux --sort=-%cpu | head -20'

top / htop

top provides a live view of system resource usage. In scripts, the -bn1 flags run it in non-interactive batch mode to get a single snapshot — useful for capturing CPU and load information.

# Non-interactive snapshot — first 20 lines
top -bn1 | head -20

# Extract CPU usage percentage from top output
cpu_idle=$(top -bn1 | grep "Cpu(s)" | awk '{print $8}' | tr -d '%')
cpu_used=$(echo "100 - $cpu_idle" | bc)
echo "CPU used: ${cpu_used}%"

# htop — interactive and more readable, needs installation
# Install: apt install htop / brew install htop
htop

lsof — List Open Files

lsof lists every file a process has open, including network sockets. On Linux, “everything is a file” — so lsof can show open regular files, directories, shared libraries, pipes, and network connections. It is invaluable for debugging “resource busy” errors and finding what is listening on a port.

lsof -p 1234           # all files opened by PID 1234
lsof -u www-data       # all files opened by user www-data
lsof /var/log/app.log  # which processes currently have this file open

# Network connections
lsof -i               # all network connections
lsof -i :80           # which process is bound to port 80
lsof -i :80 -i :443   # check multiple ports at once
lsof -i tcp           # TCP connections only
lsof -i -n -P         # skip DNS/service name resolution (faster)

# Find the PID of whatever is holding a port — useful before a restart
lsof -ti :8080        # just the PID
kill "$(lsof -ti :8080)"   # kill the process occupying port 8080

ss — Socket Statistics (replaces netstat)

ss is the modern replacement for netstat. It is faster, queries the kernel directly, and shows the same information with a similar flag syntax. If you know netstat, ss will feel immediately familiar.

ss -tlnp      # TCP listening sockets — no name resolution (-n), with process info (-p)
ss -ulnp      # UDP listening sockets
ss -s         # summary statistics across all socket types
ss -a         # all sockets (both listening and established)

# Find what is listening on a specific port
ss -tlnp | grep :5432     # find PostgreSQL's port

# Show established connections only
ss -tn state established

# Show connections to a specific remote host
ss -tn dst 10.0.0.100

# In a script — check if a service is listening before proceeding
is_port_listening() {
  ss -tlnp | grep -q ":${1} "
}

if is_port_listening 80; then
  echo "Something is listening on port 80"
fi

systemctl — Service Management

systemctl manages systemd services — the init system used by all modern Linux distributions. In scripts it is used to check service status, start and stop services, and ensure services are enabled to start on boot.

# Check service status
systemctl status nginx
systemctl is-active nginx   # prints "active" or "inactive" — good for scripting
systemctl is-enabled nginx  # prints "enabled" or "disabled"

# Start / stop / restart
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx      # reload configuration without a full restart

# Enable / disable at boot
systemctl enable nginx
systemctl disable nginx

# List services by state
systemctl list-units --type=service --state=running
systemctl list-units --type=service --state=failed

# In a script — ensure a service is running, start it if not
ensure_service_running() {
  local service="$1"

  if ! systemctl is-active --quiet "$service"; then
    echo "Starting $service..."
    systemctl start "$service"
    sleep 2
    if ! systemctl is-active --quiet "$service"; then
      echo "ERROR: failed to start $service" >&2
      systemctl status "$service" >&2   # show details for debugging
      return 1
    fi
  fi
  echo "$service is running"
}

ensure_service_running postgresql
ensure_service_running nginx

journalctl — System Logs

journalctl queries the systemd journal — the centralized log store for all services on a modern Linux system. It is the replacement for tailing individual log files under /var/log.

journalctl -u nginx              # all logs for the nginx service
journalctl -u nginx -f           # follow in real time (like tail -f)
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx -n 100       # last 100 lines
journalctl -u nginx -p err       # errors only
journalctl --since "2025-01-01" --until "2025-01-02"
journalctl -k                    # kernel messages only

Practical System Health Check Script

Combining these tools into a single health check script gives you a quick, scriptable overview of system status — useful as a cron job, a CI pre-check, or a first step in incident response.

#!/usr/bin/env bash
set -euo pipefail

HOSTNAME=$(hostname)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

echo "=== System Health: $HOSTNAME at $TIMESTAMP ==="
echo ""

# CPU load average
LOAD=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | tr -d ',')
echo "Load average (1m): $LOAD"

# Memory usage
MEM_TOTAL=$(free -m | awk 'NR==2{print $2}')
MEM_USED=$(free -m  | awk 'NR==2{print $3}')
MEM_PCT=$(( MEM_USED * 100 / MEM_TOTAL ))
echo "Memory: ${MEM_USED}MB / ${MEM_TOTAL}MB (${MEM_PCT}%)"

# Disk usage on root filesystem
DISK_PCT=$(df -h / | awk 'NR==2{gsub(/%/,""); print $5}')
echo "Disk (/): ${DISK_PCT}%"

# Service status
for svc in nginx postgresql redis; do
  if systemctl is-active --quiet "$svc" 2>/dev/null; then
    echo "Service $svc: OK"
  else
    echo "Service $svc: DOWN" >&2
  fi
done

What’s Next

The next tutorial covers cron and scheduling — crontab syntax, at, systemd timers, and logging scheduled job output.

Frequently Asked Questions

What is the difference between ps aux and ps -ef?
Both list all processes. ps aux uses BSD syntax and shows CPU/memory usage columns. ps -ef uses POSIX syntax and shows the full command with parent PID (PPID). They overlap heavily — ps aux is more common on Linux.
netstat is deprecated — what should I use instead?
Use ss (socket statistics) from the iproute2 package. It is faster, more featureful, and is the modern replacement. Most examples that use netstat translate directly to ss with similar flags.
How do I find what process is using a port?
Use ss -tlnp | grep :PORT or lsof -i :PORT. Both show the PID and process name holding the socket.