Shell 脚本基础:从语法到实战全解析
Shell 脚本基础:从语法到实战全解析
Shell 脚本是 Linux 运维中实现自动化任务的核心工具,能够将复杂的命令序列、逻辑判断与变量管理整合为可重复执行的文本文件。本文从基础概念、语法规则到实战案例,系统讲解 Shell 脚本的编写与应用。
一、Shell 脚本基础
1. Shell 编程介绍与特性
Shell 既是命令解释器(接收用户输入的命令并执行),也是编程语言(支持变量、逻辑控制、函数等特性)。其核心作用是自动化完成 Linux 系统中的重复性、复杂性任务(如系统初始化、日志分析、服务部署等)。
Shell 语言的核心特点
特点 | 说明 |
---|---|
解释性语言 | 无需编译,直接由 Shell 进程逐行解释执行,开发效率高(即编即用),但运行速度略慢。 |
基于字符串处理 | 不支持复杂数据结构(如数组、对象),仅专注于字符串操作,输出也以字符形式呈现。 |
命令级语言 | 脚本由 Linux 命令(如 ls 、grep 、echo )和语法结构组成,命令组合灵活,功能强大。 |
版本兼容性 | 不同 Shell 版本(如 Bash、Sh、Zsh)语法略有差异,Linux 中默认使用 Bash(/bin/bash )。 |
2. 变量:Shell 中的数据存储
变量是 Shell 中存储数据的“容器”,本质是一块内存空间的别名,用于在脚本中重复使用数据。
(1)变量命名规则
- 由字母、数字、下划线组成,且必须以字母或下划线开头(不能以数字开头);
- 区分大小写(
name
和Name
是两个不同变量); - 建议使用“有意义的英文单词”(如
age
、username
),避免无意义字符(如a1
、x
),提高可读性。
(2)变量类型
Shell 变量无需显式声明类型,根据赋值内容自动识别,常见类型如下:
类型 | 定义方式与示例 | 注意事项 |
---|---|---|
字符串 | 单引号(' ' )、双引号(" " )或无引号(无空格时)。示例: msg='hello' msg="hello $name" msg=hello | 1. 字符串含空格时必须加引号(如 msg="hello world" );2. 单引号(强引用):内部特殊字符(如 $ 、# )无意义;3. 双引号(弱引用):内部特殊字符(如 $ )保留意义。 |
整数 | 直接赋值数字,无需加引号。 示例: age=20 count=100 | Shell 原生不支持浮点型,需通过 bc 等工具处理小数。 |
布尔型 | 用 true (1)或 false (0)表示逻辑真假。示例: is_ok=true is_error=false | 本质是字符串,需通过逻辑运算判断(如 if $is_ok; then ... )。 |
(3)变量引用与示例
引用变量时需在变量名前加 $
,推荐用 ${变量名}
格式(避免与其他字符混淆):
# 定义变量
[root@ansible ~]# vim name.sh
name="zhang3"
age=21# 引用变量
echo "姓名:$name" # 输出:姓名:zhang3
echo "年龄:${age}岁" # 输出:年龄:21岁(${} 避免与“岁”混淆)
echo '姓名:${name}' # 输出:姓名:${name}(单引号内变量无意义)执行脚本文件:
[root@ansible ~]# bash name.sh
姓名:zhang3
年龄: 21岁
姓名:${name}
3. 脚本执行方式
Shell 脚本(通常以 .sh
为后缀)的执行需满足“语法正确”,执行方式分 5 种,核心差异在于是否需要执行权限:
执行方式 | 语法格式 | 是否需要执行权限 | 说明 |
---|---|---|---|
绝对路径执行 | /root/scripts/a.sh | 是(chmod +x a.sh ) | 需指定脚本的完整路径(从 / 开始)。 |
相对路径执行 | ./a.sh | 是 | 需在脚本所在目录执行,./ 表示“当前目录”。 |
sh/bash 命令执行 | sh a.sh 或 bash a.sh | 否 | 直接调用 Shell 解释器执行脚本,无需赋予执行权限(推荐调试时使用)。 |
. 命令执行 | . a.sh (注意 . 后有空格) | 否 | 等同于 source ,在当前 Shell 进程中执行(变量会影响当前环境)。 |
source 命令执行 | source a.sh | 否 | 常用于加载配置文件(如 /etc/profile ),使配置立即生效。 |
示例:执行脚本 test.sh
# 1. 创建脚本
[root@ansible ~]# cat > test.sh << EOF
> #!/bin/bash
> echo "hello world"
> EOF# 2. 方式1:sh 命令执行(无需权限)
[root@ansible ~]# sh test.sh
hello world# 3. 方式2:赋予权限后用相对路径执行
[root@ansible ~]# chmod +x test.sh
[root@ansible ~]# ./test.sh
hello world
4. Shebang 机制
脚本第一行的 #!
(称为 Shebang)用于告诉系统“使用哪个解释器执行脚本”,必须放在脚本开头,常见格式:
#!/bin/bash # Bash 脚本(Linux 默认)
#!/usr/bin/python # Python 脚本
#!/usr/bin/perl # Perl 脚本
- 若省略 Shebang,系统会用当前默认 Shell(通常是 Bash)执行,但推荐显式声明(避免环境差异)。
二、Shell 脚本的组成结构
一个标准的 Shell 脚本由“Shebang 头”“注释”“命令/逻辑”三部分组成,语法规则简洁清晰。
1. 脚本组成与语法规则
组成部分 | 格式与说明 |
---|---|
Shebang 头 | 第一行,#!/bin/bash ,指定解释器。 |
注释 | 以 # 开头的行(#! 除外),用于说明脚本功能、作者、日期等(提高可读性)。 |
命令与逻辑 | 一行一条命令;一行多条命令用 ; 分隔;长命令用 \ 换行(避免行过长)。 |
示例:标准脚本结构
[root@ansible ~]# vim test1.sh
#!/bin/bash
# 脚本名称:hello.sh
# 功能:输出问候信息
# 作者:zhang3
# 日期:2025-09-9# 单行命令
echo "Hello World!"# 一行多条命令(; 分隔)
name="zhang3"; echo "Hello, $name!"# 长命令换行(\ 分隔)
echo "当前系统信息:" && \
uname -r && \
cat /etc/redhat-release
执行脚本文件:
[root@ansible ~]# sh test1.sh
Hello World!
Hello, zhang3!
当前系统信息:
5.14.0-284.11.1.el9_2.x86_64
Red Hat Enterprise Linux release 9.2 (Plow)
2. 程序返回值
脚本执行后会产生两个结果:执行结果(如 echo
输出的内容)和状态返回码(判断脚本是否执行成功状态返回码变量:$?)。
状态返回码规则(0-255)
返回码 | 含义 |
---|---|
0 | 脚本执行成功(所有命令均正常完成)。 |
1-255 | 脚本执行失败(如命令不存在、权限不足、语法错误等),不同错误对应不同返回码。 |
查看返回码
执行脚本后,通过 echo $?
查看上一个命令/脚本的返回码:
# 执行成功的脚本
[root@ansible ~]# sh test.sh
hello world
[root@ansible ~]# echo $?
0# 执行失败的脚本(如不存在的命令)
vim test3.sh
#!/bin/bash
nonexistent_cmd # 不存在的命令
EOF
sh error.sh
echo $? # 输出:127(命令未找到)
执行脚本文件:
[root@ansible ~]# sh test3.sh
error.sh: line 2: nonexistent_cmd: command not found
127
三、Shell 脚本调试
脚本编写过程中难免出现语法错误或逻辑问题,需通过调试工具定位问题。
1. 脚本执行原理
Shell 执行脚本的流程:
- 加载环境变量:查找
ENV
变量指定的环境文件(如/etc/profile
、~/.bashrc
); - 逐行执行脚本:从上至下、从左至右执行,遇到子脚本(嵌套脚本)先执行子脚本,再返回父脚本;
- 派生子进程:Shell 不直接执行命令,而是创建子进程执行(避免命令错误影响 Shell 主进程)。
2. 脚本编写注意事项
注意事项 | 说明 |
---|---|
开头加 Shebang | 必须写 #!/bin/bash ,避免环境差异。 |
缩进与注释 | 用 4 个空格缩进(逻辑控制块如 if /for ),多写注释说明功能。 |
命名规范 | 变量名大写(如 NAME )、局部变量小写(如 local age )、函数名小写(如 get_info )。 |
变量作用域 | 默认变量为全局,函数内用 local 声明局部变量(避免污染全局)。 |
调试辅助命令 | set -e :遇到命令返回非 0 时退出脚本(避免错误扩散);set -x :打印执行过程(每行命令执行前显示)。 |
路径与测试 | 命令用绝对路径(如 /bin/ls 而非 ls );脚本先在测试环境运行,再部署到生产。 |
命令引用 | 引用命令结果用反引号 `cmd` 或 $(cmd) (推荐 $(cmd) ,兼容性更好)。 |
3. 脚本调试方法
核心通过 bash
命令的选项调试,常用选项:
调试选项 | 语法格式 | 功能说明 |
---|---|---|
-n | bash -n a.sh | 检查脚本语法错误(不执行脚本),若无输出则语法正确。 |
-x | bash -x a.sh | 单步执行脚本,打印每一行命令的执行过程(含变量展开结果),定位逻辑错误。 |
示例:调试脚本
# 创建有逻辑错误的脚本(计算两数之和,但变量名错误)
cat > calc.sh << EOF
#!/bin/bash
a=10
b=20
sum=\$((a + c)) # 错误:变量 c 未定义
echo "sum: \$sum"
EOF# 1. 检查语法错误(无输出,语法正确)
[root@ansible ~]# bash -n calc.sh# 2. 调试执行(查看变量展开过程)
[root@ansible ~]# bash -x calc.sh
+ a=10
+ b=20
+ sum=10
+ echo 'sum: 10'
sum: 10
# (通过输出发现 sum 计算时用了未定义的 c,定位错误)
执行脚本文件:
[root@ansible ~]# sh calc.sh
sum: 10
4. 实战案例:文件与目录管理脚本
需求:完成以下自动化操作,并用调试工具验证:
- 创建目录
/tmp/chenyu
; - 切换到该目录;
- 创建目录
a1c
、b2d
、6hina
; - 创建空文件
xy
、x123y
、123
; - 列出以
a
或6
开头的文件/目录,写入/tmp/file1
; - 列出以字母开头、后跟 1 个数字、再跟任意字符的文件/目录,写入
/tmp/file2
。
脚本实现(file_manage.sh
)
[root@ansible ~]# vim file_manage.sh#!/bin/bash
# 功能:自动化管理 /tmp/chenyu 下的文件与目录
set -x # 开启调试,打印执行过程# 1. 创建目录并切换
mkdir -p /tmp/zhang3 # -p 确保目录不存在时创建
cd /tmp/zhang3# 2. 创建目录
mkdir -p a1c b2d 6hina# 3. 创建空文件
touch xy x123y 123# 4. 筛选以 a 或 6 开头的文件/目录,写入 /tmp/file1
ls -d [a6]* > /tmp/file1 # [a6]* 匹配以 a 或 6 开头的内容# 5. 筛选“字母开头+1个数字+任意字符”的文件/目录,写入 /tmp/file2
ls -d [[:alpha:]][[:digit:]]* > /tmp/file2 # [[:alpha:]] 匹配字母,[[:digit:]] 匹配数字set +x # 关闭调试
echo "脚本执行完成!"
执行与验证
# 1. 赋予权限并执行
[root@ansible ~]# chmod +x file_manage.sh
[root@ansible ~]# ./file_manage.sh# 2. 验证结果
[root@ansible ~]# cat /tmp/file1
6hina
a1c
[root@ansible ~]# cat /tmp/file2
a1c
b2d
x123y
四、Shell 运算符
Shell 原生不支持复杂运算,需通过 expr
、let
、$((...))
等工具实现,常见运算符分“算术运算”和“逻辑运算”。
1. 算术运算符
用于整数计算(浮点型需用 bc
工具),常用运算符与示例:
运算符 | 描述 | 实现方式与示例(a=10 ,b=20 ) |
---|---|---|
+ | 加法 | val=$(expr $a + $b) 或 val=$((a + b)) (结果:30) |
- | 减法 | val=$(expr $b - $a) 或 val=$((b - a)) (结果:10) |
* | 乘法 | val=$(expr $a \* $b) (需转义 * )或 val=$((a * b)) (结果:200) |
/ | 除法 | val=$(expr $b / $a) 或 val=$((b / a)) (结果:2) |
% | 取余 | val=$(expr $b % $a) 或 val=$((b % a)) (结果:0) |
= | 赋值 | a=20 (将 20 赋值给变量 a ) |
== | 相等判断 | if [ $a == $b ]; then ... (判断 a 与 b 是否相等) |
!= | 不相等判断 | if [ $a != $b ]; then ... (判断 a 与 b 是否不相等) |
示例:
[root@ansible ~]# vim test5.sh#!/bin/bash
a=10
b=20
val=`expr $a + $b`
echo "a + b = $val"[root@ansible ~]# sh test5.sh
a + b = 30