前言:自从操作系统诞生以来,相对应的脚本语言就开始随着发展起来,Shell 是操作系统中用户与内核交互的重要工具,它的发展贯穿了计算机历史的多个阶段,随着操作系统的发展逐渐演变成熟,相较于windows的bat脚本,shell拥有更加强大的高级功能,也可以实现许多bat脚本做不到的功能,本文就着重介绍一下shell脚本语法,以供大家共同学习参考。限于篇幅,分成两个文章来解释
1,发展历程
简单介绍一下,也算是增加一下大家的知识面。。。
起源:Unix 和 Bourne Shell (1970s)
-
1969 年 - Unix 系统诞生
Unix 操作系统由贝尔实验室的 Ken Thompson 和 Dennis Ritchie 开发。最早的 Shell 是由 Ken Thompson 开发的 Thompson Shell(sh
),它是一个简单的命令解释器,能运行程序,但功能相对有限。 -
1977 年 - Bourne Shell (sh)
Steve Bourne 在 Unix V7 系统中引入了 Bourne Shell(/bin/sh
)。它成为了第一个功能强大的 Shell,也是现代 Shell 的祖先
扩展:C Shell 和 Korn Shell (1980s)
-
1978 年 - C Shell (csh)
由 Bill Joy 开发,C Shell 引入了类似 C 语言的语法和交互式功能,例如:- 历史命令(命令回溯与重用)。
- 别名(alias)。
- 改进的用户界面交互功能(如
!
命令历史回溯符号)。
-
1983 年 - Korn Shell (ksh)
由 David Korn 开发的 Korn Shell 结合了 Bourne Shell 和 C Shell 的优点:- 完全兼容 Bourne Shell 的脚本。
- 更高效的性能。
- 增加了函数、数组、正则表达式支持。
- 支持命令行编辑(如
vi
风格的命令行操作)
GNU 时代:Bash Shell (1989)
- 1989 年 - GNU Bash (Bourne Again Shell)
作为 GNU 项目的一部分,Brian Fox 开发了 Bash Shell,旨在替代 Bourne Shell,并增加了许多新特性
现代化:Z Shell 和 Fish Shell (1990s - 至今)
-
1990 年 - Z Shell (zsh)
由 Paul Falstad 开发,Z Shell 结合了 Bourne Shell、Korn Shell 和 C Shell 的优点,并引入了现代化的功能 -
2005 年 - Fish Shell (Friendly Interactive Shell)
Fish 是专为用户友好性设计的现代 Shell,强调易用性和直观性
专业应用:PowerShell (Windows 系统)
- 2006 年 - Windows PowerShell
Windows 引入了 PowerShell,专为管理和脚本编程而设计
最新趋势:Shell 与 DevOps 自动化
- 随着 DevOps 和云计算的兴起,Shell 的自动化能力被进一步发掘,尤其在构建、部署和管理系统方面。
- Shell 已逐渐和现代工具(如 Docker、Kubernetes、Ansible 等)结合,通过脚本实现高效的自动化。
2,shell脚本简介
2.1 特点
- 属于弱类型语言,所以定义变量时不需要指定变量类型
- Shell中只有字符串一种变量类型,所有数据实际上都是以字符串形式处理,所有变量和数据都可以看作是字符串;
- 属于解释型语言,编写好的Shell脚本需要指定的shell解释器才能执行
2.2 组成
- 首行的 shebang 机制,指定需要使用的shell解释器类型
- 需要执行的相关指令
- 注释信息
2.3 shell解释器
# 查看当前使用的解释器类型
ubuntu@yyy:~$ echo $SHELL
/bin/bash
# 查看当前系统支持的解释器
ubuntu@yyy:~$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/bin/dash
/usr/bin/dash
1.4 执行脚本
# 方法一:解释器名称 + 脚本路径 ,需要当前用户具有对这个脚本文件的读权限
bash backup.sh
# 方法二:类似于执行命令一样执行脚本,需要当前用户具有对这个脚本文件的读和执行权限;
# 绝对路径
/home/tom/backup.sh
# 相对路径 ./表示在当前目录下
./bash.sh
3,变量
3.1 定义变量
- =的两边不可以有任何的空格
- 变量由数字、字母和下划线组成,且不允许使用数字开头
- 在变量赋值时,无论是否加引号,赋值时变量的值都不会受到影响
3.2 销毁变量
3.3 调用变量
- 当调用变量加上引号的时候
- 当调用变量不加上双引号的时候
变量值会根据当前的 IFS(默认为空格、换行符、制表符)将变量的值进行分割。换行符会被转换为空格,多个连续的空格也会被压缩为一个。如果变量的值中包含通配符(例如 *、? 等),Shell 会将它们扩展为匹配当前目录下的文件名。
ubuntu@yyy:~/test$ ls -1
file1
file2
file3
file4
# 定义变量
ubuntu@yyy:~/test$ NAME=$(ls -1)
# 输出变量
ubuntu@yyy:~/test$ echo $NAME
file1 file2 file3 file4
# 加引号时输出变量
ubuntu@yyy:~/test$ echo "$NAME"
file1
file2
file3
file4
3.4 指定默认变量
#如果 var 没有设置或为空,结果会是默认值 "default_value",但是 var 本身不会被修改
var=""
echo ${var:-"default_value"} # 输出 "default_value" (因为 var 为空)
# 如果var 已经有值且非空,那么输出var 的值
var="Hello"
echo ${var:-"default_value"} # 输出 "Hello" (因为 var 已设置且非空)
var=""
echo ${var:="default_value"} # 输出 "default_value" ,并且 var 的值被设为 "default_value"
var="Hello"
echo ${var:="default_value"} # 输出 "Hello" ,并且 var 的值保持不变
4,数组
4.1 普通数组
- 索引是整数,不能自定义为字符串,并且定义数组时赋值的话,不能手动指定索引,索引是从0开始的连续整数
- 定义数组时指定数组元素,可以使用空格或换行符来做为元素之间的分隔符
PATTERNS=(
"lct_net_manager.tb_err_future_loc_20%"
"lct_net_manager.tb_heartbeat_data_20%"
"lct_net_manager.tb_loc_data_20%"
"lct_net_manager.tb_ori_loc_data_20%"
)
(3)操作普通数组:
# 定义一个数组
fruits=("apple" "banana" "cherry")
# 获取指定索引的值
echo ${fruits[1]} #"banana"
# 修改数组元素
fruits[1]="orange"
# 添加新元素
fruits+=("mango")
# 输出修改后的数组
echo ${fruits[@]} # 输出 apple orange cherry mango
4.2 关联数组
- 数组赋值时,可以给数组的元素加上自定义的索引,而不是默认的整数索引
- 数组赋值的时候需要在方括号 [] 内指定索引(键),否则会报错,不像普通数组一样会自动使用整数作为索引
# 定义关联数组
declare -A array_name
# 向关联数组添加元素
array_name[key]="value"
# 访问关联数组元素
${array_name[key]}
# 获取所有键(keys)
${!array_name[@]}
# 获取所有值(values)
${array_name[@]}
# 获取数组元素的个数
echo ${#array_name[@]} # 数组元素的个数
# 清空数组
unset array_name
# 清空单个数组元素
unset array_name[index]
(4)举例
# 定义关联数组
declare -A capitals
# 添加元素
capitals["USA"]="Washington,D.C."
capitals["France"]="Paris"
capitals["Japan"]="Tokyo"
# 访问元素
echo${capitals["USA"]} # 输出 Washington, D.C.
# 获取所有的键
echo${!capitals[@]} # 输出 USA France Japan
# 获取所有的值
echo${capitals[@]} # 输出 Washington, D.C. Paris Tokyo
4.3 数组总结
5, 字符串
注意:Shell只有字符串这一种类型,所有的变量或变量值都可以看作是字符串
5.1 获取变量值长度
格式:len=${#variable}
例子:
ubuntu@yyy:~$ num=123456789
ubuntu@yyy:~$ len=${#num}
ubuntu@yyy:~$ echo $len
5.2 删除变量值的前缀
- 从字符串开头删除最短匹配:
【pattern解释】固定字符串pattern="file":删除以 file 开头的部分。pattern="txt":删除以 txt 结尾的部分。通配符模式pattern="*file":删除所有以 file 结尾的前缀。pattern="file*":删除所有以 file 开头的前缀。pattern="*abc*":删除包含 abc 的任何部分。多字符通配符pattern="*abc*":表示删除包含 abc 字符串的部分,不论 abc 在字符串的哪个位置。
- 从字符串开头删除最长匹配:
variable="examplefile.txt"
result=${variable#example} # 删除最短匹配的前缀 "example"
echo $result # 输出 "file.txt"
variable="examplefileexample.txt"
result=${variable##example} # 删除最长匹配的前缀 "examplefile"
echo $result # 输出 "example.txt"
5.3 删除变量值的后缀
- 从字符串末尾删除最短匹配:
- 从字符串末尾删除最长匹配:
variable="file.txt"
result=${variable%txt} # 删除最短匹配的后缀 "txt"
echo $result # 输出 "file."
variable="examplefileexample.txt"
result=${variable%%example.txt} # 删除最长匹配的后缀 "examplefileexample.txt"
echo $result # 输出 "file"
# 你也可以使用通配符(如 * 和 ?
variable="file_123abc"
result=${variable#*_} # 删除最短匹配的前缀 "_"
echo $result # 输出 "123abc"
5.4 检查是否包含特定字符串
格式:[[ string == *substring* ]]
variable="Hello, world!"
if [[ $variable == *"world"* ]]; then
echo "字符串包含 'world'"
else
echo "字符串不包含 'world'"
fi
6,函数
6.1 函数的定义
# 格式一
function fun_name()
{
command;
}
# 格式二
function fun_name
{
command;
command;
}
# 格式三
fun_name()
{
command;
command;
}
6.2 函数的传参
- 调用函数时,只需要将参数写在函数名后面即可,使用空格进行分隔
- 函数内部通过Shell预定义的位置变量就可以接收到调用函数时传递的参数
- 函数可以接受多个参数,使用 $1, $2, ..., $N 来引用这些参数
- 使用 $@ 或 $* 来引用所有参数
- $# 可以获取参数的个数
【1 带有默认参数的传参】
#!/bin/bash
greet() {
local name=${1:-"Guest"} # 如果未提供参数,使用 "Guest" 作为默认值
echo "Hello, $name!"
}
greet "Alice" # 输出: Hello, Alice!
greet # 输出: Hello, Guest!
【2 函数的普通传参】
#!/bin/bash
print_args() {
echo "Total arguments: $#"
echo "Arguments: $@"
}
print_args "arg1" "arg2" "arg3"
# 以下的输出为
# Total arguments: 3
# Arguments: arg1 arg2 arg3
6.3 函数的返回值
- 函数可以通过 return 语句返回一个退出状态码(0 到 255 之间的整数),表示函数的执行结果
#!/bin/bash
# 定义一个返回整数的函数
add() {
result=$(( $1 + $2 )) # 计算两个参数的和
return $result # 返回计算结果(0-255之间的整数)
}
add 3 4
echo "The result is $?" # 使用 $? 获取上一个命令的返回值
- 如果你需要返回更复杂的数据(如字符串或浮动值),通常会使用 echo 输出值,而不是 return
#!/bin/bash
# 定义一个返回字符串的函数
greet() {
echo "Hello, $1!"
}
message=$(greet "Alice") # 捕获函数的输出
echo "Message: $message"
6.4 函数的局部变量与全局变量
- 默认情况下,函数内部的变量是 局部的,只在函数内有效
- 使用 local 关键字可以显式地声明局部变量,防止与外部变量冲突
#!/bin/bash
greet() {
local name=$1 # 局部变量
echo "Hello, $name!"
}
name="Global"
greet "Alice" # 输出: Hello, Alice!
echo $name # 输出: Global(函数内的局部变量不影响外部变量)
5.5 函数的终止
- 使用 exit 语句可以在函数内终止脚本的执行(并返回一个退出状态码)
#!/bin/bash
exit_function() {
echo "This is an exit function"
exit 1 # 退出脚本并返回状态码 1
}
exit_function
echo "This line will not be executed" # 不会被执行
7,算数运算
7.1 表达式
- 表达式:编程中的表达式由常量、变量、操作符和函数等组成的代码片段,可以通过计算产生一个值;
- 运算表达式:只包含数字和算术运算符,不包含赋值符号 =,用于执行数学运算。
- 赋值表达式:包含赋值操作符 =的表达式,用于将一个值(或另一个表达式的结果)赋给变量。
- 操作符表达式:包含算术运算符、逻辑运算符、比较运算符等,但不包含赋值运算符 =。主要作用是进行计算、判断条件以及执行逻辑操作,而不涉及赋值操作
7.2 算数运算命令
#############################
# let命令
#############################
#!/bin/bash
a=5
b=3
let sum=a+b # 计算 a + b
echo "Sum: $sum" # 输出 8
let a++ # 自增
echo "After increment: $a" # 输出 6
let a+=10 # 增加 10
echo "After addition: $a" # 输出 16
#############################
# expr命令
# expr 用于执行算术运算,但变量名前必须加$,且需要将运算符用空格分隔
#############################
#!/bin/bash
a=5
b=3
sum=$(expr $a + $b) # 使用 expr 计算 a + b
echo "Sum: $sum" # 输出 8
product=$(expr $a \* $b) # 乘法需要使用 \* 转义
echo "Product: $product" # 输出 15
difference=$(expr $a - $b) # 减法
echo "Difference: $difference" # 输出 2
#############################
# bc命令
# bc 用于执行精确的数学运算,包括浮点数运算。它可以通过管道传递表达式
#############################
#!/bin/bash
a=5
b=3
sum=$(echo "$a + $b" | bc) # 使用 bc 计算 a + b
echo "Sum: $sum" # 输出 8
division=$(echo "scale=2; $a / $b" | bc) # 保留两位小数
echo "Division: $division" # 输出 1.66
sqrt=$(echo "scale=2; sqrt(25)" | bc) # 求平方根
echo "Square root: $sqrt" # 输出 5.00
#############################
# (( ))命令
# (( )) 是 Bash 内建的算术扩展,支持变量直接使用,无需加 $,返回退出状态码
#############################
#!/bin/bash
a=5
b=3
(( sum = a + b )) # 使用 (( )) 计算 a + b
echo "Sum: $sum" # 输出 8
(( a++ )) # 自增
echo "After increment: $a" # 输出 6
if (( a > b )); then # 支持条件判断
echo "a is greater than b"
else
echo "a is less than or equal to b"
fi
#############################
# $(( ))命令
# $(( )) 是另一种 Bash 内建的算术扩展,结果可以直接用于赋值或输出
#############################
#!/bin/bash
a=5
b=3
sum=$(( a + b )) # 直接计算 a + b 并赋值
echo "Sum: $sum" # 输出 8
product=$(( a * b )) # 乘法
echo "Product: $product" # 输出 15
difference=$(( a - b )) # 减法
echo "Difference: $difference" # 输出 2
remainder=$(( a % b )) # 求余
echo "Remainder: $remainder" # 输出 2
8,逻辑运算
- Shell中,没有直接的布尔值类型,不像编程语言那样有 true 和 false 作为专门的布尔数据类型。逻辑判断依赖于命令的退出状态码,即0 表示真,非 0 表示假,
- Shell中,通过 $? 可以获取到上一个命令执行后的命令退出状态码;
8.1 与运算(&&)
#!/bin/bash
a=5
b=3
if (( a > 2 && b > 2 )); then
echo "Both a and b are greater than 2"
else
echo "At least one of a or b is not greater than 2"
fi
8.2 或运算(||)
#!/bin/bash
a=5
b=1
if (( a > 4 || b > 4 )); then
echo "At least one of a or b is greater than 4"
else
echo "Neither a nor b is greater than 4"
fi
8.3 非运算(!)
#!/bin/bash
a=5
if ! (( a > 10 )); then
echo "a is not greater than 10"
else
echo "a is greater than 10"
fi
8.4 [[ ]] 中使用逻辑运算符
- [[ ]] 提供了更强大的条件判断功能,可以支持字符串比较和正则匹配
【1,字符串比较】
#!/bin/bash
str1="hello"
str2="world"
if [[ -n $str1 && $str2 == "world" ]]; then
echo "str1 is not empty and str2 equals 'world'"
else
echo "Condition not met"
fi
【2,文件判断】
#!/bin/bash
file="test.txt"
if [[ -e $file && ! -d $file ]]; then
echo "$file exists and is not a directory"
else
echo "$file does not exist or is a directory"
fi