兄弟们,我是你们的老朋友运维小哥。今天咱们聊点硬核的——Shell脚本编写。
说实话,我刚入行那会儿,看着那些老员工噼里啪啦敲几行命令就能搞定一堆重复工作,心里那个羡慕啊。后来自己摸索着学,踩了不少坑,也总结了不少经验。今天这篇文章,我就把这些年写Shell脚本的心得体会,毫无保留地分享给你们。
不管你是刚入门的新手,还是有一定经验想进阶的老司机,这篇文章都值得你收藏反复看。咱们不整那些虚的,直接上干货!
一、Shell脚本到底是什么?为什么要学?
1.1 什么是Shell脚本?
简单来说,Shell脚本就是一个文本文件,里面写了一堆Linux命令,然后给这个文件加上执行权限,它就能自动帮你把这些命令挨个执行一遍。
打个比方,Shell脚本就像是一份"做菜菜谱":
• 菜谱上写着:先切菜、再热油、然后翻炒、最后出锅
• Shell脚本写着:先创建目录、再复制文件、然后修改权限、最后启动服务
有了这份"菜谱",你不需要每次都手动输入命令,直接运行脚本就完事了。
1.2 为什么要写Shell脚本?
我给你们算笔账。假设你每天要花30分钟做这几件事:
• 查看服务器磁盘使用情况
• 检查几个关键服务有没有正常运行
• 备份数据库
• 清理日志文件
一个月下来就是15个小时,一年就是180个小时!相当于整整22.5个工作日!
但如果你花2小时写个脚本,让它每天自动运行,一年下来能省多少时间?这就是自动化的魅力。
而且,脚本的好处还不止这些:
• 减少出错:人总会手滑打错命令,脚本不会
• 方便复用:写好的脚本可以给团队其他人用
• 有据可查:脚本就是文档,别人一看就知道做了什么
• 提升逼格:会写脚本的运维,工资都能多要几千
二、Shell脚本基础入门
2.1 第一个脚本:Hello World
咱们从最简单的开始。打开终端,创建一个文件:
vim hello.sh
输入以下内容:
#!/bin/bash
# 这是我的第一个Shell脚本
echo "Hello World!"
echo "欢迎来到Shell脚本的世界!"
保存退出后,给脚本加上执行权限:
chmod +x hello.sh
然后运行它:
./hello.sh
看到输出结果了吗?恭喜你,你已经写出了人生中第一个Shell脚本!
这里有几个关键点要注意:
• Shebang(#!):第一行的#!/bin/bash告诉系统,这个脚本要用bash解释器来执行
• 注释:以#开头的行都是注释,不会被执行
• echo命令:用来输出内容到屏幕
2.2 脚本的执行方式
执行脚本有好几种方法:
方法一:给执行权限后运行(推荐)
chmod +x script.sh
./script.sh
方法二:直接用bash解释器执行
bash script.sh
方法三:source执行(在当前shell环境运行)
source script.sh
方法一和方法二会开启一个子shell来执行脚本,脚本里的变量不会影响当前shell。方法三在当前shell执行,脚本里定义的变量在当前shell也能用。
三、变量:Shell脚本的数据容器
3.1 变量的定义和使用
Shell脚本里的变量用起来很简单:
NAME="运维小哥"
AGE=25
注意!等号两边不能有空格!这是新手最容易犯的错误。
使用变量:
echo $NAME
echo ${NAME}
推荐用${NAME}这种带花括号的写法,避免变量名和后续字符混淆。
3.2 变量的类型
1. 本地变量
NAME="运维小哥" # 只在当前shell有效
2. 环境变量
export PATH="/usr/local/bin:$PATH"
3. 位置参数变量
• $0 - 脚本名
• $1 - 第一个参数
• $@ - 所有参数
• $# - 参数个数
4. 特殊变量
• $? - 上一个命令的退出状态,0表示成功
• $$ - 当前进程的PID
3.3 变量的高级操作
获取字符串长度:
echo ${#NAME}
字符串截取:
FILE="example.tar.gz"
echo ${FILE%.gz} # 去掉后缀:example.tar
echo ${FILE##*.} # 取后缀:gz
字符串替换:
TEXT="hello world"
echo ${TEXT/hello/hi} # 替换第一个
echo ${TEXT//o/0} # 替换所有
默认值设置:
echo ${NAME:-默认值} # NAME为空就用默认值
echo ${NAME:=默认值} # NAME为空就赋值
四、条件判断:让脚本有脑子
4.1 test命令和[ ]
Shell脚本里的条件判断主要靠test命令或者它的简写形式[ ]。
判断文件:
if [ -e file.txt ]; then
echo "文件存在"
fi
常用文件判断选项:
• -e - 文件是否存在
• -f - 是否是普通文件
• -d - 是否是目录
• -r - 是否可读
• -w - 是否可写
• -x - 是否可执行
判断数值:
if [ $NUM -eq 10 ]; then
echo "等于10"
fi
数值比较选项:
• -eq - 等于
• -ne - 不等于
• -gt - 大于
• -ge - 大于等于
• -lt - 小于
• -le - 小于等于
4.2 [[ ]]:更强大的条件判断
bash还提供了[[ ]],比[ ]功能更强大,推荐在bash脚本中使用。
# 支持正则表达式
if [[ "$FILE" =~ \.txt$ ]]; then
echo "这是txt文件"
fi
4.3 if语句的完整结构
if 条件1; then
# 条件1成立执行这里
elif 条件2; then
# 条件2成立执行这里
else
# 都不成立执行这里
fi
4.4 case语句:多分支选择
case $变量 in
模式1)
命令1
;;
*)
默认命令
;;
esac
五、循环:批量处理的利器
5.1 for循环
for循环是Shell脚本里用得最多的循环:
# 遍历列表
for name in 张三 李四 王五; do
echo "姓名:$name"
done
# 遍历文件
for file in *.txt; do
echo "处理文件:$file"
done
# C语言风格
for ((i=1; i<=10; i++)); do
echo "第$i次循环"
done
5.2 while循环
while循环在条件为真时一直执行:
while 条件; do
命令
done
实际例子:逐行读取文件
while read line; do
echo "读取到:$line"
done < data.txt
5.3 循环控制:break和continue
• break - 立即退出整个循环
• continue - 跳过本次循环,继续下一次
六、函数:代码复用的神器
6.1 函数的定义和调用
Shell函数有两种定义方式:
# 方式一
函数名() {
命令
}
# 方式二
function 函数名 {
命令
}
调用函数直接写函数名就行:
say_hello() {
echo "你好,$1!"
}
say_hello "运维小哥"
6.2 函数的参数和返回值
函数接收参数和脚本一样,用$1、$2等:
add() {
local a=$1
local b=$2
echo $((a + b))
}
result=$(add 10 20)
echo "10 + 20 = $result"
注意local关键字:它定义的变量只在函数内部有效,不会污染全局命名空间。
七、实战案例:拿来即用的脚本
7.1 服务器资源监控脚本
#!/bin/bash
# monitor.sh - 服务器资源监控
LOG_FILE="/var/log/server_monitor.log"
ALERT_CPU=80
ALERT_MEM=80
ALERT_DISK=85
get_cpu_usage() {
top -bn1 | grep "Cpu(s)" | awk '{print 100 - $8}'
}
get_disk_usage() {
df / | tail -1 | awk '{print $5}' | tr -d '%'
}
echo "[$(date)] CPU: $(get_cpu_usage)%, DISK: $(get_disk_usage)%" >> $LOG_FILE
7.2 MySQL数据库备份脚本
#!/bin/bash
# mysql_backup.sh - MySQL数据库备份
DB_USER="backup_user"
DB_PASS="your_password"
BACKUP_DIR="/backup/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
# 创建备份目录
mkdir -p $BACKUP_DIR
# 备份每个数据库
for db in $(mysql -u$DB_USER -p$DB_PASS -e "SHOW DATABASES;" | tail -n +2); do
mysqldump -u$DB_USER -p$DB_PASS $db | gzip > "$BACKUP_DIR/${db}_${DATE}.sql.gz"
done
echo "备份完成!"
7.3 服务健康检查与自动重启
#!/bin/bash
# service_guard.sh - 服务守护脚本
SERVICES=("nginx" "mysql" "redis")
check_and_restart() {
local service=$1
if ! pgrep -x "$service" > /dev/null; then
echo "[$service] 未运行,正在启动..."
systemctl start $service
fi
}
for service in "${SERVICES[@]}"; do
check_and_restart $service
done
7.4 系统初始化配置脚本
#!/bin/bash
# init_server.sh - 新服务器初始化配置
# 设置时区
timedatectl set-timezone Asia/Shanghai
# 安装常用工具
yum install -y vim wget curl net-tools htop
# 关闭SELinux
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
echo "初始化完成!"
7.5 日志切割脚本
#!/bin/bash
# log_rotate.sh - Nginx日志按天切割
LOG_DIR="/var/log/nginx"
BACKUP_DIR="/backup/nginx_logs"
DATE=$(date +%Y%m%d)
mv "$LOG_DIR/access.log" "$BACKUP_DIR/access.log.$DATE"
gzip "$BACKUP_DIR/access.log.$DATE"
kill -USR1 $(cat /var/run/nginx.pid)
echo "[$DATE] 日志已切割"
八、调试技巧与最佳实践
8.1 脚本调试方法
使用set -x开启调试模式:
#!/bin/bash
set -x # 开启调试
echo "开始执行"
set +x # 关闭调试
检查退出状态码:
command
if [ $? -ne 0 ]; then
echo "命令执行失败"
exit 1
fi
8.2 编写健壮脚本的最佳实践
使用严格模式:
#!/bin/bash
set -euo pipefail
# -e: 命令失败立即退出
# -u: 使用未定义变量时报错
# -o pipefail: 管道中任一命令失败就退出
变量使用双引号:
# 不好的写法
echo $NAME
# 好的写法
echo "$NAME"
定义函数时使用local:
my_func() {
local var="局部变量" # 只在函数内有效
}
8.3 脚本安全注意事项
不要硬编码密码:
# 不要这样做!
DB_PASS="123456"
# 推荐做法
DB_PASS="${DB_PASSWORD:-$(cat /etc/app/db_pass)}"
验证用户输入:
read -p "请输入端口号: " PORT
if ! [[ "$PORT" =~ ^[0-9]+$ ]]; then
echo "错误:端口号必须是数字"
exit 1
fi
九、总结与进阶建议
看到这里,你已经掌握了Shell脚本编写的核心技能。让我总结一下重点:
核心知识点回顾:
• 基础语法:Shebang、注释、变量定义
• 条件判断:[ ]、[[ ]]、if语句、case语句
• 循环结构:for、while、until
• 函数定义:参数传递、返回值、local变量
• 实用技巧:字符串操作、数组、调试方法
进阶学习路线:
• 阶段1:多写多练,掌握sed、awk等文本处理工具
• 阶段2:学习Expect实现交互式自动化,掌握正则表达式
• 阶段3:学习Python,了解Ansible、SaltStack等自动化运维工具
推荐学习资源:
• 《Linux命令行与Shell脚本编程大全》——经典教材
• ShellCheck工具——在线检查脚本语法
• explainshell.com——解释命令参数含义
写Shell脚本这件事,说难也不难,说简单也不简单。关键是多写、多练、多踩坑。当你真正掌握了这门技能,你会发现它能帮你节省大量时间,让你从重复劳动中解放出来。
希望这篇文章能帮你入门Shell脚本编写。如果你觉得有用,记得点赞、收藏、转发三连!有问题欢迎在评论区留言,我会尽量回复。
咱们下期再见!