Introduction to df
The df (disk free) command is a fundamental Unix/Linux utility for reporting file system disk space usage. It provides essential information about mounted file systems, including total size, used space, available space, and usage percentages. Understanding df thoroughly is crucial for system administration, capacity planning, and monitoring disk usage.
Key Concepts
- File Systems: Mounted partitions and their mount points
- Disk Usage: Total, used, and available space
- Inodes: File system metadata storage
- Human-Readable: Formatted output for easy understanding
- Local vs Remote: Can show both local and network file systems
1. Basic Usage
Simple Disk Space Reports
#!/bin/bash # Basic df command (shows all mounted file systems) df # Show specific file system df /home df /dev/sda1 # Show multiple file systems df /home /var /tmp # Human-readable format (with -h) df -h df --human-readable # Show in kilobytes (default) df -k # Show in megabytes df -m # Show in gigabytes df -h # Already human-readable, but can force specific df -B G # Show in gigabytes (GNU) # Examples echo "=== Disk Usage Report ===" df -h echo echo "=== Root Filesystem ===" df -h /
Common Examples
#!/bin/bash # Check root filesystem usage df -h / # Check home directory usage df -h /home # Check current directory's filesystem df -h . # Show all filesystems including tmpfs df -h -a # Exclude specific filesystem types df -h -x tmpfs -x devtmpfs # Include only specific types df -h -t ext4 -t xfs # Show with total df -h --total # Create a report cat << EOF System Disk Usage Report ======================= Date: $(date) Host: $(hostname) $(df -h) Top Usage: $(df -h | sort -k5 -r | head -5) EOF
2. Essential Options
Common Options Reference
#!/bin/bash # -a, --all: Include dummy file systems df -a # -h, --human-readable: Print sizes in human readable format df -h # -H: Human-readable but with powers of 1000 (SI) df -H # -k: Use 1K blocks df -k # -m: Use 1M blocks df -m # -B, --block-size: Specify block size df -B 1K df -B 1M df -B 1G # -t, --type: Limit to filesystem type df -t ext4 df -t xfs df -t tmpfs # -x, --exclude-type: Exclude filesystem type df -x tmpfs df -x devtmpfs # -T, --print-type: Print filesystem type df -T # -l, --local: Only show local filesystems df -l # --total: Show total df --total # -i, --inodes: Show inode information instead of blocks df -i # --sync: Sync before getting usage df --sync # -P, --portability: POSIX output format df -P # Combine options df -hT df -hl --total df -h -t ext4 -t xfs
Practical Option Combinations
#!/bin/bash
# Comprehensive system view
df -hT --total
# Local filesystems only with types
df -lhT
# Check filesystems with >80% usage
df -h | awk 'NR>1 {gsub(/%/,"",$5); if($5>80) print $0}'
# Show inodes usage
df -hi
# Monitor specific mount points
df -h / /home /var
# Check network filesystems
df -h -t nfs -t cifs
# Format for monitoring
df --output='source,fstype,size,used,avail,pcent,target' -h
# Watch disk usage changes
watch -n 60 df -h
3. Understanding Output
Column Explanations
#!/bin/bash
# Standard df output
df -h
# Output columns:
# Filesystem Size Used Avail Use% Mounted on
# /dev/sda1 98G 45G 48G 49% /
# With -T (filesystem type)
df -hT
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/sda1 ext4 98G 45G 48G 49% /
# With -i (inodes)
df -hi
# Filesystem Inodes IUsed IFree IUse% Mounted on
# /dev/sda1 6.5M 235K 6.3M 4% /
# Parse specific columns
df -h | awk '{print $1, $5, $6}' # Filesystem, Use%, Mount
# Get usage percentage of root
df / | awk 'NR==2 {print $5}' | tr -d '%'
# Check if any filesystem is over threshold
df -h | awk 'NR>1 && $5+0 > 90 {print "Warning: " $6 " is " $5 " full"}'
Interpreting Values
#!/bin/bash
# Reserved blocks (typically 5% for root)
tune2fs -l /dev/sda1 | grep "Reserved block count"
# Why used + avail != total?
# - Reserved blocks for root
# - Filesystem metadata
# - Rounding in human-readable output
# Calculate actual usage including reserved
df_actual() {
local fs="$1"
df -k "$fs" | awk 'NR==2 {
total=$2
used=$3
avail=$4
reserved = total - used - avail
pct_used = (used/total)*100
pct_with_reserve = ((used+reserved)/total)*100
printf "Filesystem: %s\n", $1
printf "Total: %d KB\n", total
printf "Used: %d KB (%.1f%%)\n", used, pct_used
printf "Available: %d KB\n", avail
printf "Reserved: %d KB (%.1f%%)\n", reserved, (reserved/total)*100
}'
}
# Usage
df_actual /
4. Working with Different Filesystem Types
Filtering by Type
#!/bin/bash # List all available filesystem types cat /proc/filesystems # Show only ext4 filesystems df -t ext4 -h # Show only XFS filesystems df -t xfs -h # Show only tmpfs (temporary filesystems) df -t tmpfs -h # Exclude pseudo filesystems df -x tmpfs -x devtmpfs -x squashfs -h # Show network filesystems df -t nfs -t nfs4 -t cifs -h # Multiple types df -t ext4 -t xfs -t btrfs -h # Usage example echo "=== Local Filesystems ===" df -l -h echo -e "\n=== Network Filesystems ===" df -t nfs -t cifs -h 2>/dev/null || echo "No network filesystems" echo -e "\n=== Temporary Filesystems ===" df -t tmpfs -h
Special Filesystems
#!/bin/bash
# Show all filesystems including special ones
df -a -h
# Common special filesystems to ignore
# - devtmpfs: Device files
# - tmpfs: Temporary filesystems
# - squashfs: Read-only compressed filesystems
# - overlay: Overlay filesystems (containers)
# Clean output without pseudo filesystems
df -h -x tmpfs -x devtmpfs -x squashfs -x overlay
# Docker/container filesystems
df -h -t overlay
# Check specific mount types
check_mount_type() {
local mount_point="$1"
mount | grep " $mount_point "
}
# Example
check_mount_type "/home"
5. Inode Usage
Understanding Inodes
#!/bin/bash
# Show inode usage
df -i
df -hi # Human-readable
# Check inode usage on specific filesystem
df -i /home
# Find filesystems with high inode usage
df -i | awk 'NR>1 {gsub(/%/,"",$5); if($5>80) print $0}'
# Watch inode usage
watch -n 60 'df -hi | grep -v tmpfs'
# Check inode limits
tune2fs -l /dev/sda1 | grep -E "Inode count|Inodes per group"
# Common scenarios for inode exhaustion
# - Many small files (mail spools, cache directories)
# - Deep directory structures
# - Source code repositories
# Find directories with many files
find / -xdev -type d -exec sh -c '
count=$(find "$0" -maxdepth 1 -type f | wc -l)
if [ $count -gt 10000 ]; then
echo "$0: $count files"
fi
' {} \; 2>/dev/null
# Check inode usage percentage
inode_usage() {
local mount="$1"
df -i "$mount" | awk 'NR==2 {print $5}' | tr -d '%'
}
# Example
if [ $(inode_usage /home) -gt 90 ]; then
echo "Warning: Inode usage high on /home"
fi
Inode Troubleshooting
#!/bin/bash
# Find filesystems running out of inodes
check_inodes() {
local threshold="${1:-80}"
df -i | awk -v thresh="$threshold" '
NR>1 {
gsub(/%/,"",$5)
if ($5 > thresh) {
printf "WARNING: %s is at %d%% inode usage\n", $1, $5
}
}
'
}
# Find filesystems with many files
find_inode_heavy() {
local mount_point="$1"
echo "Top directories by file count on $mount_point:"
find "$mount_point" -xdev -type d -print0 2>/dev/null |
xargs -0 -I {} sh -c 'echo $(find "{}" -type f | wc -l) "{}"' |
sort -rn | head -20
}
# Clear inode usage by removing old files
cleanup_old_files() {
local dir="$1"
local days="${2:-30}"
echo "Removing files older than $days days in $dir"
find "$dir" -type f -mtime +"$days" -delete
find "$dir" -type d -empty -delete
}
# Monitor inode usage over time
monitor_inodes() {
local log_file="/var/log/inode_usage.log"
while true; do
echo "$(date): $(df -i /home | tail -1)" >> "$log_file"
sleep 3600 # Check every hour
done
}
6. Script Examples
Disk Space Monitoring Script
#!/bin/bash # Comprehensive disk space monitor # Configuration THRESHOLD=80 EMAIL="[email protected]" LOG_FILE="/var/log/disk_monitor.log" EXCLUDE_TYPES="tmpfs devtmpfs squashfs overlay" # Colors for output RED='\033[0;31m' YELLOW='\033[1;33m' GREEN='\033[0;32m' NC='\033[0m' # No Color log() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" echo -e "${2:-$GREEN}$1${NC}" } check_disk_usage() { local issues=0 # Build exclude pattern local exclude_args=() for type in $EXCLUDE_TYPES; do exclude_args+=(-x "$type") done df -h "${exclude_args[@]}" | tail -n +2 | while read line; do # Parse line filesystem=$(echo "$line" | awk '{print $1}') size=$(echo "$line" | awk '{print $2}') used=$(echo "$line" | awk '{print $3}') avail=$(echo "$line" | awk '{print $4}') use_pct=$(echo "$line" | awk '{print $5}' | tr -d '%') mount=$(echo "$line" | awk '{print $6}') # Check usage if [ "$use_pct" -gt "$THRESHOLD" ]; then issues=$((issues + 1)) # Critical or warning? if [ "$use_pct" -gt 90 ]; then level="CRITICAL" color="$RED" else level="WARNING" color="$YELLOW" fi message="$level: $mount ($filesystem) is ${use_pct}% full" log "$message" "$color" # Send alert if [ "$use_pct" -gt 90 ]; then echo "$message" | mail -s "Disk Alert: $mount" "$EMAIL" fi # Show top directories if [ "$use_pct" -gt 85 ]; then log "Top directories on $mount:" "$YELLOW" du -sh "$mount"/* 2>/dev/null | sort -rh | head -5 >> "$LOG_FILE" fi fi done return $issues } check_inode_usage() { local issues=0 df -i | tail -n +2 | while read line; do use_pct=$(echo "$line" | awk '{print $5}' | tr -d '%') mount=$(echo "$line" | awk '{print $6}') filesystem=$(echo "$line" | awk '{print $1}') # Skip pseudo filesystems if [[ "$filesystem" == *"tmpfs"* ]] || [[ "$filesystem" == *"devtmpfs"* ]]; then continue fi if [ "$use_pct" -gt "$THRESHOLD" ]; then issues=$((issues + 1)) log "WARNING: Inode usage on $mount is ${use_pct}% full" "$YELLOW" fi done return $issues } generate_report() { local report="/tmp/disk_report.$$" cat > "$report" << EOF Disk Space Report ================ Generated: $(date) Hostname: $(hostname) === Filesystem Usage === $(df -h -x tmpfs -x devtmpfs) === Inode Usage === $(df -hi -x tmpfs -x devtmpfs) === Largest Directories (/) === $(du -sh /* 2>/dev/null | sort -rh | head -10) === Mounted Filesystems === $(mount | column -t) EOF cat "$report" } # Main main() { log "Starting disk space check" local disk_issues=0 local inode_issues=0 check_disk_usage disk_issues=$? check_inode_usage inode_issues=$? if [ $disk_issues -gt 0 ] || [ $inode_issues -gt 0 ]; then log "Found issues: $disk_issues disk, $inode_issues inode" "$YELLOW" generate_report | mail -s "Disk Report for $(hostname)" "$EMAIL" else log "All filesystems OK" "$GREEN" fi # Archive report generate_report >> "$LOG_FILE" } # Run main main
Capacity Planning Tool
#!/bin/bash
# Disk capacity planning tool
CAPACITY_LOG="/var/log/capacity_history.log"
TREND_DAYS=30
# Record current usage
record_usage() {
local timestamp=$(date +%s)
df -k | tail -n +2 | while read line; do
filesystem=$(echo "$line" | awk '{print $1}')
used=$(echo "$line" | awk '{print $3}')
mount=$(echo "$line" | awk '{print $6}')
echo "$timestamp|$filesystem|$used|$mount" >> "$CAPACITY_LOG"
done
}
# Analyze growth trend
analyze_trend() {
local mount="$1"
local days="${2:-30}"
local cutoff=$(($(date +%s) - days * 86400))
# Get historical data
local data=$(grep "|$mount$" "$CAPACITY_LOG" | awk -F'|' -v cutoff="$cutoff" '
$1 >= cutoff {
print $1, $3
}
' | sort -n)
if [ -z "$data" ]; then
echo "No historical data for $mount"
return 1
fi
# Calculate daily growth rate
local first_usage=$(echo "$data" | head -1 | awk '{print $2}')
local last_usage=$(echo "$data" | tail -1 | awk '{print $2}')
local first_time=$(echo "$data" | head -1 | awk '{print $1}')
local last_time=$(echo "$data" | tail -1 | awk '{print $1}')
local days_diff=$(( (last_time - first_time) / 86400 ))
if [ $days_diff -lt 1 ]; then
days_diff=1
fi
local growth_rate=$(( (last_usage - first_usage) / days_diff ))
local total_size=$(df -k "$mount" | tail -1 | awk '{print $2}')
local current_usage=$(df -k "$mount" | tail -1 | awk '{print $3}')
echo "Analysis for $mount:"
echo " Current usage: $(numfmt --to=iec $((current_usage * 1024)))"
echo " Total size: $(numfmt --to=iec $((total_size * 1024)))"
echo " Growth rate: $(numfmt --to=iec $((growth_rate * 1024)))/day"
if [ $growth_rate -gt 0 ]; then
local days_until_full=$(( (total_size - current_usage) / growth_rate ))
echo " Days until full: $days_until_full"
if [ $days_until_full -lt 30 ]; then
echo " ⚠️ WARNING: Less than 30 days remaining!"
elif [ $days_until_full -lt 90 ]; then
echo " ⚠️ Less than 90 days remaining"
fi
fi
}
# Predict future usage
predict_usage() {
local mount="$1"
local days_ahead="${2:-30}"
local current=$(df -k "$mount" | tail -1 | awk '{print $3}')
local total=$(df -k "$mount" | tail -1 | awk '{print $2}')
# Get growth rate from trend
local growth_rate=$(analyze_trend "$mount" 30 | grep "Growth rate" | awk '{print $3}')
if [ -n "$growth_rate" ]; then
local predicted=$((current + (growth_rate * days_ahead)))
local pct=$(( predicted * 100 / total ))
echo "Prediction for $days_ahead days:"
echo " Current: $(numfmt --to=iec $((current * 1024)))"
echo " Predicted: $(numfmt --to=iec $((predicted * 1024))) (${pct}%)"
if [ $pct -gt 90 ]; then
echo " ⚠️ WARNING: Predicted usage exceeds 90%"
fi
fi
}
# Main menu
while true; do
clear
echo "=== Capacity Planning Tool ==="
echo "1. Record current usage"
echo "2. Show growth trends"
echo "3. Predict future usage"
echo "4. Generate report"
echo "5. Exit"
read -p "Select option: " opt
case $opt in
1)
record_usage
echo "Usage recorded"
sleep 2
;;
2)
df -h | tail -n +2 | awk '{print $6}' | while read mount; do
analyze_trend "$mount"
echo
done
read -p "Press enter to continue"
;;
3)
df -h | tail -n +2 | awk '{print $6}' | while read mount; do
predict_usage "$mount" 30
echo
done
read -p "Press enter to continue"
;;
4)
{
echo "Capacity Planning Report"
echo "========================"
echo "Date: $(date)"
echo
df -h
echo
df -h | tail -n +2 | awk '{print $6}' | while read mount; do
analyze_trend "$mount" 30
echo
done
} > capacity_report.txt
echo "Report saved to capacity_report.txt"
sleep 2
;;
5)
exit 0
;;
esac
done
7. Integration with Other Commands
Using with awk, grep, sort
#!/bin/bash
# Find filesystems with usage > 80%
df -h | awk 'NR>1 && $5+0 > 80 {print $0}'
# Sort by usage percentage
df -h | tail -n +2 | sort -k5 -r
# Show only specific columns
df -h | awk '{print $1, $5, $6}' | column -t
# Create summary with awk
df -h | awk '
BEGIN {print "Filesystem Summary"}
NR>1 {
used[$6] = $3
total[$6] = $2
pct[$6] = $5
}
END {
for (m in used) {
printf "%-20s %-8s %-8s %s\n", m, total[m], used[m], pct[m]
}
}'
# Find and sort by available space
df -h | tail -n +2 | sort -k4 -r
# Exclude certain filesystems
df -h | grep -Ev '(tmpfs|devtmpfs|udev)'
# Format as CSV
df -h | awk '
BEGIN {print "Filesystem,Size,Used,Avail,Use%,Mounted"}
NR>1 {
gsub(/%/,"",$5)
printf "%s,%s,%s,%s,%d,%s\n", $1, $2, $3, $4, $5, $6
}'
With find and du
#!/bin/bash
# Find large directories on filesystems with low space
check_large_dirs() {
local threshold="${1:-80}"
df -h | awk -v thresh="$threshold" 'NR>1 && $5+0 > thresh {print $6}' | while read mount; do
echo "Large directories on $mount:"
du -sh "$mount"/* 2>/dev/null | sort -rh | head -10
echo
done
}
# Find filesystems with specific usage patterns
find_filesystems() {
local usage_min="${1:-0}"
local usage_max="${2:-100}"
df -h | awk -v min="$usage_min" -v max="$usage_max" '
NR>1 {
gsub(/%/,"",$5)
if ($5 >= min && $5 <= max) {
print $0
}
}
'
}
# Monitor filesystem changes
monitor_changes() {
local log_file="/tmp/fs_changes.log"
local snapshot1="/tmp/fs_snapshot1"
local snapshot2="/tmp/fs_snapshot2"
# Take first snapshot
df -k > "$snapshot1"
sleep 60
# Take second snapshot
df -k > "$snapshot2"
# Compare
diff "$snapshot1" "$snapshot2" | grep -E '^[<>]' | while read line; do
echo "$(date): $line" >> "$log_file"
done
}
8. Performance Monitoring
Real-time Monitoring
#!/bin/bash
# Watch disk usage in real-time
watch -n 5 'df -h | grep -v tmpfs'
# Monitor specific mount point with alerts
monitor_mount() {
local mount="$1"
local interval="${2:-10}"
local threshold="${3:-90}"
while true; do
usage=$(df -h "$mount" | tail -1 | awk '{print $5}' | tr -d '%')
if [ "$usage" -gt "$threshold" ]; then
echo "$(date): WARNING - $mount is ${usage}% full"
fi
sleep "$interval"
done
}
# Graph disk usage over time
graph_usage() {
local mount="$1"
local points="${2:-60}"
local interval="${3:-10}"
for i in $(seq 1 $points); do
usage=$(df -h "$mount" | tail -1 | awk '{print $5}' | tr -d '%')
bars=$((usage / 2))
printf "%3d%% [%s%s]\n" $usage \
"$(printf '█%.0s' $(seq 1 $bars))" \
"$(printf '░%.0s' $(seq 1 $((50 - bars))))"
sleep "$interval"
done
}
# Usage
# monitor_mount / 30 85
# graph_usage / 100 5
Historical Tracking
#!/bin/bash
# Log disk usage to database (simple flat file)
LOG_DIR="/var/log/disk_usage"
mkdir -p "$LOG_DIR"
log_usage() {
local timestamp=$(date +%s)
local date=$(date +%Y%m%d)
df -k | tail -n +2 | while read line; do
filesystem=$(echo "$line" | awk '{print $1}')
used=$(echo "$line" | awk '{print $3}')
mount=$(echo "$line" | awk '{print $6}' | tr '/' '_')
echo "$timestamp $used" >> "$LOG_DIR/${date}_${mount}.log"
done
}
# Analyze historical data
analyze_history() {
local mount_point="$1"
local days="${2:-7}"
local mount_safe=$(echo "$mount_point" | tr '/' '_')
echo "Analysis for $mount_point over last $days days:"
echo "Date Used(MB) Change"
echo "---------- -------- ------"
local prev_used=0
for log in $(ls -t "$LOG_DIR"/*_"$mount_safe".log | head -"$days"); do
date=$(basename "$log" | cut -d_ -f1)
used=$(tail -1 "$log" | awk '{print $2}')
used_mb=$((used / 1024))
if [ $prev_used -ne 0 ]; then
change=$((used_mb - prev_used))
printf "%s %8d %+6d\n" "$date" "$used_mb" "$change"
else
printf "%s %8d %6s\n" "$date" "$used_mb" "-"
fi
prev_used=$used_mb
done
}
# Setup cron job
# */5 * * * * /usr/local/bin/disk_logger.sh
9. Formatting and Output Control
Custom Output Formats
#!/bin/bash
# Pretty print with colors
color_df() {
df -h | awk '
BEGIN {
red = "\033[31m"
yellow = "\033[33m"
green = "\033[32m"
blue = "\033[34m"
reset = "\033[0m"
}
NR==1 {
print blue $0 reset
}
NR>1 {
gsub(/%/,"",$5)
if ($5 > 90) color = red
else if ($5 > 75) color = yellow
else color = green
$5 = $5"%"
printf "%s%s%s\n", color, $0, reset
}'
}
# CSV output
df_csv() {
df -h | awk '
BEGIN {print "filesystem,size,used,avail,use_pct,mount"}
NR>1 {
gsub(/%/,"",$5)
printf "%s,%s,%s,%s,%d,%s\n", $1, $2, $3, $4, $5, $6
}'
}
# JSON output
df_json() {
echo "{"
echo ' "timestamp": "'$(date -Iseconds)'",'
echo ' "filesystems": ['
df -h | tail -n +2 | awk '
BEGIN {sep=""}
{
gsub(/%/,"",$5)
printf "%s {\n", sep
printf " \"filesystem\": \"%s\",\n", $1
printf " \"size\": \"%s\",\n", $2
printf " \"used\": \"%s\",\n", $3
printf " \"available\": \"%s\",\n", $4
printf " \"use_percent\": %d,\n", $5
printf " \"mount\": \"%s\"\n", $6
printf " }"
sep=",\n"
}'
echo ""
echo ' ]'
echo "}"
}
# HTML report
df_html() {
cat << EOF
<html>
<head>
<title>Disk Usage Report</title>
<style>
body { font-family: Arial, sans-serif; }
table { border-collapse: collapse; width: 100%; }
th { background: #4CAF50; color: white; padding: 8px; }
td { border: 1px solid #ddd; padding: 8px; }
tr:nth-child(even) { background: #f2f2f2; }
.critical { background: #ff4444; color: white; }
.warning { background: #ffaa00; }
</style>
</head>
<body>
<h2>Disk Usage Report - $(date)</h2>
<table>
<tr>
<th>Filesystem</th>
<th>Size</th>
<th>Used</th>
<th>Avail</th>
<th>Use%</th>
<th>Mounted on</th>
</tr>
EOF
df -h | tail -n +2 | awk '
{
gsub(/%/,"",$5)
class = ""
if ($5 > 90) class = "critical"
else if ($5 > 75) class = "warning"
printf " <tr class=\"%s\">\n", class
printf " <td>%s</td>\n", $1
printf " <td>%s</td>\n", $2
printf " <td>%s</td>\n", $3
printf " <td>%s</td>\n", $4
printf " <td>%d%%</td>\n", $5
printf " <td>%s</td>\n", $6
printf " </tr>\n"
}'
cat << EOF
</table>
</body>
</html>
EOF
}
# Usage
# color_df
# df_csv > report.csv
# df_json > report.json
# df_html > report.html
10. Alerting and Notifications
Alert Scripts
#!/bin/bash
# Multi-channel alert system
CONFIG_FILE="/etc/disk_alert.conf"
ALERT_LOG="/var/log/disk_alerts.log"
# Default thresholds
WARN_THRESHOLD=80
CRIT_THRESHOLD=90
# Load configuration
if [ -f "$CONFIG_FILE" ]; then
source "$CONFIG_FILE"
fi
# Alert channels
send_email() {
local subject="$1"
local message="$2"
echo "$message" | mail -s "$subject" "${ALERT_EMAIL:[email protected]}"
}
send_slack() {
local message="$1"
local webhook="${SLACK_WEBHOOK:-}"
if [ -n "$webhook" ]; then
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"$message\"}" \
"$webhook" 2>/dev/null
fi
}
log_alert() {
local level="$1"
local message="$2"
echo "$(date -Iseconds) [$level] $message" >> "$ALERT_LOG"
}
# Check and alert
check_and_alert() {
local issues=0
df -h | tail -n +2 | while read line; do
use_pct=$(echo "$line" | awk '{print $5}' | tr -d '%')
mount=$(echo "$line" | awk '{print $6}')
filesystem=$(echo "$line" | awk '{print $1}')
if [ "$use_pct" -gt "$CRIT_THRESHOLD" ]; then
issues=$((issues + 1))
message="CRITICAL: $mount ($filesystem) is ${use_pct}% full"
log_alert "CRITICAL" "$message"
send_email "CRITICAL Disk Alert" "$message"
send_slack ":fire: $message"
elif [ "$use_pct" -gt "$WARN_THRESHOLD" ]; then
issues=$((issues + 1))
message="WARNING: $mount ($filesystem) is ${use_pct}% full"
log_alert "WARNING" "$message"
# Only email for warnings, maybe not Slack
send_email "Disk Warning" "$message"
fi
done
return $issues
}
# Escalation if issues persist
escalate_alert() {
local mount="$1"
local count_file="/tmp/alert_count_$(echo "$mount" | tr '/' '_')"
local max_count=3
if [ -f "$count_file" ]; then
count=$(cat "$count_file")
else
count=0
fi
count=$((count + 1))
echo "$count" > "$count_file"
if [ "$count" -ge "$max_count" ]; then
# Escalate - page on-call
send_email "ESCALATED: Disk alert for $mount" "Issue persisting"
# Could also send SMS, page, etc.
rm -f "$count_file" # Reset after escalation
fi
}
# Main loop
while true; do
check_and_alert
sleep 300 # Check every 5 minutes
done
11. Troubleshooting
Common Issues and Solutions
#!/bin/bash
# Issue: "df: cannot read table of mounted filesystems"
# Solution: Check /etc/mtab or /proc/mounts
if [ ! -f /etc/mtab ]; then
echo "mtab missing, using /proc/mounts"
alias df='df -P'
fi
# Issue: Stale NFS mount causing hang
# Solution: Use timeout and exclude stale mounts
timeout_df() {
local timeout="${1:-10}"
timeout "$timeout" df -h 2>/dev/null || echo "df command timed out"
}
# Find stale NFS mounts
find_stale_nfs() {
mount -t nfs | awk '{print $3}' | while read mount; do
timeout 5 ls "$mount" >/dev/null 2>&1 || echo "Stale NFS: $mount"
done
}
# Issue: Permission denied for some mounts
# Solution: Use sudo for system mounts
sudo_df() {
sudo df -h "$@"
}
# Issue: Inconsistent sizes due to mounted filesystems
# Solution: Use --direct to avoid stat overhead (GNU)
df --direct -h
# Issue: Binary mount point names
# Solution: Use -P for POSIX format
df -P
# Debug df operations
debug_df() {
strace -e trace=file df -h 2>&1 | grep -E 'open|stat'
}
Diagnostic Tools
#!/bin/bash
# Comprehensive disk diagnostic
diagnose_disk() {
local mount="${1:-/}"
echo "=== Disk Diagnostics for $mount ==="
echo
echo "1. Basic filesystem info:"
df -h "$mount"
echo
echo "2. Filesystem type:"
df -T "$mount"
echo
echo "3. Inode usage:"
df -i "$mount"
echo
echo "4. Mount options:"
mount | grep " $mount "
echo
echo "5. Filesystem features:"
device=$(df "$mount" | tail -1 | awk '{print $1}')
if [ -b "$device" ]; then
tune2fs -l "$device" 2>/dev/null | head -10 || echo "Not an ext filesystem"
fi
echo
echo "6. Disk I/O stats:"
iostat -x 1 2 2>/dev/null | tail -20 || echo "iostat not available"
echo
echo "7. Top directories by size:"
du -sh "$mount"/* 2>/dev/null | sort -rh | head -10
echo
echo "8. Open files on this filesystem:"
lsof "$mount" 2>/dev/null | head -10 || echo "No open files or lsof not available"
}
# Check for disk errors
check_disk_errors() {
dmesg | grep -i "error\|i/o error\|disk full" | tail -20
}
# Validate filesystem
validate_fs() {
local mount="$1"
local device=$(df "$mount" | tail -1 | awk '{print $1}')
# Unmount first (if possible)
echo "This would check $device - requires unmount"
# sudo fsck -f "$device"
}
12. Best Practices and Tips
Shell Configuration
# ~/.bashrc additions
# Aliases for common df operations
alias df='df -h' # Human-readable by default
alias dfc='df -h --total' # With total
alias dfl='df -h -l' # Local only
alias dfi='df -hi' # Inodes
alias dfext='df -h -t ext4 -t xfs' # Only ext4 and xfs
alias dfhome='df -h /home' # Home directory
alias dfroot='df -h /' # Root filesystem
# Function to show usage with colors
dfcolor() {
df -h | awk '
BEGIN {
red="\033[31m"
yellow="\033[33m"
green="\033[32m"
blue="\033[34m"
reset="\033[0m"
}
NR==1 {print blue $0 reset; next}
{
gsub(/%/,"",$5)
color = ($5 > 90) ? red : ($5 > 75) ? yellow : green
printf "%s%s%s\n", color, $0, reset
}'
}
# Function to show top usage
dftop() {
local n="${1:-5}"
df -h | tail -n +2 | sort -k5 -rn | head -"$n"
}
# Function to monitor specific mount
dfwatch() {
local mount="${1:-/}"
local interval="${2:-5}"
watch -n "$interval" "df -h '$mount'"
}
# Complete with mount points
_df_complete() {
COMPREPLY=($(compgen -W "$(mount | awk '{print $3}')" -- "${COMP_WORDS[COMP_CWORD]}"))
}
complete -F _df_complete dfwatch dftop
System Administration Tips
#!/bin/bash # 1. Regular monitoring setup (cron) cat > /etc/cron.d/disk_monitor << 'EOF' */15 * * * * root /usr/local/bin/check_disk.sh >> /var/log/disk_check.log 2>&1 0 0 * * * root /usr/local/bin/disk_report.sh | mail -s "Daily Disk Report" [email protected] EOF # 2. Alert thresholds configuration cat > /etc/disk_alert.conf << 'EOF' WARN_THRESHOLD=80 CRIT_THRESHOLD=90 [email protected] SLACK_WEBHOOK=https://hooks.slack.com/services/xxx/yyy/zzz EXCLUDE_FS="tmpfs devtmpfs squashfs" EOF # 3. Automated cleanup when low on space auto_cleanup() { local mount="$1" local threshold="${2:-90}" usage=$(df -h "$mount" | tail -1 | awk '{print $5}' | tr -d '%') if [ "$usage" -gt "$threshold" ]; then echo "Disk usage critical on $mount, cleaning up..." # Clean package manager cache if [ -d /var/cache/apt ]; then apt-get clean fi # Remove old logs find /var/log -name "*.log" -mtime +30 -delete find /var/log -name "*.gz" -mtime +30 -delete # Remove old kernels (Ubuntu) if command -v apt-get >/dev/null; then apt-get autoremove --purge -y fi # Clear journal logs older than 7 days journalctl --vacuum-time=7d 2>/dev/null fi } # 4. Create alerts for trending trend_alert() { local mount="$1" local log_file="/tmp/${mount}_trend.log" # Record current usage usage=$(df -h "$mount" | tail -1 | awk '{print $5}' | tr -d '%') echo "$(date +%s) $usage" >> "$log_file" # Keep last 30 days tail -n 30 "$log_file" > "${log_file}.tmp" mv "${log_file}.tmp" "$log_file" # Calculate trend if [ $(wc -l < "$log_file") -ge 7 ]; then first=$(head -1 "$log_file" | awk '{print $2}') last=$(tail -1 "$log_file" | awk '{print $2}') if [ "$last" -gt "$first" ]; then echo "Warning: $mount usage trending up ($first% -> $last%)" fi fi } # 5. Emergency actions emergency_actions() { local mount="$1" # Find and kill processes using too much space lsof "$mount" 2>/dev/null | awk '{print $2}' | sort -u | while read pid; do if [ -d "/proc/$pid" ]; then size=$(ls -l "/proc/$pid/fd" 2>/dev/null | grep -c "deleted") if [ "$size" -gt 100 ]; then echo "Process $pid has deleted files consuming space" # kill -9 "$pid" # Uncomment with caution fi fi done }
Conclusion
The df command is essential for system administration and disk space monitoring:
Key Takeaways
- Basic Usage:
dfshows filesystem disk usage - Human-readable:
-hfor understandable sizes - Filesystem Types:
-Tshows type,-tfilters by type - Inodes:
-ishows inode usage - Local Only:
-lexcludes network filesystems - Total:
--totalshows sum of all - Custom Output: Can be formatted with awk, sed
- Integration: Works great with other commands
- Monitoring: Essential for automated monitoring
- Troubleshooting: Helps identify disk issues
Best Practices Summary
- Always use
-hfor human-readable output - Monitor regularly with cron jobs
- Set up alerts for high usage
- Check both space and inodes
- Exclude pseudo filesystems for clean reports
- Track trends for capacity planning
- Integrate with monitoring systems
- Document thresholds and actions
- Test cleanup procedures before emergencies
- Keep historical data for analysis
The df command, while simple, is crucial for maintaining system health and preventing outages due to full disks. Combined with other tools and proper monitoring, it forms the foundation of disk space management in Unix/Linux systems.