Whenever I’m debugging a slow server, writing a health-check script, or just building a status dashboard, the first thing I reach for is a handful of Bash commands that expose everything about the system: CPU, memory, disk, network, and processes. Linux exposes almost all of this through plain text files in /proc and /sys, plus a set of standard utilities, which makes system information one of the easiest things to work with in Bash once you know where to look. This article covers exactly how I gather and use that information.
Where System Information Lives
On Linux, most system information comes from two places:
/proc: a virtual filesystem exposing kernel and process information as plain text files (e.g.,/proc/cpuinfo,/proc/meminfo).- Standard utilities: commands like
uname,uptime,df,free,top,ps, andlscputhat wrap and format the raw data from/procand/sysin a more human-friendly way.
Beginner Example: Basic System Info
#!/usr/bin/env bash
echo "Hostname: $(hostname)"
echo "Kernel: $(uname -r)"
echo "OS: $(uname -o)"
echo "Architecture: $(uname -m)"
echo "Uptime: $(uptime -p)"
Example output:
Hostname: web-server-01
Kernel: 6.8.0-45-generic
OS: GNU/Linux
Architecture: x86_64
Uptime: up 3 weeks, 2 days, 4 hours
uname reads directly from kernel data structures; -r gives the kernel release, -o the operating system name, and -m the machine hardware name (architecture).
CPU Information
echo "CPU model: $(grep 'model name' /proc/cpuinfo | head -1 | cut -d ':' -f2 | xargs)"
echo "CPU cores: $(nproc)"
/proc/cpuinfolists one block per logical CPU core, sogrep+head -1grabs just the first entry since the model name repeats identically for every core on most systems.cut -d ':' -f2splits on the colon and takes everything after it, thenxargstrims leading/trailing whitespace.nprocis a dedicated Coreutils command that reports the number of available processing units, which is more reliable than manually counting lines in/proc/cpuinfo.
To see current CPU load:
uptime
Output:
14:32:01 up 22 days, 3:14, 2 users, load average: 0.15, 0.22, 0.31
The three numbers are the load average over the last 1, 5, and 15 minutes respectively — a widely used, quick indicator of how busy the system has been.
Memory Information
free -h
Output:
total used free shared buff/cache available
Mem: 15Gi 4.2Gi 6.1Gi 210Mi 5.0Gi 10Gi
Swap: 2.0Gi 0B 2.0Gi
-h prints sizes in human-readable units (GiB, MiB) instead of raw kilobytes. Internally, free is just a formatted view of /proc/meminfo, which you can also read directly:
grep -E 'MemTotal|MemFree|MemAvailable' /proc/meminfo
Disk Usage
df -h
Output:
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 50G 32G 16G 67% /
For directory-level breakdown of what’s actually consuming space:
du -sh /var/log/*
du -sh summarizes (-s) the total size of each item in human-readable form (-h), which I use constantly when hunting down what’s filling up a disk.
Network Information
ip addr show
For a quick summary of just IPv4 addresses:
ip -4 addr show | grep inet | awk '{print $2}'
To check open listening ports:
ss -tuln
-tshows TCP sockets,-ushows UDP sockets,-lshows only listening sockets, and-nshows numeric addresses/ports instead of resolving service names, which is both faster and avoids DNS lookups.
Process Information
ps aux --sort=-%mem | head -10
This lists all processes (aux), sorted by memory usage descending (--sort=-%mem), and shows the top 10 — one of the most useful one-liners I run when a server feels sluggish.
Step-by-Step: Building a System Info Script
Here’s a complete script that pulls together everything above into a single readable report:
#!/usr/bin/env bash
set -euo pipefail
echo "===== System Information Report ====="
echo "Generated: $(date)"
echo ""
echo "--- Host ---"
echo "Hostname: $(hostname)"
echo "OS: $(uname -o)"
echo "Kernel: $(uname -r)"
echo "Uptime: $(uptime -p)"
echo ""
echo "--- CPU ---"
echo "Model: $(grep 'model name' /proc/cpuinfo | head -1 | cut -d ':' -f2 | xargs)"
echo "Cores: $(nproc)"
echo "Load average: $(cut -d ' ' -f1-3 /proc/loadavg)"
echo ""
echo "--- Memory ---"
free -h
echo ""
echo "--- Disk ---"
df -h --output=source,size,used,avail,pcent,target | grep -v tmpfs
echo ""
echo "--- Top 5 Processes by Memory ---"
ps aux --sort=-%mem | head -6
echo ""
echo "--- Network Interfaces ---"
ip -4 addr show | grep inet | awk '{print $2, $NF}'
How This Works Internally
- Every value here ultimately traces back to either a
/procfile that the kernel updates in real time, or a syscall wrapped by a utility likepsorip. --output=ondflets you pick exactly which columns to display, which is more scriptable than parsing the default table layout withawk.ps auxreads process information from/proc/[pid]/statand/proc/[pid]/statusfor every running process, then formats it as a table.
Real-World Use Case: A Health-Check Script for Monitoring
#!/usr/bin/env bash
set -euo pipefail
DISK_THRESHOLD=85
MEM_THRESHOLD=90
disk_usage=$(df / --output=pcent | tail -1 | tr -dc '0-9')
mem_usage=$(free | awk '/Mem:/ {printf "%.0f", $3/$2 * 100}')
if [ "$disk_usage" -ge "$DISK_THRESHOLD" ]; then
echo "WARNING: Disk usage at ${disk_usage}%"
fi
if [ "$mem_usage" -ge "$MEM_THRESHOLD" ]; then
echo "WARNING: Memory usage at ${mem_usage}%"
fi
I run this every few minutes via cron and pipe warnings into the email script covered elsewhere in this series.
Automation Example: Logging System Metrics Over Time
#!/usr/bin/env bash
set -euo pipefail
LOG_FILE="/var/log/system_metrics.csv"
if [ ! -f "$LOG_FILE" ]; then
echo "timestamp,cpu_load,mem_used_pct,disk_used_pct" > "$LOG_FILE"
fi
timestamp=$(date +%Y-%m-%dT%H:%M:%S)
cpu_load=$(cut -d ' ' -f1 /proc/loadavg)
mem_used_pct=$(free | awk '/Mem:/ {printf "%.1f", $3/$2 * 100}')
disk_used_pct=$(df / --output=pcent | tail -1 | tr -dc '0-9.')
echo "${timestamp},${cpu_load},${mem_used_pct},${disk_used_pct}" >> "$LOG_FILE"
Run every minute via cron to build a simple CSV time series I can later graph.
Security Considerations
- Some information exposed by
/proc(like full command lines of other users’ processes viaps aux) can reveal sensitive details, such as passwords accidentally passed as command-line arguments. Be cautious about who has read access to system monitoring output. - Restrict access to scripts and logs that capture system information, especially if they include IP addresses, hostnames, or process details that could aid an attacker mapping out your infrastructure.
- If exposing any of this data through a web dashboard, never do so without authentication — system information is valuable reconnaissance for anyone attempting to attack a server.
Optimization Tips
- Reading directly from
/procfiles is faster than shelling out to formatted utilities likefreeordfwhen you need to poll metrics very frequently (e.g., every second), since it avoids the overhead of spawning a new process for the formatting tool. - Cache values that don’t change often (like CPU model or total memory) instead of re-querying them on every loop iteration in a monitoring script.
- For very frequent polling, consider a purpose-built monitoring agent (like
node_exporterfor Prometheus) instead of a custom Bash loop, which will be more efficient at scale.
Troubleshooting
- “nproc: command not found”: install
coreutils, though this is extremely rare sincenprocships by default on virtually all Linux distributions. ps auxtruncates long command lines: useps auxwwto disable line-width truncation.dfshows confusing tmpfs/overlay entries: filter them out withgrep -v tmpfsor usedf -h -x tmpfs -x devtmpfs.- Load average looks high but CPU seems idle: load average includes processes waiting on I/O, not just CPU-bound processes, so high disk I/O wait can inflate this number without heavy CPU usage.
Common Mistakes to Avoid
- Confusing “used memory” with “available memory” — modern Linux uses free RAM aggressively for disk caching, so
free‘s “used” column can look alarmingly high even when plenty of memory is actually available (check theavailablecolumn instead). - Parsing
ps/df/freeoutput with fragile column-position assumptions instead of using their built-in formatting flags (--output=, custom format strings). - Running frequent system-info scripts as root unnecessarily when the same data is readable as a normal user.
FAQs
Does this approach work the same way on macOS? Partially. macOS doesn’t have /proc, so tools like sysctl, vm_stat, and top are used instead of reading /proc/cpuinfo or /proc/meminfo directly.
What’s the difference between uptime‘s load average and actual CPU usage percentage? Load average is a count of processes wanting CPU (or waiting on I/O) averaged over time, not a direct percentage of CPU utilization. A load average of 4 on a 4-core system roughly means full utilization; the same number on a 16-core system means the system is much less loaded.
How can I get information about a Docker container’s resource usage from Bash? docker stats gives a live view, or read the container’s own cgroup files under /sys/fs/cgroup/ from inside the container for programmatic access.
Is there a single command that gives an overview of everything at once? Tools like htop, glances, or neofetch provide nice interactive overviews, but for scripting purposes, combining the individual commands shown above gives you full control over formatting and automation.
Summary
Nearly everything you’d want to know about a Linux system — CPU, memory, disk, network, and processes — is available through a small set of standard commands and the /proc virtual filesystem. Once I understood that these tools are really just structured views into files the kernel maintains in real time, building health checks, monitoring scripts, and system reports in Bash became straightforward and fast, without needing any external monitoring agent for simple use cases.
References
- GNU Coreutils manual: https://www.gnu.org/software/coreutils/manual/coreutils.html
- Linux
proc(5)man page: https://man7.org/linux/man-pages/man5/proc.5.html procps-ngproject (source ofps,free,uptime,top): https://gitlab.com/procps-ng/procps- iproute2 documentation (
ipcommand): https://wiki.linuxfoundation.org/networking/iproute2
