Skip to content

Linux 常用命令与 Shell 脚本编程基础

本文整理了 Linux 系统中高频使用的命令及其常用选项,并介绍了 Shell 脚本编程基础。

包含AI辅助生成,在执行前请务必再次检查


文件与目录操作

ls — 列出目录内容

选项 说明
-l 以长格式显示(权限、所有者、大小、时间等)
-a 显示所有文件,包括以 . 开头的隐藏文件
-h 配合 -l 使用,以人类可读格式显示文件大小(KB/MB)
-R 递归列出子目录内容
-t 按修改时间排序(最新在前)
-S 按文件大小排序(最大在前)
-i 显示文件的 inode 编号
-d 仅列出目录本身,而非目录内的内容
-F 在文件名后追加类型标识(/ 表示目录,* 表示可执行文件)
--color 高亮显示不同类型的文件
ls -lah          # 常用组合:长格式 + 隐藏文件 + 可读大小
ls -lt /var/log  # 按时间排序查看日志目录
ls -laF          # 长格式 + 隐藏文件 + 类型标识

cd — 切换目录

cd /path/to/dir   # 切换到绝对路径
cd ..              # 返回上一级目录
cd ~               # 回到当前用户家目录
cd -               # 切换到上一次所在的目录
cd ../..           # 返回上两级目录
cd !$              # 使用上一条命令的最后一个参数作为目录

pwd — 显示当前工作目录

pwd                # 输出当前绝对路径
pwd -P             # 显示物理路径(解析符号链接)

mkdir — 创建目录

选项 说明
-p 递归创建多级目录(父目录不存在时自动创建)
-m 设置目录权限,如 -m 755
-v 显示创建过程
mkdir -p /tmp/a/b/c       # 一次创建多级目录
mkdir -m 700 secure_dir   # 创建时指定权限
mkdir -pv project/{src,bin,doc}   # 批量创建子目录

cp — 复制文件/目录

选项 说明
-r / -R 递归复制目录
-i 覆盖前提示确认
-v 显示复制过程
-p 保留文件权限、时间戳等属性
-a 归档模式,等同于 -dpR(保留所有属性并递归)
-u 仅在源文件比目标文件新时才复制
--backup 覆盖前创建备份文件
cp -av /etc/nginx /backup/nginx_backup   # 备份并保留属性
cp -ri src_dir/* /dest_dir/              # 递归复制并确认覆盖
cp --backup=numbered file.txt /dest/     # 覆盖时自动创建编号备份

mv — 移动/重命名文件

选项 说明
-i 覆盖前提示确认
-v 显示操作过程
-u 仅在源文件较新时才移动
-n 不覆盖已存在的文件
-b 覆盖前创建备份
mv -i old_name.txt new_name.txt    # 重命名
mv -v *.log /var/log/archive/      # 移动并显示过程
mv -bn file.txt /dest/             # 不覆盖 + 自动备份

rm — 删除文件/目录

选项 说明
-r / -R 递归删除目录
-f 强制删除,不提示确认
-i 删除前逐个确认
-v 显示删除过程
rm -rf /tmp/old_data          # 强制递归删除(谨慎使用!)
rm -i *.tmp                   # 逐个确认删除临时文件

安全提示:生产环境建议使用 rm -i 或先用 ls 确认目标,避免误删。可设置别名 alias rm='rm -i' 增加安全防护。

touch — 创建空文件/更新时间戳

选项 说明
-a 仅修改访问时间
-m 仅修改修改时间
-c 文件不存在时不创建
-t 指定时间戳,格式 [[CC]YY]MMDDhhmm[.ss]
-r 使用参考文件的时间戳
touch newfile.txt                    # 创建空文件
touch -t 202601011200 file.txt       # 设置指定时间戳
touch -r reference.txt target.txt    # 将 target 的时间设为与 reference 相同

ln — 创建链接

选项 说明
-s 创建符号链接(软链接),不加则创建硬链接
-f 目标已存在时强制覆盖
-v 显示详细信息
ln -s /usr/local/bin/app /usr/bin/app    # 创建软链接
ln source.txt hard_link.txt              # 创建硬链接

stat — 查看文件详细信息

stat file.txt                # 显示文件的 inode、大小、权限、时间戳等
stat -c "%a %U %n" file.txt  # 自定义输出格式(权限 所有者 文件名)
stat -f /                    # 显示文件系统状态

file — 识别文件类型

file document.pdf            # 识别文件类型
file -b file.txt             # 仅输出类型描述(不含文件名)
file -i file.txt             # 输出 MIME 类型
file *                       # 批量识别当前目录所有文件类型
readlink -f symlink          # 显示符号链接指向的绝对路径
realpath symlink             # 同上,解析为绝对路径

文件查看与编辑

cat — 查看文件内容

选项 说明
-n 显示行号
-b 仅对非空行编号
-s 压缩连续空行为一行
-A 显示所有不可见字符(制表符 $、行尾 ^I
cat -n /etc/hosts              # 带行号查看
cat file1.txt file2.txt > merged.txt   # 合并文件
cat -A file.txt                # 查看隐藏字符(排查格式问题)
选项 说明
-n N 显示前 N 行(默认 10 行)
-c N 显示前 N 个字节
-q 多文件时不显示文件名头
head -n 20 /var/log/syslog     # 查看前 20 行
head -c 1024 large_file.bin    # 查看前 1024 字节

tail — 查看文件尾部

选项 说明
-n N 显示最后 N 行
-f 持续跟踪文件新增内容(实时监控日志)
-F -f,但文件被轮转时会自动重新打开
-c N 显示最后 N 个字节
--pid 进程结束后自动停止跟踪
tail -f /var/log/syslog               # 实时监控日志
tail -n 100 /var/log/auth.log          # 查看最后 100 行
tail -F /var/log/app/app.log           # 跟踪并自动处理轮转
tail -n +5 file.txt                    # 从第 5 行开始输出到末尾

more / less — 分页查看

more /var/log/syslog          # 空格翻页,q 退出
less /var/log/syslog          # 支持上下翻页、搜索(/keyword)

less 常用快捷键:

快捷键 功能
/keyword 向下搜索
?keyword 向上搜索
n / N 跳到下一个/上一个匹配
G 跳到文件末尾
g 跳到文件开头
q 退出
&pattern 仅显示匹配行
F 实时跟踪(类似 tail -f

find — 搜索文件

选项 说明
-name 按文件名匹配(支持通配符)
-iname -name,但忽略大小写
-type f/d/l 按类型筛选:文件/目录/链接
-size +N/-N 按大小筛选(+ 大于,- 小于)
-mtime N 按修改时间筛选(N 天前)
-atime N 按访问时间筛选
-ctime N 按 inode 变更时间筛选
-user 按所有者筛选
-group 按所属组筛选
-perm 按权限筛选
-exec 对匹配结果执行命令
-newer file 比指定文件更新
-empty 查找空文件或空目录
find / -name "*.conf" -type f 2>/dev/null      # 全局搜索配置文件
find /var/log -mtime -7 -type f                 # 查找 7 天内修改的日志
find /tmp -type f -perm 777                     # 查找权限为 777 的文件
find . -name "*.log" -exec rm -f {} \;          # 批量删除日志文件
find / -size +100M -type f 2>/dev/null          # 查找大于 100MB 的文件
find /var/www -type f -name "*.php" -exec grep -l "eval(" {} \;  # 查找可疑 PHP 文件
find . -type f -newer /tmp/reference             # 查找比 reference 更新的文件
find / -nouser -o -nogroup 2>/dev/null           # 查找无主文件(安全隐患)

grep — 文本搜索

选项 说明
-i 忽略大小写
-r / -R 递归搜索目录
-n 显示匹配行号
-v 反向匹配(显示不匹配的行)
-c 仅输出匹配行数
-l 仅输出包含匹配的文件名
-w 全词匹配
-E 使用扩展正则表达式(等同于 egrep
-A N 显示匹配行及后 N 行
-B N 显示匹配行及前 N 行
-C N 显示匹配行及前后各 N 行
--color 高亮匹配内容
-o 仅输出匹配的部分(而非整行)
-P 使用 Perl 正则表达式
--include 仅搜索匹配的文件
--exclude 排除匹配的文件
grep -rn "error" /var/log/               # 递归搜索并显示行号
grep -i "failed" /var/log/auth.log       # 忽略大小写搜索
grep -v "^#" /etc/nginx/nginx.conf       # 过滤掉注释行
grep -E "error|warning" syslog           # 匹配多个关键词
grep -A3 -B1 "exception" app.log         # 显示匹配行的上下文
grep -oP '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' access.log  # 提取 IP 地址
grep -rl "password" /etc/ --include="*.conf"  # 递归搜索配置文件中的 password
grep -c "404" access.log | awk -F: '{sum+=$1} END{print sum}'  # 统计总数

wc — 统计行数/字数/字节数

选项 说明
-l 统计行数
-w 统计单词数
-c 统计字节数
-m 统计字符数
wc -l /etc/passwd              # 统计用户数
cat access.log | wc -l         # 统计日志行数
wc -lwcm file.txt              # 同时显示行数、单词数、字符数、字节数

diff / cmp — 文件比较

diff file1.txt file2.txt                # 逐行比较两个文件
diff -u file1.txt file2.txt             # 统一格式输出(适合生成补丁)
diff -rq dir1/ dir2/                    # 递归比较两个目录
cmp file1.bin file2.bin                 # 逐字节比较(二进制文件)

tee — 同时输出到屏幕和文件

command | tee output.txt                # 输出到屏幕并写入文件
command | tee -a output.txt             # 追加模式
command | tee file1.txt file2.txt       # 同时写入多个文件

权限与用户管理

chmod — 修改文件权限

选项 说明
-R 递归修改
-v 显示修改过程
chmod 755 script.sh             # rwxr-xr-x
chmod 600 id_rsa                # rw-------(仅所有者可读写)
chmod u+x deploy.sh             # 给所有者添加执行权限
chmod -R o-w /var/www/html      # 递归去除其他用户写权限
chmod u+s /usr/bin/program      # 设置 SUID 位
chmod g+s /shared_dir/          # 设置 SGID 位
chmod +t /tmp/shared/           # 设置 Sticky 位

权限数字对照:

数字 权限 含义
7 rwx 读 + 写 + 执行
6 rw- 读 + 写
5 r-x 读 + 执行
4 r-- 只读
0 --- 无权限

特殊权限位:

权限 含义 用途
SUID (4) 执行时以文件所有者身份运行 passwd 命令
SGID (2) 执行时以文件所属组身份运行;目录下新文件继承组 共享目录
Sticky (1) 目录中只有文件所有者才能删除自己的文件 /tmp 目录

chown — 修改文件所有者

选项 说明
-R 递归修改
-v 显示修改过程
chown user:group file.txt          # 修改所有者和组
chown -R www-data:www-data /var/www   # 递归修改
chown :developers project/         # 仅修改组

umask — 默认权限掩码

umask                   # 查看当前 umask 值
umask 022               # 设置 umask(新文件 644,新目录 755)
umask 077               # 设置 umask(新文件 600,新目录 700,最严格)

说明:umask 值从默认权限(文件 666,目录 777)中减去。例如 umask 022 → 文件权限 644,目录权限 755。

getfacl / setfacl — ACL 权限管理

getfacl file.txt                    # 查看文件的 ACL 权限
setfacl -m u:alice:rw file.txt      # 给用户 alice 添加读写权限
setfacl -m g:devs:r file.txt        # 给组 devs 添加读权限
setfacl -x u:alice file.txt         # 移除用户 alice 的 ACL 权限
setfacl -b file.txt                 # 清除所有 ACL 权限
setfacl -R -m u:www-data:rwx /var/www  # 递归设置 ACL

useradd / usermod / userdel — 用户管理

# 创建用户
useradd -m -s /bin/bash -G sudo,docker newuser

# 修改用户
usermod -aG docker existinguser       # 将用户追加到 docker 组
usermod -L user_to_lock               # 锁定用户
usermod -U user_to_unlock             # 解锁用户

# 删除用户
userdel -r olduser                     # 删除用户及其家目录

passwd — 修改密码

passwd                  # 修改当前用户密码
passwd username         # 修改指定用户密码(需 root)
passwd -l username      # 锁定账户
passwd -u username      # 解锁账户
passwd -e username      # 强制下次登录时修改密码
passwd -S username      # 查看密码状态

su / sudo — 切换用户与提权

su - username           # 切换到指定用户(加载环境变量)
su -                    # 切换到 root
sudo command            # 以 root 身份执行命令
sudo -u username cmd    # 以指定用户身份执行
sudo -i                 # 切换到 root 的交互式 shell
sudo visudo             # 安全编辑 sudoers 文件
sudo -l                 # 查看当前用户的 sudo 权限
sudo !!                 # 以 sudo 执行上一条命令

groupadd / groupmod / groupdel — 组管理

groupadd developers             # 创建组
groupmod -n newname oldname     # 重命名组
groupdel oldgroup               # 删除组
gpasswd -a user group           # 将用户添加到组
gpasswd -d user group           # 将用户从组中移除

查看用户与组信息

cat /etc/passwd                 # 查看所有用户信息
cat /etc/shadow                 # 查看密码哈希(需 root)
cat /etc/group                  # 查看所有组信息
groups username                 # 查看用户所属的组
lid -g groupname                # 查看组中包含的用户(需安装 libuser)

进程管理

ps — 查看进程

选项 说明
aux 显示所有进程(BSD 风格)
-ef 显示所有进程(System V 风格)
-u user 显示指定用户的进程
-p PID 显示指定 PID 的进程
--sort 按指定字段排序
ps aux                          # 查看所有进程
ps aux --sort=-%mem | head      # 按内存排序,取前几
ps -ef | grep nginx             # 查找特定进程
ps -eo pid,ppid,cmd,%mem,%cpu   # 自定义输出列
ps -ejH                         # 以树形显示进程层级

pgrep / pidof — 按名称查找进程

pgrep nginx                     # 查找 nginx 进程的 PID
pgrep -a nginx                  # 显示 PID 和完整命令行
pgrep -u www-data               # 查找指定用户的进程
pidof nginx                     # 显示 nginx 的所有 PID

top / htop — 实时进程监控

top 快捷键:

快捷键 功能
P 按 CPU 使用率排序
M 按内存使用率排序
k 终止指定 PID 的进程
q 退出
1 显示每个 CPU 核心的使用情况
c 显示完整命令行
f 选择显示的列
H 显示线程

kill / killall / pkill — 终止进程

信号 说明
SIGTERM (15) 优雅终止(默认)
SIGKILL (9) 强制终止
SIGHUP (1) 重新加载配置
SIGSTOP (19) 暂停进程
SIGCONT (18) 恢复暂停的进程
kill 1234                  # 优雅终止 PID 1234
kill -9 1234               # 强制终止
killall nginx              # 按名称终止所有匹配进程
pkill -f "python app.py"   # 按命令行模式匹配终止

nohup / & — 后台运行

nohup ./long_task.sh &              # 后台运行,断开终端不受影响
nohup ./server.sh > output.log 2>&1 &   # 后台运行并重定向输出

disown — 从作业表中移除

disown %1                   # 移除作业 1,使其不受终端关闭影响
disown -a                   # 移除所有作业

jobs / bg / fg — 作业控制

jobs                    # 查看后台作业
bg %1                   # 将作业 1 放到后台运行
fg %1                   # 将作业 1 调回前台
Ctrl + Z                # 暂停当前前台进程

nice / renice — 调整进程优先级

nice -n 10 command              # 以较低优先级运行命令(值越大优先级越低)
nice -n -5 command              # 以较高优先级运行(需 root)
renice -n 10 -p 1234            # 调整运行中进程的优先级
renice -n -5 -u username        # 调整用户所有进程的优先级

systemctl — 服务管理(systemd)

systemctl start nginx          # 启动服务
systemctl stop nginx           # 停止服务
systemctl restart nginx        # 重启服务
systemctl reload nginx         # 重新加载配置
systemctl status nginx         # 查看服务状态
systemctl enable nginx         # 设置开机自启
systemctl disable nginx        # 取消开机自启
systemctl list-units --type=service   # 列出所有服务
systemctl list-units --state=failed   # 列出失败的服务
systemctl daemon-reload        # 重新加载 systemd 配置
journalctl -u nginx -f         # 跟踪服务日志
journalctl -u nginx --since "1 hour ago"  # 查看最近 1 小时日志
journalctl -p err              # 仅显示错误级别日志
journalctl --disk-usage        # 查看日志占用磁盘空间

at / cron / crontab — 定时任务

# at:一次性定时任务
at now + 30 minutes             # 30 分钟后执行
at 2:00 PM                      # 下午 2 点执行
atq                             # 查看待执行的 at 任务
atrm 1                          # 删除 at 任务 1

# crontab:周期性定时任务
crontab -l                      # 查看当前用户的 crontab
crontab -e                      # 编辑 crontab
crontab -r                      # 删除所有 crontab 任务
crontab -u username -l          # 查看指定用户的 crontab

Crontab 时间格式:分 时 日 月 周 命令

# 示例
0 2 * * * /scripts/backup.sh           # 每天凌晨 2 点执行
*/5 * * * * /scripts/check.sh          # 每 5 分钟执行一次
0 9 * * 1-5 /scripts/report.sh         # 工作日早上 9 点执行
0 0 1 * * /scripts/monthly_cleanup.sh  # 每月 1 号零点执行

网络相关

ip — 网络配置(推荐,替代 ifconfig)

ip addr show                        # 查看所有网络接口信息
ip addr show eth0                   # 查看指定接口
ip addr add 192.168.1.100/24 dev eth0   # 添加 IP 地址
ip route show                       # 查看路由表
ip route get 8.8.8.8                # 查看到达目标的路由
ip route add 10.0.0.0/8 via 192.168.1.1  # 添加静态路由
ip link show                        # 查看链路层信息
ip link set eth0 up/down            # 启用/禁用接口
ip neigh show                       # 查看 ARP 表

ss — 套接字统计(推荐,替代 netstat)

选项 说明
-t 显示 TCP 连接
-u 显示 UDP 连接
-l 仅显示监听状态
-n 不解析服务名称(显示端口号)
-p 显示进程信息
-a 显示所有状态
-s 连接统计摘要
ss -tlnp                    # 查看 TCP 监听端口及进程
ss -tunap                   # 查看所有 TCP/UDP 连接
ss -s                       # 连接统计
ss state established        # 仅显示已建立的连接
ss -tn | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn  # 统计连接数最多的 IP

curl — HTTP 请求

选项 说明
-o / -O 保存输出到文件(-o 指定文件名,-O 使用原文件名)
-L 跟随重定向
-I 仅获取响应头
-v 显示详细请求/响应过程
-k 忽略 SSL 证书验证
-H 设置请求头
-X 指定 HTTP 方法(GET/POST/PUT/DELETE)
-d 发送 POST 数据
-u user:pass Basic 认证
--connect-timeout 连接超时时间
-s 静默模式(不显示进度条)
-w 自定义输出格式
-b 发送 Cookie
-e 设置 Referer
curl -I https://example.com                    # 仅获取响应头
curl -L -O https://example.com/file.tar.gz     # 下载文件并跟随重定向
curl -X POST -d '{"key":"val"}' -H "Content-Type: application/json" API_URL
curl -s -o /dev/null -w "%{http_code}" URL     # 仅输出 HTTP 状态码
curl -b "session=abc123" https://example.com   # 带 Cookie 请求
curl -u admin:password https://api.example.com # Basic 认证
curl --retry 3 --retry-delay 5 URL             # 失败自动重试

wget — 文件下载

选项 说明
-O 指定保存文件名
-c 断点续传
-q 静默模式
--limit-rate 限速下载
-r 递归下载
--mirror 镜像网站
-b 后台下载
--spider 仅检查链接是否有效
wget -c https://example.com/large_file.iso       # 断点续传
wget --limit-rate=1m URL                          # 限速 1MB/s
wget -r -np -l 1 https://example.com/            # 递归下载一层
wget --spider https://example.com/file.zip        # 检查链接是否有效
wget -i urls.txt                                  # 从文件读取 URL 列表下载

ping / traceroute — 网络连通性

ping -c 4 8.8.8.8                 # 发送 4 个包
ping -i 0.2 target                # 间隔 0.2 秒
ping -s 1500 target               # 指定包大小(测试 MTU)
traceroute 8.8.8.8                # 追踪路由
traceroute -T -p 443 target       # 使用 TCP 443 端口追踪
traceroute -I 8.8.8.8             # 使用 ICMP 追踪

dig / nslookup — DNS 查询

dig example.com                     # 查询 A 记录
dig example.com MX                  # 查询邮件记录
dig @8.8.8.8 example.com           # 指定 DNS 服务器查询
dig +short example.com              # 仅输出 IP
dig -x 1.2.3.4                     # 反向解析
dig +trace example.com              # 追踪完整 DNS 解析路径
dig example.com ANY                 # 查询所有记录
nslookup example.com                # 简单 DNS 查询
nslookup -type=MX example.com      # 查询邮件记录

nc / netcat — 网络调试利器

nc -zv host 80                      # 测试端口是否开放
nc -zv host 1-1000                  # 扫描端口范围
nc -l -p 8888                       # 监听端口(简易服务器)
nc host 8888                        # 连接到监听端口
echo "test" | nc host 80            # 发送数据到端口
nc -w 3 host 80                     # 设置超时时间

nmap — 网络扫描(需安装)

nmap host                           # 基本端口扫描
nmap -sV host                       # 检测服务版本
nmap -O host                        # 检测操作系统
nmap -p 80,443 host                 # 扫描指定端口
nmap -sn 192.168.1.0/24             # 存活主机发现
nmap -sS -T4 host                   # SYN 扫描(快速)

iptables / nftables — 防火墙管理

# iptables 常用规则
iptables -L -n -v                          # 列出所有规则
iptables -A INPUT -p tcp --dport 22 -j ACCEPT   # 允许 SSH
iptables -A INPUT -p tcp --dport 80 -j ACCEPT   # 允许 HTTP
iptables -A INPUT -s 10.0.0.0/8 -j DROP         # 拒绝指定网段
iptables -D INPUT 3                            # 删除第 3 条规则
iptables -F                                    # 清空所有规则

# nftables(新一代防火墙)
nft list ruleset                   # 列出所有规则
nft add table inet filter          # 创建表

tcpdump — 网络抓包

tcpdump -i eth0                          # 抓取 eth0 接口的包
tcpdump -i any port 80                   # 抓取 80 端口的包
tcpdump host 192.168.1.1                 # 抓取指定主机的包
tcpdump -n -i eth0 'tcp port 443'        # 抓取 HTTPS 流量
tcpdump -w capture.pcap -c 100           # 保存到文件(100 个包)
tcpdump -r capture.pcap                  # 读取抓包文件
tcpdump -A 'port 80'                     # 以 ASCII 显示包内容

ssh — 远程登录

ssh user@host                           # 基本登录
ssh -p 2222 user@host                   # 指定端口
ssh -i ~/.ssh/key.pem user@host         # 使用密钥文件
ssh -L 8080:localhost:80 user@host      # 本地端口转发
ssh -R 9090:localhost:90 user@host      # 远程端口转发
ssh -D 1080 user@host                   # SOCKS 代理
ssh -N -f -L 8080:target:80 user@host   # 后台端口转发
ssh-keygen -t ed25519                   # 生成 SSH 密钥对
ssh-copy-id user@host                   # 复制公钥到远程主机

scp / rsync — 远程文件传输

# scp
scp file.txt user@host:/path/           # 上传文件
scp user@host:/path/file.txt ./         # 下载文件
scp -r dir/ user@host:/path/            # 递归上传目录
scp -P 2222 file.txt user@host:/path/   # 指定端口

# rsync(推荐,支持增量同步)
rsync -avz src/ user@host:/dest/        # 增量同步目录
rsync -avz --delete src/ user@host:/dest/  # 同步并删除目标多余文件
rsync -avz --exclude="*.log" src/ dest/ # 排除文件
rsync -avz -e "ssh -p 2222" src/ user@host:/dest/  # 指定 SSH 端口
rsync --progress -avz large_file user@host:/dest/   # 显示传输进度

磁盘与存储

df — 查看磁盘使用

选项 说明
-h 人类可读格式
-T 显示文件系统类型
-i 显示 inode 使用情况
df -hT                              # 查看磁盘使用及文件系统类型
df -i                               # 检查 inode 是否耗尽
df -h --total                       # 显示总计
df -h /home                         # 查看指定挂载点使用情况

du — 查看目录大小

选项 说明
-h 人类可读格式
-s 仅显示总计
--max-depth 限制显示深度
-a 显示所有文件(不仅是目录)
--exclude 排除指定模式
du -sh /var/log                     # 查看目录总大小
du -h --max-depth=1 /var            # 查看 /var 下一级目录大小
du -h --max-depth=1 / | sort -hr    # 按大小排序
du -sh --exclude="*.log" /data      # 排除日志文件后统计

mount / umount — 挂载/卸载

mount /dev/sdb1 /mnt/usb           # 挂载设备
mount -t nfs server:/share /mnt    # 挂载 NFS
mount -t cifs //server/share /mnt -o username=user  # 挂载 SMB/CIFS
mount -o loop image.iso /mnt/iso   # 挂载 ISO 镜像
umount /mnt/usb                     # 卸载
umount -l /mnt/usb                  # 延迟卸载(忙碌时)
umount -f /mnt/nfs                  # 强制卸载(NFS 挂死时)

lsblk — 查看块设备

lsblk                          # 列出所有块设备
lsblk -f                       # 显示文件系统信息
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT   # 自定义输出列
lsblk -d                       # 仅显示磁盘(不显示分区)

blkid — 查看块设备 UUID 和文件系统类型

blkid                          # 列出所有块设备的 UUID 和类型
blkid /dev/sda1                # 查看指定设备

fdisk / parted — 磁盘分区

fdisk -l                        # 列出所有磁盘及分区
fdisk /dev/sdb                  # 对指定磁盘进行分区操作(MBR)
parted /dev/sdb                 # 对指定磁盘进行分区操作(GPT)
parted /dev/sdb print           # 显示分区表

mkfs — 创建文件系统

mkfs.ext4 /dev/sdb1             # 创建 ext4 文件系统
mkfs.xfs /dev/sdb1              # 创建 XFS 文件系统
mkfs.vfat /dev/sdb1             # 创建 FAT32 文件系统(U 盘)
mkfs.btrfs /dev/sdb1            # 创建 Btrfs 文件系统

dd — 底层数据复制

dd if=/dev/zero of=file.img bs=1M count=1024   # 创建 1GB 空文件
dd if=/dev/sda of=disk.img bs=4M               # 磁盘镜像备份
dd if=disk.img of=/dev/sda bs=4M               # 磁盘镜像恢复
dd if=/dev/urandom of=/dev/sda bs=1M           # 安全擦除磁盘(慎用!)
dd if=/dev/sda bs=512 count=1 | xxd            # 查看磁盘前 512 字节(MBR)

iostat — I/O 统计

iostat -x 1 5           # 扩展信息,每秒采集,共 5 次
iostat -h               # 人类可读格式
iostat -d -x            # 仅显示磁盘扩展统计

压缩与归档

tar — 打包/解包

选项 说明
-c 创建归档
-x 解开归档
-v 显示过程
-f 指定文件名
-z 使用 gzip 压缩/解压
-j 使用 bzip2 压缩/解压
-J 使用 xz 压缩/解压
-t 列出归档内容
-C 指定解压目标目录
--exclude 排除文件/目录
--strip-components 解压时去掉前 N 层目录
tar -czvf backup.tar.gz /data              # gzip 压缩
tar -xzvf backup.tar.gz                    # gzip 解压
tar -xzvf backup.tar.gz -C /restore/       # 解压到指定目录
tar -tzvf backup.tar.gz                    # 查看归档内容
tar -czvf backup.tar.gz --exclude="*.log" /data   # 排除日志文件
tar -xzvf archive.tar.gz --strip-components=1     # 解压时去掉顶层目录
tar -cvf - /data | gzip > backup.tar.gz    # 流式压缩(适合大目录)

gzip / gunzip / zcat

gzip file.txt                   # 压缩文件(原文件被替换)
gzip -d file.txt.gz             # 解压
gunzip file.txt.gz              # 解压
zcat file.txt.gz                # 不解压直接查看内容
gzip -9 file.txt                # 最高压缩比
gzip -k file.txt                # 保留原文件

bzip2 / bunzip2 / bzcat

bzip2 file.txt                  # 压缩(比 gzip 压缩率更高)
bzip2 -d file.txt.bz2           # 解压
bunzip2 file.txt.bz2            # 解压
bzcat file.txt.bz2              # 不解压直接查看

xz / unxz / xzcat

xz file.txt                     # 压缩(最高压缩率)
xz -d file.txt.xz               # 解压
unxz file.txt.xz                # 解压
xzcat file.txt.xz               # 不解压直接查看
xz -T 0 file.txt                # 使用所有 CPU 核心压缩

zstd / unzstd — Zstandard 压缩(高速)

zstd file.txt                   # 压缩
zstd -d file.txt.zst            # 解压
unzstd file.txt.zst             # 解压
zstd -1 file.txt                # 快速压缩(低压缩率)
zstd -19 file.txt               # 最高压缩率
zstd -T 0 file.txt              # 使用所有 CPU 核心

zip / unzip

zip -r archive.zip /dir/           # 递归压缩目录
zip -e secure.zip file.txt         # 加密压缩
unzip archive.zip                  # 解压
unzip -l archive.zip               # 仅查看内容
unzip archive.zip -d /target/      # 解压到指定目录
zip -P password secret.zip file.txt  # 使用密码加密

7z — 7-Zip 压缩(需安装 p7zip)

7z a archive.7z dir/               # 创建 7z 归档
7z x archive.7z                    # 解压
7z l archive.7z                    # 列出内容
7z a -p"password" secure.7z file   # 加密压缩

文本处理

awk — 文本分析

awk '{print $1}' access.log                  # 打印第一列
awk -F: '{print $1, $3}' /etc/passwd         # 指定分隔符
awk '$3 > 1000' /etc/passwd                  # 条件过滤
awk '{sum+=$1} END {print sum}' numbers.txt  # 求和
awk 'NR>=10 && NR<=20' file.txt              # 打印第 10-20 行
awk '{print NR, $0}' file.txt                # 打印行号和内容
awk 'length > 80' file.txt                   # 打印长度超过 80 的行
awk '{count[$1]++} END{for(k in count) print k, count[k]}' access.log  # 统计 IP 出现次数

sed — 流编辑器

选项 说明
-i 直接修改文件(不加则仅输出到 stdout)
-e 执行多个编辑命令
-n 静默模式,配合 p 使用
sed 's/old/new/g' file.txt                   # 替换所有匹配
sed -i 's/old/new/g' file.txt                # 直接修改文件
sed -n '10,20p' file.txt                     # 打印第 10-20 行
sed '/^#/d' config.conf                      # 删除注释行
sed -i '3d' file.txt                         # 删除第 3 行
sed -i '/^$/d' file.txt                      # 删除空行
sed -n '/start/,/end/p' file.txt             # 打印两个模式之间的内容
sed -i.bak 's/old/new/g' file.txt            # 修改前自动创建备份

sort / uniq — 排序与去重

sort 选项 说明
-n 按数值排序
-r 逆序
-k N 按第 N 列排序
-t 指定分隔符
-u 排序后去重
-h 按人类可读大小排序(如 1K, 2M)
uniq 选项 说明
-c 统计重复次数
-d 仅显示重复行
-u 仅显示不重复行
-i 忽略大小写
sort -k2 -n data.txt                  # 按第 2 列数值排序
sort access.log | uniq -c | sort -rn  # 统计并排序(如 IP 访问次数)
sort -h -k5 data.txt                  # 按人类可读大小排序

cut — 按列截取

选项 说明
-d 指定分隔符
-f 指定字段(列)
-c 按字符位置截取
cut -d: -f1,3 /etc/passwd       # 截取用户名和 UID
cut -c1-10 file.txt              # 截取每行前 10 个字符
cut -d' ' -f1-3 file.txt        # 截取前 3 列

tr — 字符转换/删除

tr 'a-z' 'A-Z' < file.txt        # 小写转大写
tr -d '\r' < file.txt > out.txt  # 删除回车符
tr -s ' ' < file.txt             # 压缩连续空格
tr -d '[:digit:]' < file.txt     # 删除所有数字
tr ':' '\n' < file.txt           # 将冒号替换为换行

column — 格式化为表格

mount | column -t                  # 将输出格式化为对齐的表格
cat /etc/passwd | column -t -s:    # 以冒号为分隔符格式化

jq — JSON 处理(需安装)

cat data.json | jq '.'                            # 格式化输出 JSON
cat data.json | jq '.name'                        # 提取字段
cat data.json | jq '.users[] | .email'            # 提取数组中的字段
cat data.json | jq '.users[] | select(.age > 25)' # 条件过滤
curl -s API_URL | jq '.data[0].id'                # 处理 API 响应
jq -r '.name' data.json                           # 原始输出(去掉引号)

系统信息与监控

uname — 系统信息

uname -a            # 显示所有系统信息
uname -r            # 显示内核版本
uname -m            # 显示架构(x86_64 等)
uname -n            # 显示主机名
uname -o            # 显示操作系统

hostname — 主机名

hostname                    # 显示主机名
hostname -I                 # 显示所有 IP 地址
hostnamectl set-hostname newname   # 修改主机名(systemd)
hostnamectl status          # 查看主机名详情

uptime — 运行时间与负载

uptime                  # 显示运行时间和平均负载

free — 内存使用

选项 说明
-h 人类可读格式
-s N 每 N 秒刷新
-t 显示总计行
free -h                 # 查看内存使用
free -h -s 5            # 每 5 秒刷新

vmstat — 虚拟内存统计

vmstat 1 5              # 每秒采集一次,共 5 次
vmstat -d               # 磁盘统计
vmstat -s               # 内存统计摘要
vmstat -f               # 自系统启动以来的 fork 数

lscpu — CPU 信息

lscpu                   # 显示 CPU 架构、核心数、频率等
lscpu | grep "Model name"  # 查看 CPU 型号

lsmem / lsusb / lspci — 硬件信息

lsmem                   # 查看内存块信息
lsusb                   # 查看 USB 设备
lsusb -v                # 详细 USB 信息
lspci                   # 查看 PCI 设备
lspci -v                # 详细 PCI 信息
lspci | grep -i vga     # 查看显卡信息

dmidecode — 硬件详细信息(需 root)

dmidecode -t bios               # BIOS 信息
dmidecode -t system             # 系统信息(厂商、型号、序列号)
dmidecode -t memory             # 内存信息
dmidecode -t processor          # 处理器信息
dmidecode -s system-serial-number   # 获取序列号

lsof — 列出打开的文件

选项 说明
-i 显示网络连接
-p PID 显示指定进程打开的文件
-u user 显示指定用户打开的文件
-c name 显示指定命令打开的文件
:port 查看指定端口被谁占用
lsof -i :80                     # 查看 80 端口被谁占用
lsof -p 1234                    # 查看进程打开的文件
lsof -u www-data                # 查看用户打开的文件
lsof +D /var/log                # 查看目录下被打开的文件
lsof -i -nP                     # 查看所有网络连接(不解析主机名和端口名)

dmesg — 内核消息

dmesg | tail                    # 查看最新的内核消息
dmesg -T | grep -i error        # 带可读时间戳,搜索错误
dmesg --level=err,warn          # 仅显示错误和警告
dmesg -T | grep -i usb          # 查看 USB 设备相关消息
dmesg -T | grep -i "out of memory"  # 查看 OOM 事件

watch — 定时刷新命令输出

watch -n 2 df -h                # 每 2 秒刷新磁盘使用
watch -n 1 'ss -tlnp'          # 每秒刷新监听端口
watch -d free -h                # 高亮变化的部分
watch -n 5 'ps aux | sort -nrk 3 | head -10'  # 每 5 秒显示 CPU 前 10 进程

timeout — 限时执行命令

timeout 10 long_command         # 10 秒后强制终止
timeout --signal=SIGKILL 5 cmd  # 5 秒后发送 SIGKILL
timeout --preserve-status 30 cmd  # 保留原始退出状态

安全与审计

whoami / id — 当前用户信息

whoami                      # 当前用户名
id                          # 当前用户 UID、GID 及所属组
id username                 # 查看指定用户信息

last / lastb — 登录记录

last                        # 查看成功登录记录
lastb                       # 查看失败登录记录(需 root)
last -n 20                  # 查看最近 20 条记录
last reboot                 # 查看重启记录
last -i                     # 显示登录 IP 地址

who / w — 当前登录用户

who                         # 查看当前登录用户
w                           # 更详细的当前登录信息(含负载)
who -a                      # 显示所有登录信息

lastlog — 最后一次登录记录

lastlog                     # 查看所有用户最后一次登录
lastlog -u username         # 查看指定用户最后登录

history — 命令历史

history                     # 查看命令历史
history | grep ssh          # 搜索历史命令
history -c                  # 清空当前会话历史
!!                          # 执行上一条命令
!n                          # 执行第 n 条历史命令
!string                     # 执行最近一条以 string 开头的命令
^old^new                    # 替换上一条命令中的 old 为 new 并执行

安全提示:敏感命令(含密码)前加空格可避免记录到 history(需配置 HISTCONTROL=ignorespace)。

strace — 系统调用追踪

strace -p PID                    # 追踪运行中的进程
strace command                   # 追踪命令的系统调用
strace -e open command           # 仅追踪 open 调用
strace -e network command        # 追踪网络相关调用
strace -c command                # 统计系统调用摘要
strace -f -p PID                 # 同时追踪子进程
strace -o output.log command     # 输出到文件

ltrace — 库函数调用追踪

ltrace command                   # 追踪库函数调用
ltrace -p PID                    # 追踪运行中的进程
ltrace -c command                # 统计调用摘要

auditd / ausearch / aureport — 审计系统

# 配置审计规则
auditctl -w /etc/passwd -p wa -k passwd_changes   # 监控 /etc/passwd 的写和属性变更
auditctl -w /etc/shadow -p wa -k shadow_changes   # 监控 /etc/shadow
auditctl -l                      # 列出所有审计规则

# 搜索审计日志
ausearch -k passwd_changes       # 按键名搜索
ausearch -ua root                # 搜索 root 用户的操作
ausearch -ts recent              # 搜索最近的事件
auseark -m LOGIN --success no    # 搜索失败的登录

# 审计报告
aureport                         # 生成审计摘要报告
aureport --login                 # 登录报告
aureport --auth                  # 认证报告
aureport --file                  # 文件访问报告

openssl — 证书与加密

openssl x509 -in cert.pem -text -noout     # 查看证书详情
openssl x509 -in cert.pem -enddate -noout  # 查看证书过期时间
openssl s_client -connect host:443         # 测试 SSL 连接
openssl dgst -sha256 file.txt              # 计算文件 SHA256 哈希
openssl rand -base64 32                    # 生成 32 字节随机数
openssl enc -aes-256-cbc -in file -out file.enc  # 加密文件
openssl enc -d -aes-256-cbc -in file.enc -out file  # 解密文件
openssl req -new -x509 -days 365 -keyout key.pem -out cert.pem  # 生成自签名证书

gpg — GPG 加密与签名

gpg --gen-key                          # 生成密钥对
gpg --list-keys                        # 列出公钥
gpg --list-secret-keys                 # 列出私钥
gpg -e -r recipient file.txt           # 用公钥加密文件
gpg -d file.txt.gpg                    # 解密文件
gpg --sign file.txt                    # 签名文件
gpg --verify file.txt.sig              # 验证签名
gpg --export -a "user" > public.key    # 导出公钥
gpg --import public.key                # 导入公钥

fail2ban — 防暴力破解

fail2ban-client status                 # 查看 fail2ban 状态
fail2ban-client status sshd            # 查看 SSH jail 状态
fail2ban-client set sshd banip 1.2.3.4  # 手动封禁 IP
fail2ban-client set sshd unbanip 1.2.3.4  # 手动解封 IP
fail2ban-client reload                 # 重新加载配置

ssh 安全加固

# /etc/ssh/sshd_config 推荐配置
# PermitRootLogin no                  # 禁止 root 直接登录
# PasswordAuthentication no           # 禁用密码认证(仅允许密钥)
# MaxAuthTries 3                      # 最大尝试次数
# Port 2222                           # 修改默认端口
# AllowUsers user1 user2              # 仅允许特定用户登录
# LoginGraceTime 30                   # 登录超时时间
# ClientAliveInterval 300             # 客户端心跳间隔
# ClientAliveCountMax 2               # 心跳失败次数上限

加密与哈希

# 计算文件哈希
sha256sum file.txt                     # SHA256 哈希
sha1sum file.txt                       # SHA1 哈希
md5sum file.txt                        # MD5 哈希
sha256sum -c checksums.txt             # 校验文件完整性

# base64 编解码
echo "text" | base64                   # 编码
echo "dGV4dA==" | base64 -d            # 解码
base64 file.txt                        # 编码文件
base64 -d file.b64 > file.txt          # 解码文件

# 安全生成随机密码
openssl rand -base64 16                # 生成 16 字节随机密码
tr -dc 'A-Za-z0-9!@#$%^&*' < /dev/urandom | head -c 20  # 生成 20 字符随机密码

管道与重定向

管道 |

将前一个命令的输出作为后一个命令的输入。

cat access.log | grep "404" | wc -l          # 统计 404 错误数量
ps aux | grep nginx | grep -v grep           # 查找 nginx 进程(排除 grep 自身)

重定向

操作符 说明
> 输出重定向(覆盖)
>> 输出重定向(追加)
2> 错误输出重定向
2>&1 将错误输出合并到标准输出
< 输入重定向
<<EOF Here Document(多行输入)
command > output.txt 2>&1          # 标准输出和错误都写入文件
command > /dev/null 2>&1           # 丢弃所有输出
command >> log.txt 2>&1            # 追加到日志
cat <<EOF > config.txt
line1
line2
EOF

xargs — 构建命令行参数

选项 说明
-n N 每次传递 N 个参数
-I {} 指定占位符
-0 以 null 分隔输入(配合 find -print0
-P N 并行执行 N 个进程
find . -name "*.tmp" | xargs rm -f                      # 批量删除
find . -name "*.log" -print0 | xargs -0 gzip            # 安全处理含空格文件名
cat urls.txt | xargs -n 1 -P 4 curl -sO                 # 并行下载
echo "file1 file2 file3" | xargs -n 1 ls -la            # 逐个处理

进程替换 <()>()

diff <(ls dir1) <(ls dir2)                 # 比较两个目录的文件列表
command > >(tee log.txt) 2>&1              # 同时输出到屏幕和文件

实用组合技巧

# 查找占用磁盘最多的前 10 个目录
du -h --max-depth=2 / 2>/dev/null | sort -hr | head -10

# 实时查看最多连接的 IP
ss -tn | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head

# 查找最近 24 小时内被修改的可执行文件
find / -type f -mtime -1 -executable 2>/dev/null

# 批量查找并替换文件中的字符串
grep -rl "old_string" /path/ | xargs sed -i 's/old_string/new_string/g'

# 统计每个 HTTP 状态码的出现次数
awk '{print $9}' access.log | sort | uniq -c | sort -rn

# 查看当前系统所有开放端口
ss -tlnp | awk 'NR>1 {print $4}' | grep -oE '[0-9]+$' | sort -n | uniq

# 监控日志中的特定关键词
tail -f /var/log/auth.log | grep --line-buffered "Failed password"

# 批量重命名文件(将 .jpeg 改为 .jpg)
for f in *.jpeg; do mv "$f" "${f%.jpeg}.jpg"; done

# 快速搭建 HTTP 文件服务器(Python)
python3 -m http.server 8080

# 查找并删除 30 天前的日志文件
find /var/log -name "*.log" -mtime +30 -exec rm -f {} \;

# 统计当前目录下文件数量(按类型)
find . -type f | sed 's/.*\.//' | sort | uniq -c | sort -rn

# 实时监控 TCP 连接状态
watch -n 1 'ss -s'

# 查找权限为 777 的文件并修改为 644
find /var/www -type f -perm 777 -exec chmod 644 {} \;

# 备份文件时自动添加时间戳
cp file.txt "file_$(date +%Y%m%d_%H%M%S).txt"

# 批量检查多个主机的连通性
for host in 192.168.1.{1..254}; do ping -c 1 -W 1 $host &>/dev/null && echo "$host is up"; done

# 统计代码行数(按语言)
find . -name "*.py" -o -name "*.js" -o -name "*.go" | xargs wc -l | sort -rn

# 安全删除文件(先移到回收站)
mv file.txt ~/.trash/$(date +%s)_file.txt

# 查看系统启动时间线
journalctl --list-boots

# 检查 SSL 证书过期时间
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

# 查找包含特定内容的文件并高亮显示
grep -rn --color=always "TODO\|FIXME\|HACK" src/ | less -R

# 实时监控进程的系统调用
strace -p $(pgrep -f "your_process") -e trace=network -t

# 批量压缩日志文件并删除原文件
find /var/log -name "*.log" -mtime +7 -exec gzip {} \;

# 快速统计目录中各类文件的数量和大小
find . -type f -printf '%s %f\n' | awk '{size[$NF]+=$1; count[$NF]++} END{for(f in count) printf "%10d %6d %s\n", size[f], count[f], f}' | sort -rn

Shell 脚本编程

Shell 脚本是将多条命令组合为一个可执行文件的自动化方式,是 Linux 运维和安全审计的核心技能。

脚本基础

Shebang 与脚本结构

#!/bin/bash
# 脚本说明:脚本的用途描述
# 作者:xxx
# 日期:2026-01-01

set -euo pipefail    # 严格模式:出错即退出 + 未定义变量报错 + 管道错误传播

# 脚本主体
echo "Hello, World!"

严格模式说明: - set -e:命令返回非零状态时立即退出 - set -u:使用未定义变量时报错(而非静默展开为空) - set -o pipefail:管道中任意命令失败则整个管道失败 - set -x:调试模式,打印每条执行的命令

脚本执行方式

chmod +x script.sh       # 添加执行权限
./script.sh              # 方式一:子 shell 中执行
bash script.sh           # 方式二:显式用 bash 执行
source script.sh         # 方式三:在当前 shell 中执行(影响当前环境)
. script.sh              # 方式四:同 source

变量

变量定义与使用

# 定义变量(等号两边不能有空格!)
name="Linux"
version=6
readonly PI=3.14        # 只读变量

# 使用变量
echo "System: $name"
echo "System: ${name}"  # 花括号明确边界
echo "Path: ${name}_server/bin"

# 删除变量
unset name

特殊变量

变量 含义
$0 脚本名称
$1 ~ $9 第 1 ~ 9 个位置参数
${10} 第 10 个及以上的参数
$# 参数个数
$@ 所有参数(各自独立,推荐用于循环)
$* 所有参数(合并为一个字符串)
$? 上一条命令的退出状态码
$$ 当前脚本的 PID
$! 最近一个后台进程的 PID
$_ 上一条命令的最后一个参数
#!/bin/bash
echo "脚本名: $0"
echo "参数个数: $#"
echo "所有参数: $@"

for arg in "$@"; do
    echo "参数: $arg"
done

变量替换与默认值

${var:-default}      # var 未定义或为空时返回 default(不改变 var)
${var:=default}      # var 未定义或为空时赋值为 default 并返回
${var:+alternative}  # var 已定义且非空时返回 alternative
${var:?error_msg}    # var 未定义或为空时报错并退出
${#var}              # 变量长度
${var:offset:length} # 子字符串提取
${var#pattern}       # 从左侧删除最短匹配
${var##pattern}      # 从左侧删除最长匹配
${var%pattern}       # 从右侧删除最短匹配
${var%%pattern}      # 从右侧删除最长匹配
${var/old/new}       # 替换第一个匹配
${var//old/new}      # 替换所有匹配
${var^^}             # 转大写
${var,,}             # 转小写
file="/home/user/document.tar.gz"
echo "${file##*/}"        # document.tar.gz(取文件名)
echo "${file%.*}"         # /home/user/document.tar(去掉最后一个扩展名)
echo "${file%%.*}"        # /home/user/document(去掉所有扩展名)
echo "${file%/*}"         # /home/user(取目录)

name=""
echo "${name:-anonymous}" # 输出 anonymous
echo "${#name}"           # 输出 0(空字符串长度)

环境变量

export VAR="value"          # 导出为环境变量(子进程可见)
env                         # 查看所有环境变量
printenv PATH               # 查看指定环境变量
echo $PATH                  # 同上
echo $HOME                  # 用户家目录
echo $USER                  # 当前用户名
echo $SHELL                 # 默认 shell
echo $PWD                   # 当前目录
echo $LANG                  # 语言/编码设置
echo $HOSTNAME              # 主机名
echo $RANDOM                # 随机数(0-32767)
echo $LINENO                # 当前行号
echo $SECONDS               # 脚本已运行秒数

数据类型与运算

字符串操作

str="Hello World"

# 拼接
greeting="$str, Welcome!"

# 长度
echo ${#str}                  # 11

# 截取
echo ${str:0:5}               # Hello
echo ${str:6}                 # World

# 替换
echo ${str/World/Linux}       # Hello Linux

# 大小写转换
echo ${str^^}                 # HELLO WORLD
echo ${str,,}                 # hello world

整数运算

a=10; b=3

# 方式一:$(( ))
echo $((a + b))               # 13
echo $((a - b))               # 7
echo $((a * b))               # 30
echo $((a / b))               # 3(整除)
echo $((a % b))               # 1(取余)
echo $((a ** 2))              # 100(幂运算)

# 自增自减
((a++))
((a--))
((a += 5))

# 方式二:let
let "c = a + b"
let "a += 1"

# 方式三:expr(注意空格和转义)
c=$(expr $a + $b)
d=$(expr $a \* $b)            # 乘法需要转义 *

浮点运算(需 bc)

echo "scale=4; 10 / 3" | bc          # 3.3333
echo "scale=2; sqrt(144)" | bc -l    # 12.00
echo "scale=4; 3.14 * 2" | bc        # 6.28

# 在变量中使用
result=$(echo "scale=2; $a / $b" | bc)

数组

# 索引数组
fruits=("apple" "banana" "cherry" "date")

echo ${fruits[0]}             # apple(第一个元素)
echo ${fruits[-1]}            # date(最后一个元素,Bash 4.2+)
echo ${fruits[@]}             # 所有元素
echo ${#fruits[@]}            # 数组长度 4
echo ${fruits[@]:1:2}         # 从索引 1 开始取 2 个:banana cherry

# 添加元素
fruits+=("elderberry")

# 删除元素
unset fruits[1]

# 遍历
for fruit in "${fruits[@]}"; do
    echo "$fruit"
done

# 关联数组(Bash 4.0+)
declare -A user
user[name]="Alice"
user[age]=30
user[role]="admin"

for key in "${!user[@]}"; do
    echo "$key = ${user[$key]}"
done

条件判断

test / [ ] / [[ ]] 比较

# 文件测试
[ -f file ]        # 文件存在且是普通文件
[ -d dir ]         # 目录存在
[ -e path ]        # 路径存在(文件或目录)
[ -r file ]        # 可读
[ -w file ]        # 可写
[ -x file ]        # 可执行
[ -s file ]        # 文件存在且非空
[ -L file ]        # 是符号链接
[ -nt f1 -a -nt f2 ]  # f1 比 f2 新(newer than)
[ -ot f1 ]         # f1 比 f2 旧(older than)

# 字符串测试
[ -z "$str" ]      # 字符串为空
[ -n "$str" ]      # 字符串非空
[ "$a" = "$b" ]    # 相等
[ "$a" != "$b" ]   # 不等
[ "$a" \< "$b" ]   # 字典序小于(需转义)
[[ "$a" < "$b" ]]  # 字典序小于([[ ]] 无需转义)
[[ "$str" =~ ^[0-9]+$ ]]  # 正则匹配(纯数字)
[[ "$str" == *.log ]]     # 通配符匹配

# 整数比较
[ "$a" -eq "$b" ]  # 等于(equal)
[ "$a" -ne "$b" ]  # 不等于(not equal)
[ "$a" -gt "$b" ]  # 大于(greater than)
[ "$a" -ge "$b" ]  # 大于等于
[ "$a" -lt "$b" ]  # 小于(less than)
[ "$a" -le "$b" ]  # 小于等于

# 逻辑运算
[ "$a" -gt 0 ] && [ "$a" -lt 100 ]     # AND
[ "$a" -eq 0 ] || [ "$a" -eq 1 ]       # OR
[ ! -f "file.txt" ]                     # NOT

# [[ ]] 高级用法(推荐)
[[ "$a" -gt 0 && "$a" -lt 100 ]]       # 无需多个 []
[[ "$str" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]  # 邮箱正则

if / elif / else

#!/bin/bash
set -euo pipefail

file="$1"

if [[ -z "$file" ]]; then
    echo "Usage: $0 <filename>"
    exit 1
elif [[ ! -f "$file" ]]; then
    echo "Error: '$file' not found"
    exit 1
elif [[ ! -r "$file" ]]; then
    echo "Error: '$file' not readable"
    exit 1
else
    echo "Processing $file ($(wc -l < "$file") lines)"
fi

case 模式匹配

#!/bin/bash
case "$1" in
    start)
        echo "Starting service..."
        ;;
    stop)
        echo "Stopping service..."
        ;;
    restart|reload)
        echo "Restarting service..."
        ;;
    status)
        echo "Checking status..."
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|status}"
        exit 1
        ;;
esac

三元运算与算术条件

# 三元运算
[[ "$age" -ge 18 ]] && status="adult" || status="minor"

# 算术条件((( )) 中可以直接写 C 风格表达式)
if (( a > 0 && a < 100 )); then
    echo "In range"
fi

循环

for 循环

# 列表遍历
for fruit in apple banana cherry; do
    echo "I like $fruit"
done

# C 风格 for
for ((i = 0; i < 10; i++)); do
    echo "Index: $i"
done

# 范围
for i in {1..100}; do
    echo "$i"
done

for i in {0..10..2}; do         # 步长为 2
    echo "$i"
done

# 遍历文件
for file in /var/log/*.log; do
    [[ -f "$file" ]] || continue
    echo "Processing: $file"
done

# 遍历命令输出
for user in $(cat /etc/passwd | cut -d: -f1); do
    echo "User: $user"
done

# 遍历数组
for item in "${array[@]}"; do
    echo "$item"
done

while 循环

# 基本 while
count=0
while [[ $count -lt 10 ]]; do
    echo "Count: $count"
    ((count++))
done

# 逐行读取文件(推荐方式,正确处理空格和特殊字符)
while IFS= read -r line; do
    echo "Line: $line"
done < /etc/passwd

# 逐行读取命令输出
while IFS= read -r line; do
    echo "$line"
done < <(ls -la)

# 读取带分隔符的字段
while IFS=: read -r user _ uid gid _ home shell; do
    echo "$user (UID=$uid) -> $shell"
done < /etc/passwd

# 无限循环
while true; do
    echo "Running... $(date)"
    sleep 5
done

# 等待条件满足
while ! ping -c 1 -W 1 target_host &>/dev/null; do
    echo "Waiting for target_host..."
    sleep 2
done
echo "target_host is reachable!"

until 循环(条件为假时执行)

until [[ -f /tmp/ready.flag ]]; do
    echo "Waiting for ready flag..."
    sleep 1
done
echo "Ready!"

循环控制

continue              # 跳过本次迭代,进入下一次
break                 # 跳出当前循环
break 2               # 跳出两层循环
continue 2            # 跳到外层循环的下一次迭代

函数

函数定义与调用

# 方式一(推荐)
greet() {
    local name="$1"          # local 声明局部变量
    local greeting="${2:-Hello}"
    echo "$greeting, $name!"
}

# 方式二
function greet() {
    echo "Hello, $1!"
}

# 调用
greet "Alice"                # Hello, Alice!
greet "Bob" "Hi"             # Hi, Bob!

返回值与输出捕获

# 通过 return 返回状态码(0-255)
is_root() {
    [[ "$(id -u)" -eq 0 ]]
    return $?       # 0 = true, 非0 = false
}

if is_root; then
    echo "Running as root"
fi

# 通过 echo 返回值,用 $() 捕获
get_ip() {
    hostname -I | awk '{print $1}'
}

my_ip=$(get_ip)
echo "IP: $my_ip"

# 通过 nameref 返回多个值(Bash 4.3+)
get_user_info() {
    local -n result=$1       # nameref
    result[name]="Alice"
    result[uid]=1000
}

declare -A info
get_user_info info
echo "${info[name]} (${info[uid]})"

函数最佳实践

# 使用 local 避免污染全局作用域
process_file() {
    local file="$1"
    local line_count
    line_count=$(wc -l < "$file")
    echo "$line_count"
}

# 错误处理函数
die() {
    echo "ERROR: $*" >&2
    exit 1
}

# 使用示例
[[ -f "$config" ]] || die "Config file not found: $config"

输入/输出

读取用户输入

# 基本输入
read -p "Enter your name: " name
echo "Hello, $name"

# 密码输入(不回显)
read -sp "Enter password: " password
echo
echo "Password length: ${#password}"

# 带超时
read -t 5 -p "You have 5 seconds: " answer

# 限制字符数
read -n 1 -p "Continue? (y/n): " choice

# 带默认值
read -p "Port [8080]: " port
port="${port:-8080}"

# 读取到数组
read -ra words <<< "one two three four"
echo "${words[2]}"    # three

格式化输出

# echo
echo "Hello World"
echo -e "Line1\nLine2"          # 解释转义字符
echo -e "Tab\there"             # 制表符
echo -n "No newline"            # 不换行
echo "Error: $msg" >&2         # 输出到标准错误

# printf(更精确的格式化)
printf "Name: %-10s Age: %d\n" "Alice" 30
printf "Pi: %.4f\n" 3.14159
printf "%05d\n" 42              # 00042(前导零)
printf "%-20s %10s\n" "File" "Size"
printf "%-20s %10s\n" "--------" "----"
printf "%-20s %10d\n" "data.txt" 1024

# 表格输出
print_table() {
    printf "%-20s %-10s %-15s\n" "NAME" "SIZE" "MODIFIED"
    printf "%-20s %-10s %-15s\n" "----" "----" "--------"
    for file in "$@"; do
        printf "%-20s %-10s %-15s\n" \
            "$(basename "$file")" \
            "$(stat -c %s "$file")" \
            "$(stat -c %y "$file" | cut -d' ' -f1)"
    done
}

Here Document

# 基本用法
cat <<EOF
Server: $(hostname)
Date: $(date)
User: $(whoami)
EOF

# 写入文件
cat > /tmp/config.txt <<EOF
server_name example.com
listen 80
root /var/www/html
EOF

# 禁止变量展开(引号包裹标记)
cat <<'EOF'
This $HOME will not be expanded
$(date) will not run
EOF

# 去除前导缩进(-)
if true; then
    cat <<-EOF
    Indented content
        will be stripped
    EOF
fi

# Here String(<<<)
grep "error" <<< "This is an error message"
read -r first rest <<< "Hello World"

错误处理与调试

退出状态码

command
status=$?

# 常见状态码
# 0   成功
# 1   通用错误
# 2   误用 shell 命令
# 126 无法执行(权限问题)
# 127 命令未找到
# 128 无效退出参数
# 129+ 被信号终止(128 + 信号编号)

# 自定义退出码
exit 0      # 成功
exit 1      # 失败

trap — 信号捕获与清理

#!/bin/bash
set -euo pipefail

TEMP_DIR=$(mktemp -d)

# 清理函数
cleanup() {
    echo "Cleaning up..."
    rm -rf "$TEMP_DIR"
}

# 注册 trap:脚本退出、中断、终止时执行清理
trap cleanup EXIT
trap 'echo "Ctrl+C pressed"; exit 130' INT
trap 'echo "Terminated"; exit 143' TERM

echo "Working in $TEMP_DIR"
# ... 脚本主逻辑 ...

# 退出时自动调用 cleanup

常用 trap 信号:

信号 编号 触发方式
EXIT 0 脚本退出时(包括正常退出和错误退出)
INT 2 Ctrl+C
TERM 15 kill 命令
HUP 1 终端断开
USR1 10 用户自定义信号 1
USR2 12 用户自定义信号 2
DEBUG - 每条命令执行前(调试用)
ERR - 命令返回非零时

错误处理模式

# 模式一:set -e(遇错即停)
set -euo pipefail

# 模式二:手动检查返回值
if ! command; then
    echo "Command failed" >&2
    exit 1
fi

# 模式三:|| 短路
command || { echo "Failed"; exit 1; }

# 模式四:|| true(忽略错误)
command || true

# 模式五:ERROR trap
trap 'echo "Error on line $LINENO" >&2; exit 1' ERR

# 模式六:封装函数
run() {
    echo ">>> $*"
    if ! "$@"; then
        echo "FAILED: $*" >&2
        return 1
    fi
}
run ls /nonexistent

调试技巧

# 方式一:bash -x(执行时打印每条命令)
bash -x script.sh

# 方式二:set -x / set +x(局部调试)
set -x              # 开启调试
some_command
set +x              # 关闭调试

# 方式三:自定义调试函数
DEBUG=${DEBUG:-false}
debug() {
    if [[ "$DEBUG" == "true" ]]; then
        echo "[DEBUG $(date +%T)] $*" >&2
    fi
}
debug "Variable x = $x"

# 使用 DEBUG=true ./script.sh 启用调试

# 方式四:PS4 自定义调试前缀
export PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]:+${FUNCNAME[0]}():} '
set -x

字符串处理进阶

正则匹配

# [[ =~ ]] 正则匹配
email="user@example.com"
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "Valid email"
    echo "Full match: ${BASH_REMATCH[0]}"
fi

# 提取 IP 地址
log_line='192.168.1.100 - - [10/May/2026:13:55:36] "GET /index.html"'
if [[ "$log_line" =~ ([0-9]{1,3}\.){3}[0-9]{1,3} ]]; then
    echo "IP: ${BASH_REMATCH[0]}"
fi

# 提取多个捕获组
date_str="2026-05-11"
if [[ "$date_str" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})$ ]]; then
    year="${BASH_REMATCH[1]}"
    month="${BASH_REMATCH[2]}"
    day="${BASH_REMATCH[3]}"
    echo "Year: $year, Month: $month, Day: $day"
fi

多行文本处理

# 生成多行文本
read -r -d '' USAGE <<'EOF' || true
Usage: script.sh [OPTIONS] <file>

Options:
  -h, --help     Show this help
  -v, --verbose  Verbose output
  -o, --output   Output file
EOF
echo "$USAGE"

# 逐行处理命令输出
while IFS= read -r line; do
    echo "Processing: $line"
done < <(find /tmp -name "*.tmp")

# 处理 CSV
while IFS=, read -r name age city; do
    echo "$name is $age years old, lives in $city"
done < data.csv

进程与并行

后台任务与等待

# 后台执行
long_task &
pid=$!
echo "Task PID: $pid"

# 等待完成
wait $pid
echo "Task finished with status: $?"

# 等待所有后台任务
wait

# 并行执行多个任务
task1() { sleep 2; echo "Task 1 done"; }
task2() { sleep 3; echo "Task 2 done"; }
task3() { sleep 1; echo "Task 3 done"; }

task1 &
task2 &
task3 &
wait
echo "All tasks done"

并行执行控制

#!/bin/bash
# 使用命名管道(FIFO)控制并发数
MAX_JOBS=4
JOB_PIPE=$(mktemp -u)
mkfifo "$JOB_PIPE"
exec 3<>"$JOB_PIPE"
rm "$JOB_PIPE"

# 初始化令牌
for ((i = 0; i < MAX_JOBS; i++)); do
    echo >&3
done

for item in {1..20}; do
    read -u 3           # 获取令牌(阻塞等待)
    {
        echo "Processing item $item (PID $$)"
        sleep $((RANDOM % 3 + 1))
        echo >&3        # 归还令牌
    } &
done

wait
exec 3>&-
echo "All done"

xargs 并行

# -P 指定并行数
find . -name "*.jpg" | xargs -P 4 -I {} convert {} -resize 50% small_{}

# 并行压缩
find /var/log -name "*.log" -mtime +7 | xargs -P $(nproc) gzip

文件与目录操作脚本

安全的临时文件

# 创建临时目录(自动清理)
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT

# 创建临时文件
TEMP_FILE=$(mktemp)
trap 'rm -f "$TEMP_FILE"' EXIT

# 使用
echo "data" > "$TEMP_DIR/process.txt"

安全的文件操作

# 安全读取文件
safe_read() {
    local file="$1"
    if [[ ! -f "$file" ]]; then
        echo "File not found: $file" >&2
        return 1
    fi
    if [[ ! -r "$file" ]]; then
        echo "File not readable: $file" >&2
        return 1
    fi
    cat "$file"
}

# 安全写入文件(先写临时文件再移动)
safe_write() {
    local target="$1"
    local temp
    temp=$(mktemp "${target}.XXXXXX")
    cat > "$temp"
    mv "$temp" "$target"
}

echo "new content" | safe_write /etc/config.conf

# 文件锁(防止并发写入)
LOCK_FILE="/tmp/myapp.lock"
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
    echo "Another instance is running" >&2
    exit 1
fi
# ... 临界区操作 ...

目录遍历

# 递归遍历目录
walk_dir() {
    local dir="$1"
    for item in "$dir"/*; do
        [[ -e "$item" ]] || continue
        if [[ -d "$item" ]]; then
            echo "DIR:  $item"
            walk_dir "$item"
        else
            echo "FILE: $item"
        fi
    done
}

walk_dir "/var/www"

日志与输出规范

日志函数

#!/bin/bash

LOG_FILE="/var/log/myscript.log"

log() {
    local level="$1"
    shift
    local msg="$*"
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] [$level] $msg" | tee -a "$LOG_FILE"
}

log_info()  { log "INFO"  "$@"; }
log_warn()  { log "WARN"  "$@"; }
log_error() { log "ERROR" "$@"; }
log_debug() { [[ "${DEBUG:-false}" == "true" ]] && log "DEBUG" "$@"; }

# 使用
log_info "Script started"
log_warn "Disk usage above 80%"
log_error "Failed to connect to database"
log_debug "Variable x = $x"

彩色输出

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'  # No Color

# 使用
echo -e "${GREEN}Success:${NC} Operation completed"
echo -e "${RED}Error:${NC} File not found"
echo -e "${YELLOW}Warning:${NC} Disk space low"

# 带颜色的日志函数
color_log() {
    local color="$1"
    shift
    echo -e "${color}$*${NC}"
}
color_log "$GREEN" "All checks passed"
color_log "$RED" "Critical error detected"

进度条

# 简单进度条
progress_bar() {
    local current=$1
    local total=$2
    local width=50
    local percent=$((current * 100 / total))
    local filled=$((current * width / total))
    local empty=$((width - filled))
    printf "\r[%-${width}s] %d%%" "$(printf '#%.0s' $(seq 1 $filled))" "$percent"
}

total=100
for ((i = 1; i <= total; i++)); do
    progress_bar $i $total
    sleep 0.05
done
echo

参数解析

基本参数解析

#!/bin/bash
set -euo pipefail

# 默认值
VERBOSE=false
OUTPUT=""
INPUT=""

# 短选项解析
while getopts "vo:i:h" opt; do
    case $opt in
        v) VERBOSE=true ;;
        o) OUTPUT="$OPTARG" ;;
        i) INPUT="$OPTARG" ;;
        h) echo "Usage: $0 [-v] [-o output] [-i input] [-h]"; exit 0 ;;
        ?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;;
    esac
done
shift $((OPTIND - 1))   # 移除已解析的选项,剩余参数在 $@

[[ -n "$INPUT" ]] || { echo "Error: -i is required" >&2; exit 1; }

长选项解析

#!/bin/bash
set -euo pipefail

# 使用 getopt(GNU 版本)
PARSED=$(getopt -o "vho:i:" -l "verbose,help,output:,input:" -- "$@") || exit 1
eval set -- "$PARSED"

VERBOSE=false
OUTPUT=""
INPUT=""

while true; do
    case "$1" in
        -v|--verbose) VERBOSE=true; shift ;;
        -o|--output)  OUTPUT="$2"; shift 2 ;;
        -i|--input)   INPUT="$2"; shift 2 ;;
        -h|--help)    echo "Usage: $0 [OPTIONS]"; exit 0 ;;
        --)           shift; break ;;
        *)            echo "Error" >&2; exit 1 ;;
    esac
done

echo "Verbose: $VERBOSE"
echo "Input: $INPUT"
echo "Output: $OUTPUT"
echo "Remaining args: $@"

手动解析(更灵活)

#!/bin/bash
set -euo pipefail

show_help() {
    cat <<'EOF'
Usage: deploy.sh [OPTIONS] <target>

Options:
  -e, --env ENV       Environment (dev|staging|prod) [required]
  -v, --version VER   Version to deploy [required]
  -n, --dry-run       Show what would be done without executing
  -f, --force         Skip confirmation prompts
  -h, --help          Show this help message
  -V, --verbose       Enable verbose output
EOF
}

ENV=""
VERSION=""
DRY_RUN=false
FORCE=false
VERBOSE=false

while [[ $# -gt 0 ]]; do
    case "$1" in
        -e|--env)      ENV="$2"; shift 2 ;;
        -v|--version)  VERSION="$2"; shift 2 ;;
        -n|--dry-run)  DRY_RUN=true; shift ;;
        -f|--force)    FORCE=true; shift ;;
        -V|--verbose)  VERBOSE=true; shift ;;
        -h|--help)     show_help; exit 0 ;;
        -*)            echo "Unknown option: $1" >&2; show_help >&2; exit 1 ;;
        *)             break ;;  # 第一个非选项参数,后续作为位置参数处理
    esac
done

# 验证必需参数
[[ -n "$ENV" ]]     || { echo "Error: --env is required" >&2; exit 1; }
[[ -n "$VERSION" ]] || { echo "Error: --version is required" >&2; exit 1; }

echo "Deploying v$VERSION to $ENV (dry_run=$DRY_RUN, force=$FORCE)"

常用脚本模板

带完整错误处理的脚本模板

#!/bin/bash
set -euo pipefail

# ===== 配置 =====
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
readonly LOG_FILE="/var/log/${SCRIPT_NAME%.sh}.log"

# ===== 颜色 =====
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'

# ===== 日志 =====
log()  { echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
info() { log "${GREEN}[INFO]${NC} $*"; }
warn() { log "${YELLOW}[WARN]${NC} $*"; }
err()  { log "${RED}[ERROR]${NC} $*" >&2; }
die()  { err "$*"; exit 1; }

# ===== 清理 =====
TEMP_DIR=""
cleanup() {
    [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]] && rm -rf "$TEMP_DIR"
}
trap cleanup EXIT
trap 'die "Interrupted"' INT TERM

# ===== 参数 =====
VERBOSE=false
TARGET=""

show_help() {
    echo "Usage: $SCRIPT_NAME [OPTIONS] <target>"
    echo "  -v, --verbose   Verbose output"
    echo "  -h, --help      Show help"
}

while [[ $# -gt 0 ]]; do
    case "$1" in
        -v|--verbose) VERBOSE=true; shift ;;
        -h|--help)    show_help; exit 0 ;;
        -*)           die "Unknown option: $1" ;;
        *)            TARGET="$1"; shift ;;
    esac
done

[[ -n "$TARGET" ]] || die "Target argument is required"

# ===== 主逻辑 =====
main() {
    info "Starting $SCRIPT_NAME"
    TEMP_DIR=$(mktemp -d)
    info "Temp directory: $TEMP_DIR"

    # ... 业务逻辑 ...

    info "Completed successfully"
}

main "$@"

服务启停脚本模板

#!/bin/bash
set -euo pipefail

readonly SERVICE_NAME="myapp"
readonly PID_FILE="/var/run/${SERVICE_NAME}.pid"
readonly LOG_FILE="/var/log/${SERVICE_NAME}.log"
readonly COMMAND="/usr/local/bin/${SERVICE_NAME}"

start() {
    if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
        echo "$SERVICE_NAME is already running (PID $(cat "$PID_FILE"))"
        return 1
    fi
    echo "Starting $SERVICE_NAME..."
    nohup "$COMMAND" >> "$LOG_FILE" 2>&1 &
    echo $! > "$PID_FILE"
    echo "Started $SERVICE_NAME (PID $!)"
}

stop() {
    if [[ ! -f "$PID_FILE" ]]; then
        echo "$SERVICE_NAME is not running"
        return 1
    fi
    local pid
    pid=$(cat "$PID_FILE")
    if kill -0 "$pid" 2>/dev/null; then
        echo "Stopping $SERVICE_NAME (PID $pid)..."
        kill "$pid"
        # 等待进程退出
        local timeout=10
        while [[ $timeout -gt 0 ]] && kill -0 "$pid" 2>/dev/null; do
            sleep 1
            ((timeout--))
        done
        if kill -0 "$pid" 2>/dev/null; then
            echo "Force killing $SERVICE_NAME..."
            kill -9 "$pid"
        fi
        rm -f "$PID_FILE"
        echo "Stopped $SERVICE_NAME"
    else
        echo "$SERVICE_NAME is not running (stale PID file)"
        rm -f "$PID_FILE"
    fi
}

status() {
    if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
        echo "$SERVICE_NAME is running (PID $(cat "$PID_FILE"))"
    else
        echo "$SERVICE_NAME is not running"
        return 1
    fi
}

case "${1:-}" in
    start)   start ;;
    stop)    stop ;;
    restart) stop; sleep 1; start ;;
    status)  status ;;
    *)       echo "Usage: $0 {start|stop|restart|status}"; exit 1 ;;
esac

批量主机操作脚本

#!/bin/bash
set -euo pipefail

readonly HOSTS_FILE="hosts.txt"
readonly SSH_USER="admin"
readonly SSH_KEY="~/.ssh/id_ed25519"
readonly SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=5"
readonly COMMAND="$*"
readonly MAX_PARALLEL=10

[[ -f "$HOSTS_FILE" ]] || echo "Hosts file not found: $HOSTS_FILE" >&2 && exit 1
[[ -n "$COMMAND" ]] || echo "Usage: $0 <command>" >&2 && exit 1

run_on_host() {
    local host="$1"
    if ssh $SSH_OPTS -i "$SSH_KEY" "${SSH_USER}@${host}" "$COMMAND" 2>&1; then
        echo "[OK]    $host"
    else
        echo "[FAIL]  $host" >&2
    fi
}

export -f run_on_host
export SSH_OPTS SSH_KEY SSH_USER COMMAND

cat "$HOSTS_FILE" | xargs -P "$MAX_PARALLEL" -I {} bash -c 'run_on_host "$@"' _ {}

系统安全检查脚本

#!/bin/bash
set -euo pipefail

readonly REPORT_FILE="/tmp/security_audit_$(date +%Y%m%d_%H%M%S).txt"

log() { echo "$*" | tee -a "$REPORT_FILE"; }
section() { log ""; log "===== $* ====="; }

log "Security Audit Report - $(date)"
log "Host: $(hostname)"
log "Kernel: $(uname -r)"

# 检查 root 用户
section "Users with UID 0"
awk -F: '$3 == 0 {print $1}' /etc/passwd | tee -a "$REPORT_FILE"

# 检查空密码用户
section "Users with empty passwords"
awk -F: '($2 == "" || $2 == "!") {print $1}' /etc/shadow 2>/dev/null | tee -a "$REPORT_FILE" || log "Permission denied (need root)"

# 检查 SUID 文件
section "SUID Files"
find / -perm -4000 -type f 2>/dev/null | tee -a "$REPORT_FILE"

# 检查 world-writable 文件
section "World-Writable Files (excluding /tmp)"
find / -xdev -perm -0002 -type f ! -path "/tmp/*" 2>/dev/null | tee -a "$REPORT_FILE"

# 检查 SSH 配置
section "SSH Security Settings"
if [[ -f /etc/ssh/sshd_config ]]; then
    grep -E "^(PermitRootLogin|PasswordAuthentication|Port |MaxAuthTries)" \
        /etc/ssh/sshd_config 2>/dev/null | tee -a "$REPORT_FILE" || log "Using defaults"
fi

# 检查开放端口
section "Open Ports"
ss -tlnp 2>/dev/null | tee -a "$REPORT_FILE"

# 检查最近失败登录
section "Recent Failed Logins (last 10)"
lastb 2>/dev/null | head -10 | tee -a "$REPORT_FILE" || log "Permission denied"

# 检查磁盘使用
section "Disk Usage (>80%)"
df -h | awk 'NR>1 && +$5 > 80 {print}' | tee -a "$REPORT_FILE"

# 检查 /etc/passwd 中的可登录 shell
section "Users with Login Shell"
grep -v '/nologin\|/false' /etc/passwd | tee -a "$REPORT_FILE"

log ""
log "===== Audit Complete ====="
log "Report saved to: $REPORT_FILE"

日志分析脚本

#!/bin/bash
set -euo pipefail

readonly ACCESS_LOG="${1:?Usage: $0 <access.log>}"
readonly TOP_N=10

[[ -f "$ACCESS_LOG" ]] || echo "File not found: $ACCESS_LOG" >&2 && exit 1

echo "===== Access Log Analysis: $ACCESS_LOG ====="
echo ""

# 总请求数
total=$(wc -l < "$ACCESS_LOG")
echo "Total requests: $total"
echo ""

# HTTP 状态码统计
echo "--- Top HTTP Status Codes ---"
awk '{print $9}' "$ACCESS_LOG" | sort | uniq -c | sort -rn | head -"$TOP_N"
echo ""

# 最活跃 IP
echo "--- Top $TOP_N IPs ---"
awk '{print $1}' "$ACCESS_LOG" | sort | uniq -c | sort -rn | head -"$TOP_N"
echo ""

# 最常请求的 URL
echo "--- Top $TOP_N URLs ---"
awk '{print $7}' "$ACCESS_LOG" | sort | uniq -c | sort -rn | head -"$TOP_N"
echo ""

# 4xx/5xx 错误
echo "--- Error Requests (4xx/5xx) ---"
awk '$9 ~ /^[45]/' "$ACCESS_LOG" | awk '{print $9, $7}' | sort | uniq -c | sort -rn | head -"$TOP_N"
echo ""

# 每小时请求量分布
echo "--- Requests per Hour ---"
awk -F'[/: ]' '{print $5":"$6}' "$ACCESS_LOG" | sort | uniq -c | sort -k2
echo ""

# 带宽统计
echo "--- Total Bandwidth ---"
awk '{sum += $10} END {printf "%.2f MB\n", sum/1024/1024}' "$ACCESS_LOG"

自动备份脚本

#!/bin/bash
set -euo pipefail

readonly BACKUP_SRC="/var/www"
readonly BACKUP_DEST="/backup"
readonly RETENTION_DAYS=30
readonly DATE=$(date +%Y%m%d_%H%M%S)
readonly BACKUP_FILE="${BACKUP_DEST}/www_${DATE}.tar.gz"
readonly LOG_FILE="/var/log/backup.log"

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }

# 检查磁盘空间
check_space() {
    local avail
    avail=$(df -BG "$BACKUP_DEST" | awk 'NR==2 {print $4}' | tr -d 'G')
    if [[ "$avail" -lt 10 ]]; then
        log "ERROR: Less than 10GB available on $BACKUP_DEST"
        exit 1
    fi
}

# 执行备份
do_backup() {
    log "Starting backup: $BACKUP_SRC -> $BACKUP_FILE"
    tar -czf "$BACKUP_FILE" \
        --exclude="*.log" \
        --exclude="cache/*" \
        --exclude="tmp/*" \
        "$BACKUP_SRC"
    log "Backup completed: $(du -sh "$BACKUP_FILE" | cut -f1)"
}

# 清理旧备份
cleanup_old() {
    log "Removing backups older than $RETENTION_DAYS days..."
    local count
    count=$(find "$BACKUP_DEST" -name "*.tar.gz" -mtime +"$RETENTION_DAYS" | wc -l)
    find "$BACKUP_DEST" -name "*.tar.gz" -mtime +"$RETENTION_DAYS" -delete
    log "Removed $count old backup(s)"
}

# 验证备份
verify_backup() {
    if tar -tzf "$BACKUP_FILE" >/dev/null 2>&1; then
        log "Backup verification: PASSED"
    else
        log "ERROR: Backup verification FAILED!"
        rm -f "$BACKUP_FILE"
        exit 1
    fi
}

# 主流程
main() {
    log "=== Backup Job Started ==="
    check_space
    do_backup
    verify_backup
    cleanup_old
    log "=== Backup Job Completed ==="
}

main "$@"

数据库备份与轮转脚本

#!/bin/bash
set -euo pipefail

readonly DB_HOST="${DB_HOST:-localhost}"
readonly DB_PORT="${DB_PORT:-3306}"
readonly DB_USER="${DB_USER:-root}"
readonly DB_PASS="${DB_PASS:-}"
readonly DB_NAME="${1:?Usage: $0 <database_name>}"
readonly BACKUP_DIR="/backup/mysql"
readonly RETENTION_DAYS=7
readonly DATE=$(date +%Y%m%d_%H%M%S)
readonly BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_${DATE}.sql.gz"

mkdir -p "$BACKUP_DIR"

# 备份
mysqldump \
    -h "$DB_HOST" \
    -P "$DB_PORT" \
    -u "$DB_USER" \
    ${DB_PASS:+-p"$DB_PASS"} \
    --single-transaction \
    --routines \
    --triggers \
    --events \
    "$DB_NAME" | gzip > "$BACKUP_FILE"

echo "Backup saved: $BACKUP_FILE ($(du -sh "$BACKUP_FILE" | cut -f1))"

# 清理旧备份
find "$BACKUP_DIR" -name "${DB_NAME}_*.sql.gz" -mtime +${RETENTION_DAYS} -delete
echo "Cleaned backups older than ${RETENTION_DAYS} days"

用户管理脚本

#!/bin/bash
set -euo pipefail

readonly SCRIPT=$(basename "$0")

show_help() {
    cat <<EOF
Usage: $SCRIPT <command> [options]

Commands:
  add     -u <user> -p <pass> [-g <groups>] [-s <shell>]
  del     -u <user> [-r]
  lock    -u <user>
  unlock  -u <user>
  info    -u <user>
  list    [-g <group>]
EOF
}

user_add() {
    local user="" pass="" groups="" shell="/bin/bash"
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -u) user="$2"; shift 2 ;;
            -p) pass="$2"; shift 2 ;;
            -g) groups="$2"; shift 2 ;;
            -s) shell="$2"; shift 2 ;;
            *) shift ;;
        esac
    done

    [[ -n "$user" ]] || { echo "Error: -u required" >&2; return 1; }
    [[ -n "$pass" ]] || { echo "Error: -p required" >&2; return 1; }

    if id "$user" &>/dev/null; then
        echo "Error: User '$user' already exists" >&2
        return 1
    fi

    useradd -m -s "$shell" "$user"
    echo "$user:$pass" | chpasswd

    if [[ -n "$groups" ]]; then
        usermod -aG "$groups" "$user"
    fi

    echo "User '$user' created successfully"
    id "$user"
}

user_del() {
    local user="" remove_home=false
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -u) user="$2"; shift 2 ;;
            -r) remove_home=true; shift ;;
            *) shift ;;
        esac
    done

    [[ -n "$user" ]] || { echo "Error: -u required" >&2; return 1; }

    local flags=""
    $remove_home && flags="-r"

    userdel $flags "$user"
    echo "User '$user' deleted"
}

user_lock() {
    local user="$2"
    passwd -l "$user"
    usermod -L "$user"
    echo "User '$user' locked"
}

user_unlock() {
    local user="$2"
    passwd -u "$user"
    usermod -U "$user"
    echo "User '$user' unlocked"
}

user_info() {
    local user="$2"
    echo "User: $user"
    id "$user"
    passwd -S "$user"
    last -n 5 "$user"
}

case "${1:-}" in
    add)    shift; user_add "$@" ;;
    del)    shift; user_del "$@" ;;
    lock)   user_lock "$@" ;;
    unlock) user_unlock "$@" ;;
    info)   user_info "$@" ;;
    list)   cat /etc/passwd | column -t -s: ;;
    *)      show_help; exit 1 ;;
esac

Shell 脚本最佳实践

编码规范

#!/bin/bash
# 1. 始终使用 shebang
# 2. 开启严格模式
set -euo pipefail

# 3. 使用 readonly 声明常量
readonly SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
readonly CONFIG_FILE="${SCRIPT_DIR}/config.conf"

# 4. 使用 local 声明函数内变量
my_func() {
    local result=""
    local -a items=()
    # ...
}

# 5. 变量始终加双引号
echo "$variable"
echo "${array[@]}"
[[ -f "$file" ]]

# 6. 使用 [[ ]] 而非 [ ]
if [[ -n "$var" ]]; then ...

# 7. 使用 $() 而非反引号
result=$(command)

# 8. 命令替换中的变量用双引号
files=$(find . -name "*.txt")

# 9. 使用 printf 而非 echo -e 进行格式化输出
printf "Name: %-20s Value: %d\n" "$name" "$value"

安全注意事项

# 1. 永远不要解析 ls 的输出
# 错误:
for f in $(ls /path); do ...   # 无法处理空格和特殊字符
# 正确:
for f in /path/*; do ...

# 2. 使用 -- 终止选项解析
rm -- "$filename"    # 防止文件名以 - 开头被误认为选项

# 3. 临时文件使用 mktemp
tmpfile=$(mktemp)
trap 'rm -f "$tmpfile"' EXIT

# 4. 避免 eval(除非绝对必要)
# eval "echo $user_input"   # 危险!可执行任意命令

# 5. 输入验证
validate_input() {
    local input="$1"
    if [[ ! "$input" =~ ^[a-zA-Z0-9_.-]+$ ]]; then
        echo "Invalid input: $input" >&2
        return 1
    fi
}

# 6. 使用 shellcheck 静态分析
# 安装: sudo apt install shellcheck
# 使用: shellcheck script.sh

性能优化

# 1. 避免在循环中调用外部命令
# 慢:
for f in *.txt; do
    count=$(wc -l < "$f")
done
# 快:
wc -l *.txt

# 2. 使用内建命令而非外部命令
# 慢:
echo "$var" | grep "pattern"
# 快:
[[ "$var" == *pattern* ]]

# 3. 减少子 shell 创建
# 慢:
line=$(cat file)
# 快:
read -r line < file

# 4. 使用 xargs 并行处理
find . -name "*.jpg" | xargs -P $(nproc) -I {} convert {} -resize 50% small_{}

# 5. 避免不必要的 cat
# 慢:
cat file | grep "pattern"
# 快:
grep "pattern" file

脚本检查清单

# 运行 shellcheck 静态分析
shellcheck -x script.sh

# 检查 bash 语法
bash -n script.sh

# 常见 shellcheck 警告及修复
# SC2086: 变量未加引号        → 加上双引号 "$var"
# SC2046: 命令替换未加引号     → 加上双引号 "$(cmd)"
# SC2034: 变量已赋值但未使用   → 检查是否遗漏使用
# SC2155: declare 和赋值分开   → local var; var=$(cmd)
# SC2164: cd 未检查失败        → cd dir || exit 1
# SC2012: 用 find 替代 ls     → find . -maxdepth 1 -name "*.txt"
# SC2154: 变量引用但未定义     → 检查变量名拼写

可移植性提示

# POSIX sh 兼容(#!/bin/sh)
# 以下特性在 POSIX sh 中不可用:
# - [[ ]]        → 使用 [ ]
# - arrays       → 使用空格分隔的字符串
# - (( ))        → 使用 expr 或 test
# - ${var,,}     → 使用 tr
# - <()          → 使用临时文件
# - function f() → 使用 f()

# 检查命令是否存在
command -v git >/dev/null 2>&1 || { echo "git not found"; exit 1; }

# 获取 CPU 核心数(跨平台)
get_nproc() {
    if command -v nproc >/dev/null 2>&1; then
        nproc
    elif [[ -f /proc/cpuinfo ]]; then
        grep -c ^processor /proc/cpuinfo
    elif command -v sysctl >/dev/null 2>&1; then
        sysctl -n hw.ncpu
    else
        echo 1
    fi
}

# 跨平台日期
get_timestamp() {
    if date +%s.%N >/dev/null 2>&1; then
        date +%s.%N          # GNU date
    else
        python3 -c "import time; print(time.time())"
    fi
}