Ubuntu 22.04中的Shell编程详细知识点(含案例代码)
一、Shell编程基础认知
1. 什么是Shell
Shell是Linux系统的命令解释器,是用户与内核之间的交互桥梁。Ubuntu 22.04默认使用bash(Bourne Again Shell),是最常用的Shell解释器。
2. 环境准备(Ubuntu 22.04)
Ubuntu 22.04默认已预装bash,无需额外安装。若需验证或安装:
bash
# 验证bash是否安装
bash --version
# 若未安装,执行以下命令安装
sudo apt update && sudo apt install -y bash
3. 脚本编辑器选择
推荐使用vim(功能强大)或nano(简单易用),安装步骤:
bash
# 安装vim
sudo apt install -y vim
# 安装nano(Ubuntu默认已预装,若缺失则执行)
sudo apt install -y nano
二、Shell编程基本步骤
1. 编写Shell脚本
核心要求:脚本第一行必须指定解释器(#!/bin/bash),称为"shebang"。
编写步骤:
- 打开编辑器创建脚本文件(后缀建议为
.sh,便于识别) - 写入脚本内容(命令+逻辑)
- 保存文件
案例:编写简单的问候脚本
bash
# 步骤1:创建脚本文件
vim hello.sh
# 步骤2:写入以下内容(带注释)
#!/bin/bash
# 这是一个简单的Shell脚本,功能:输出问候语和系统信息
echo "====================================="
echo "欢迎使用Ubuntu 22.04 Shell编程演示"
echo "当前系统时间:$(date)" # 命令替换,获取当前时间
echo "当前登录用户:$(whoami)"
echo "====================================="
# 步骤3:保存退出(vim中按Esc,输入:wq回车)
2. 执行Shell脚本
Shell脚本有4种执行方式,核心区别是是否需要脚本文件有执行权限、是否创建子Shell。
方式1:赋予执行权限后直接执行(推荐)
bash
# 步骤1:给脚本添加执行权限(chmod +x 文件名)
chmod +x hello.sh
# 步骤2:执行脚本(当前目录下需加./,否则系统会在PATH中查找)
./hello.sh
方式2:通过bash解释器直接执行(无需执行权限)
bash
# 语法:bash 脚本文件名
bash hello.sh
# 等价写法(sh是bash的软链接,Ubuntu中默认相同)
sh hello.sh
方式3:使用. 执行(加载到当前Shell,无新进程)
bash
# 语法:. 脚本文件名(注意.和文件名之间有空格)
. hello.sh
# 作用:脚本中定义的变量/别名会保留在当前终端
方式4:使用source执行(与.等价,推荐用于加载配置)
bash
source hello.sh
4种执行方式对比:
| 执行方式 | 是否需要执行权限 | 是否创建子Shell | 适用场景 |
|---|---|---|---|
./脚本.sh |
是 | 是 | 常规脚本执行 |
bash 脚本.sh |
否 | 是 | 快速测试脚本(无需改权限) |
. 脚本.sh |
否 | 否 | 加载环境变量、别名等配置 |
source 脚本.sh |
否 | 否 | 同上,更直观的配置加载方式 |
三、Shell变量
变量是存储数据的容器,Shell变量无需声明类型,直接赋值即可。
1. 变量类型
| 变量类型 | 定义范围 | 作用域 | 案例 |
|---|---|---|---|
| 局部变量 | 脚本/函数内部定义 | 仅当前脚本/函数有效 | name="张三" |
| 全局变量 | 脚本中用export声明 |
当前Shell及子Shell有效 | export PATH=$PATH:/usr/local |
| 环境变量 | 系统预定义或用户配置 | 所有Shell进程有效 | $HOME(用户家目录)、$PATH |
| 内部变量 | Shell内置的特殊变量 | 自动赋值,无需定义 | $0(脚本名)、$?(返回值) |
2. 变量赋值和访问
核心语法规则:
- 赋值时等号两边不能有空格(Shell语法严格)
- 访问变量时用
$变量名或${变量名}(推荐后者,避免歧义) - 变量值含空格时,需用单引号
'或双引号"包裹
案例:变量赋值与访问
bash
#!/bin/bash
# 1. 基本赋值(无空格)
name="李四" # 双引号:允许变量替换和特殊字符
age=25 # 数字无需引号
address='北京市海淀区' # 单引号:原样输出,不解析变量和特殊字符
# 2. 访问变量(${}推荐)
echo "姓名:${name}"
echo "年龄:${age}"
echo "地址:${address}"
# 3. 变量拼接(无需连接符)
echo "拼接结果1:${name}今年${age}岁"
echo "拼接结果2:${address}-${name}"
# 4. 错误示范(等号两边有空格,会报错)
# wrong_var = "错误" # 执行会提示:wrong_var: 未找到命令
3. 内部变量(Shell内置特殊变量)
核心内部变量,带案例演示:
| 变量名 | 含义 | 案例场景 |
|---|---|---|
$0 |
当前脚本文件名(含路径) | 脚本中打印自身名称 |
$n |
位置参数(n≥1,1第一个参数,2第二个) | 脚本接收外部传入参数 |
$# |
位置参数的总个数 | 判断传入参数是否足够 |
$* |
所有位置参数(作为一个整体) | 遍历所有传入参数 |
$@ |
所有位置参数(每个参数独立) | 遍历所有传入参数(推荐) |
$? |
上一条命令的返回值(0成功,非0失败) | 判断命令执行结果 |
$$ |
当前Shell进程ID(PID) | 脚本中记录自身进程ID |
$! |
上一个后台进程的PID | 管理后台运行的程序 |
内部变量案例:
bash
#!/bin/bash
# 内部变量演示脚本:inner_var.sh
echo "====================================="
echo "当前脚本名:$0" # 输出脚本文件名(如./inner_var.sh)
echo "传入参数总个数:$#"
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "第三个参数:$3"
echo "====================================="
# 遍历所有位置参数($@推荐,支持空格参数)
echo "用\$@遍历参数(每个参数独立):"
for param in "$@"; do
echo "参数:$param"
done
echo "====================================="
# 测试$?(上一条命令的返回值)
echo "执行ls命令..."
ls /tmp # 执行一个正确的命令
echo "ls命令返回值:$?" # 输出0(成功)
echo "执行一个不存在的命令..."
wrong_cmd # 执行错误命令
echo "错误命令返回值:$?" # 输出非0(如127,代表命令未找到)
echo "当前脚本PID:$$"
执行测试(传入3个参数):
bash
chmod +x inner_var.sh
./inner_var.sh "参数1" "参数2 带空格" 参数3
输出结果(示例):
=====================================
当前脚本名:./inner_var.sh
传入参数总个数:3
第一个参数:参数1
第二个参数:参数2 带空格
第三个参数:参数3
=====================================
用$@遍历参数(每个参数独立):
参数:参数1
参数:参数2 带空格
参数:参数3
=====================================
执行ls命令...
tmpfile1 tmpfile2
ls命令返回值:0
执行一个不存在的命令...
./inner_var.sh: 第22行: wrong_cmd: 未找到命令
错误命令返回值:127
当前脚本PID:12345
4. 位置参数(脚本外部传入参数)
通过$1、$2...$n接收,$#判断参数个数,$*/$@获取所有参数。
案例:计算两个数的和(接收外部参数)
bash
#!/bin/bash
# 脚本名:add.sh,功能:接收两个整数参数,计算和
echo "====================================="
# 判断传入参数个数是否为2
if [ $# -ne 2 ]; then
echo "错误:请传入2个整数参数!"
echo "使用方式:$0 数字1 数字2"
exit 1 # 退出脚本,返回非0值(表示执行失败)
fi
# 接收参数并计算($1是第一个参数,$2是第二个)
num1=$1
num2=$2
sum=$((num1 + num2)) # 算术运算
echo "${num1} + ${num2} = ${sum}"
echo "====================================="
执行测试:
bash
chmod +x add.sh
./add.sh 10 25 # 正确传入2个参数,输出35
./add.sh 5 # 传入1个参数,提示错误
5. 变量值输出
常用输出命令:echo(简单输出)、printf(格式化输出)。
5.1 echo命令
- 语法:
echo [选项] 内容 - 常用选项:
-n(不换行)、-e(解析转义字符,如\n换行、\t制表符)
5.2 printf命令
- 语法:
printf "格式字符串" 变量1 变量2... - 支持格式化符号:
%s(字符串)、%d(整数)、%f(浮点数)、%x(十六进制)
案例:输出命令对比
bash
#!/bin/bash
# 输出命令演示
echo "=== echo命令 ==="
echo "普通输出"
echo -n "不换行输出:" # 不换行
echo "后续内容"
echo -e "解析转义字符:\n第一行\n第二行\t制表符" # \n换行,\t制表符
echo -e "\n=== printf命令 ==="
name="张三"
age=28
score=95.5
printf "姓名:%s,年龄:%d,分数:%.1f\n" $name $age $score # %.1f保留1位小数
printf "十六进制:%x\n" 255 # 255的十六进制是ff
6. 变量值读取(接收用户输入)
使用read命令,语法:read [选项] 变量名
常用选项:
-p "提示信息":输入前显示提示-t 秒数:超时时间(超过时间未输入则退出)-n 字符数:读取指定个数的字符后自动结束(无需按回车)-s:静默输入(不显示输入内容,适用于密码输入)
案例:read命令实用场景
bash
#!/bin/bash
# 读取用户输入演示
echo "====================================="
# 1. 基础读取(带提示)
read -p "请输入你的姓名:" name
echo "你好,${name}!"
# 2. 超时读取(5秒超时)
read -t 5 -p "请在5秒内输入你的年龄(超时自动退出):" age
if [ -z "$age" ]; then # 判断age是否为空(超时则为空)
echo -e "\n超时未输入!"
else
echo "你的年龄是:${age}"
fi
# 3. 静默输入(密码)
read -s -p "请输入密码(不显示):" password
echo -e "\n密码已接收(仅演示,实际脚本中不要明文输出密码)"
# 4. 读取2个字符(无需回车)
read -n 2 -p "请输入2个字符:" char
echo -e "\n你输入的字符是:${char}"
echo "====================================="
7. 变量替换(灵活处理变量值)
Shell支持多种变量替换语法,用于处理变量为空、默认值、字符串截取等场景。
| 替换语法 | 含义(变量var不存在或为空时) | 变量var有值时 |
|---|---|---|
${var:-默认值} |
返回默认值,不改变var本身 | 返回var的值 |
${var:=默认值} |
返回默认值,且将默认值赋值给var | 返回var的值 |
${var:?提示信息} |
输出提示信息并退出脚本(适用于必填变量) | 返回var的值 |
${var:+替代值} |
返回空(无替代) | 返回替代值,不改变var |
${var:起始:长度} |
字符串截取(起始索引从0开始,长度可选) | 截取后的字符串 |
${#var} |
返回变量值的字符长度 | 字符长度 |
案例:变量替换实战
bash
#!/bin/bash
# 变量替换演示
echo "====================================="
# 1. ${var:-默认值}(不修改var)
var1="" # 空变量
var2="已赋值"
echo "var1默认值:${var1:-未设置}" # 输出"未设置"
echo "var1原值:${var1}" # 仍为空
echo "var2默认值:${var2:-未设置}" # 输出"已赋值"
# 2. ${var:=默认值}(修改var)
var3=""
echo "var3默认值:${var3:=默认值}" # 输出"默认值",且var3被赋值
echo "var3新值:${var3}" # 输出"默认值"
# 3. ${var:?提示}(必填变量校验)
# read -p "请输入必填参数:" required_var
# ${required_var:?错误:必填参数不能为空!} # 为空则退出并提示
# 4. 字符串截取
str="Hello,Ubuntu22.04"
echo "原字符串:${str}"
echo "从索引0截取5个字符:${str:0:5}" # 输出"Hello"
echo "从索引6截取(到末尾):${str:6}" # 输出"Ubuntu22.04"
echo "字符串长度:${#str}" # 输出14(H-e-l-l-o-,-U-b-u-n-t-u-2-2-.0-4共14个)
echo "====================================="
四、表达式与运算符
Shell中的表达式包括算术表达式、关系表达式、逻辑表达式等,需通过特定语法解析(如$((...))、expr、[])。
1. 表达式分类与解析方式
| 表达式类型 | 常用解析方式 | 适用场景 |
|---|---|---|
| 算术表达式 | $((...))、let、expr |
整数运算(Shell不支持浮点数) |
| 关系表达式 | []、[[ ]]、test |
数值比较、字符串比较 |
| 逻辑表达式 | &&(与)、` |
2. 算术运算符(仅支持整数)
| 运算符 | 含义 | 案例(a=10,b=3) | 结果 |
|---|---|---|---|
+ |
加法 | $((a + b)) |
13 |
- |
减法 | $((a - b)) |
7 |
* |
乘法 | $((a * b)) |
30 |
/ |
除法(整数) | $((a / b)) |
3 |
% |
取余(模运算) | $((a % b)) |
1 |
++ |
自增(前/后) | $((a++))、$((++a)) |
10、12 |
-- |
自减(前/后) | $((a--))、$((--a)) |
10、8 |
** |
幂运算 | $((a ** 2)) |
100 |
案例:算术运算实战
bash
#!/bin/bash
# 算术运算符演示
a=10
b=3
echo "====================================="
echo "初始值:a=${a},b=${b}"
# 1. 基础运算($((...))推荐)
echo "a + b = $((a + b))"
echo "a - b = $((a - b))"
echo "a * b = $((a * b))"
echo "a / b = $((a / b))" # 整数除法,结果3
echo "a % b = $((a % b))" # 取余,结果1
echo "a的平方 = $((a ** 2))" # 100
# 2. 自增自减
echo -e "\n自增自减演示:"
echo "a++ = $((a++))" # 先使用a的值(10),再自增(a变为11)
echo "a = ${a}" # 11
echo "++a = $((++a))" # 先自增(a变为12),再使用
echo "a = ${a}" # 12
# 3. let命令(直接执行算术运算,无需$((...)))
let c=a*b # c = 12 * 3 = 36
echo -e "\nlet命令:c = a * b = ${c}"
# 4. expr命令(注意运算符两边需空格,乘法需转义\*)
d=$(expr $a + $b) # 12 + 3 = 15
e=$(expr $a \* $b) # 12 * 3 = 36
echo "expr命令:a + b = ${d},a * b = ${e}"
echo "====================================="
3. 整数关系运算符(比较两个整数)
用于判断两个整数的大小关系,需结合[]、[[ ]]或test使用。
| 运算符 | 含义 | 语法示例(a=10,b=3) | 结果 |
|---|---|---|---|
-eq |
等于(equal) | [ $a -eq $b ] |
假(0) |
-ne |
不等于(not equal) | [ $a -ne $b ] |
真(1) |
-gt |
大于(greater than) | [ $a -gt $b ] |
真(1) |
-lt |
小于(less than) | [ $a -lt $b ] |
假(0) |
-ge |
大于等于(>=) | [ $a -ge $b ] |
真(1) |
-le |
小于等于(<=) | [ $a -le $b ] |
假(0) |
案例:整数比较
bash
#!/bin/bash
# 整数关系运算符演示
read -p "请输入第一个整数:" num1
read -p "请输入第二个整数:" num2
echo "====================================="
# 注意:[]两边必须有空格,变量需加$
if [ $num1 -eq $num2 ]; then
echo "${num1} 等于 ${num2}"
elif [ $num1 -gt $num2 ]; then
echo "${num1} 大于 ${num2}"
else
echo "${num1} 小于 ${num2}"
fi
# 进阶:使用((...))简化语法(无需-gt等,直接用>、<、==)
echo -e "\n使用((...))简化比较:"
if ((num1 >= num2)); then
echo "${num1} >= ${num2}"
else
echo "${num1} < ${num2}"
fi
echo "====================================="
4. 字符串检测运算符
用于判断字符串是否为空、是否相等、长度等。
| 运算符 | 含义 | 语法示例(str1="abc",str2="def") | 结果 |
|---|---|---|---|
= |
字符串相等(==等价) | [ "$str1" = "$str2" ] |
假 |
!= |
字符串不相等 | [ "$str1" != "$str2" ] |
真 |
-z |
字符串长度为0(空字符串) | [ -z "$str3" ](str3="") |
真 |
-n |
字符串长度不为0(非空) | [ -n "$str1" ] |
真 |
< |
字符串字典序小于(需转义) | [ "$str1" \< "$str2" ] |
真(abc<def) |
> |
字符串字典序大于(需转义) | [ "$str1" \> "$str2" ] |
假 |
案例:字符串检测
bash
#!/bin/bash
# 字符串检测运算符演示
str1="hello"
str2="hello"
str3="" # 空字符串
str4="world"
echo "====================================="
# 1. 字符串相等判断(推荐加双引号,避免空格问题)
if [ "$str1" = "$str2" ]; then
echo "${str1} 和 ${str2} 相等"
else
echo "${str1} 和 ${str2} 不相等"
fi
# 2. 字符串不相等
if [ "$str1" != "$str4" ]; then
echo "${str1} 和 ${str4} 不相等"
fi
# 3. 空字符串判断(-z)
if [ -z "$str3" ]; then
echo "str3 是空字符串"
fi
# 4. 非空判断(-n)
if [ -n "$str1" ]; then
echo "str1 是非空字符串,长度:${#str1}"
fi
# 5. 字典序比较(需转义<、>)
if [ "$str1" \< "$str4" ]; then
echo "${str1} 的字典序小于 ${str4}"
fi
echo "====================================="
5. 文件测试运算符
用于判断文件/目录的属性(是否存在、是否为目录、是否可执行等),是运维脚本的核心运算符。
| 运算符 | 含义 | 语法示例 |
|---|---|---|
-e |
文件/目录是否存在 | [ -e /etc/passwd ] |
-f |
是否为普通文件(非目录) | [ -f ~/.bashrc ] |
-d |
是否为目录 | [ -d /home ] |
-r |
是否有读权限 | [ -r ./test.sh ] |
-w |
是否有写权限 | [ -w ./test.sh ] |
-x |
是否有执行权限 | [ -x ./test.sh ] |
-s |
是否为非空文件(大小>0) | [ -s ./log.txt ] |
-L |
是否为符号链接 | [ -L /usr/bin/sh ] |
-nt |
文件A是否比文件B新 | [ fileA -nt fileB ] |
-ot |
文件A是否比文件B旧 | [ fileA -ot fileB ] |
案例:文件属性检测
bash
#!/bin/bash
# 文件测试运算符演示
file_path="./test_file.txt"
dir_path="./test_dir"
echo "====================================="
# 1. 判断文件是否存在
if [ -e "$file_path" ]; then
echo "${file_path} 已存在"
else
echo "${file_path} 不存在,创建文件..."
touch "$file_path" # 创建文件
fi
# 2. 判断是否为普通文件
if [ -f "$file_path" ]; then
echo "${file_path} 是普通文件"
fi
# 3. 判断是否为目录
if [ -d "$dir_path" ]; then
echo "${dir_path} 是目录"
else
echo "${dir_path} 不是目录,创建目录..."
mkdir -p "$dir_path" # 创建目录
fi
# 4. 判断文件权限
if [ -r "$file_path" ]; then
echo "${file_path} 有读权限"
fi
if [ -w "$file_path" ]; then
echo "${file_path} 有写权限"
fi
if [ -x "$file_path" ]; then
echo "${file_path} 有执行权限"
else
echo "${file_path} 无执行权限,添加权限..."
chmod +rwx "$file_path"
fi
# 5. 判断文件是否非空
if [ -s "$file_path" ]; then
echo "${file_path} 是非空文件"
else
echo "${file_path} 是空文件,写入内容..."
echo "测试内容" > "$file_path"
fi
echo "====================================="
6. 布尔运算符(多条件组合)
用于组合多个条件判断,支持逻辑与、或、非。
| 运算符 | 含义 | 语法示例(结合文件测试) | 说明 |
|---|---|---|---|
-a |
逻辑与(and) | [ -f "$file" -a -r "$file" ] |
文件是普通文件且有读权限 |
-o |
逻辑或(or) | [ -d "$dir" -o -f "$dir" ] |
路径是目录或普通文件 |
! |
逻辑非(not) | [ ! -e "$file" ] |
文件不存在 |
&& |
逻辑与(推荐,[[ ]]中使用) | [[ -f "$file" && -r "$file" ]] |
同-a,更直观 |
| ` | ` | 逻辑或(推荐,[[ ]]中使用) |
案例:布尔运算组合条件
bash
#!/bin/bash
# 布尔运算符演示
file="./test.sh"
echo "====================================="
# 1. -a 逻辑与(文件存在且是普通文件)
if [ -e "$file" -a -f "$file" ]; then
echo "${file} 存在且是普通文件"
fi
# 2. -o 逻辑或(文件存在或目录存在)
dir="./nonexistent_dir"
if [ -e "$file" -o -d "$dir" ]; then
echo "${file} 存在 或 ${dir} 存在"
fi
# 3. ! 逻辑非(文件不存在)
if [ ! -e "$dir" ]; then
echo "${dir} 不存在"
fi
# 4. 推荐用法:[[ ]] + &&/||(支持空格,更易读)
if [[ -f "$file" && -x "$file" ]]; then
echo "${file} 是普通文件且有执行权限"
fi
# 5. 复杂组合(文件存在且(有读权限或有写权限))
if [[ -e "$file" && ( -r "$file" || -w "$file" ) ]]; then
echo "${file} 存在且有读权限或写权限"
fi
echo "====================================="
7. 三目运算(条件判断简化)
Shell的三目运算语法:条件表达式 ? 表达式1 : 表达式2,需结合$((...))(数值)或$([[ ... ]] && ... || ...)(字符串/文件)。
案例:三目运算实战
bash
#!/bin/bash
# 三目运算演示
echo "====================================="
# 1. 数值三目运算($((...)))
a=15
b=20
max=$((a > b ? a : b)) # a>b则取a,否则取b
echo "${a} 和 ${b} 中较大的数是:${max}"
# 2. 字符串三目运算([[ ... ]] && ... || ...)
str="test"
result=$([[ -n "$str" ]] && "非空" || "空字符串")
echo "字符串状态:${result}"
# 3. 文件三目运算
file="./test.sh"
file_status=$([[ -e "$file" ]] && "存在" || "不存在")
echo "${file} 的状态:${file_status}"
# 4. 结合read的三目运算
read -p "请输入分数(0-100):" score
grade=$((score >= 90 ? 1 : (score >= 60 ? 2 : 3)))
case $grade in
1) echo "成绩等级:优秀";;
2) echo "成绩等级:及格";;
3) echo "成绩等级:不及格";;
esac
echo "====================================="
五、流程控制语句
Shell流程控制包括多命令组合、条件判断(if/case)、循环(for/while/until),是脚本逻辑的核心。
1. 多命令的组合执行
通过特定符号连接多个命令,控制执行顺序(顺序、条件、并行)。
| 组合符号 | 含义 | 语法示例 | 说明 |
|---|---|---|---|
; |
顺序执行(忽略前命令结果) | cd /tmp; touch test.txt |
不管cd是否成功,都执行touch |
&& |
逻辑与(前命令成功才执行后命令) | cd /tmp && touch test.txt |
cd成功才执行touch |
| ` | ` | 逻辑或(前命令失败才执行后命令) |
案例:多命令组合
bash
#!/bin/bash
# 多命令组合执行演示
echo "====================================="
# 1. ; 顺序执行(忽略失败)
echo "=== ; 组合 ==="
cd /nonexistent; touch test1.txt # cd失败,但仍执行touch(当前目录创建test1.txt)
ls test1.txt # 存在
# 2. && 逻辑与(前成功后执行)
echo -e "\n=== && 组合 ==="
cd /tmp && touch test2.txt # cd成功,执行touch(/tmp/test2.txt)
ls /tmp/test2.txt # 存在
# 3. || 逻辑或(前失败后执行)
echo -e "\n=== || 组合 ==="
cd /nonexistent || echo "错误:目录不存在,无法切换" # cd失败,输出提示
# 4. 组合使用(&& + ||)
echo -e "\n=== 复杂组合 ==="
read -p "请输入文件名:" filename
touch "$filename" && echo "文件${filename}创建成功" || echo "文件${filename}创建失败"
echo "====================================="
2. 条件语句
2.1 if语句(单条件判断)
语法:
bash
if [ 条件表达式 ]; then
命令1
命令2
fi
或(推荐,支持更复杂条件):
bash
if [[ 条件表达式 ]]; then
命令1
命令2
fi
2.2 if-else语句(二选一条件)
语法:
bash
if [ 条件表达式 ]; then
条件成立时执行的命令
else
条件不成立时执行的命令
fi
2.3 if-elif-else语句(多条件判断)
语法:
bash
if [ 条件1 ]; then
命令1
elif [ 条件2 ]; then
命令2
elif [ 条件3 ]; then
命令3
else
所有条件不成立时执行的命令
fi
2.4 case语句(多值匹配)
适用于变量值为固定几个选项的场景,比if-elif更简洁。
语法:
bash
case $变量名 in
选项1)
命令1
;; # 结束当前分支(必须)
选项2)
命令2
;;
选项3|选项4) # 多个选项匹配同一命令
命令3
;;
*) # 默认选项(所有不匹配的情况)
命令4
;;
esac
案例1:if-elif-else判断文件类型
bash
#!/bin/bash
# if-elif-else演示:判断路径类型
read -p "请输入一个文件/目录路径:" path
echo "====================================="
if [[ -z "$path" ]]; then
echo "错误:路径不能为空!"
elif [[ ! -e "$path" ]]; then
echo "错误:${path} 不存在!"
elif [[ -d "$path" ]]; then
echo "${path} 是一个目录"
echo "目录下的文件:"
ls "$path"
elif [[ -f "$path" ]]; then
echo "${path} 是一个普通文件"
echo "文件大小:$(du -sh "$path" | awk '{print $1}')" # 显示文件大小
elif [[ -L "$path" ]]; then
echo "${path} 是一个符号链接"
echo "链接指向:$(readlink "$path")" # 显示链接目标
else
echo "${path} 是特殊文件(如设备文件、管道等)"
fi
echo "====================================="
案例2:case语句实现菜单交互
bash
#!/bin/bash
# case语句演示:简易菜单
echo "====================================="
echo " 简易系统工具菜单"
echo "1. 查看系统时间"
echo "2. 查看磁盘空间"
echo "3. 查看内存使用"
echo "4. 退出"
echo "====================================="
read -p "请输入选项(1-4):" choice
case $choice in
1)
echo "当前系统时间:"
date
;;
2)
echo "磁盘空间使用情况:"
df -h # 人类可读格式显示磁盘空间
;;
3)
echo "内存使用情况:"
free -h # 人类可读格式显示内存
;;
4)
echo "退出程序,再见!"
exit 0 # 正常退出
;;
*)
echo "错误:无效选项,请输入1-4!"
exit 1 # 错误退出
;;
esac
echo "====================================="
3. 循环结构
3.1 for循环(遍历式循环)
两种语法:
-
语法1(遍历列表):
bashfor 变量名 in 列表; do 命令 done -
语法2(类C风格循环):
bashfor ((初始值; 循环条件; 步长)); do 命令 done
3.2 while循环(条件式循环,条件成立则执行)
语法:
bash
while [ 条件表达式 ]; do
命令
done
3.3 until循环(条件式循环,条件不成立则执行)
语法:
bash
until [ 条件表达式 ]; do
命令
done
3.4 循环控制符
break:跳出当前循环(多层循环需指定层数,如break 2跳出2层)continue:跳过当前循环的剩余命令,直接进入下一次循环
案例1:for循环遍历列表
bash
#!/bin/bash
# for循环演示1:遍历列表
echo "====================================="
# 1. 遍历固定列表
echo "=== 遍历固定列表 ==="
fruits=("苹果" "香蕉" "橙子" "葡萄") # 数组
for fruit in "${fruits[@]}"; do
echo "水果:${fruit}"
done
# 2. 遍历文件列表(当前目录下的.sh文件)
echo -e "\n=== 遍历.sh文件 ==="
for file in *.sh; do
if [[ -f "$file" ]]; then # 确保是文件(避免无.sh文件时匹配*.sh本身)
echo "Shell脚本文件:${file}"
fi
done
# 3. 类C风格循环(1到10求和)
echo -e "\n=== 类C风格循环(1-10求和) ==="
sum=0
for ((i=1; i<=10; i++)); do
sum=$((sum + i))
done
echo "1+2+...+10 = ${sum}" # 输出55
echo "====================================="
案例2:while循环实现计数器与用户交互
bash
#!/bin/bash
# while循环演示
echo "====================================="
# 1. 计数器(1到5)
echo "=== 计数器 ==="
count=1
while [[ $count -le 5 ]]; do
echo "计数:${count}"
count=$((count + 1)) # 自增
sleep 1 # 暂停1秒
done
# 2. 无限循环(用户输入q退出)
echo -e "\n=== 无限循环(输入q退出) ==="
while true; do
read -p "请输入内容(输入q退出):" input
if [[ "$input" == "q" || "$input" == "Q" ]]; then
echo "退出循环,再见!"
break # 跳出循环
fi
echo "你输入的内容是:${input}"
done
# 3. until循环(条件不成立时执行,直到条件成立)
echo -e "\n=== until循环(计数到5) ==="
num=1
until [[ $num -gt 5 ]]; do
echo "until计数:${num}"
num=$((num + 1))
done
echo "====================================="
案例3:循环控制符break/continue
bash
#!/bin/bash
# break/continue演示
echo "====================================="
# 1. break:跳出循环(遍历1-10,遇到5退出)
echo "=== break演示 ==="
for ((i=1; i<=10; i++)); do
if [[ $i -eq 5 ]]; then
echo "遇到5,跳出循环"
break
fi
echo "当前值:${i}"
done
# 2. continue:跳过当前循环(遍历1-10,跳过偶数)
echo -e "\n=== continue演示 ==="
for ((i=1; i<=10; i++)); do
if [[ $((i % 2)) -eq 0 ]]; then # 判断偶数
continue # 跳过偶数,进入下一次循环
fi
echo "奇数:${i}"
done
# 3. break 2:跳出2层循环
echo -e "\n=== break 2演示 ==="
for ((i=1; i<=3; i++)); do
echo "外层循环:${i}"
for ((j=1; j<=3; j++)); do
if [[ $j -eq 2 ]]; then
echo "内层循环j=2,跳出2层循环"
break 2 # 同时跳出内层和外层循环
fi
echo " 内层循环:${j}"
done
done
echo "====================================="
六、函数
Shell函数用于封装重复执行的代码块,提高脚本复用性和可读性。
1. 函数的定义和调用
定义方式(两种等价):
-
方式1(推荐,清晰):
bash函数名() { 函数体(命令/逻辑) } -
方式2(带function关键字):
bashfunction 函数名 { 函数体(命令/逻辑) }
调用方式:
直接输入函数名(无需括号,参数直接跟在后面,用空格分隔)。
案例:函数定义与调用
bash
#!/bin/bash
# 函数定义与调用演示
echo "====================================="
# 定义函数1:无参数无返回值
hello() {
echo "Hello, Shell函数!"
}
# 定义函数2:带参数(计算两个数的和)
add() {
# 函数内的参数通过$1、$2获取(与脚本位置参数相同)
local num1=$1 # local:定义局部变量(仅函数内有效)
local num2=$2
local sum=$((num1 + num2))
echo "${num1} + ${num2} = ${sum}"
}
# 定义函数3:带function关键字
function print_info {
echo "当前系统时间:$(date)"
echo "当前登录用户:$(whoami)"
}
# 调用函数
echo "=== 调用hello函数 ==="
hello
echo -e "\n=== 调用add函数 ==="
add 20 30 # 传递两个参数
add 5 15 # 再次调用
echo -e "\n=== 调用print_info函数 ==="
print_info
echo "====================================="
2. 函数的返回值
Shell函数的返回值有两种方式,注意:return仅支持返回0-255的整数(0成功,非0失败)。
方式1:使用return(返回状态码,0-255)
- 语法:
return 状态码 - 调用后通过
$?获取返回值
方式2:使用echo输出(返回任意值,推荐)
- 函数内用
echo输出结果 - 调用时通过
变量=$(函数名)捕获输出
案例:函数返回值
bash
#!/bin/bash
# 函数返回值演示
echo "====================================="
# 方式1:return返回状态码(0-255)
check_file() {
local file=$1
if [[ -f "$file" ]]; then
return 0 # 成功(文件存在)
else
return 1 # 失败(文件不存在)
fi
}
echo "=== return返回状态码 ==="
check_file "./test.sh"
if [[ $? -eq 0 ]]; then
echo "文件存在"
else
echo "文件不存在"
fi
# 方式2:echo返回任意值(计算阶乘)
factorial() {
local n=$1
local result=1
if [[ $n -lt 0 ]]; then
echo "错误:负数没有阶乘"
return 1 # 状态码表示失败
fi
for ((i=1; i<=n; i++)); do
result=$((result * i))
done
echo $result # 输出结果作为返回值
}
echo -e "\n=== echo返回任意值 ==="
read -p "请输入一个非负整数:" num
fact_result=$(factorial $num) # 捕获echo输出
if [[ $? -eq 0 ]]; then # 判断函数执行状态
echo "${num}的阶乘是:${fact_result}"
else
echo "${fact_result}" # 输出错误信息
fi
echo "====================================="
3. 函数参数
函数参数的传递和获取与脚本位置参数完全一致:
- 传递参数:
函数名 参数1 参数2 ... 参数n - 获取参数:
$1(第一个参数)、$2(第二个)、$#(参数个数)、$@(所有参数)
案例:函数参数实战(批量创建用户)
bash
#!/bin/bash
# 函数参数演示:批量创建用户
echo "====================================="
# 定义函数:创建单个用户
create_user() {
local username=$1
local password=$2
# 检查参数是否完整
if [[ $# -ne 2 ]]; then
echo "错误:函数需要2个参数(用户名、密码)"
return 1
fi
# 检查用户是否已存在
if id -u "$username" &>/dev/null; then # &>/dev/null:屏蔽输出
echo "用户${username}已存在,跳过"
return 0
fi
# 创建用户(-m:创建家目录,-s:指定shell)
sudo useradd -m -s /bin/bash "$username" &>/dev/null
if [[ $? -ne 0 ]]; then
echo "用户${username}创建失败"
return 1
fi
# 设置密码(echo "密码" | passwd --stdin 用户名)
echo "${password}" | sudo passwd --stdin "$username" &>/dev/null
if [[ $? -eq 0 ]]; then
echo "用户${username}创建成功,初始密码:${password}"
else
echo "用户${username}密码设置失败"
sudo userdel -r "$username" # 删除创建失败的用户
return 1
fi
}
# 批量创建用户(调用函数多次)
echo "=== 批量创建用户 ==="
create_user "user1" "123456"
create_user "user2" "654321"
create_user "user3" "abc123"
# 遍历参数创建用户(脚本接收外部参数列表)
echo -e "\n=== 从脚本参数创建用户 ==="
if [[ $# -gt 0 ]]; then
for ((i=1; i<=$#; i+=2)); do
username=${!i} # 间接引用参数(i=1时取$1,i=3时取$3)
password=${!((i+1))}
create_user "$username" "$password"
done
else
echo "未传入脚本参数,跳过批量创建"
fi
echo "====================================="
执行测试(需root权限,或sudo):
bash
chmod +x create_users.sh
# 直接运行(创建user1、user2、user3)
sudo ./create_users.sh
# 从外部参数创建(创建user4、user5)
sudo ./create_users.sh user4 111222 user5 333444
七、管道、重定向与命令扩展
1. 管道(|)
将前一个命令的标准输出 作为后一个命令的标准输入,实现命令协作。
语法:命令1 | 命令2 | 命令3...
常用场景:
- 过滤内容(
grep) - 排序(
sort) - 去重(
uniq) - 统计(
wc)
案例:管道命令实战
bash
#!/bin/bash
# 管道命令演示
echo "====================================="
# 1. 过滤进程(查找bash相关进程)
echo "=== 查找bash进程 ==="
ps aux | grep bash # ps aux列出所有进程,grep过滤含bash的行
# 2. 排序+去重(统计当前目录下文件类型)
echo -e "\n=== 统计文件类型 ==="
ls -l | awk '{print $1}' | sort | uniq -c # 列1是文件类型,排序后去重并计数
# 3. 统计行数(统计/etc/passwd中用户数)
echo -e "\n=== 统计系统用户数 ==="
cat /etc/passwd | wc -l # /etc/passwd每行一个用户,wc -l统计行数
# 4. 复杂管道(查找占用内存前5的进程)
echo -e "\n=== 占用内存前5的进程 ==="
ps aux --sort=-%mem | head -n 6 # --sort=-%mem按内存使用率降序,head取前6行(含表头)
# 5. 过滤日志(假设存在access.log,查找404错误)
echo -e "\n=== 查找404错误日志(模拟) ==="
# 模拟日志内容,过滤含404的行
echo -e "192.168.1.1 - - [01/Jan/2025:12:00:00 +0800] \"GET / HTTP/1.1\" 200 1024" > access.log
echo -e "192.168.1.2 - - [01/Jan/2025:12:01:00 +0800] \"GET /nonexistent HTTP/1.1\" 404 512" >> access.log
cat access.log | grep "404" # 过滤404错误行
rm -f access.log # 清理临时文件
echo "====================================="
2. 重定向
Shell默认有3种I/O流,重定向用于改变流的方向(如输出到文件、从文件读取):
- 标准输入(stdin):
0,默认从键盘输入 - 标准输出(stdout):
1,默认输出到终端 - 标准错误(stderr):
2,默认输出到终端
常用重定向符号:
| 符号 | 含义 | 语法示例 |
|---|---|---|
> |
标准输出重定向(覆盖文件) | ls > file.txt |
>> |
标准输出重定向(追加到文件) | echo "内容" >> file.txt |
< |
标准输入重定向(从文件读取) | read var < file.txt |
2> |
标准错误重定向(覆盖文件) | ls /nonexistent 2> error.log |
2>> |
标准错误重定向(追加到文件) | command 2>> error.log |
&> |
标准输出+标准错误重定向(覆盖) | command &> all.log |
&>> |
标准输出+标准错误重定向(追加) | command &>> all.log |
>/dev/null |
丢弃标准输出(黑洞设备) | command > /dev/null |
2>&1 |
将标准错误重定向到标准输出 | command > all.log 2>&1 |
案例:重定向实战
bash
#!/bin/bash
# 重定向演示
log_file="output.log"
error_file="error.log"
all_file="all.log"
# 清理旧文件
rm -f $log_file $error_file $all_file
echo "====================================="
# 1. > 覆盖输出
echo "=== > 覆盖输出 ==="
ls -l > $log_file # 将ls结果覆盖写入log_file
echo "ls结果已写入${log_file}"
cat $log_file
# 2. >> 追加输出
echo -e "\n=== >> 追加输出 ==="
echo "当前时间:$(date)" >> $log_file # 追加内容到log_file
echo "追加时间后:"
cat $log_file
# 3. 2> 错误重定向
echo -e "\n=== 2> 错误重定向 ==="
ls /nonexistent 2> $error_file # 将错误信息写入error_file
echo "错误信息已写入${error_file}"
cat $error_file
# 4. &> 输出+错误重定向
echo -e "\n=== &> 输出+错误重定向 ==="
ls -l /tmp /nonexistent &> $all_file # 正确输出和错误都写入all_file
echo "所有输出(含错误)已写入${all_file}"
cat $all_file
# 5. < 输入重定向(从文件读取输入)
echo -e "\n=== < 输入重定向 ==="
echo "用户名列表:" > users.txt
echo "user1" >> users.txt
echo "user2" >> users.txt
echo "user3" >> users.txt
echo "从users.txt读取用户名:"
while read username; do
echo "用户名:${username}"
done < users.txt # 从users.txt读取输入
# 6. 丢弃输出(>/dev/null)
echo -e "\n=== 丢弃输出 ==="
ls -l > /dev/null # 不显示ls结果(丢弃)
echo "ls结果已丢弃,无输出"
# 清理临时文件
rm -f $log_file $error_file $all_file users.txt
echo "====================================="
3. 命令续行(\)
当命令过长时,用\(反斜杠)实现换行,Shell会将其视为一行命令。
语法:
bash
长命令部分1 \
长命令部分2 \
长命令部分3
注意:\后面不能有任何字符(包括空格),否则会失效。
案例:命令续行
bash
#!/bin/bash
# 命令续行演示
echo "====================================="
# 长命令换行(查找包含"root"且非注释的行,显示行号)
grep -n "root" /etc/passwd | \
awk -F: '{print "行号:"$1", 用户名:"$1", UID:"$3}' | \
sort -n
echo -e "\n=== 复杂命令续行 ==="
# 统计系统中UID大于1000的用户数
cat /etc/passwd | \
grep -v "^#" | \ # 排除注释行
awk -F: '{if($3 > 1000) print $1}' | \ # 筛选UID>1000的用户
wc -l | \
xargs echo "UID大于1000的用户数:" # xargs去除多余空格
echo "====================================="
4. 命令替换($() 或 ``)
将命令的输出结果作为变量值或命令参数,推荐使用$()(比``更易读,支持嵌套)。
语法:
bash
变量=$(命令)
# 或(兼容旧版本Shell)
变量=`命令`
案例:命令替换实战
bash
#!/bin/bash
# 命令替换演示
echo "====================================="
# 1. 基本命令替换(获取系统信息)
current_date=$(date +"%Y-%m-%d %H:%M:%S") # 获取格式化时间
hostname=$(hostname) # 获取主机名
ip_addr=$(hostname -I | awk '{print $1}') # 获取第一个IP地址
echo "当前时间:${current_date}"
echo "主机名:${hostname}"
echo "IP地址:${ip_addr}"
# 2. 嵌套命令替换(获取当前目录下.sh文件的个数)
sh_count=$(ls -l $(pwd) | grep -c "\.sh$") # 嵌套$(pwd)获取当前目录
echo -e "\n当前目录下.sh脚本文件个数:${sh_count}"
# 3. 命令替换作为参数(备份日志文件)
log_dir="/var/log"
backup_dir="./log_backup_$(date +%Y%m%d)" # 备份目录名含日期
echo -e "\n=== 备份日志 ==="
mkdir -p $backup_dir
cp -r $log_dir/*.log $backup_dir 2>/dev/null # 备份日志,忽略错误
echo "日志已备份到:${backup_dir}"
ls -l $backup_dir
# 4. 兼容写法(``)
disk_usage=`df -h / | awk 'NR==2 {print $5}'` # 获取根目录使用率
echo -e "\n根目录磁盘使用率:${disk_usage}"
# 清理备份目录
rm -rf $backup_dir
echo "====================================="
5. 命令别名(alias)
为常用命令创建简化别名,提高操作效率,分为临时别名(当前终端有效)和永久别名(所有终端有效)。
语法:
bash
# 创建别名
alias 别名="原命令"
# 查看所有别名
alias
# 取消别名
unalias 别名
案例:命令别名实战
bash
#!/bin/bash
# 命令别名演示
echo "====================================="
# 1. 创建临时别名(当前终端有效)
echo "=== 创建临时别名 ==="
alias ll="ls -l" # ll替代ls -l
alias la="ls -la" # la替代ls -la
alias grep="grep --color=auto" # grep结果高亮
echo "执行ll(等价于ls -l):"
ll
echo -e "\n执行la(等价于ls -la):"
la
echo -e "\n执行grep高亮:"
echo "hello world" | grep "world" # 匹配到的world会高亮
# 2. 查看别名
echo -e "\n=== 查看所有别名 ==="
alias
# 3. 取消别名
echo -e "\n=== 取消别名 ==="
unalias ll
# ll # 取消后执行ll会报错(未找到命令)
# 4. 创建永久别名(写入配置文件)
echo -e "\n=== 创建永久别名(需手动执行或重启终端) ==="
echo 'alias ll="ls -l"' >> ~/.bashrc # 写入用户bash配置文件
echo 'alias la="ls -la"' >> ~/.bashrc
echo 'alias grep="grep --color=auto"' >> ~/.bashrc
# 生效配置(无需重启终端)
source ~/.bashrc
echo "永久别名已添加,重启终端或执行source ~/.bashrc生效"
echo "====================================="
八、Linux命令行帮助系统
在Shell编程中,快速获取命令帮助是必备技能,Ubuntu 22.04提供3种核心帮助方式。
1. 使用man命令获取帮助(最全面)
man(manual)是最常用的帮助命令,提供命令的详细手册(语法、选项、示例)。
语法:man [选项] 命令/配置文件
常用选项:
-f 命令:快速查看命令的功能(等价于whatis)-k 关键词:搜索包含关键词的帮助手册(等价于apropos)- 数字:指定手册章节(如
man 2 open查看系统调用open的帮助)
手册章节说明:
| 章节 | 内容类型 | 示例 |
|---|---|---|
| 1 | 用户命令(/bin、/usr/bin) | man 1 ls |
| 2 | 系统调用(内核提供的函数) | man 2 open |
| 3 | 库函数(C标准库等) | man 3 printf |
| 4 | 设备文件(/dev下的文件) | man 4 tty |
| 5 | 配置文件格式 | man 5 passwd(/etc/passwd) |
| 6 | 游戏相关 | man 6 fortune |
| 7 | 杂项(协议、文件系统等) | man 7 regex(正则) |
| 8 | 系统管理命令(/sbin) | man 8 useradd |
案例:man命令使用
bash
#!/bin/bash
# man命令帮助演示
echo "====================================="
# 1. 查看ls命令帮助(默认章节1)
echo "=== 查看ls命令帮助 ==="
man ls # 按q退出帮助页面
# 2. 快速查看命令功能(-f)
echo -e "\n=== 快速查看grep功能 ==="
man -f grep # 等价于whatis grep
# 3. 搜索关键词(-k)
echo -e "\n=== 搜索与磁盘相关的命令 ==="
man -k disk # 等价于apropos disk
# 4. 查看配置文件帮助(章节5)
echo -e "\n=== 查看/etc/passwd配置文件格式 ==="
man 5 passwd
# 5. 查看系统调用帮助(章节2)
echo -e "\n=== 查看open系统调用帮助 ==="
man 2 open
echo "====================================="
2. 使用info命令获取帮助(更详细的文档)
info提供比man更详细的结构化文档,支持超链接跳转,适合深入学习命令。
语法:info 命令
常用操作:
回车:进入超链接n:下一个节点p:上一个节点u:上一级节点q:退出info
案例:info命令使用
bash
#!/bin/bash
# info命令帮助演示
echo "====================================="
# 查看bash的info帮助
echo "=== 查看bash的info帮助(按q退出) ==="
info bash
# 查看grep的info帮助
echo -e "\n=== 查看grep的info帮助 ==="
info grep
# 查看核心工具的info文档
echo -e "\n=== 查看coreutils的info文档(包含ls、cp等命令) ==="
info coreutils
echo "====================================="
3. 使用help选项获取帮助(快速简洁)
大多数命令支持--help或-h选项,提供简洁的语法和常用选项说明,适合快速。