Linux 常用命令与 Shell 脚本编程基础
本文整理了 Linux 系统中高频使用的命令及其常用选项,并介绍了 Shell 脚本编程基础。
文件与目录操作
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 / realpath — 解析链接路径
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 # 查看隐藏字符(排查格式问题)
head — 查看文件头部
| 选项 |
说明 |
-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 — 修改文件权限
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 — 修改文件所有者
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 — 运行时间与负载
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
}