Shell语言编程(炼气)
1. Shell脚本执行方式
执行方式 | 应用及场景 |
---|---|
通过sh或bash | 书写脚本后,最常用的方式,在其他非红帽系统中,建议使用bash运行脚本 |
通过.点或source | 加载/生效配置文件(环境变量,别名);常用:可以用来实现include功能,把其他脚本引入到当前脚本中 |
通过相对或绝对路径 | 一般不推荐使用这种,系统脚本/服务使用脚本(加上执行权限) |
输入重定向符号 | 不推荐使用 |
2. 变量
2.1 变量类型
分类 | 说明 |
---|---|
普通变量(局部变量) | 在shell脚本中通过name=value的方式创建的变量 |
环境变量(全局变量) | 一般是系统创建的,如PATH, PS1... |
特殊变量 | shell脚本命令,如:$? |
2.1.1 普通变量
普通变量赋值、引用、删除
#!/bin/bash
# 赋值
name="John"
# 引用
echo "Hello, $name"
# 删除
unset name
设置只读变量
# 将name变量设置为只读,其值将不能被修改
readonly name
2.1.2 环境变量
环境变量相关命令
- env
- declare
- export
常见的系统环境变量:
环境变量名称 | 含义 | 使用场景 |
---|---|---|
HOME | 指向当前用户的主目录的路径 | 用于脚本中引用用户主目录的路径 |
USER/LOGNAME | 当前用户的用户名 | 用于获取当前脚本运行的用户的用户名 |
PATH | 指定在哪些目录中查找可执行文件 | 用于添加自定义脚本或程序的路径,一遍shell能够直接执行这些脚本或程序 |
PWD | 当前工作目录的路径 | 在脚本中获取当前工作目录的路径 |
SHELL | 当前用户使用的shell的路径 | 确定当前shell的类型以及路径 |
IFS | 输入字段分隔符 | 通常用于查找当前进程的父进程 |
PPID | 父进程的进程ID | 通常用于查找当前进程的父进程 |
RANDOM | 一个随机数 | 常用于生成随机数或在脚本中引入一些随机性 |
$# | 传递给脚本的参数个数 | 用于获取脚本中接收到的参数个数 |
环境变量相关文件和目录
文件或目录 | 说明 | 使用场景 |
---|---|---|
/etc/profile | 包含系统范围的全局shell配置,其中可以设置系统范围的环境变量 | |
~/.bashrc | 这是用户的Bash Shell的启动文件 | ,通常用于设置用户级别的Shell配置和环境变量。 |
2.1.3 特殊变量
变量名称 | 说明 | 使用场景 |
---|---|---|
$0 | 表示脚本本身的名称 | 常用于脚本中获取脚本的名称 |
$1,$2,... | 表示接受到的参数,$1表示第一个参数,$2表示第二个参数,以此类推 | 用于处理脚本中的输入参数 |
$# | 表示传递给脚本或函数的参数个数 | 常用于检查传递给脚本的参数数量是否符合期望 |
$*和$@ | $*表示所有参数作为单个字符串,$@表示所有参数分别被引用 | 用于处理脚本中的所有参数 |
$? | 表示上一个命令的退出状态或返回值 | 通常用于检查命令是否成功执行 |
$$ | 表示当前shell进程的进程id | 常用于创建临时文件名或进行进程控制 |
$! | 表示后台运行的最后一个进程的进程id | 常用于监控后台任务的执行状态 |
2.2 变量命名规则
- 大部分编程语言通用规则:只能包含字母、数字、下划线,且不能以数字开头,区分大小写,不能与shell中的关键字如(if,then,else,for,while等)同名,使用大写字母表示常量或任何可能导出到环境中的变量(如:PI=3.14)
- shell语言特有规则:变量名和等号之间不能有空格,必须是:name=value的形式,而不是name = value
- 函数名由全小写单词和数字构成,单词间以下划线分隔。函数名之前必须要有关键词function,函数名之后必须有圆括号且中间没有空格。如:function show_time()
2.3 变量引用
引用方式 | 说明 |
---|---|
变量前使用美元符号 | 示例:$var,适用于大部分情况下获取变量的值 |
变量前使用美元符号+大括号 | 示例:${var},用以明确变量的边界,适用于在变量名后面直接接非变量字符时,以明确变量名的结束 |
变量前使用``(反引号) | 示例:`command`,用于执行命令,并将命令的输出赋值给变量 |
变量前使用$() | 示例:$(command),与反引号类似,用于执行命令并将输出复制给变量,适用于执行命令并获取其输出的现代方式,优于反引号的使用 |
变量使用双引号"" | 示例:"$var",用于保留变量的原始值,防止变量值中的空格或特殊字符被解释 |
变量使用单引号'' | 示例:'$var',用于保持引号内文本的字面值,不对其中的变量进行替换 |
变量前使用@符号 | 示例:${array[@]},用于引用数组中的所有元素。适用于处理数组中的多个元素 |
3. 文档与注释
3.1 Here文档
Here文档(Here Document)是一种在Shell脚本中用来传递多行输入的方法。通过Here文档,您可以在Shell脚本中直接嵌入多行文本、命令或代码块,而无需使用临时文件或多行字符串拼接的方式。Here文档通常使用<<操作符来定义开始标识符和结束标识符。
Here文档的语法如下:
#!/bin/bash
command <<DELIMITER
Text or commands
to be passed
as input
DELIMITER
Here文档的作用和用途包括:
- 传递多行输入:Here文档允许您在Shell脚本中传递多行文本或命令,方便处理需要大段输入的情况。
- 嵌入代码块:您可以将需要执行的代码块直接嵌入到脚本中,避免在脚本中大量使用echo或拼接字符串。
- 生成配置文件:通过Here文档,您可以方便地生成配置文件或其他文本文件,将需要的内容直接嵌入到Shell脚本中。
- 传递输入给命令:Here文档也可以用于将输入传递给需要交互式输入的命令或程序。
- 注释:在Shell脚本中,您可以使用Here文档来暂时注释掉一段代码,实现类似多行注释的效果。
3.2 单行注释
shell中单行注释与python一样,以#
开头
3.3 多行注释
3.2.1 使用here文档
<<COMMENT
This is a
multi-line comment block.
It can span across multiple lines.
COMMENT
注意:here文档中的标识符(即开始和结束标记)可以是用户自定义的任何合法标识符
3.2.2 使用空命令:
: '
This is a
multi-line comment block.
It can span across multiple lines.
'
4. 运算符
4.1 算术运算符(命令)
常见的几种算数运算方式(按推荐指数排序):
算术运算方式 | 说明 |
---|---|
$(()) | 最常用,语法简洁,执行效率高。 |
let | 次常用,语法灵活,执行效率高。 |
expr | 早期使用较多,语法繁琐,效率较低 |
bc | 适用于浮点运算,语法复杂,效率较低 |
awk | 适用于复杂计算,语法灵活,效率较低 |
4.1.1 通过$(())命令实现算术运算
result=$((5+3))
echo $result # 输出 8
4.1.2 通过let命令实现算术运算
let result=5+3
echo $result # 输出 8
4.1.3 通过expr命令实现算术运算
result=$(expr 5 + 3)
echo $result # 输出 8
4.1.4 通过bc命令实现浮点运算
a=5.5
b=3.2
result=$(echo "$a + $b" | bc)
echo $result # 输出 8.7
4.1.5 通过awk命令实现算术运算
awk 是一个强大的文本处理工具,但它也可以用来进行算术运算。awk 支持各种基本的算术运算符和一些高级的数学函数,如幂运算,平方根,三角函数运算等
a=5
b=3
result=$(awk "BEGIN {print $a + $b}")
echo $result # 输出 8
4.2 关系运算符
运算符 | 说明 |
---|---|
-eq | 等于(==),即:equal |
-ne | 不等于(!=),即:not equal |
-gt | 大于(>), 即:greater than |
-lt | 小于(<),即:less than |
-ge | 大于等于(>=),即:greater equal |
-le | 小于等于(<=),即:less equal |
注意:关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
4.3 逻辑运算符
旧版:
运算符 | 说明 |
---|---|
! | 非运算 |
-o | 或运算(or) |
-a | 与运算(and) |
新版:
运算符 | 说明 |
---|---|
! | 非运算 |
|| | 或运算(or) |
&& | 与运算(and) |
4.4 文件检测运算符
运算符 | 说明 |
---|---|
-b file | 检测文件是否是块设备 |
-c file | 检测文件是否是字符设备文件 |
-d file | 检测文件是否是目录 |
-f file | 检测文件是否是普通文件(既不是目录,也不是块设备或字符设备文件) |
-g file | 检测文件是否设置了SGID位 |
-k file | 检测文件是否设置了粘着位(Sticky Bit) |
-p file | 检测文件是否是有名管道 |
-d file | 检测文件是否设置了SUID位 |
-r file | 检测文件是否可读 |
-w file | 检测文件是否可写 |
-x file | 检测文件是否可执行 |
-s file | 检测文件是否不为空(文件大小是否大于0) |
-e file | 检测文件(包括目录)是否存在 |
-S file | 检测文件是否socket |
-L file | 检测文件是否存在并且是一个符号链接 |
4.5 自增和自减运算
- 使用$(())进行自增/自减运算
num=0 num=$((num + 1)) num=$((num - 1)) echo $num
- 使用(())进行自增/自减运算
num=0 ((num++)) ((num--)) echo $num
- 使用let命令进行自增/自减运算
num=0 let num++ let num-- echo $num
- 使用expr进行自增/自减运算
num=0 num=$(expr $num + 1) num=$(expr $num - 1) echo $num
5. Shell字符串处理
5.1 Shell子串用法总结
语法 | 说明 |
---|---|
${string} |
获取字符串变量值 |
${#string} |
获取字符串长度 |
${string:start} |
提取从位置start开始到结束的子字符串 |
${string:start:length} |
提取从位置start开始提取长度为length的子字符串 |
${string#pattern} |
删除字符串开头最短匹配 pattern 的部分 |
${string##pattern} |
删除字符串开头最长匹配 pattern 的部分 |
${string%pattern} |
删除字符串结尾最短匹配 pattern 的部分 |
${string%%pattern} |
删除字符串结尾最长匹配 pattern 的部分 |
${string/pattern/replacement} |
将字符串中符合pattern的第一个子串替换为replacement |
${string//pattern/replacement} |
将字符串中符合pattern的所有子串替换为replacement |
5.2 字符串基本操作
5.2.1 字符串赋值
str="Hello, World!"
5.2.2 字符串拼接
greeting="Hello, "
name="Alice"
message=$greeting$name
5.2.3 字符串比较
str1="hello"
str2="world"
if [ $str1 = $str2 ]; then
echo "Strings are equal"
else
echo "Strings are not equal"
fi
5.2.4 字符串空值判断
str=""
if [ -z $str ]; then
echo "String is empty"
fi
5.2.5 字符串提取
- 提取从位置position开始到结束的子字符串
语法:${string:position:length}
str="Hello, World!" echo ${str:7} # 输出: "World!" echo ${str:7:5} # 输出: "World"
- 提取从位置position开始提取长度为length的子字符串
语法:${string:position:length}
str="Hello, World!" echo ${str:7} # 输出: "World!" echo ${str:7:5} # 输出: "World"
5.2.6 字符串截取
- 从字符串开头截取
- 最短匹配
语法:${string#pattern}
str="Hello, World!" echo ${str#Hello,} # 输出: " World!"
- 最长匹配
语法:${string##pattern}
str="Hello, World!" echo ${str##H*o} # 输出: "rld!"
- 最短匹配
- 从字符串结尾截取
- 最短匹配
语法:${string%pattern}
str="Hello, World!" echo ${str%World!} # 输出: "Hello, "
- 最长匹配
语法:${string%%pattern}
str="Hello, World!" echo ${str%%W*ld!} # 输出: "Hello, "
- 最短匹配
5.2.7 字符串替换
str="Hello, World!"
5.2.8 获取字符串长度
str="Hello, World!"
echo ${#str} # 输出字符串长度: 13
6. 数组
6.1 数组定义
6.1.1 直接定义
#!/bin/bash
array=(value1 value2 value3)
6.1.2 使用declare定义数组
# 情形1:声明的同时进行初始化
declare -a array=(value1 value2 value3)
# 情形2:先声明,再赋值
declare -a array
array[0]=value1
array[1]=value2
使用declare进行数组定义的好处:
- 明确性:declare -a 明确指定了变量是一个数组,增强了代码的可读性和可维护性。其他开发者阅读代码时可以立即知道这是一个数组。
- 类型检查:虽然 Shell 脚本本身是弱类型的,但 declare -a 可以防止意外地将数组变量用作其他类型的变量。例如,尝试将字符串赋值给一个已经声明为数组的变量会引发错误。
- 初始化:declare -a 允许在声明时初始化数组,使得代码更加简洁和清晰。
- 范围控制:在函数中使用 declare -a 可以创建局部数组变量,避免与全局变量冲突。
6.2 访问数组元素
declare -a array=(value1 value2 value3)
# 访问单个元素
echo ${array[0]} # 输出 value1
# 访问所有元素
echo ${array[@]} # 输出 value1 value2 value3
# 访问数组子集
echo ${array[@]:1:2} # 输出 value2 value3
6.3 获取数组长度
declare -a array=(value1 value2 value3)
# 获取数组长度
echo ${#array[@]} # 输出 3
# 获取数组中单个元素的长度
echo ${#array[0]} # 输出 value1 的字符数
6.4 修改数组
6.4.1 添加元素
array+=(value4) # 在数组末尾添加 value4
6.4.2 删除元素
unset array[0] # 删除索引为 0 的元素
6.4.3 修改元素
???
6.5 遍历数组
6.5.1 使用for循环遍历
#!/bin/bash
declare -a array=(value1 value2 value3)
for element in "${array[@]}"; do
echo $element
done
6.5.2 使用索引遍历
for i in "${!array[@]}"; do
echo "Index: $i, Value: ${array[$i]}"
done
6.6 多维数组
Shell本身并不支持多维数组,但是可以通过模拟来实现
# 模拟二维数组
array[0]="row1_col1 row1_col2"
array[1]="row2_col1 row2_col2"
# 访问元素
row1=(${array[0]})
echo ${row1[0]} # 输出 row1_col1
6.7 字符串分割成数组
IFS=' ' read -r -a array <<< "value1 value2 value3"
echo ${array[0]} # 输出 value1
7. 流程控制
7.1 分支
7.1.1 if分支
基本语法:
if [ condition ]; then
# 执行的命令
elif [ another_condition ]; then
# 另一种情况下的命令
else
# 其他情况下的命令
fi
示例:
age=20
if [ $age -lt 18 ]; then
echo "未成年"
elif [ $age -ge 18 ] && [ $age -lt 60 ]; then
echo "成年"
else
echo "老年"
fi
7.1.2 case分支
基本语法:
case $variable in
pattern1)
# 执行的命令
;;
pattern2)
# 执行的命令
;;
*)
# 默认情况下的命令
;;
esac
示例:
choice="apple"
case $choice in
apple)
echo "选择了苹果"
;;
banana)
echo "选择了香蕉"
;;
*)
echo "选择了其他水果"
;;
esac
7.2 循环
7.2.1 for循环
基本语法:
for variable in list; do
# 执行的命令
done
示例:
# 一般遍历方式
for i in 1 2 3 4 5; do
echo "数字: $i"
done
# C语言风格
for ((i=0; i<5; i++)); do
echo "数字: $i"
done
7.2.2 while循环
基本语法:
while [ condition ]; do
# 执行的命令
done
示例:
count=1
while [ $count -le 5 ]; do
echo "计数: $count"
((count++))
done
7.2.3 until循环
基本语法:
until [ condition ]; do
# 执行的命令
done
示例:
count=1
until [ $count -gt 5 ]; do
echo "计数: $count"
((count++))
done
8. 函数
8.1 函数定义
- 使用function关键字
function my_function { # 函数体 }
- 直接使用函数名
my_function() { # 函数体 }
8.2 函数调用
# 直接通过函数名调用
my_function
8.3 参数传递
# $1, $2, $3等表示传递给函数第n个参数,$# 表示传递给函数的参数个数,$@ 和 $* 分别表示所有参数的列表
my_function() {
echo "First argument: $1"
echo "Second argument: $2"
echo "Number of arguments: $#"
echo "All arguments: $@"
}
my_function arg1 arg2 arg3
8.4 返回值
函数可以通过 return 语句返回一个整数值,通常用于表示函数的执行状态。返回值范围是 0 到 255,其中 0 通常表示成功,非零值表示失败
my_function() {
echo "This is a function"
return 0
}
my_function
echo "Return value: $?"
8.5 局部变量
在函数内部,可以使用 local 或 declare 关键字来定义局部变量,这些变量的作用域仅限于函数内部。
my_function() {
local local_var="I am local"
echo "Inside function: $local_var"
}
my_function
echo "Outside function: ${local_var:-'Not defined'}"
8.6 全局变量
如果在函数内部定义的变量没有使用 local 或 declare 关键字,那么这些变量将是全局变量。
my_function() {
global_var="I am global"
}
my_function
echo "Global variable: $global_var"
8.7 函数嵌套
# 支持但不推荐
outer_function() {
inner_function() {
echo "Inner function"
}
inner_function
}
outer_function
8.8 默认参数
${parameter:-word}
: 如果 parameter 未设置或为空,则使用 word 的值作为默认值。${parameter:=word}
: 如果 parameter 未设置或为空,则将 word 赋值给 parameter 并使用其值。
8.9 参数替换
${parameter:+word}
: 如果 parameter 已设置且非空,则使用 word 的值;否则,结果为空字符串。${parameter:?word}
: 如果 parameter 未设置或为空,则输出错误信息 word 并退出脚本。
9. 重定向
大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回到您的终端。一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端。
标准文件描述符:
- 标准输入 (stdin):文件描述符 0,默认从键盘输入。
- 标准输出 (stdout):文件描述符 1,默认输出到终端。
- 标准错误 (stderr):文件描述符 2,默认输出到终端。
9.1 输出重定向
- 以覆盖方式重定向标准输出到文件
# 将 command 的标准输出重定向到 file。如果 file 不存在,则创建它;如果存在,则覆盖它
command > file
- 以追加方式重定向标准输出到文件
# 将 command 的标准输出追加到 file。如果 file 不存在,则创建它
command >> file
- 以覆盖方式重定向标准错误到文件
# 将 command 的标准错误重定向到 file。如果 file 不存在,则创建它;如果存在,则覆盖它
command 2> file
- 以追加方式重定向标准错误到文件
# 将 command 的标准错误追加到 file。如果 file 不存在,则创建它;如果存在,则覆盖它
command 2>> file
- 同时重定向标准输出和标准错误到同一个文件
command &> file
或
command > file 2>&1
9.2 输入重定向
- 从文件读取标准输入
# 将 file 的内容作为 command 的标准输入
command < file
- 从单行文本读取标准输入(Here String)
command <<< "string"
- 从多行文本读取标准输入
command << EOF
line1
line2
...
EOF