Shell 脚本入门:变量、条件判断与自动化任务一次讲清
对于刚接触 Linux 服务器的初学者来说,Shell 脚本是最值得先学会的自动化工具。它不需要安装额外的运行环境,不需要编译,只要把平时在终端里一条一条敲的命令写进一个文件,再给这个文件可执行权限,就能一遍遍重复执行。备份文件、清理日志、检查服务状态、批量改名,这些日常运维工作用 Shell 脚本都能写成几行到几十行的小程序。
本文面向零基础读者,讲清楚 Shell 脚本的四个基本功:怎么写第一个脚本、变量怎么用、条件判断怎么写、循环和函数怎么组织,最后给出一个可以落地的日志清理脚本示例。文中示例均以 Bash 为准,这是绝大多数 Linux 发行版的默认 Shell。
一、第一个脚本:从 Hello World 到可执行文件
新建一个文件 hello.sh,写入下面三行:
#!/bin/bash
# 这是注释,以 # 开头,不会被执行
echo "Hello, Shell!"
第一行 #!/bin/bash 叫 Shebang,它告诉系统用哪个解释器来执行这个脚本。没有它,系统会用默认 Shell 执行,大多数情况下也能跑,但显式写出来更可靠、可移植。
让脚本可执行并运行它:
chmod +x hello.sh
./hello.sh
注意 ./hello.sh 前面的 ./ 不能省。当前目录通常不在系统的可执行文件搜索路径 PATH 里,直接写 hello.sh 系统会找不到文件。运行后终端输出 Hello, Shell!,第一个脚本就完成了。
一个好习惯是:脚本开头除了 Shebang,再写两行注释说明用途和作者日期。三个月后回头看,注释能帮你立刻想起这个脚本是干什么的。
二、变量:赋值不加空格,引用要加引号
Shell 变量的赋值语法很简单,但有一个新手最容易踩的坑:等号两边不能有空格。
name="linux"
echo $name
echo "欢迎使用 ${name} 服务器"
name=“linux” 是正确的,name = “linux” 会被解析成"执行一个叫 name 的命令",从而报错。引用变量时建议用双引号包裹:“$name”。不加引号时,如果变量值里有空格,Shell 会按空格把它拆成多个词,命令的行为就变了。
${name} 这种带花括号的写法在拼接字符串时尤其重要。写 echo “${name}server” 能明确区分变量名和后面的文字,而写 echo “$nameserver” 时,Shell 会去找一个叫 nameserver 的变量,结果是空。
常用的系统内置变量也值得记住:$HOME 是当前用户的主目录,$PWD 是当前工作目录,$USER 是当前用户名,$? 是上一条命令的退出状态码,0 表示成功,非零表示失败。写脚本时用 $? 判断上一条命令是否成功,是很常见的写法。
只读变量可以用 readonly 声明,环境变量用 export 导出给子进程。例如 export PATH=“$PATH:/opt/mybin” 就是把一个目录追加到可执行文件搜索路径里。注意,脚本里 export 的变量只对该脚本及其子进程有效,脚本退出后就失效了,不会永久改变系统环境。
三、条件判断:if、test 与方括号
Shell 的条件判断围绕 if 语句展开,基本结构是:
if [ -f "/var/log/app.log" ]; then
echo "日志文件存在"
else
echo "日志文件不存在"
fi
这里的方括号 [ ] 实际上是一个命令(test 命令的简写),方括号两边必须留空格,[-f 或 ] 前面不留空格都会报错。常用的判断条件有:
- -f 文件:文件存在且是普通文件
- -d 目录:目录存在
- -z 字符串:字符串为空
- -n 字符串:字符串非空
- 字符串比较:= 相等,!= 不相等
- 数字比较:-eq 等于,-ne 不等于,-gt 大于,-lt 小于,-ge 大于等于,-le 小于等于
数字比较和字符串比较不能混用,这是第二个常见坑。判断端口号、文件数量这类数字要用 -eq、-gt;判断用户名、文件路径这类字符串要用 =、!=。用 = 去比两个数字在某些情况下也能"碰巧"工作,但语义不对,写脚本时应当分清。
多条件组合可以用 &&(并且)和 ||(或者),也可以用 elif 写多分支:
read -p "请输入一个数字: " num
if [ "$num" -gt 100 ]; then
echo "大于 100"
elif [ "$num" -gt 50 ]; then
echo "大于 50,小于等于 100"
else
echo "小于等于 50"
fi
read -p 会在终端提示用户输入并把输入存入变量。交互式脚本里常用它,但自动化脚本应当尽量避免交互,改用命令行参数。脚本的命令行参数用 $1、$2 表示第一个、第二个参数,$# 表示参数个数,$@ 表示全部参数。写脚本时先检查 $# 是否符合预期,参数不对就打印用法说明并退出,能省下很多调试时间。
四、循环与函数:把重复劳动交给机器
循环解决"同一件事做很多遍"的问题。for 循环遍历列表,while 循环在条件成立时一直执行:
# 遍历目录下的所有 .log 文件
for file in /var/log/*.log; do
echo "正在处理 $file"
done
# 计数循环
count=1
while [ "$count" -le 5 ]; do
echo "第 $count 次"
count=$((count + 1))
done
$((count + 1)) 是算术扩展的写法,Shell 里做整数运算就用它。注意 for file in /var/log/*.log 里如果目录下没有任何 .log 文件,通配符可能原样保留而不展开,处理重要文件前先判断文件是否存在更稳妥。
函数把一段逻辑封装成可复用的单元:
check_service() {
if systemctl is-active --quiet "$1"; then
echo "$1 运行正常"
else
echo "$1 未运行"
fi
}
check_service nginx
check_service mysql
函数定义用 函数名() { …; },调用时直接写函数名加参数,函数内部用 $1 取第一个参数。把检查、备份、清理这类逻辑写成函数,主流程就会变得非常清晰,也方便以后复用。
五、实战:一个日志清理脚本
把前面讲的知识点拼起来,可以写一个实用的日志清理脚本:删除指定目录下 7 天前、后缀为 .log 的文件,删除前先打印要删的文件,执行后汇报删除结果:
#!/bin/bash
# 用途:清理 /var/log/myapp 下 7 天前的日志文件
# 注意:请先把 LOG_DIR 改成你自己的目录再运行
LOG_DIR="/var/log/myapp"
DAYS=7
if [ ! -d "$LOG_DIR" ]; then
echo "目录不存在: $LOG_DIR"
exit 1
fi
echo "正在清理 $LOG_DIR 下 ${DAYS} 天前的 .log 文件..."
find "$LOG_DIR" -name "*.log" -type f -mtime +$DAYS -print -delete
if [ $? -eq 0 ]; then
echo "清理完成"
else
echo "清理过程中出现错误,请检查权限"
exit 1
fi
这个脚本有几个值得注意的设计。第一,开头先判断目录是否存在,不存在就报错退出,而不是让 find 在一个不存在的目录上静默失败。第二,find 的 -print -delete 组合会先打印再删除,日志里留下了删除记录,出问题可以追溯。第三,用 $? 判断 find 是否成功,失败时给出明确提示并以非零状态码退出,方便被其他自动化工具(如定时任务)感知到失败。
真正跑清理、删除类脚本之前,务必先在测试目录里试一遍,或者先把 -delete 换成只打印,确认要删的文件列表无误后再加回删除动作。删除操作不可逆,多一层确认永远不亏。
六、调试与定时:让脚本真正跑起来
脚本出问题时,在 Shebang 后加 -x 可以开启调试模式:把 #!/bin/bash 改成 #!/bin/bash -x,执行时 Shell 会把每一条命令连同展开后的参数都打印出来,变量值不对、条件判断走错分支,一眼就能看出来。也可以只对某一段开启调试:set -x 开启,set +x 关闭。
脚本写好后,可以用 cron 让它定时执行。执行 crontab -e,加上一行:
0 3 * * * /home/user/clean_logs.sh >> /var/log/clean_logs.log 2>&1
这表示每天凌晨 3 点执行一次,输出重定向到日志文件。cron 环境里的 PATH 比交互终端小,脚本里用到的命令建议写绝对路径,或者在脚本开头显式设置 PATH,否则可能出现"终端能跑、定时任务跑不了"的情况。
七、常见错误清单
最后总结新手最容易犯的几个错误,写脚本时可以逐项自查:赋值等号两边加了空格;方括号两边没留空格;变量引用没加引号导致空格拆词;数字比较用了 =、字符串比较用了 -eq;当前目录脚本没加 ./ 就执行;删除、覆盖类操作没先在测试目录验证。把这张清单贴在手边,能避开 Shell 入门阶段的大多数坑。
Shell 脚本的价值不在于语法多优雅,而在于把重复劳动变成一次写好、反复运行的工具。从今天的第一个 hello.sh 开始,遇到第二次要做的重复操作,就试着把它写成脚本。几次下来,你会发现服务器运维的大部分体力活,都可以交给几十行脚本安静地完成。
- 点赞
- 收藏
- 关注作者
评论(0)