在Linux学习过程中,Shell编程是提升效率、实现自动化运维的核心技能。Shell作为Linux内核与用户之间的"翻译官",不仅是执行单个命令的交互终端,更是一门简洁高效的脚本编程语言,能够将一系列Linux命令组合起来,实现批量处理、自动化部署、定时任务等复杂操作。无论是后端开发、运维工作,还是日常系统管理,掌握Shell编程都能大幅提升工作效率。本笔记将从Shell基础认知、脚本编写规范、核心语法、实战案例到调试排错,全面详解Shell编程,帮助快速入门并能独立编写实用脚本。
一、Shell编程基础认知(必懂核心)
在学习Shell编程前,首先要明确Shell的本质、常见解释器及脚本的核心作用,理清"Shell终端"与"Shell脚本"的区别,为后续学习奠定基础。
1. 什么是Shell
Shell是Linux系统中的命令解释器,它接收用户输入的命令,将其翻译为内核能理解的指令,执行后返回结果,是用户与Linux内核交互的桥梁。简单来说,我们平时在终端输入的ls、cd、mkdir等命令,都是通过Shell解释执行的。
而Shell编程,就是将一系列Linux命令按逻辑顺序编写到一个文本文件中,形成"Shell脚本",通过执行脚本,实现命令的批量、自动化执行,无需手动逐条输入命令。例如,批量备份文件、监控服务器状态、一键部署服务等场景,都可以通过Shell脚本来实现。
2. 常见Shell解释器
Linux系统中存在多种Shell解释器,不同解释器的语法略有差异,其中最常用的是Bash(Bourne Again Shell),它是绝大多数Linux发行版(Ubuntu、CentOS等)的默认解释器,兼容绝大多数Shell语法,也是我们本次学习的核心。
查看系统中所有可用的Shell解释器,可执行以下命令:
bash
cat /etc/shells
常见Shell解释器及说明:
-
/bin/bash:默认主流解释器,功能完善,兼容所有Shell命令,支持脚本编程,是本次学习的重点;
-
/bin/sh:简化版Shell,语法简单,功能有限,不支持部分高级特性(如数组、函数);
-
/bin/csh:C语言风格的Shell,语法与C语言相似,适用于习惯C语言的用户;
-
/bin/ksh:结合了bash和csh的优点,兼容性强,但使用频率低于bash。
查看当前系统默认Shell,执行命令:echo $SHELL,输出结果通常为/bin/bash。
3. Shell脚本的特点与适用场景
Shell脚本作为一门脚本语言,无需编译,直接由解释器执行,具有简洁、高效、易上手的特点,其核心适用场景的如下:
-
批量处理:批量修改文件名、批量备份文件、批量创建用户等;
-
自动化运维:服务器状态监控、日志轮转、服务自动启停、一键部署服务;
-
定时任务:结合crond守护进程,实现定时执行脚本(如定时备份、定时清理日志);
-
简单工具开发:编写简单的交互工具,如用户信息收集、密码验证等。
注意:Shell脚本适合处理简单的自动化任务,复杂的逻辑(如大量数据计算、复杂算法)更适合用Python、Java等编程语言实现。
二、Shell脚本编写规范(入门必备)
编写Shell脚本有固定的规范,遵循规范不仅能让脚本更易读、易维护,还能避免语法错误,这是新手入门的关键。一个标准的Shell脚本,通常包含3个核心要素:解释器声明、注释、可执行语句。
1. 脚本命名规范
-
文件名简洁明了,体现脚本功能(如log_backup.sh表示日志备份脚本,disk_monitor.sh表示磁盘监控脚本);
-
避免使用特殊字符(如空格、@、#)和中文,防止执行时出现异常。
2. 脚本开头(解释器声明)
脚本第一行必须声明解释器,告诉系统使用哪个Shell解释器来执行脚本,格式固定为:
bash
#!/bin/bash
其中,#! 称为"谢邦线"(Shebang),后面紧跟解释器的绝对路径(/bin/bash是bash解释器的默认路径)。如果不声明解释器,系统会使用默认Shell执行脚本,可能导致语法兼容问题。
3. 注释规范
注释用于说明脚本的功能、作者、编写时间、关键代码的作用,便于自己和他人阅读维护,Shell脚本中注释分为两种:
-
单行注释:以
#开头,后面紧跟注释内容,从#开始到行尾均为注释,不会被执行; -
多行注释:没有专门的多行注释语法,可使用
:<<EOF ... EOF实现(EOF可替换为任意自定义字符,前后需一致)。
示例:
bash
#!/bin/bash
# 作者:XXX
# 编写时间:2026-05-13
# 脚本功能:批量备份/var/log目录下的日志文件
# 单行注释:以下命令用于创建备份目录
# 多行注释示例
:<<EOF
这是多行注释
用于说明脚本的详细功能、执行步骤
以及注意事项等
EOF
4. 脚本执行方式
编写完Shell脚本后,需要赋予脚本执行权限,才能运行。常用的执行方式有3种,结合实例详细说明(以test.sh脚本为例):
bash
# 1. 赋予执行权限后,直接运行(推荐)
# 赋予执行权限(chmod +x 脚本路径)
chmod +x test.sh
# 运行脚本(当前目录下,需加./)
./test.sh
# 绝对路径运行(无论当前在哪个目录,均可执行)
/root/test.sh
# 2. 指定解释器运行(无需赋予执行权限)
bash test.sh
sh test.sh # sh是简化版解释器,可能不支持部分高级语法
# 3. source命令运行(无需赋予执行权限,不新开子进程)
source test.sh
. test.sh # 与source等价,注意前面的点和空格
关键区别:前两种执行方式会新开一个子进程执行脚本,脚本中修改的环境变量(如export PATH)不会影响当前终端;source命令执行脚本不会新开子进程,脚本中修改的环境变量会生效于当前终端。
三、Shell编程核心语法(重点掌握)
Shell脚本的核心语法包括变量、输入输出、条件判断、循环、函数等,这些是编写脚本的基础,结合实例讲解,更易上手,重点掌握语法格式和使用场景。
1. 变量(存储数据的容器)
Shell脚本中的变量无需声明类型,直接赋值即可使用,核心用于存储数据(如路径、数值、用户输入等),分为自定义变量、环境变量、位置参数变量三类。
(1)自定义变量(最常用)
语法规则:变量名=值(注意:等号两侧不能有空格 ,否则会报错),使用变量时,在变量名前加 $,复杂场景可加 ${变量名} 明确变量边界,避免歧义。
示例:
bash
#!/bin/bash
# 定义自定义变量(字符串变量,含空格需加引号)
name="Shell编程"
# 数字变量(无需加引号)
age=25
# 路径变量
log_path="/var/log"
# 使用变量
echo "学习内容:$name"
echo "当前年龄:${age}岁" # 大括号明确变量边界,避免与后面的"岁"混淆
echo "日志路径:$log_path"
# 变量重新赋值
age=26
echo "修改后的年龄:$age"
# 变量拼接(字符串与变量、变量与变量拼接)
echo "学习${name}的第${age}天"
注意:变量名命名规则:只能包含字母、数字、下划线,不能以数字开头,不能使用Shell关键字(如if、for、while)。
(2)环境变量(系统预定义变量)
环境变量是系统提前定义好的变量,用于存储系统运行的相关信息,可直接在脚本中使用,常用环境变量如下:
-
$HOME:当前用户的家目录(如root用户的家目录是/root); -
$PWD:当前工作目录; -
$PATH:系统命令搜索路径,新增命令路径需修改此变量; -
$USER:当前登录用户; -
$?:上一条命令的执行状态(0表示执行成功,非0表示执行失败,常用于判断命令是否执行成功); -
$HOSTNAME:当前服务器主机名。
示例:
bash
#!/bin/bash
echo "当前用户:$USER"
echo "用户家目录:$HOME"
echo "当前工作目录:$PWD"
echo "系统命令路径:$PATH"
# 执行一条命令,查看其执行状态
ls /root
echo "上一条命令执行状态:$?" # 输出0表示执行成功
(3)位置参数变量(接收脚本外部参数)
当运行脚本时,需要向脚本传递外部参数(如脚本名 参数1 参数2),可通过位置参数变量接收,常用变量如下:
-
$0:脚本本身的文件名; -
$1~$9:第1个到第9个外部参数(超过9个参数,需用{10}、{11}表示); -
$*:所有外部参数的集合(将所有参数视为一个整体); -
$@:所有外部参数的集合(将每个参数视为独立个体); -
$#:外部参数的个数。
示例(编写calc.sh脚本,接收两个参数,计算两数之和):
bash
#!/bin/bash
# 接收两个外部参数,计算两数之和
a=$1
b=$2
# 整数运算(Shell中整数运算需用$((运算式))或let)
sum=$((a + b))
echo "脚本名:$0"
echo "参数个数:$#"
echo "所有参数:$*"
echo "参数1:$1,参数2:$2"
echo "$1 + $2 = $sum"
运行脚本并传递参数:./calc.sh 10 20,输出结果如下:
bash
脚本名:./calc.sh
参数个数:2
所有参数:10 20
参数1:10,参数2:20
10 + 20 = 30
2. 输入输出(与用户交互、输出结果)
Shell脚本中常用的输入输出命令有echo(输出)、read(输入)、printf(格式化输出),用于实现与用户交互、输出脚本执行结果。
(1)echo(最常用输出命令)
功能:将内容输出到终端,默认换行,常用选项:
-
-n:不换行输出; -
-e:支持转义字符(如\n换行、\t制表符)。
示例:
bash
#!/bin/bash
# 普通输出
echo "Hello Shell"
# 不换行输出
echo -n "请输入你的名字:"
# 支持转义字符
echo -e "\n姓名:张三\t年龄:25"
echo -e "日志路径:\t/var/log"
# 输出变量内容
name="Alice"
echo -e "你好,$name!\n欢迎学习Shell编程"
2)read(接收用户输入)
功能:从终端接收用户输入,将输入内容赋值给变量,常用选项:
-
-p:显示提示信息(无需单独用echo输出提示); -
-t:设置输入超时时间(单位:秒,超时后脚本继续执行); -
-s:隐藏输入内容(常用于输入密码)。
示例:
bash
#!/bin/bash
# 基本用法:接收用户输入,赋值给name变量
read -p "请输入你的名字:" name
echo "你好,$name!"
# 设置超时时间(5秒内未输入,脚本继续执行)
read -t 5 -p "请输入你的年龄(5秒内):" age
if [ -z "$age" ]; then # 判断age是否为空
echo -e "\n未输入年龄,默认年龄20"
age=20
else
echo -e "\n你的年龄是:$age"
fi
# 隐藏输入(输入密码时常用)
read -s -p "请输入密码:" pwd
echo -e "\n密码已接收(出于安全,不显示密码)"
(3)printf(格式化输出)
功能:与echo类似,用于输出内容,但格式更严谨,支持格式化占位符,默认不换行,需手动添加\n换行。常用占位符:
-
%s:输出字符串; -
%d:输出整数; -
%f:输出浮点数; -
%c:输出单个字符。
示例:
bash
#!/bin/bash
name="张三"
age=25
score=98.5
# 格式化输出
printf "姓名:%s,年龄:%d,成绩:%.1f\n" $name $age $score
# 输出结果:姓名:张三,年龄:25,成绩:98.5
3. 条件判断(分支逻辑)
条件判断是Shell脚本的核心逻辑,用于根据不同的条件执行不同的命令,常用的判断方式有两种:[ 条件表达式 ](注意括号两侧必须有空格)和test 条件表达式,两种方式等价。
条件判断的核心场景:数字比较、字符串比较、文件判断,结合if语句实现分支逻辑。
(1)常用条件判断符号
按场景分类,整理常用判断符号,便于记忆和使用:
-
数字比较(用于比较两个整数,不能比较浮点数):
-
-eq:等于(equal),如 [ a -eq b ] 表示a等于b; -
-ne:不等于(not equal); -
-gt:大于(greater than); -
-lt:小于(less than); -
-ge:大于等于(greater or equal); -
-le:小于等于(less or equal)。
-
-
字符串比较:
-
=:等于,如 [ "str1" = "str2" ](字符串需加引号,避免空格干扰); -
!=:不等于; -
-z:判断字符串是否为空(zero),如 [ -z "$str" ]; -
-n:判断字符串是否非空(not zero)。
-
-
文件判断(最常用,用于判断文件/目录的状态):
-
-f:判断是否为普通文件(file),且文件存在; -
-d:判断是否为目录(directory),且目录存在; -
-e:判断文件/目录是否存在(exist); -
-r:判断文件是否有读权限(read); -
-w:判断文件是否有写权限(write); -
-x:判断文件是否有执行权限(execute)。
-
(2)if语句(分支逻辑实现)
Shell脚本中if语句的语法格式,分为单分支、双分支、多分支三种,注意:if语句必须以fi结尾(反向拼写),避免语法错误。
示例1:单分支(满足条件执行,不满足不执行)
bash
#!/bin/bash
# 判断文件是否存在,存在则输出提示
file="/var/log/messages"
if [ -f "$file" ]; then
echo "$file 文件存在"
fi
示例2:双分支(if-else,满足条件执行一个分支,不满足执行另一个)
bash
#!/bin/bash
# 接收用户输入的分数,判断是否及格
read -p "请输入你的分数:" score
if [ $score -ge 60 ]; then
echo "恭喜!及格了"
else
echo "不及格,要加油哦"
fi
示例3:多分支(if-elif-else,多个条件判断,匹配第一个满足的分支)
bash
#!/bin/bash
# 接收用户输入的分数,进行成绩评级
read -p "请输入你的分数:" score
if [ $score -ge 90 ]; then
echo "优秀"
elif [ $score -ge 80 ]; then
echo "良好"
elif [ $score -ge 70 ]; then
echo "中等"
elif [ $score -ge 60 ]; then
echo "及格"
else
echo "不及格"
fi
4. 循环语句(重复执行命令)
Shell脚本中常用的循环语句有for循环和while循环,for循环适合固定次数的循环(如遍历列表、文件),while循环适合按条件重复执行(如无限循环、等待用户输入)。
(1)for循环(常用场景:遍历、固定次数循环)
语法格式1:遍历列表(最常用)
bash
for 变量名 in 列表
do
循环执行的命令
done
示例1:遍历列表,输出所有元素
bash
#!/bin/bash
# 遍历水果列表
fruits="苹果 香蕉 橙子 葡萄"
for fruit in $fruits
do
echo "水果:$fruit"
done
示例2:遍历目录下的所有.txt文件,批量修改文件名(将.txt改为.log)
bash
#!/bin/bash
# 批量修改文件名:将当前目录下的.txt文件改为.log
for file in $(ls *.txt)
do
# 截取文件名(去掉.txt后缀)
filename=${file%.txt}
# 重命名
mv $file ${filename}.log
done
echo "批量重命名完成!"
语法格式2:固定次数循环(类似其他语言的for i in range)
bash
for ((变量=初始值; 条件; 变量变化))
do
循环执行的命令
done
示例:循环5次,输出1-5的数字
bash
#!/bin/bash
# 循环5次,输出1-5
for ((i=1; i<=5; i++))
do
echo "第 $i 次循环"
done
(2)while循环(常用场景:条件循环、无限循环)
语法格式:
bash
while [ 条件表达式 ]
do
循环执行的命令
done
示例1:条件循环(输入密码,直到输入正确为止)
bash
#!/bin/bash
# 猜密码游戏,直到输入正确退出
password="123456"
guess=""
while [ "$guess" != "$password" ]
do
read -p "请输入密码:" guess
if [ "$guess" != "$password" ]; then
echo "密码错误,请重试"
fi
done
echo "密码正确!"
示例2:无限循环(结合break退出循环)
bash
#!/bin/bash
# 无限循环,输入exit退出
while true
do
read -p "请输入内容(输入exit退出):" input
if [ "$input" == "exit" ]; then
echo "退出循环"
break # 退出循环
fi
echo "你输入的是:$input"
done
5. 函数(封装可复用代码)
当脚本中需要多次执行同一组命令时,可将这组命令封装为函数,实现代码复用,简化脚本结构,便于维护。Shell函数无需声明返回值,默认返回上一条命令的执行状态(0表示成功)。
语法格式:
bash
函数名() {
函数体(需要执行的命令)
# 可选:return 返回值(返回值范围0-255)
return 0
}
示例:封装一个"备份文件"的函数,可重复调用
bash
#!/bin/bash
# 封装备份函数
backup_file() {
# 接收一个参数(需要备份的文件路径)
local file=$1 # local关键字:定义局部变量,仅在函数内生效
# 判断文件是否存在
if [ ! -f "$file" ]; then
echo "错误:$file 文件不存在,备份失败"
return 1 # 返回非0,表示函数执行失败
fi
# 备份文件(备份到当前目录,后缀加.bak)
cp $file ${file}.bak
if [ $? -eq 0 ]; then
echo "$file 备份成功,备份文件:${file}.bak"
return 0 # 返回0,表示函数执行成功
else
echo "$file 备份失败"
return 1
fi
}
# 调用函数(传递参数)
backup_file /var/log/messages
backup_file /root/test.txt
四、Shell编程实战案例(实操落地)
结合前面的核心语法,编写3个高频实用的Shell脚本案例,覆盖日常运维场景,可直接复制到Linux中执行,巩固所学知识,提升实操能力。
案例1:日志批量备份脚本
功能:备份/var/log目录下所有.log日志文件,按日期命名备份包,自动创建备份目录,判断备份是否成功。
bash
#!/bin/bash
# 日志批量备份脚本
# 作者:XXX
# 功能:备份/var/log下的.log日志,按日期命名,自动创建备份目录
# 定义变量
backup_dir="/backup/logs" # 备份目录
log_dir="/var/log" # 日志目录
date=$(date +%Y%m%d) # 获取当前日期(格式:20260513)
backup_file="log_backup_$date.tar.gz" # 备份文件名
# 1. 创建备份目录(不存在则创建,-p递归创建多级目录)
if [ ! -d "$backup_dir" ]; then
mkdir -p $backup_dir
echo "备份目录 $backup_dir 不存在,已自动创建"
fi
# 2. 压缩备份日志文件(tar -zcvf 备份包路径 需备份的文件)
tar -zcvf $backup_dir/$backup_file $log_dir/*.log > /dev/null # 屏蔽压缩输出
# 3. 判断备份是否成功
if [ $? -eq 0 ]; then
echo "日志备份成功!"
echo "备份文件路径:$backup_dir/$backup_file"
else
echo "日志备份失败!"
fi
案例2:服务器磁盘空间监控脚本
功能:监控服务器所有挂载点的磁盘使用率,当使用率超过80%时,输出告警信息(实际场景可替换为邮件/短信告警)。
bash
#!/bin/bash
# 服务器磁盘空间监控脚本
# 功能:监控磁盘使用率,超过80%输出告警信息
threshold=80 # 磁盘使用率阈值(80%)
# 遍历所有挂载点(排除tmpfs、loop等虚拟文件系统)
df -h | grep -vE 'tmpfs|loop|udev' | awk '{print $5, $6}' | while read usage mount; do
# 提取使用率数字(去掉%号)
usage_num=${usage%\%}
# 判断使用率是否超过阈值
if [ $usage_num -gt $threshold ]; then
echo "⚠️ 告警:$mount 磁盘使用率已达 $usage,超过阈值 $threshold%!"
fi
done
echo "✅ 磁盘监控完成,未超过阈值的挂载点运行正常"
案例3:一键部署Nginx服务脚本
功能:自动安装Nginx服务,启动服务并设置开机自启,查看服务运行状态和监听端口,适合CentOS系统。
bash
#!/bin/bash
# Nginx一键部署脚本(CentOS系统)
# 功能:安装、启动、设置开机自启Nginx,查看运行状态
# 1. 安装Nginx(CentOS使用yum安装)
echo "开始安装Nginx服务..."
yum install nginx -y > /dev/null
# 2. 判断安装是否成功
if [ $? -ne 0 ]; then
echo "❌ Nginx安装失败,请检查网络或yum源"
exit 1
fi
# 3. 启动Nginx服务并设置开机自启
systemctl start nginx
systemctl enable nginx --now
# 4. 查看Nginx运行状态
echo -e "\nNginx服务运行状态:"
systemctl status nginx --no-pager
# 5. 查看Nginx监听端口(默认80端口)
echo -e "\nNginx监听端口:"
ss -tulnp | grep nginx
echo -e "\n✅ Nginx一键部署完成,可通过服务器IP访问Nginx默认页面"
五、Shell脚本调试与避坑指南(新手必备)
新手编写Shell脚本时,容易出现语法错误、逻辑错误,掌握调试方法和避坑技巧,能大幅提升脚本编写效率,减少报错。
1. 脚本调试方法
常用的调试方法有3种,根据报错情况选择合适的方式:
-
直接运行脚本,查看报错信息:运行脚本后,终端会输出报错行和错误原因,根据提示排查(如"syntax error: unexpected end of file"表示脚本缺少fi、done等关键字);
-
使用bash -x 调试(推荐) :执行
bash -x 脚本名.sh,会输出脚本执行的每一步命令和变量值,清晰看到脚本的执行流程,便于定位逻辑错误; -
在关键位置添加echo输出:在脚本的关键步骤(如变量赋值、条件判断后)添加echo命令,输出变量值或执行状态,判断脚本是否按预期执行。
示例(调试test.sh脚本):
bash
bash -x test.sh
2. 新手常见坑及解决方案
-
坑1:变量赋值时,等号两侧有空格 错误:
name = "Shell"(等号两侧有空格) 正确:name="Shell"(无空格) 解决方案:牢记变量赋值"等号无空格"原则。 -
坑2:条件判断时,括号两侧无空格 错误:
[ $a -eq $b ]写成[$a -eq $b](括号两侧无空格) 正确:[ $a -eq $b ](括号两侧必须有空格) 解决方案:编写条件判断时,先写[ ,再写条件,最后写 ],养成加空格的习惯。 -
坑3:字符串比较时,未加引号 错误:
[ $str1 = $str2 ](字符串含空格时会报错) 正确:[ "$str1" = "$str2" ](字符串加双引号,避免空格干扰) -
坑4:忘记添加fi、done等关键字 错误:if语句只写if,未写fi;forwhile循环只写do,未写done 解决方案:编写分支或循环语句时,先写框架(如if...fi、for...done),再填充内部命令。
-
坑5:路径使用相对路径,导致脚本在不同目录执行失败 错误:使用
cp test.txt backup/(相对路径,切换目录后执行失败) 正确:使用绝对路径cp /root/test.txt /root/backup/解决方案:脚本中尽量使用绝对路径,避免相对路径带来的异常。
六、总结与拓展
本文详解了Linux Shell编程的核心知识,从基础认知、脚本规范、核心语法,到实战案例和调试避坑,覆盖了新手入门所需的全部内容,核心要点总结如下:
-
Shell是Linux命令解释器,Shell编程是将Linux命令组合成脚本,实现自动化、批量执行,核心解释器是bash;
-
标准Shell脚本必须包含解释器声明(#!/bin/bash)、注释和可执行语句,执行前需赋予执行权限;
-
核心语法重点掌握:变量(自定义、环境、位置参数)、输入输出(echo、read)、条件判断(if语句)、循环(for、while)、函数;
-
实战案例是巩固语法的关键,重点掌握日志备份、磁盘监控、服务部署等高频场景脚本;
-
新手避坑重点:变量赋值无空格、条件判断加空格、字符串比较加引号、牢记fi/done关键字。