1. Shell脚本实战:从入门到精通的100个关键技巧
在Linux系统管理和自动化运维领域,Shell脚本就像瑞士军刀一样不可或缺。作为一名与Shell脚本打交道超过十年的系统工程师,我见证了无数新手因为掌握几个关键技巧而实现效率的飞跃提升。不同于市面上那些只讲基础语法的教程,本文将聚焦真正能让你在工作中脱颖而出的实战技巧。
Shell脚本的强大之处在于它能够将复杂的系统操作封装成简单的命令组合。从日常的文件处理到服务器集群管理,一个精心编写的脚本往往能节省数小时的手动操作时间。但要让脚本既高效又可靠,需要掌握许多教科书上不会提及的实战经验。
提示:本文所有示例均在Bash 5.0环境下测试通过,适用于大多数现代Linux发行版。建议读者在尝试时使用
bash --version确认环境兼容性。
2. 基础篇:新手必须掌握的20个核心技巧
2.1 变量操作的进阶用法
大多数教程都会教你var=value这样的基础赋值,但实际工作中这些技巧更实用:
bash复制# 设置默认值
${var:-default} # 如果var未设置或为空,使用default
${var:=default} # 如果var未设置或为空,将其设置为default
# 字符串处理
${#string} # 获取字符串长度
${string:2:4} # 截取子串(从第2个字符开始取4个)
${string#*:} # 删除直到第一个:的所有内容
我在处理日志文件时经常使用字符串截取功能。比如要从/var/log/app/error.log中提取文件名:
bash复制path="/var/log/app/error.log"
filename=${path##*/} # 得到"error.log"
2.2 条件判断的隐藏技巧
if语句是脚本的基础,但这些用法能让你写出更健壮的代码:
bash复制# 检查文件属性
if [[ -e "file" ]]; then # 文件存在
if [[ -x "file" ]]; then # 文件可执行
if [[ -O "file" ]]; then # 我是文件所有者
# 使用正则表达式匹配
if [[ "string" =~ ^[0-9]+$ ]]; then
echo "纯数字"
fi
一个常见错误是混淆[ ]和[[ ]]。后者是Bash内置的,支持更多特性如模式匹配和正则表达式,而前者是传统的test命令。
2.3 循环控制的高效写法
for循环的几种实用变体:
bash复制# 传统数字范围
for i in {1..10}; do
echo $i
done
# C语言风格(适合复杂循环)
for ((i=0; i<10; i++)); do
echo $i
done
# 遍历文件
for file in *.log; do
gzip "$file"
done
在处理大量文件时,我发现这种写法既高效又安全:
bash复制while IFS= read -r -d '' file; do
process "$file"
done < <(find . -type f -print0)
-print0和-d ''配合使用可以正确处理包含空格或特殊字符的文件名。
3. 中级篇:提升脚本质量的40个技巧
3.1 错误处理与调试
一个健壮的脚本必须妥善处理错误。这是我的标准错误处理模板:
bash复制#!/bin/bash
set -euo pipefail # 严格模式
trap 'echo "Error at line $LINENO"; exit 1' ERR
# 记录日志的函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> script.log
}
# 使用示例
mkdir -p temp || { log "创建目录失败"; exit 1; }
set -euo pipefail这行魔法命令的作用是:
-e: 任何命令失败时立即退出-u: 使用未定义变量时报错-o pipefail: 管道中任何命令失败都视为整个管道失败
3.2 函数的高级用法
函数是组织复杂脚本的关键。这些技巧能让你的函数更强大:
bash复制# 返回数组的函数
get_users() {
local users=("alice" "bob" "charlie")
echo "${users[@]}"
}
# 调用并接收数组
users=($(get_users))
# 带默认参数的函数
connect() {
local host=${1:-localhost}
local port=${2:-80}
echo "连接到 $host:$port"
}
在大型脚本中,我习惯使用这种结构:
bash复制# 在脚本开头定义所有函数
function main() {
init
process_data
cleanup
}
# 然后调用主函数
main "$@"
3.3 文本处理的瑞士军刀
Shell脚本最擅长的就是文本处理。这些组合技巧能解决90%的需求:
bash复制# 统计单词频率
cat file.txt | tr ' ' '\n' | sort | uniq -c | sort -nr
# 提取两个标记之间的内容
sed -n '/<start>/,/<end>/p' input.txt
# CSV文件处理
while IFS=, read -r col1 col2 col3; do
echo "第一列: $col1"
done < data.csv
处理日志文件时,这个awk单行命令非常有用:
bash复制awk '{print $1}' access.log | sort | uniq -c | sort -nr | head -10
它会显示访问最频繁的10个IP地址。
4. 高级篇:系统管理员必备的30个技巧
4.1 进程管理与系统监控
这些命令组合能帮你有效管理系统资源:
bash复制# 找出内存占用最高的进程
ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem | head
# 监控磁盘空间变化
watch -n 60 'df -h | grep /dev/sda1'
# 杀死所有匹配的进程
pkill -f "pattern"
我常用的系统监控脚本框架:
bash复制#!/bin/bash
threshold=90
current=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if (( current > threshold )); then
echo "磁盘空间警告: $current%" | mail -s "磁盘警报" admin@example.com
fi
4.2 网络相关操作
网络调试和测试的实用技巧:
bash复制# 测试端口是否开放
nc -zv example.com 80
# 下载文件并显示进度条
wget --progress=dot:giga http://example.com/large.file
# 简单的HTTP服务器
python3 -m http.server 8000
在自动化部署中,我经常使用这个函数检查服务是否就绪:
bash复制wait_for_service() {
local host=$1 port=$2
while ! nc -z "$host" "$port"; do
sleep 1
done
}
4.3 安全相关实践
编写安全脚本的几个要点:
bash复制# 安全地处理密码
read -s -p "输入密码: " password
echo
# 使用临时文件的安全方式
tempfile=$(mktemp /tmp/script.XXXXXX)
trap 'rm -f "$tempfile"' EXIT
# 检查脚本是否以root运行
if [[ $EUID -ne 0 ]]; then
echo "请以root用户运行此脚本" >&2
exit 1
fi
在共享环境中,我推荐使用这种模式设置权限:
bash复制umask 077 # 创建的文件默认权限600
mkdir -p private_dir
chmod 700 private_dir
5. 实战篇:10个完整脚本案例分析
5.1 日志轮转自动化
这是一个实用的日志轮转脚本,比简单的logrotate更灵活:
bash复制#!/bin/bash
set -euo pipefail
LOG_DIR="/var/log/myapp"
MAX_DAYS=30
COMPRESS_DAYS=7
# 创建按日期命名的日志副本
for log in "$LOG_DIR"/*.log; do
[ -f "$log" ] || continue
timestamp=$(date -r "$log" +%Y%m%d)
cp "$log" "${log}.${timestamp}"
> "$log" # 清空原日志文件
done
# 压缩旧日志
find "$LOG_DIR" -name "*.log.*" -mtime +$COMPRESS_DAYS -exec gzip {} \;
# 删除过期的日志
find "$LOG_DIR" -name "*.log.*" -mtime +$MAX_DAYS -delete
5.2 多服务器并行命令执行
这个脚本可以同时在多台服务器上执行命令:
bash复制#!/bin/bash
servers=("server1" "server2" "server3")
command="df -h"
for server in "${servers[@]}"; do
{
output=$(ssh "$server" "$command" 2>&1)
echo "=== $server ==="
echo "$output"
} &
done
wait # 等待所有后台任务完成
使用&将ssh命令放到后台执行,大大加快了在多台服务器上执行命令的速度。
5.3 数据库备份与验证
一个完整的MySQL备份脚本,包含验证步骤:
bash复制#!/bin/bash
set -euo pipefail
DB_USER="backup"
DB_PASS="securepassword"
BACKUP_DIR="/backups/mysql"
DATE=$(date +%Y%m%d)
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 获取数据库列表
databases=$(mysql -u"$DB_USER" -p"$DB_PASS" -e "SHOW DATABASES;" | grep -Ev "(Database|information_schema|performance_schema)")
# 备份每个数据库
for db in $databases; do
dump_file="$BACKUP_DIR/${db}_${DATE}.sql.gz"
mysqldump -u"$DB_USER" -p"$DB_PASS" --single-transaction "$db" | gzip > "$dump_file"
# 验证备份完整性
if ! gzip -t "$dump_file"; then
echo "备份验证失败: $dump_file" >&2
exit 1
fi
done
# 删除7天前的备份
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +7 -delete
这个脚本的关键点是--single-transaction参数,它可以在不锁表的情况下进行备份,适合生产环境使用。
6. 性能优化篇:10个提升脚本效率的技巧
6.1 减少子进程调用
每次调用外部命令都会创建新的子进程,影响性能。替代方案:
bash复制# 不好: 多次调用grep
grep "error" log.txt | grep -v "ignored"
# 好: 使用单个awk命令
awk '/error/ && !/ignored/' log.txt
# 不好: 使用cut处理列
cut -d: -f1 /etc/passwd
# 好: 使用shell内置功能
while IFS=: read -r user _; do
echo "$user"
done < /etc/passwd
6.2 使用内置字符串处理
Bash内置的字符串操作比调用外部命令快得多:
bash复制# 慢
echo "STRING" | tr '[:upper:]' '[:lower:]'
# 快
str="STRING"
echo "${str,,}" # 转为小写
echo "${str^^}" # 转为大写
6.3 并行处理技巧
利用GNU parallel或简单的后台任务加速处理:
bash复制# 使用xargs并行处理
find . -name "*.log" | xargs -P 4 -I {} gzip {}
# 简单的并行循环
for file in *.mp4; do
encode "$file" &
((count++))
if (( count % 4 == 0 )); then wait; fi
done
wait
在8核服务器上处理视频文件时,这种并行处理可以将总时间从几小时缩短到几分钟。
7. 跨平台兼容篇:10个编写可移植脚本的技巧
7.1 Shebang的兼容写法
不要简单地用#!/bin/bash,更健壮的写法是:
bash复制#!/usr/bin/env bash
这样会使用用户PATH中的第一个bash,而不是固定路径。
7.2 避免Linuxism
一些只在Linux上可用的特性:
bash复制# 避免
echo -e "hello\nworld"
# 兼容写法
printf "hello\nworld\n"
# 避免
sed -i 's/foo/bar/' file
# 兼容写法
tmp=$(mktemp)
sed 's/foo/bar/' file > "$tmp" && mv "$tmp" file
7.3 检测系统类型
编写跨平台脚本时需要检测系统:
bash复制case "$(uname -s)" in
Linux*) system=Linux;;
Darwin*) system=Mac;;
CYGWIN*) system=Cygwin;;
MINGW*) system=MinGw;;
*) system="Unknown"
esac
然后可以根据$system变量执行不同的代码分支。
8. 代码质量篇:10个编写可维护脚本的技巧
8.1 代码风格一致性
保持一致的代码风格:
bash复制# 函数定义风格
function myfunc1() {
...
}
myfunc2() {
...
}
# 变量命名
local_var="value"
GLOBAL_CONST="value"
# 缩进使用4个空格,不是制表符
8.2 添加文档头
每个脚本文件应该有这样的文档头:
bash复制#!/usr/bin/env bash
#
# 名称: backup_mysql.sh
# 描述: MySQL数据库备份脚本,包含验证和清理功能
# 作者: 你的名字
# 创建日期: 2023-08-20
# 版本: 1.0
#
# 用法: ./backup_mysql.sh
# 参数: 无
#
# 退出代码:
# 0 - 成功
# 1 - 参数错误
# 2 - 备份失败
8.3 使用shellcheck
安装shellcheck并定期检查脚本:
bash复制# 安装shellcheck
sudo apt install shellcheck # Debian/Ubuntu
sudo brew install shellcheck # Mac
# 使用
shellcheck myscript.sh
它能发现许多常见的语法错误和潜在问题。
9. 调试与排错篇:10个快速定位问题的技巧
9.1 调试模式运行
几种调试脚本的方式:
bash复制# 打印执行的每一行
bash -x script.sh
# 只打印特定部分的调试信息
set -x
要调试的代码
set +x
# 检查语法而不执行
bash -n script.sh
9.2 记录详细日志
完善的日志记录应该包括:
bash复制log() {
local level=$1 msg=$2
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $msg" >> "$LOG_FILE"
}
log INFO "脚本启动"
log ERROR "文件未找到: $file"
9.3 处理信号
正确处理信号可以让脚本更健壮:
bash复制trap 'cleanup; exit 1' INT TERM HUP
cleanup() {
echo "正在清理临时文件..."
rm -rf "$TEMP_DIR"
}
这样即使用户按Ctrl+C,也能正确清理资源。
10. 资源推荐篇:10个提升Shell技能的工具和资源
10.1 交互式学习工具
- Bash Academy:交互式学习Bash的网站
- ExplainShell:输入命令查看每个部分的解释
10.2 实用工具
- jq:处理JSON数据
- yq:处理YAML文件
- ripgrep:更快的grep替代品
10.3 参考书籍
- 《Linux命令行与Shell脚本编程大全》
- 《Bash Cookbook》
- 《Shell脚本学习指南》
这些资源在我学习Shell的过程中提供了巨大帮助,特别是《Bash Cookbook》中的实际案例,让我理解了许多复杂概念的实际应用场景。
