Bash 脚本编写教程 / 16 - 系统管理
16 - 系统管理
16.1 用户与权限管理
用户信息查询
# 当前用户信息
echo "用户名: $(whoami)"
echo "用户ID: $(id -u)"
echo "组ID: $(id -g)"
echo "所有组: $(id -Gn)"
# 用户列表
awk -F: '{printf "%-20s UID=%-6s GID=%-6s %s\n", $1, $3, $4, $7}' /etc/passwd | head -10
# 登录用户
who
w
# 最近登录
last | head -10
# sudo 日志
journalctl _COMM=sudo --no-pager | tail -20
批量用户管理
#!/bin/bash
# user_mgmt.sh —— 批量用户管理工具
set -euo pipefail
readonly GROUP="developers"
create_user() {
local username="$1"
local fullname="$2"
local shell="${3:-/bin/bash}"
# 检查用户是否已存在
if id "$username" &>/dev/null; then
echo "⚠️ 用户 $username 已存在"
return 0
fi
# 生成随机密码
local password
password=$(openssl rand -base64 12)
# 创建用户
useradd -m -c "$fullname" -s "$shell" -G "$GROUP" "$username"
echo "$username:$password" | chpasswd
# 强制首次登录修改密码
chage -d 0 "$username"
echo "✅ 创建用户: $username"
echo " 初始密码: $password"
echo " 全名: $fullname"
echo " 组: $GROUP"
}
disable_user() {
local username="$1"
if ! id "$username" &>/dev/null; then
echo "❌ 用户 $username 不存在" >&2
return 1
fi
# 锁定账户
passwd -l "$username"
# 禁用登录 Shell
chsh -s /usr/sbin/nologin "$username"
# 终止所有进程
pkill -u "$username" 2>/dev/null || true
echo "🔒 已禁用用户: $username"
}
delete_user() {
local username="$1"
local keep_home="${2:-no}"
if ! id "$username" &>/dev/null; then
echo "❌ 用户 $username 不存在" >&2
return 1
fi
# 终止进程
pkill -u "$username" 2>/dev/null || true
sleep 1
if [[ "$keep_home" == "yes" ]]; then
userdel "$username"
else
userdel -r "$username"
fi
echo "🗑️ 已删除用户: $username"
}
# 从 CSV 文件批量创建
batch_create() {
local csv_file="$1"
# CSV 格式: username,fullname,shell
while IFS=, read -r username fullname shell; do
[[ -z "$username" || "$username" == \#* ]] && continue
create_user "$username" "$fullname" "${shell:-/bin/bash}"
done < "$csv_file"
}
# 用法
case "${1:-help}" in
create) create_user "$2" "$3" "${4:-/bin/bash}" ;;
disable) disable_user "$2" ;;
delete) delete_user "$2" "${3:-no}" ;;
batch) batch_create "$2" ;;
*)
echo "用法: $0 {create|disable|delete|batch} [参数...]"
echo " create <用户名> <全名> [shell]"
echo " disable <用户名>"
echo " delete <用户名> [保留主目录:yes/no]"
echo " batch <csv文件>"
;;
esac
16.2 服务管理
#!/bin/bash
# service_mgr.sh —— 服务管理工具
set -euo pipefail
readonly SERVICE_NAME="${1:?用法: $0 <服务名> {start|stop|restart|status|logs|enable|disable}}"
# 检查 systemd 是否可用
if ! command -v systemctl &>/dev/null; then
echo "系统不使用 systemd" >&2
exit 1
fi
case "${2:-status}" in
start)
echo "启动服务: $SERVICE_NAME"
systemctl start "$SERVICE_NAME"
systemctl status "$SERVICE_NAME" --no-pager
;;
stop)
echo "停止服务: $SERVICE_NAME"
systemctl stop "$SERVICE_NAME"
;;
restart)
echo "重启服务: $SERVICE_NAME"
systemctl restart "$SERVICE_NAME"
sleep 2
systemctl status "$SERVICE_NAME" --no-pager
;;
status)
systemctl status "$SERVICE_NAME" --no-pager
;;
logs)
journalctl -u "$SERVICE_NAME" -f --no-pager
;;
enable)
systemctl enable "$SERVICE_NAME"
echo "✅ 服务已设置为开机自启"
;;
disable)
systemctl disable "$SERVICE_NAME"
echo "✅ 服务已取消开机自启"
;;
*)
echo "未知命令: ${2}" >&2
exit 1
;;
esac
16.3 磁盘与文件系统
# 磁盘使用情况
df -h
# 指定挂载点
df -h / /home /var
# 目录大小
du -sh /var/log
# 最大的文件/目录
du -ah /var 2>/dev/null | sort -rh | head -20
# 查找大文件
find / -type f -size +100M -exec ls -lh {} \; 2>/dev/null | sort -k5 -rh | head -20
# 查找并清理日志
find /var/log -name "*.log" -mtime +30 -exec ls -lh {} \;
find /var/log -name "*.log" -mtime +30 -delete
# inode 使用情况
df -i
# 挂载点信息
findmnt -t ext4,xfs
# 磁盘健康
smartctl -a /dev/sda 2>/dev/null || echo "需要安装 smartmontools"
16.4 进程管理
# 查看进程
ps aux | head -20
# 按 CPU 排序
ps aux --sort=-%cpu | head -10
# 按内存排序
ps aux --sort=-%mem | head -10
# 查找特定进程
ps aux | grep -E "[n]ginx" # 使用 [] 技巧排除 grep 自身
# 进程树
pstree -p
# 实时监控
top -bn1 | head -20
# 查看进程的打开文件
lsof -p $$ | head -20
# 查看端口占用
ss -tlnp | head -20
netstat -tlnp 2>/dev/null | head -20
# 杀死进程
kill PID # 发送 SIGTERM
kill -9 PID # 强制杀死 (SIGKILL)
pkill -f "pattern" # 按名称杀死
# 查找占用端口的进程
fuser 80/tcp 2>/dev/null
lsof -i :80 2>/dev/null
16.5 系统备份脚本
#!/bin/bash
# backup.sh —— 系统备份脚本
set -euo pipefail
readonly BACKUP_DIR="/backup"
readonly DATE=$(date +%Y%m%d_%H%M%S)
readonly RETENTION_DAYS=30
readonly LOG_FILE="/var/log/backup.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
# 创建备份目录
mkdir -p "$BACKUP_DIR"/{daily,weekly,monthly}
# 备份数据库
backup_database() {
local db_name="$1"
local backup_file="$BACKUP_DIR/daily/${db_name}_${DATE}.sql.gz"
log "备份数据库: $db_name"
mysqldump --single-transaction --routines --triggers "$db_name" | \
gzip > "$backup_file"
local size
size=$(du -h "$backup_file" | cut -f1)
log "数据库备份完成: $backup_file ($size)"
}
# 备份文件目录
backup_files() {
local source="$1"
local name=$(basename "$source")
local backup_file="$BACKUP_DIR/daily/${name}_${DATE}.tar.gz"
log "备份目录: $source"
tar czf "$backup_file" "$source" 2>/dev/null
local size
size=$(du -h "$backup_file" | cut -f1)
log "文件备份完成: $backup_file ($size)"
}
# 清理旧备份
cleanup_old_backups() {
log "清理 ${RETENTION_DAYS} 天前的备份..."
local count=0
while IFS= read -r file; do
rm -f "$file"
((count++))
done < <(find "$BACKUP_DIR" -type f -mtime +$RETENTION_DAYS -name "*.gz")
log "清理完成,删除 $count 个文件"
}
# 验证备份完整性
verify_backup() {
local file="$1"
if [[ ! -f "$file" ]]; then
log "❌ 备份文件不存在: $file"
return 1
fi
if [[ ! -s "$file" ]]; then
log "❌ 备份文件为空: $file"
return 1
fi
if file "$file" | grep -q "gzip"; then
if gzip -t "$file" 2>/dev/null; then
log "✅ 备份完整性验证通过: $file"
return 0
fi
fi
log "❌ 备份文件损坏: $file"
return 1
}
# 主函数
main() {
log "========================================"
log "备份开始"
log "========================================"
# 执行备份
backup_database "myapp"
backup_files "/etc"
backup_files "/home"
backup_files "/opt/myapp"
# 验证
verify_backup "$BACKUP_DIR/daily/myapp_${DATE}.sql.gz"
# 清理
cleanup_old_backups
log "========================================"
log "备份完成"
log "========================================"
}
main "$@"
16.6 cron 集成
cron 基础
# crontab 格式:
# ┌──── 分钟 (0-59)
# │ ┌──── 小时 (0-23)
# │ │ ┌──── 日 (1-31)
# │ │ │ ┌──── 月 (1-12)
# │ │ │ │ ┌──── 星期 (0-7, 0和7都是周日)
# │ │ │ │ │
# * * * * * command
# 查看 crontab
crontab -l
# 编辑 crontab
crontab -e
# 常用 cron 表达式
# 每天凌晨 2 点
# 0 2 * * * /opt/scripts/backup.sh
# 每 5 分钟
# */5 * * * * /opt/scripts/monitor.sh
# 工作日每天 9 点
# 0 9 * * 1-5 /opt/scripts/report.sh
# 每月 1 号
# 0 0 1 * * /opt/scripts/monthly_cleanup.sh
cron 脚本模板
#!/bin/bash
# cron_job.sh —— 适配 cron 的脚本模板
set -euo pipefail
# cron 环境变量很少,需要显式设置
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
export HOME="$(getent passwd "$(whoami)" | cut -d: -f6)"
readonly SCRIPT_NAME=$(basename "$0")
readonly LOG_DIR="/var/log/cron"
readonly LOCK_DIR="/tmp/cron_locks"
mkdir -p "$LOG_DIR" "$LOCK_DIR"
# 锁文件防重复执行
LOCK_FILE="$LOCK_DIR/${SCRIPT_NAME}.lock"
cleanup() {
rm -f "$LOCK_FILE"
}
trap cleanup EXIT
# 检查是否已在运行
if [[ -f "$LOCK_FILE" ]]; then
old_pid=$(cat "$LOCK_FILE")
if kill -0 "$old_pid" 2>/dev/null; then
echo "脚本已在运行 (PID: $old_pid),退出" >> "$LOG_DIR/${SCRIPT_NAME}.log"
exit 0
fi
fi
echo $$ > "$LOCK_FILE"
# 日志输出(cron 不显示终端)
exec > >(tee -a "$LOG_DIR/${SCRIPT_NAME}.log") 2>&1
echo "=== 任务开始: $(date) ==="
# 业务逻辑
your_main_function
echo "=== 任务结束: $(date) ==="
安装 cron 任务
#!/bin/bash
# install_cron.sh —— 安装 cron 任务
set -euo pipefail
readonly SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
readonly CRON_ENTRIES=(
"0 2 * * * $SCRIPT_DIR/backup.sh >> /var/log/cron/backup.log 2>&1"
"*/5 * * * * $SCRIPT_DIR/monitor.sh >> /var/log/cron/monitor.log 2>&1"
"0 9 * * 1-5 $SCRIPT_DIR/report.sh >> /var/log/cron/report.log 2>&1"
)
# 备份现有 crontab
crontab -l > /tmp/crontab_backup_$(date +%Y%m%d) 2>/dev/null || true
# 添加 cron 任务(避免重复)
for entry in "${CRON_ENTRIES[@]}"; do
if crontab -l 2>/dev/null | grep -qF "${entry%% *}"; then
echo "⚠️ 任务已存在: ${entry%% *}"
else
(crontab -l 2>/dev/null; echo "$entry") | crontab -
echo "✅ 已添加: $entry"
fi
done
echo ""
echo "当前 cron 任务:"
crontab -l
16.7 日志管理
# 日志轮转配置
cat > /etc/logrotate.d/myapp << 'EOF'
/var/log/myapp/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0640 myapp myapp
sharedscripts
postrotate
systemctl reload myapp > /dev/null 2>&1 || true
endscript
}
EOF
# 手动触发日志轮转
logrotate -f /etc/logrotate.d/myapp
# 查看系统日志
journalctl -xe # 最近日志
journalctl -u nginx --since "1 hour ago" # 指定服务
journalctl -p err # 仅错误级别
journalctl --disk-usage # 日志占用空间
journalctl --vacuum-size=1G # 清理到指定大小
16.8 注意事项
| 陷阱 | 说明 | 解决方案 |
|---|
| cron 环境变量缺失 | PATH 很短 | 脚本中显式设置 PATH |
| cron 日志不可见 | 没有终端 | 重定向到文件 |
| 重复执行 | cron 可能重叠执行 | 使用锁文件 |
| root 权限滥用 | 脚本以 root 运行 | 使用最小权限原则 |
| 备份空间不足 | 备份文件不断增长 | 设置保留策略 |
16.9 扩展阅读