Shell脚本编程基本认识
Shell脚本编程基本认识
一、脚本基础
1. Shell编程介绍与特性
Shell定义与作用
Shell是命令解释程序,同时也是命令语言解释器。用户与Shell交互时,输入的命令行需符合Shell语法与语义规范,才能被理解并执行。其使用的命令语言称为Shell语言,具备双重属性:
- 交互式语言:支持用户实时输入命令并获取反馈,是Linux系统中用户与内核交互的核心桥梁。
- 可编程语言:将多个Shell命令写入文本文件,构成Shell程序(Shell脚本) ,Shell会逐条解释执行。通过变量、参数、控制结构的组合,可自动化完成系统初启、配置、管理等复杂操作。
在Linux系统中,Shell脚本是自动化运维的关键工具,熟练掌握Shell语言能高效管理系统,深入理解系统运行机制。
Shell语言特点
特点分类 | 详细说明 | 优势与不足 |
---|---|---|
解释性语言 | 无需编译,Shell进程直接解释执行脚本代码 | 优势:即编即用,开发效率高;不足:运行速度低于编译型语言目标程序 |
基于字符串的语言 | 仅处理字符串数据,不支持复杂数据结构(如数组、链表)与复杂运算(如浮点运算),输出均为字符形式 | 优势:处理文本、字符串任务(日志分析、数据筛选)简洁高效;不足:无法满足复杂数据处理场景 |
命令级语言 | 脚本核心由Shell命令(如ls 、cd )和系统可执行程序构成,支持通过管道(` | )、重定向( >/ <`)组合命令 |
Shell脚本构成
Shell脚本是文本文件,内容分为两类:
- 简单脚本:仅包含有序排列的Shell命令,如
cd /tmp && mkdir test
,执行固定流程操作。 - 高级脚本:除基础命令外,包含变量定义、参数传递、条件判断(
if-else
)、循环(for
/while
)等结构,应对动态复杂场景(如服务器负载监控脚本)。
2. 变量
变量本质
变量是程序中保存用户数据的内存空间,变量名是该内存空间的“地址标识”。程序执行时,内存中的数据(变量值)可动态变化,但变量名固定。例如age=25
中,age
是变量名(指向内存地址),25
是变量值(内存中存储的数据)。
变量命名规则
- 字符组成:仅允许字母(A-Z、a-z)、数字(0-9)、下划线(_),且必须以字母或下划线开头(如
123name
为非法变量名)。 - 长度限制:Shell未明确限制变量名长度,为保证可读性,建议使用“简短且有意义”的名称(如
user_count
表示用户数量,而非uc
)。 - 命名规范(建议):
- 全局变量:大写字母(如
SYSTEM_PATH
)。 - 局部变量(函数内):小写字母(如
temp_file
)。 - 避免拼音或无意义字符(如
mingzi
、a1
),通过变量名直观理解用途。
- 全局变量:大写字母(如
变量类型与使用
变量类型 | 定义与使用规则 | 示例 | 注意事项 |
---|---|---|---|
整型(int) | 直接赋值数字,无需声明类型,支持整数运算 | age=10 (表示年龄为10) | 不支持浮点型数据,若赋值salary=3.1 ,会被当作字符串处理 |
浮点型(float) | Shell原生不支持浮点运算,需借助bc 、awk 等工具实现 | 计算浮点和:`echo “3.1+2.2” | bc` |
字符型(字符串) | 赋值时可加引号(单引号、双引号)或不加引号,包含空格时必须加引号 | - 无空格:msg=cy /msg="cy" /msg='cy' - 有空格: msg="hello cy" (不加引号会将hello 和cy 视为两个参数) | 单引号(强引用):引号内特殊字符($ 、# )均失效,如echo '${name} is good' 输出${name} is good ;双引号(弱引用):引号内特殊字符( $ )仍生效,如name="cy" 后,echo "${name} is good" 输出cy is good |
布尔型(boolean) | 仅支持True (真)和False (假),主要用于条件判断 | is_ok=True (操作成功)、is_error=False (无错误) | Shell中布尔值本质是字符串,需通过条件表达式判断“真假”,非原生布尔类型 |
变量引用方式
引用变量时,变量名前加$
符号,格式为$变量名
或${变量名}
(推荐后者,避免变量名与后续字符混淆),示例:
name="cy"
echo $name # 输出cy
echo "${name}_test" # 输出cy_test(若用$name_test,Shell会误认为变量是name_test,导致输出空值)
3. 脚本执行方式
Shell脚本执行需先确保为文本格式(如.sh
后缀),不同执行方式对“执行权限”要求不同,常用方式如下:
执行方式 | 语法格式 | 权限要求 | 原理与说明 |
---|---|---|---|
sh/bash命令执行 | sh 脚本名.sh 或 bash 脚本名.sh | 无需执行权限(chmod +x ) | 直接调用sh /bash 解释器,读取脚本内容执行,适用于脚本未授权或临时测试 |
相对路径执行 | ./脚本名.sh (如./test.sh ) | 必须添加执行权限:chmod +x 脚本名.sh | 以当前目录为基准定位脚本,“. ”表示当前目录;直接输入test.sh 会在PATH 路径中查找,可能提示“命令未找到” |
绝对路径执行 | /绝对路径/脚本名.sh (如/home/user/test.sh ) | 必须添加执行权限 | 通过完整路径定位脚本,不受当前目录影响,适用于脚本固定存放场景(系统运维脚本) |
. (点号)执行 | . 脚本名.sh (点号后有空格) | 无需执行权限 | 在当前Shell进程中执行脚本,脚本中定义的变量、环境变量直接影响当前Shell(如配置文件生效);其他执行方式创建子Shell,变量不影响当前环境 |
source命令执行 | source 脚本名.sh | 无需执行权限 | 与“点号执行”原理一致,更偏向“加载配置文件”场景(如source /etc/profile 使环境变量生效),是.(空格)脚本名 的别名 |
执行方式示例
假设当前目录有test.sh
脚本(内容echo "Hello Shell"
):
sh test.sh
:直接执行,无需授权,输出Hello Shell
。chmod +x test.sh && ./test.sh
:先授权,再通过相对路径执行,输出Hello Shell
。source test.sh
:在当前Shell执行,若脚本中定义VAR=123
,执行后echo $VAR
输出123
(其他方式无此效果)。
4. Shebang机制
#!
(Shebang/Hashbang)是脚本文件第一行特殊标记,用于告诉系统“使用哪个解释器执行脚本”,语法格式#!/路径/解释器
,常见脚本对应的Shebang:
脚本类型 | Shebang格式 | 说明 |
---|---|---|
Bash脚本 | #!/bin/bash | 最常用,指定Bash解释器(Linux中/bin/bash 是Bash默认路径) |
Python脚本 | #!/usr/bin/python 或 #!/usr/bin/env python | 前者指定固定路径Python解释器;后者通过env 查找系统配置的Python路径,更灵活,避免路径不一致问题 |
Perl脚本 | #!/usr/bin/perl | 指定Perl解释器,用于Perl语言脚本 |
注意事项
执行python脚本的方法:
vim a.pypython a.py
- Shebang必须在脚本第一行,前面有空白行或注释会导致解释器识别失败。
- 未写Shebang时,执行需明确指定解释器(如
sh test.sh
),否则系统可能默认使用/bin/sh
(部分系统中/bin/sh
是dash
,与Bash兼容性有差异,可能导致脚本报错)。
二、脚本的组成结构
1. 脚本核心组成部分
规范的Shell脚本由“Shebang、注释、命令行”三部分组成,结构清晰、易于维护:
组成部分 | 作用 | 示例 | 规范要求 |
---|---|---|---|
Shebang | 指定脚本解释器 | #!/bin/bash | 必须位于脚本第一行,无前置字符 |
注释 | 说明脚本功能、作者、日期、关键步骤,提高可读性 | # 功能:自动备份MySQL数据库<br># 作者:xxx<br># 日期:2024-05-01 | 以# 开头,单行注释;复杂逻辑前需添加注释,解释“为什么做”(而非“做了什么”) |
命令行 | 脚本核心执行代码,包含变量定义、条件判断、循环、系统命令等 | # 定义备份路径<br>BACKUP_PATH="/data/backup"<br># 创建备份目录(若不存在)<br>mkdir -p $BACKUP_PATH | 一行一条命令,同一行多条命令用分号(; )分隔(如cd /tmp; mkdir test );长命令用反斜杠( \ )换行(如echo "This is a long \ command" ),增强可读性 |
规范脚本示例
#!/bin/bash
# 脚本名称:hello.sh
# 功能:输出欢迎信息并显示当前时间
# 作者:xxx
# 日期:2024-05-01# 定义欢迎信息变量
WELCOME_MSG="Hello, Shell Script!"
# 输出欢迎信息
echo $WELCOME_MSG
# 输出当前时间(使用date命令)
echo "Current Time: $(date +'%Y-%m-%d %H:%M:%S')"
##输出
Current Time: 2025-09-09 11:03:36
2. 程序返回值
Shell脚本执行后产生两类返回值,分别对应“执行结果”和“执行状态”:
(1)程序执行的结果
指脚本中命令的输出内容,如echo "Hello"
的Hello
、ls /tmp
的/tmp
目录文件列表。可通过重定向(>
/>>
)保存到文件,或通过管道(|
)传递给其他命令,示例:
# 将ls /tmp结果保存到/tmp/file_list.txt
ls /tmp > /tmp/file_list.txt
# 将echo结果通过管道传递给grep,筛选含"test"的行
echo "test1 test2 abc" | grep "test"
(2)程序状态返回代码(退出码)
指脚本或命令执行后的“状态标识”,用0-255的整数表示执行是否成功:
- 0:执行成功(如
ls /tmp
执行成功,退出码0)。 - 1-255:执行失败(如
ls /nonexistent
(目录不存在)退出码2,grep
未找到匹配内容退出码1)。
退出码的查看与使用
- 查看上一条命令的退出码:用特殊变量
$?
,示例:ls /tmp echo $? # 输出0(执行成功) ls /nonexistent echo $? # 输出2(执行失败)
- 脚本中自定义退出码:通过
exit 数字
告知调用者脚本执行状态,示例:有待考证!!!# 检查备份目录是否存在,不存在则退出并返回1 if [ ! -d /data/backup ]; thenecho "Backup directory not found!"exit 1 # 自定义退出码1,表示目录不存在错误 fi # 备份成功后,退出并返回0 echo "Backup completed successfully!" exit 0
三、Shell脚本调试
脚本开发中需通过调试定位语法、逻辑错误,以下是调试核心内容:
1. 脚本执行的过程解析
Shell脚本执行流程为“环境加载→逐行执行→子脚本处理”:
(1)环境变量加载
通过./脚本.sh
、sh 脚本.sh
等方式执行脚本时,Shell先查找环境变量ENV
(未定义则加载默认配置文件),加载顺序(不同Linux发行版略有差异):
- 系统级配置:
/etc/profile
(全局环境变量,所有用户生效)。 - 用户级配置:
- 当前用户家目录
.bash_profile
(登录时加载,仅当前用户生效)。 - 当前用户家目录
.bashrc
(非登录时加载,如打开终端)。 - 系统级
/etc/bashrc
(所有用户Bash配置,补充.bashrc
)。
- 当前用户家目录
环境加载完成后,脚本才能使用PATH
(命令查找路径)、HOME
(用户家目录)等变量。
(2)脚本逐行执行
加载环境后,Shell从Shebang后开始,从上至下、从左至右执行每条命令/语句:
- 普通命令(
ls
、mkdir
):直接调用系统命令并等待结果。 - 变量定义(
VAR=123
):在当前Shell(或子Shell)内存中创建变量。 - 控制结构(
if-else
、for
):根据条件执行分支或循环命令块。
(3)子脚本(脚本嵌套)处理
脚本中调用其他脚本(子脚本,如./sub_script.sh
)时:
- 暂停父脚本执行,创建子Shell进程。
- 在子Shell中执行子脚本(遵循“环境加载→逐行执行”流程)。
- 子脚本执行完成后,销毁子Shell,返回父脚本继续执行后续命令。
示例(父脚本parent.sh
调用子脚本child.sh
):
#child.sh # 调用子脚本
echo "test2"#parent.sh
echo "test1"
./child.sh
echo "test3"
执行./parent.sh
输出:
test1
test2
test3
2. 脚本执行的原理
Shell执行命令时不直接执行,而是创建子进程让子进程完成,核心目的是“隔离风险,保护Shell主进程”:
(1)子进程的创建与作用
- Shell接收到命令(如
ls
、sh test.sh
)时,通过fork()
创建子进程(复制当前Shell内存空间,含环境变量、变量等)。 - 子进程通过
exec()
加载并执行目标命令(命令路径由PATH
查找)。 - 父进程(Shell主进程)通过
wait()
等待子进程执行完成,回收资源(避免僵尸进程)。
(2)风险隔离的意义
Shell是用户与Linux内核交互的核心工具,若主进程因命令出错崩溃,用户无法输入命令,需重新登录。子进程执行命令可隔离风险,即使子进程崩溃,也不影响主进程,保障系统交互连续性。
(3)无subprocess执行的特殊场景
以下两种方式不创建子进程,命令直接在Shell主进程中执行:
. 脚本.sh
(点号执行)。source 脚本.sh
(source执行)。
示例:执行source test.sh
,脚本中定义的VAR=123
会存入Shell主进程内存,执行后echo $VAR
输出123
;而sh test.sh
执行时,变量仅存在于子进程,主进程中VAR
为空。
3. 写脚本注意事项
遵循以下规范可减少错误,提高脚本可读性、可维护性:
注意事项 | 详细说明 | 示例 |
---|---|---|
开头加Shebang | 第一行指定解释器(如#!/bin/bash ),避免系统默认/bin/sh 导致兼容性问题 | 错误:无Shebang,直接echo "test" ;正确: #!/bin/bash 后写echo "test" |
语法缩进与注释 | 控制结构(if /for /while )代码块用4个空格缩进(不推荐Tab,避免编辑器显示异常);关键步骤、复杂逻辑前加注释 |
(2)执行过程调试:bash -x 脚本名
- 功能:执行脚本并打印每步执行过程,每条命令前输出带
+
的命令内容(含变量替换后的实际命令),便于跟踪变量值和流程。 - 适用场景:脚本能执行但结果不符合预期(逻辑错误、变量值异常)。
- 示例:
# 脚本test.sh(判断参数是否大于10) #!/bin/bash if [ $1 -gt 10 ]; thenecho "$1 is greater than 10" elseecho "$1 is less than or equal to 10" fi# 开启调试执行(传入参数5) bash -x test.sh 5 # 输出调试过程: # + '[' 5 -gt 10 ']'(显示替换后的条件判断) # + echo '5 is less than or equal to 10'(显示执行的echo命令) # 5 is less than or equal to 10(脚本输出结果)
(3)特殊字符匹配:正则表达式辅助调试
处理文件、目录筛选时,Shell支持通过特殊字符类简化正则匹配:
字符类 | 含义 | 示例 |
---|---|---|
[[:alpha:]] | 任意大小写字母(A-Z、a-z) | 匹配a 、B ,不匹配1 、_ |
[[:digit:]] | 任意数字(0-9) | 匹配5 、9 ,不匹配a 、# |
(4)综合调试案例
需求:编写脚本完成以下操作,用调试工具验证正确性:
- 创建目录
/tmp/chenyu
。 - 切换到
/tmp/chenyu
目录。 - 创建目录
a1b
、b2c
、6cy
。 - 创建空文件
xy
、x123y
、123
。 - 列出当前目录下以
a
或6
开头的文件/目录,导入到/tmp/file1
。 - 列出当前目录下以字母开头、后接1个任意数字、再后接任意长度字符的文件/目录,导入到
/tmp/file2
。
实现脚本与调试过程:
#!/bin/bash
# 脚本名:file_operation.sh
# 功能:完成目录/文件创建与筛选
set -x # 开启执行日志(调试用)
set -e # 出错即退出# 步骤1:创建目录/tmp/chenyu
mkdir -p /tmp/chenyu
# 步骤2:切换到该目录
cd /tmp/chenyu
# 步骤3:创建3个目录
mkdir -p a1b b2c 6cy
# 步骤4:创建3个空文件
touch xy x123y 123
# 步骤5:筛选以a或6开头的文件/目录,写入/tmp/file1
# 正则:^a匹配以a开头,^6匹配以6开头,|表示“或”
ls -d * | grep -E '^a|^6' > /tmp/file1
ls -d * | grep "^[a|b]" > /tmp/file1
# 步骤6:筛选“字母开头]"个数字+任意字符”的文件/目录,写入/tmp/file2
# 正则:^[[:alpha:]]匹配字母开头,[[:digit:]]匹配1个数字,.*匹配任意长度字符
ls -d * | grep -E '^[[:alpha:]][[:digit:]].*' > /tmp/file2echo "Operation completed! Check /tmp/file1 and /tmp/file2."
调试执行:
- 语法检查:
bash -n file_operation.sh
,无输出表示语法正确。 - 过程调试:
bash -x file_operation.sh
,观察每步命令执行结果(如mkdir
是否创建目录、grep
是否正确筛选)。 - 结果验证:
cat /tmp/file1
:输出a1b
、6cy
(以a或6开头的目录)。cat /tmp/file2
:输出a1b
、b2c
(字母开头+1个数字+任意字符的目录)、x123y
(字母x开头+数字1+字符23y的文件)。
四、运算符
Shell原生不支持复杂运算,需通过工具或特定语法实现,主要分为“算术运算符”和“逻辑运算符”:
1. 算术运算符
用于整数运算(Shell原生不支持浮点运算),常见运算符及使用方式:
运算符 | 功能 | 语法格式(变量a、b为例) | 说明 |
---|---|---|---|
+ | 加法 | expr $a + $b 、let c=$a+$b 、c=$[a+b] 、c=$((a+b)) | expr 中运算符两侧需加空格(如expr 1 + 2 ,非expr 1+2 ) |
- | 减法 | expr $a - $b 、let c=$a-$b 、c=$[a-b] 、c=$((a-b)) | 同上,expr 需加空格 |
* | 乘法 | expr $a \* $b (* 需转义)、let c=$a*$b 、c=$[a*b] 、c=$((a*b)) | expr 中* 是通配符,需\ 转义;其他方式无需转义 |
/ | 除法 | expr $b / $a 、let c=$b/$a 、c=$[b/a] 、c=$((b/a)) | 仅支持整数除法(expr 5 / 2 输出2,非2.5) |
% | 取余(模运算) | expr $b % $a 、let c=$b%$a 、c=$[b%a] 、c=$((b%a)) | 结果为除法后的余数(expr 5 % 2 输出1) |
= | 赋值 | a=$b 、let a=$b | 将变量b的值赋给变量a |
== | 相等判断 | [ $a == $b ] (注意空格) | 用于条件判断,返回布尔值(真为0,假为非0),如if [ $a == $b ]; then echo "Equal"; fi |
!= | 不相等判断 | [ $a != $b ] (注意空格) | 与== 相反,如if [ $a != $b ]; then echo "Not Equal"; fi |
算术运算示例
#!/bin/bash
# 定义两个整数变量
a=10
b=20# 1. 使用expr运算(需加空格,乘法转义)
sum_expr=`expr $a + $b`
mul_expr=`expr $a \* $b`
echo "expr sum: $sum_expr" # 输出30
echo "expr multiply: $mul_expr" # 输出200# 2. 使用let运算(无需空格,支持赋值)
let sum_let=$a+$b
let mul_let=$a*$b
echo "let sum: $sum_let" # 输出30
echo "let multiply: $mul_let" # 输出200# 3. 使用$[]运算(简洁,无需空格)
sum_bracket=$[a+b]
mul_bracket=$[a*b]
echo "$[] sum: $sum_bracket" # 输出30
echo "$[] multiply: $mul_bracket" # 输出200# 4. 使用$(( ))运算(推荐,兼容性好)
sum_double=$((a+b))
mul_double=$((a*b))
echo "$(( )) sum: $sum_double" # 输出30
echo "$(( )) multiply: $mul_double" # 输出200# 5. 相等判断
if [ $a == $b ]; thenecho "$a == $b"
elseecho "$a != $b" # 输出10 != 20
fi
实战例题:编写脚本计算两个整数的四则运算
需求:传递两个整数作为脚本参数,计算并显示和、差、积、商。
#!/bin/bash
# 脚本名:calc.sh
# 功能:计算两个整数的四则运算
# 参数:$1(第一个整数)、$2(第二个整数)# 检查参数数量是否为2
if [ $# -ne 2 ]; thenecho "Usage: $0 <integer1> <integer2>"exit 1
fi# 检查参数是否为整数(正则匹配)
if ! [[ $1 =~ ^[0-9]+$ && $2 =~ ^[0-9]+$ ]]; thenecho "Error: Both parameters must be integers!"exit 1
fi# 定义两个整数变量
num1=$1
num2=$2# 计算四则运算
sum=$((num1 + num2))
diff=$((num1 - num2))
product=$((num1 * num2))# 处理除法(避免除数为0)
if [ $num2 -eq 0 ]; thenquotient="Error: Division by zero!"
elsequotient=$((num1 / num2))
fi# 输出结果
echo "Calculation Result:"
echo "Sum: $num1 + $num2 = $sum"
echo "Difference: $num1 - $num2 = $diff"
echo "Product: $num1 * $num2 = $product"
echo "Quotient: $num1 / $num2 = $quotient"
执行示例:
# 正常执行(参数10和5)
./calc.sh 10 5
# 输出:
# Calculation Result:
# Sum: 10 + 5 = 15
# Difference: 10 - 5 = 5
# Product: 10 * 5 = 50
# Quotient: 10 / 5 = 2# 除数为0
./calc.sh 10 0
# 输出:
# Calculation Result:
# Sum: 10 + 0 = 10
# Difference: 10 - 0 = 10
# Product: 10 * 0 = 0
# Quotient: 10 / 0 = Error: Division by zero!# 参数非整数
./calc.sh 10 abc
# 输出:Error: Both parameters must be integers!
2. 逻辑运算符
用于条件判断组合,实现“与”“或”逻辑,遵循“短路求值”规则(提前判断结果,避免不必要运算):
(1)逻辑与(&&):AND关系
- 规则:两个条件都为真,结果为真;一个为假,结果为假。
- 短路特性:第一个条件为假,直接判定结果为假,不判断第二个条件。
- 适用场景:需满足多个条件才执行命令(如“文件存在且有读写权限”)。
示例:
# 1. 两个条件都为真(文件存在且是普通文件)
file="/tmp/test.txt"
touch $file # 创建文件
if [ -f $file ] && [ -r $file ]; thenecho "$file exists and is readable" # 输出(文件存在且可读)
fi# 2. 第一个条件为假(文件不存在),第二个条件不判断
rm -f $file # 删除文件
if [ -f $file ] && [ -r $file ]; thenecho "$file exists" # 不输出(第一个条件为假,短路)
fi
(2)逻辑或(||):OR关系
- 规则:一个条件为真,结果为真;两个都为假,结果为假。
- 短路特性:第一个条件为真,直接判定结果为真,不判断第二个条件。
- 适用场景:满足任一条件即执行命令(如“文件不存在则创建,存在则输出提示”)。
示例:
file="/tmp/test.txt"# 1. 第一个条件为真(文件存在),第二个条件不判断
touch $file
if [ -f $file ] || [ mkdir -p /tmp/test ]; thenecho "$file exists or directory created" # 输出(第一个条件为真,短路)
fi# 2. 两个条件都为假(文件不存在且目录创建失败,模拟无权限)
rm -f $file
chmod 000 /tmp # 取消/tmp写权限
if [ -f $file ] || [ mkdir -p /tmp/test ]; thenecho "Condition met" # 不输出(两个条件都为假)
elseecho "Both conditions failed" # 输出
fi
chmod 1777 /tmp # 恢复/tmp权限
逻辑运算符实际应用
脚本中常与命令退出码结合,实现“执行成功/失败后的分支处理”,示例:
# 备份MySQL数据库,成功输出提示,失败发送邮件告警
mysqldump -u root -p123456 dbname > /data/backup/db.sql && \
echo "MySQL backup completed successfully" || \
echo "MySQL backup failed! Please check." | mail -s "Backup Alert" admin@example.com
mysqldump
执行成功(退出码0):执行&&
后echo
(成功提示),||
后命令不执行。mysqldump
执行失败(退出码非0):跳过&&
后命令,执行||
后echo
和mail
(告警邮件)。