Linux Shell 编程
一、Shell 基础概念
1. 什么是 Shell?
Shell 是 Linux 内核与用户之间的交互接口,它接收用户输入的命令,解析后传递给内核执行,并将结果返回给用户。简单来说,Shell 是 “命令解释器”,也是一种编程语言(解释型语言,无需编译)。
2. 常见的 Shell 类型
Linux 系统中默认或常用的 Shell 有多种,不同 Shell 语法略有差异,编程常用 Bash(Bourne Again Shell)(大多数 Linux 发行版默认 Shell)。
Shell 类型 | 特点与用途 | 常见系统 |
---|---|---|
Bash | 兼容 Bourne Shell,支持别名、函数、脚本编程 | CentOS、Ubuntu、Debian |
Sh(Bourne) | 基础 Shell,功能简单,兼容性强 | 早期 Unix、最小系统 |
Zsh | 增强型 Shell,支持自动补全、主题定制 | 开发者常用(需手动安装) |
Csh/Tcsh | 语法类似 C 语言,适合习惯 C 编程的用户 | 少数 Unix 系统 |
查看当前系统默认 Shell:
echo $SHELL # 输出示例:/bin/bash
查看系统支持的 Shell:
cat /etc/shells
二、Shell 脚本的创建与执行
1. 脚本文件的基本结构
Shell 脚本文件以 .sh
为后缀(非强制,但建议规范),核心结构包含 3 部分:
- Shebang(脚本解释器指定):第一行必须写
#!/bin/bash
,指定脚本由 Bash 解释执行(若用其他 Shell,需改为对应路径,如#!/bin/zsh
)。 - 注释:以
#
开头的行(除 Shebang 外),用于说明脚本功能,提高可读性(单行注释,无多行注释,可通过: '注释内容'
模拟多行注释)。 - 命令 / 代码:实现具体功能的 Shell 命令或语法逻辑。
示例:第一个 Shell 脚本(hello.sh)
#!/bin/bash
# 第一个 Shell 脚本:输出欢迎信息
echo "Hello, Linux Shell!" # 打印字符串
whoami # 输出当前登录用户
date # 输出当前系统时间
2. 脚本的执行方式
执行脚本前需确保文件有 可执行权限(x
权限),常见执行方式有 3 种:
执行方式 | 命令格式 | 特点 |
---|---|---|
1. 绝对路径执行 | /home/user/hello.sh | 需指定脚本完整路径,依赖可执行权限 |
2. 相对路径执行 | ./hello.sh (脚本在当前目录) | 需加 ./ (避免与系统命令重名),依赖权限 |
3. 指定 Shell 执行 | bash hello.sh 或 sh hello.sh | 无需可执行权限(直接调用解释器) |
权限设置命令
chmod +x hello.sh # 给脚本添加可执行权限(所有用户)
chmod u+x hello.sh # 仅当前用户(所有者)添加可执行权限
三、Shell 变量与数据类型
Shell 是 弱类型语言,变量无需声明类型,直接赋值即可;变量默认存储字符串,需显式转换才能用于数值计算。
1. 变量的定义与使用
定义规则
- 变量名由 字母、数字、下划线 组成,不能以数字开头。
- 赋值时 等号两侧不能有空格(如
a=10
正确,a = 10
错误)。 - 变量值含空格或特殊字符(如
$
、*
)时,需用 双引号(") 或 单引号(') 包裹。
变量赋值与引用
#!/bin/bash
# 1. 普通变量(字符串/数值)
name="Alice" # 双引号:允许变量嵌套(如引用其他变量)
age=25 # 数值默认按字符串存储
msg='Hello $name' # 单引号:完全原样输出($name 不会解析)# 2. 引用变量:用 $变量名 或 ${变量名}(避免歧义)
echo "Name: $name" # 输出:Name: Alice
echo "Age: ${age}岁" # 输出:Age: 25岁(${} 明确变量边界)
echo "Message: $msg" # 输出:Message: Hello $name# 3. 系统环境变量(预定义,全大写)
echo "当前用户:$USER" # 输出当前登录用户(如 root)
echo "当前目录:$PWD" # 输出当前工作目录
echo "PATH路径:$PATH" # 输出系统命令搜索路径
2. 变量的分类
按作用域和来源,Shell 变量分为 3 类:
变量类型 | 定义方式 | 作用域 | 示例 |
---|---|---|---|
局部变量 | 用户在脚本 / 终端中直接定义 | 仅当前 Shell 进程 | name="Bob" |
环境变量 | export 变量名=值 或 /etc/profile | 当前 Shell 及子进程 | export PATH=$PATH:/opt |
预定义变量 | Shell 内置,无需定义 | 全局可用 | $? (上一条命令返回值) |
常用预定义变量
变量 | 含义 | 示例场景 |
---|---|---|
$0 | 当前脚本的文件名 | 脚本中打印自身路径:echo $0 |
$n | 脚本的第 n 个参数(n≥1,$1 是第一个参数) | 执行 ./test.sh 10 20 ,$1=10 |
$# | 脚本接收的参数总数 | 上例中 $#=2 |
$* / $@ | 所有参数($* 合并为一个字符串,$@ 分开) | 遍历所有参数时用 $@ |
$? | 上一条命令的执行结果(0 = 成功,非 0 = 失败) | 判断命令是否执行成功 |
$$ | 当前脚本的进程 ID(PID) | 记录脚本进程号 |
示例:参数处理脚本(param.sh)
#!/bin/bash
echo "脚本名:$0"
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "参数总数:$#"
echo "所有参数:$@"# 遍历所有参数
echo "遍历参数:"
for param in $@
doecho "- $param"
done
执行脚本并传参:
./param.sh apple banana cherry
# 输出:
# 脚本名:./param.sh
# 第一个参数:apple
# 第二个参数:banana
# 参数总数:3
# 所有参数:apple banana cherry
# 遍历参数:
# - apple
# - banana
# - cherry
3. 数值计算
Shell 变量默认是字符串,需通过 算术扩展 或工具(expr
、bc
)实现数值计算。
常用计算方式
- 算术扩展:
$((表达式))
或${表达式}
(支持+
、-
、*
、/
、%
(取余)、**
(幂))。 - expr 命令:
expr 数值1 运算符 数值2
(运算符两侧需空格,*
需转义为\*
)。 - bc 工具:支持浮点数计算(Shell 原生不支持浮点数)。
示例:数值计算
#!/bin/bash
a=10
b=3# 1. 算术扩展(整数计算)
sum=$((a + b)) # 13
diff=$((a - b)) # 7
mul=$((a * b)) # 30
div=$((a / b)) # 3(整数除法,舍去小数)
mod=$((a % b)) # 1(取余)
pow=$((a ** 2)) # 100(10的平方)echo "sum: $sum, diff: $diff, mul: $mul, div: $div, mod: $mod, pow: $pow"# 2. expr 命令
sum_expr=$(expr $a + $b)
mul_expr=$(expr $a \* $b) # * 需转义
echo "sum_expr: $sum_expr, mul_expr: $mul_expr"# 3. bc 工具(浮点数计算)
float_div=$(echo "scale=2; $a / $b" | bc) # scale=2 保留2位小数
echo "float_div: $float_div" # 输出:3.33
四、Shell 流程控制
流程控制是脚本逻辑的核心,包括 条件判断、循环、分支 三大类,语法与其他编程语言差异较大,需注意格式细节(如 then
、do
的位置)。
1. 条件判断(if 语句)
语法结构
Shell 条件判断依赖 测试表达式(用 [ 表达式 ]
或 [[ 表达式 ]]
,注意括号两侧空格),判断结果为 true
(0)时执行对应分支。
# 单分支
if [ 条件 ]; then命令1命令2
fi# 双分支(if-else)
if [ 条件 ]; then命令(条件成立)
else命令(条件不成立)
fi# 多分支(if-elif-else)
if [ 条件1 ]; then命令1
elif [ 条件2 ]; then命令2
else命令3
fi
常见测试表达式
条件判断主要针对 文件属性、数值大小、字符串比较,需注意运算符的差异:
测试类型 | 运算符 / 表达式 | 含义 | 示例(判断为真) |
---|---|---|---|
文件属性 | -f 文件 | 文件是否存在且为普通文件 | [ -f /etc/passwd ] |
-d 目录 | 目录是否存在 | [ -d /home ] | |
-r 文件 | 文件是否存在且有读权限 | [ -r hello.sh ] | |
-x 文件 | 文件是否存在且有可执行权限 | [ -x hello.sh ] | |
数值比较 | -eq (equal) | 等于 | [ $a -eq $b ] (a=10, b=10) |
-ne (not equal) | 不等于 | [ $a -ne $b ] | |
-gt (greater than) | 大于 | [ $a -gt $b ] (a=10, b=3) | |
-lt (less than) | 小于 | [ $a -lt $b ] | |
-ge (greater or equal) | 大于等于 | [ $a -ge $b ] | |
-le (less or equal) | 小于等于 | [ $a -le $b ] | |
字符串比较 | == 或 = | 字符串相等 | [ "$name" == "Alice" ] |
!= | 字符串不相等 | [ "$name" != "Bob" ] | |
-z 字符串 | 字符串长度为 0(空字符串) | [ -z "$empty_str" ] | |
-n 字符串 | 字符串长度不为 0(非空) | [ -n "$name" ] |
示例:条件判断脚本(if_test.sh)
#!/bin/bash
read -p "请输入一个数字:" num # read:读取用户输入,-p 显示提示信息# 判断输入是否为数字(用正则表达式,[[ 支持正则 ]])
if [[ $num =~ ^[0-9]+$ ]]; thenecho "你输入的是数字:$num"# 数值比较if [ $num -gt 100 ]; thenecho "该数字大于100"elif [ $num -eq 100 ]; thenecho "该数字等于100"elseecho "该数字小于100"fi
elseecho "错误:你输入的不是有效数字!"
fi
2. 分支选择(case 语句)
当需要判断一个变量与多个值匹配时,用 case
比 if-elif-else
更简洁,适合 “多值匹配” 场景(如菜单选择)。
语法结构
case $变量 in模式1)命令1;; # 结束当前分支(类似其他语言的 break)模式2|模式3) # 多个模式用 | 分隔(或关系)命令2;;*) # 默认分支(所有不匹配的情况)命令3;;
esac # case 的反向拼写,标志结束
示例:菜单脚本(case_menu.sh)
#!/bin/bash
echo "===== 菜单选择 ====="
echo "1. 查看当前目录"
echo "2. 查看系统时间"
echo "3. 查看当前用户"
echo "4. 退出"
read -p "请输入选择(1-4):" choicecase $choice in1)echo "当前目录内容:"ls -l;;2)echo "当前系统时间:"date;;3)echo "当前登录用户:"whoami;;4)echo "退出脚本,再见!"exit 0 # exit 0:正常退出(返回值0);;*)echo "错误:无效选择!"exit 1 # exit 1:异常退出(返回值非0);;
esac
3. 循环语句(for、while、until)
循环用于重复执行一系列命令,Shell 支持 for
(遍历集合)、while
(条件为真时循环)、until
(条件为假时循环)三种循环。
(1)for 循环
主要用于 遍历列表(数组、参数、文件),语法有两种:
语法 1:遍历指定列表
for 变量 in 列表; do命令
done
语法 2:C 语言风格(数值范围循环)
for ((初始化; 条件; 增量)); do命令
done
示例:for 循环应用
#!/bin/bash
# 示例1:遍历字符串列表
echo "遍历水果列表:"
fruits="apple banana cherry"
for fruit in $fruits; doecho "- $fruit"
done# 示例2:遍历文件(当前目录下的 .sh 文件)
echo -e "\n当前目录的 Shell 脚本:"
for file in *.sh; doecho "- $file"
done# 示例3:C 风格循环(1到5)
echo -e "\nC 风格循环(1-5):"
for ((i=1; i<=5; i++)); doecho "i = $i"
done
(2)while 循环
条件为真时持续循环(类似其他语言的 while),适合 “不确定循环次数,依赖条件终止” 的场景(如读取文件行、等待用户输入)。
语法
while [ 条件 ]; do命令
done
示例 :读取文件行(逐行处理文件内容)
#!/bin/bash
# 读取 test.txt 文件的每一行(假设文件已存在)
echo "读取 test.txt 内容:"
while read line; do # read line:每次读取一行存入 line 变量echo "Line: $line"
done < test.txt # < test.txt:将文件作为 while 循环的