Shell脚本学习笔记
第一章 Shell基础认知
一、什么是Shell?
作为命令解释器和操作系统内核之间的接口,负责解析并执行用户输入的命令。
二、Bash/Zsh的区别
特性 | Bash | Zsh |
---|---|---|
稳定性 | 稳定 | 较稳定,插件过多可能启动慢 |
定制化 | 定制化复杂 | 定制化简单,支持丰富插件 |
版本特性 | 较老旧 | 较新 |
三、脚本文件结构
1. 脚本声明行
通过#!
提示系统选择Shell解释器,需放在脚本第一行。
示例:
bash
#!/bin/bash
2. 注释部分
以#
开头的内容为注释,不参与脚本执行,用于说明代码功能。
示例:
bash
# 这是单行注释
name="John" # 定义用户名变量(行尾注释)
3. 多行注释
通过<<COMMENT
和COMMENT
包裹实现多行注释。
示例:
bash
#!/bin/bash
<<COMMENT
这是第一行注释内容
这是第二行注释内容
可以有多行注释内容在此处
COMMENT
echo "脚本继续执行"
4. 变量定义
用于存储数据(字符串、数字等),等号两边不能有空格 ,引用变量需加$
。
语法:变量名="值"
示例:
bash
name="John" # 字符串变量
age=25 # 数字变量
echo "Hello, $name!" # 引用变量,输出:Hello, John!
5. 函数定义
将特定功能代码封装,方便重复调用,调用时需写函数名。
语法1(带function
关键字):
bash
function 函数名() {
函数体
}
语法2(简化版):
bash
函数名() {
函数体
}
示例:
bash
# 定义函数
function greet() {
echo "Welcome to the script!"
}
# 调用函数(必须调用才执行)
greet
6. 主程序逻辑
脚本核心部分,包含文件操作、条件判断、循环等核心任务。
示例(判断文件是否存在):
bash
# 判断当前目录下是否存在test.txt文件
if [ -f "test.txt" ]; then
echo "File test.txt exists."
else
echo "File test.txt does not exist."
fi
7. 退出状态和返回值
- 退出状态码:
0
表示执行成功,非0
表示失败。 - 通过
exit
指定退出状态码。
完整脚本示例:
bash
#!/bin/bash
# 这是一个完整的Shell脚本示例,用于判断目录是否存在并创建新文件
# 定义变量
dir_name="my_directory"
# 定义函数,用于创建文件
create_file() {
touch "$1/new_file.txt"
echo "File new_file.txt created in $1."
}
# 主程序逻辑
if [ -d "$dir_name" ]; then
echo "Directory $dir_name exists."
create_file "$dir_name"
else
echo "Directory $dir_name does not exist. Creating it..."
mkdir "$dir_name"
create_file "$dir_name"
fi
# 脚本执行成功,返回退出状态码0
exit 0
四、文件权限管理
Shell脚本需添加执行权限才能运行,通过chmod
命令设置:
bash
chmod +x 脚本.sh # 给脚本添加执行权限
五、执行脚本的4种方式
1. 作为可执行程序执行
- 步骤1:添加执行权限(
chmod +x test.sh
) - 步骤2:指定路径执行(当前目录需加
./
)
示例:
bash
./test.sh # 当前目录执行
/home/user/test.sh # 绝对路径执行
2. 使用bash或者sh命令执行
无需添加执行权限,直接通过解释器调用。
示例:
bash
bash test.sh # 使用bash执行
sh test.sh # 使用sh执行(sh通常指向bash)
3. 通过环境变量(PATH)执行
- 步骤1:将脚本目录添加到PATH(
export PATH=$PATH:.
,.
表示当前目录) - 步骤2:直接执行脚本
示例:
bash
export PATH=$PATH:. # 临时添加当前目录到PATH
test.sh # 直接执行脚本
4. 使用source命令执行
在当前Shell环境中执行,脚本内变量/函数会影响当前Shell。
示例:
bash
source 脚本.sh # 方式1
. 脚本.sh # 方式2(.后需加空格)
第二章 变量和运算符
一、变量的定义
1. 基本变量的定义
- 规则:变量名由字母、数字、下划线组成,不能以数字开头,等号两边无空格。
示例:
bash
# 字符串变量
name="John"
# 数字变量
age=25
2. 环境变量的定义
- 作用域:当前Shell及子Shell可访问,通过
export
定义。
示例:
bash
# 定义并导出环境变量
export MY_VARIABLE="Hello, World!"
3. 多组变量定义(数组)
- 存储多个值,元素间用空格分隔,索引从
0
开始。
示例:
bash
# 方式1:直接初始化
fruits=("apple" "banana" "cherry")
# 方式2:按索引赋值
my_array[0]="apple"
my_array[1]="banana"
my_array[2]="cherry"
# 打印数组元素
echo ${my_array[0]} # 输出:apple
echo ${my_array[1]} # 输出:banana
echo ${my_array[2]} # 输出:cherry
二、变量的拼接和引用
1. 基本变量引用
通过$变量名
引用,示例:
bash
name="John"
echo "My name is $name." # 输出:My name is John.
2. 变量值的拼接
直接将变量与字符串拼接,示例:
bash
greeting="Hello"
name="John"
message="$greeting, $name!"
echo $message # 输出:Hello, John!
3. 数组变量的引用
- 单个元素:
${数组名[索引]}
- 整个数组:
${数组名[@]}
或${数组名[*]}
示例:
bash
fruits=("apple" "banana" "cherry")
echo ${fruits[0]} # 输出:apple
echo ${fruits[@]} # 输出:apple banana cherry
4. 变量的间接引用
通过另一个变量的值引用目标变量,语法:${!变量名}
。
示例:
bash
var1="name"
name="John"
echo ${!var1} # 输出:John
5. 变量的作用域
类型 | 定义位置 | 作用域范围 | 关键字 |
---|---|---|---|
局部变量 | 函数内部 | 仅函数内可见 | local |
全局变量 | 函数外部 | 整个脚本可见 | 无 |
示例(局部变量):
bash
function test_function {
local local_var="This is a local variable."
echo $local_var # 函数内可输出
}
test_function
echo $local_var # 函数外输出为空
示例(全局变量):
bash
global_var="This is a global variable."
function test_function {
echo $global_var # 函数内可输出
}
test_function
echo $global_var # 函数外可输出
三、环境变量和局部变量
1. 环境变量
- 作用域:整个系统或特定Shell会话,所有用户/进程可访问。
- 常见环境变量:
PATH
:可执行文件搜索路径HOME
:当前用户主目录LANG
:系统语言和字符集
2. export命令
- 导出局部变量:让子Shell可访问。
示例:
bash
# 局部变量(子Shell不可见)
local_variable="Hello, World!"
bash -c 'echo $local_variable' # 输出空
# 导出为环境变量(子Shell可见)
export global_variable="Hello, Global!"
bash -c 'echo $global_variable' # 输出:Hello, Global!
- 导出函数:
export -f 函数名
,让子Shell可调用。
示例:
bash
# 定义函数
my_function() {
echo "This is a function."
}
# 导出函数
export -f my_function
# 子Shell调用
bash -c 'my_function' # 输出:This is a function.
3. 局部变量
- 使用场景:临时存储数据、控制循环/条件判断。
- 定义:函数内用
local
关键字,示例:
bash
function test_func {
local var="local value" # 局部变量
echo $var
}
四、算数运算
Shell仅支持整数运算,常用方式有3种:
1. $((a+b))
语法简洁,支持+
、-
、*
、/
、%
。
示例:
bash
a=5
b=3
result=$((a + b))
echo "a + b 的结果是: $result" # 输出:a + b 的结果是: 8
2. let
执行算术表达式,变量名无需加$
。
示例:
bash
a=5
b=3
let result=a+b
echo "a + b 的结果是: $result" # 输出:a + b 的结果是: 8
3. expr
外部命令,运算符与操作数间需有空格。
示例:
bash
a=5
b=3
result=$(expr $a + $b)
echo "a + b 的结果是: $result" # 输出:a + b 的结果是: 8
五、字符串操作
1. 字符串拼接
- 直接拼接:
bash
str1="Hello"
str2=" World"
result=$str1$str2
echo $result # 输出:Hello World
- 双引号拼接:
bash
str1="Hello"
str2=" World"
result="$str1$str2"
echo $result # 输出:Hello World
2. 获取字符串长度
语法:${#字符串}
示例:
bash
str="Hello, World!"
length=${#str}
echo "字符串的长度是: $length" # 输出:字符串的长度是: 13
3. 字符串的提取
语法:${string:offset:length}
(offset起始位置,length可选)
示例:
bash
str="Hello, World!"
# 从索引7开始提取
substr1=${str:7}
echo "从索引7开始的子串是: $substr1" # 输出:从索引7开始的子串是: World!
# 从索引0开始,提取长度5的子串
substr2=${str:0:5}
echo "从索引0开始,长度为5的子串是: $substr2" # 输出:从索引0开始,长度为5的子串是: Hello
第三章 控制结构入门
一、条件判断
1. if语句
语法:
bash
if [ 条件判断 ]; then
# 条件为真执行
elif [ 另一个条件判断 ]; then
# 前条件为假,此条件为真执行
else
# 所有条件为假执行
fi
示例:
bash
num=10
if [ $num -gt 20 ]; then
echo "数字大于20"
elif [ $num -gt 5 ]; then
echo "数字大于5但不大于20"
else
echo "数字小于等于5"
fi # 输出:数字大于5但不大于20
2. case语句
用于多分支判断,语法:
bash
case 值 in
模式1)
# 匹配模式1执行
;;
模式2)
# 匹配模式2执行
;;
*)
# 所有模式不匹配执行
;;
esac
示例:
bash
fruit="apple"
case $fruit in
apple)
echo "这是苹果"
;;
banana)
echo "这是香蕉"
;;
*)
echo "未知水果"
;;
esac # 输出:这是苹果
3. test命令
用于数据比较和文件测试,语法:test 条件
(等价于[ 条件 ]
,[]
两边需有空格)。
数值比较
运算符 | 描述 |
---|---|
-eq | 等于 |
-ne | 不等于 |
-gt | 大于 |
-ge | 大于等于 |
-lt | 小于 |
-le | 小于等于 |
字符串比较
运算符 | 描述 |
---|---|
= | 等于 |
!= | 不等于 |
-z | 字符串长度为零 |
-n | 字符串长度不为零 |
文件测试
运算符 | 描述 |
---|---|
-e | 文件/目录存在 |
-f | 文件存在且为普通文件 |
-d | 目录存在 |
-r | 文件可读 |
-w | 文件可写 |
-x | 文件可执行 |
二、"[]" 和 "[[]]" 命令的区别
1. 语法差异
[]
:[
和]
与操作数间需有空格,否则语法错误。
示例:if [ "$a" = "$b" ]; then
[[]]
:语法要求宽松,建议加空格但非强制。
示例:if [[ "$a" = "$b" ]]; then
2. 字符串比较差异
-
[]
:不支持模式匹配,特殊字符需转义。 -
[[]]
:支持模式匹配(如*
、?
),无需转义。
示例:bashstr="abc123" if [[ $str = abc* ]]; then echo "字符串以 abc 开头" # 输出此内容 fi
3. 正则表达式匹配差异
-
[]
:不支持。 -
[[]]
:支持=~
进行正则匹配。
示例:bashstr="abc123" if [[ $str =~ ^abc[0-9]+$ ]]; then echo "字符串符合正则表达式" # 输出此内容 fi
4. 逻辑操作差异
[]
:用-a
(与)、-o
(或)。
示例:if [ $a -gt 5 -a $b -lt 30 ]; then
[[]]
:用&&
(与)、||
(或)。
示例:if [[ $a -gt 5 && $b -lt 30 ]]; then
5. 短路求值差异
[]
:不支持,无论前条件结果,后条件都会求值。[[]]
:支持,前条件可确定结果时,后条件不求值。
6. 兼容性差异
[]
:POSIX标准,兼容所有Shell。[[]]
:Bash扩展,仅支持Bash、Zsh等。
三、基础循环
1. for循环遍历列表
语法:
bash
for 变量名 in 取值列表; do
命令序列
done
示例:
bash
# 遍历数字1-5
for i in {1..5}; do
echo "数字: $i"
done
2. while循环基础用法
语法:
bash
while [ 条件判断 ]; do
# 条件为真执行
命令序列
done
示例:
bash
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
count=$((count + 1)) # 变量自增
done
3. until循环语句
与while
相反,条件为假时执行循环。
语法:
bash
until 条件; do
命令序列
done
第四章 高级控制结构
一、case多分支选择结构
示例(根据输入输出星期):
bash
#!/bin/bash
# 提示用户输入一个数字
echo "请输入一个1 - 7之间的数字:"
read day
# 使用case语句根据输入的数字输出对应的星期几
case $day in
1)
echo "星期一"
;;
2)
echo "星期二"
;;
3)
echo "星期三"
;;
4)
echo "星期四"
;;
5)
echo "星期五"
;;
6)
echo "星期六"
;;
7)
echo "星期日"
;;
*)
echo "输入无效,请输入1 - 7之间的数字。"
;;
esac
二、break循环语句
- 作用:终止循环,
break [n]
可终止n层循环。
示例(for循环中使用):
bash
for i in {1..10}; do
if [ $i -eq 5 ]; then
break # 遇到5终止循环
fi
echo $i
done
echo "Loop finished." # 输出:1 2 3 4 + Loop finished.
示例(while循环中使用):
bash
count=1
while [ $count -le 10 ]; do
if [ $count -eq 3 ]; then
break # 遇到3终止循环
fi
echo $count
((count++))
done
echo "Loop finished." # 输出:1 2 + Loop finished.
三、continue循环语句
- 作用:跳过当前循环剩余部分,直接进入下一次循环。
示例:
bash
for i in {1..10}; do
if [ $i -eq 5 ]; then
continue # 遇到5跳过后续echo
fi
echo $i
done # 输出:1 2 3 4 6 7 8 9 10
四、shell退出码($?)
- 规则:命令执行成功返回
0
,失败返回非0
。 - 查看方式:
$?
获取上一命令的退出码。
示例:
bash
grep "pattern" file.txt
if [ $? -eq 0 ]; then
echo "找到了匹配的内容。"
else
echo "未找到匹配的内容或者出现错误。"
fi
第五章 函数和模块化
一、函数定义和参数传递(1−1-1−n的用法)
- 函数内通过
$1
-$n
获取参数($1
第一个参数,$n
第n个参数)。
示例:
bash
# 定义函数(计算两数之和)
add() {
echo $(( $1 + $2 )) # $1第一个参数,$2第二个参数
}
# 调用函数(传递参数3和5)
result=$(add 3 5)
echo $result # 输出:8
二、返回值处理(return与输出结果的区别)
return
:返回状态码(0
成功,非0
失败),仅支持整数。
示例:
bash
function check_number() {
if [ $1 -gt 10 ]; then
return 0 # 成功
else
return 1 # 失败
fi
}
check_number 15
echo "函数返回状态码: $?" # 输出:0
- 输出结果:通过
echo
输出计算结果,外部用$(函数名)
捕获。
示例:
bash
function add() {
echo $(( $1 + $2 )) # 输出结果
}
result=$(add 2 3)
echo $result # 输出:5
三、局部变量和作用域(local关键字)
local
:函数内定义局部变量,仅函数内可见。
示例:
bash
# 定义函数
function test_function {
local var="I am a local variable" # 局部变量
echo $var
}
# 调用函数
test_function # 输出:I am a local variable
# 函数外部访问
echo $var # 输出空
第六章 输入输出处理
一、shell脚本中的数据流
数据流 | 编号 | 默认来源/目的地 |
---|---|---|
标准输入(stdin) | 0 | 键盘 |
标准输出(stdout) | 1 | 屏幕 |
标准错误(stderr) | 2 | 屏幕 |
二、重定向技巧(>,>>,2>,&>)
1. > 覆盖重定向
覆盖写入文件,示例:
bash
# 将ls结果覆盖写入file_list.txt
ls -l > file_list.txt
2. >> 追加重定向
在文件末尾追加,示例:
bash
# 将日期追加写入log.txt
date >> log.txt
3. 2> 错误输出重定向
将错误信息覆盖写入文件,示例:
bash
# 错误信息写入error.log
cat no_such_file.txt 2> error.log
4. &> 合并输出重定向
合并标准输出和错误,覆盖写入文件,示例:
bash
# 所有输出写入all.log
bash script.sh &> all.log
5. 黑洞文件(/dev/null)
丢弃输出,示例:
bash
# 丢弃所有输出
command &> /dev/null
三、管道的高级应用(多命令组合)
- 管道(
|
):前一命令的stdout作为后一命令的stdin。
示例(筛选并排序文件大小):
bash
du -sh /var/log/* | grep -E "[0-9]+M" | sort -hr
四、用户交互:read -p "提示" var
read -p "提示" var
:显示提示并读取输入到变量。- 常用选项:
-t 秒数
:超时时间-s
:输入不回显(密码)-n 字符数
:读取指定字符数后结束
示例:
bash
# 基础交互
read -p "请输入姓名:" name
echo "你好,$name!"
# 输入密码(不回显)
read -s -p "请输入密码:" pwd
echo -e "\n密码已接收"
第七章 字符串与数组
一、数组
1. 数组的定义
示例:
bash
# 数组定义
arr=(a b c d)
# 访问数组元素(索引从0开始)
echo ${arr[0]} # 输出:a
echo ${arr[2]} # 输出:c
# 访问所有元素
echo ${arr[@]} # 输出:a b c d
echo ${arr[*]} # 输出:a b c d
# 获取数组长度
echo ${#arr[@]} # 输出:4
2. 数组的遍历方式
- 方式1:for循环遍历
bash
for item in "${arr[@]}"; do
echo $item
done
- 方式2:按索引遍历
bash
for ((i=0; i<${#arr[@]}; i++)); do
echo "索引 $i: ${arr[$i]}"
done
3. 数组的扩展和修改
bash
# 添加元素
arr+=(e f) # 数组变为(a b c d e f)
# 修改元素
arr[1]=new # 数组变为(a new c d e f)
# 删除元素
unset arr[2] # 删除索引2的元素
二、字符串模式匹配与截取
1. 字符串长度
示例:
bash
str="hello world"
echo ${#str} # 输出:11
2. # 操作符号(从左侧截取)
bash
path="/usr/local/bin/bash"
# 移除最短匹配的前缀
echo ${path#/*/} # 输出:local/bin/bash
# 移除最长匹配的前缀
echo ${path##/*/} # 输出:bash
3. % 操作符号(从右侧截取)
bash
file="document.txt.bak"
# 移除最短匹配的后缀
echo ${file%.*} # 输出:document.txt
# 移除最长匹配的后缀
echo ${file%%.*} # 输出:document
4. 字符串切片
bash
str="abcdefgh"
echo ${str:2:3} # 输出:cde(索引2开始,截取3个字符)
echo ${str:4} # 输出:efgh(索引4开始,截取到末尾)
实践任务
任务1:开发文件类型统计脚本(分类统计目录下各类文件数量)
bash
#!/bin/bash
# 检查参数
if [ $# -ne 1 ]; then
echo "使用方法: $0 <目录路径>"
exit 1
fi
dir="$1"
if [ ! -d "$dir" ]; then
echo "错误: $dir 不是有效的目录"
exit 1
fi
# 声明关联数组
declare -A type_count
# 遍历目录
for file in "$dir"/*; do
if [ -f "$file" ]; then
filename=$(basename "$file")
# 提取后缀
if [[ "$filename" == *.* ]]; then
ext="${filename##*.}"
ext=$(echo "$ext" | tr 'A-Z' 'a-z') # 转为小写
else
ext="无扩展名"
fi
((type_count["$ext"]++))
fi
done
# 输出结果
echo "目录 $dir 中的文件类型统计:"
echo "=========================="
for ext in "${!type_count[@]}"; do
echo "$ext: ${type_count[$ext]}"
done | sort -k2nr
任务2:编写用户管理系统(通过函数实现增删查功能)
bash
#!/bin/bash
# 声明关联数组
declare -A users
# 加载初始用户
load_users() {
users["admin"]="管理员,admin@example.com,1001"
users["guest"]="访客,guest@example.com,1002"
}
# 显示所有用户
list_users() {
echo "==================== 用户列表 ===================="
echo "用户名 | 姓名 | 邮箱 | UID"
echo "-------------------------------------------------"
for username in "${!users[@]}"; do
IFS=',' read -r name email uid <<< "${users[$username]}"
echo "$username | $name | $email | $uid"
done
echo "================================================="
}
# 添加用户
add_user() {
read -p "请输入用户名: " username
if [ -n "${users[$username]}" ]; then
echo "错误: 用户 $username 已存在"
return 1
fi
read -p "请输入姓名: " name
read -p "请输入邮箱: " email
read -p "请输入UID: " uid
users["$username"]="$name,$email,$uid"
echo "用户 $username 添加成功"
}
# 删除用户
delete_user() {
read -p "请输入要删除的用户名: " username
if [ -z "${users[$username]}" ]; then
echo "错误: 用户 $username 不存在"
return 1
fi
unset users["$username"]
echo "用户 $username 已删除"
}
# 查询用户
search_user() {
read -p "请输入要查询的用户名: " username
if [ -z "${users[$username]}" ]; then
echo "用户 $username 不存在"
return 1
fi
IFS=',' read -r name email uid <<< "${users[$username]}"
echo "==================== 用户详情 ===================="
echo "用户名: $username"
echo "姓名: $name"
echo "邮箱: $email"
echo "UID: $uid"
echo "================================================="
}
# 显示菜单
show_menu() {
echo "=============== 用户管理系统 ==============="
echo "1. 显示所有用户"
echo "2. 添加用户"
echo "3. 删除用户"
echo "4. 查询用户"
echo "5. 退出"
echo "============================================"
read -p "请选择操作 (1-5): " choice
}
# 主程序
main() {
load_users
while true; do
show_menu
case $choice in
1) list_users ;;
2) add_user ;;
3) delete_user ;;
4) search_user ;;
5)
echo "谢谢使用,再见!"
exit 0
;;
*) echo "无效的选择,请重试" ;;
esac
read -p "按回车键继续..."
clear
done
}
# 启动程序
main