Shell编程
Shell基础
大多数Linux发行版不安装桌面环境,需要使用Shell管理操作系统。
Shell是一个命令解释器,一次命令交互的过程为:Shell将用户输入的命令解释称Linux内核能够识别的命令,传递给内核执行,内核返回执行结果给Shell,Shell显示输出结果。
Shell分类:
- GUI图形界面Shell
- CLI命令行Shell 通常所说的Shell是这种
Shell命令的各种组合形成了Shell脚本(Shell Scipt)。Shell脚本的主要内容就是各种命令,每个命令可以单独执行,经过一系列操作符的连接、判断、循环形成了新的编程语言。
Shell脚本中的‘’hello world‘’
创建并执行Shell脚本的基本流程:
- 文本编译器创建 .sh文件
- 写入代码
- 添加执行权限
- 执行
变量
Shell不严格区分变量类型,变量类型取决于内存中存储的内容。在第一次给变量赋值时决定变量类型。同时,未声明类型的变量,在赋值时会自动进行转换。
变量声明
变量赋值
-
使用
=
赋值变量名=变量值
-
read交互变量赋值
read 变量名 # 执行后即可在终端输入变量值
变量引用
语法
$变量名 # 最简便
${变量名} # 适用在变量名之后还有其他字符在
"$变量名" # 避免变量值中空格的干扰
"${变量名}" # 避免变量值中空格的干扰
变量分类
-
内部变量 如
?
-
环境变量
每个操作系统都会有默认的变量,称为环境变量。可以使用export查看
-
参数变量
参数变量是用户在代码编写过程中自定义的变量。
Shell中的引号
在Shell中,对字符串并没有严格的引号要求。如果字符串中包含有空格,那么就需要经字符串包含到引号中去。因为空格后的字符可能被当做命令的选项或参数,可能会出错。
-
示例,定义变量并赋值含有空格的字符串
【注意】双引号之中对变量的引用能够正常的输出变量值;单引号之中对变量的引用不能正常的输出变量值。
所以有时不想引用变量值可以使用单引号。
变量的间接引用
间接引用,是指某个变量的值是另外一个变量的变量名的情况。Shell使用!
操作符实现间接引用。
-
示例 变量的间接引用
命令别名
在Shell的使用过程中,很多时候会遇到很长的命令。可以起别名进行替换。
语法
alias newname=command
-
示例
运算符
算术运算符
Shell中常用的算术运算符
++、--
+、-
*
/
%
-
示例
位运算符
Shell中常见的位运算符
~ # 按位取反
<<、>> # 左移、右移
& # 按位与
^ # 按位异或
| # 按位或
逻辑运算符
使用逻辑运算符可以实现多个条件的测试。
-a # 逻辑与
-o # 逻辑或
! # 逻辑非
&& # 逻辑与
|| # 逻辑或
-
示例 测试逻辑元素运算符
三元运算符
三元运算符?
,语法
表达式1 ? 表达式2 : 表达式3
# 表达式1 条件判断
# 如果未真 输出表达式2
# 如果为假 输出表达式3
三元运算符需要三个表达式。
赋值运算符
注意赋值运算符左右两侧不可以有空格
运算符优先级
let命令
let是Shell中的内部命令,功能是计算一个算术表达式。
语法
let 表达式
-
示例 使用let进行计算
其他常见表达式
除了let命令外,还有由符号组成的表达式运算操作,这些语法更常用更方便
语法
$((表达式))
$[表达式]
条件测试
在Shell中通过成条件判断为条件测试。
条件测试语法
Shell支持三种条件测试语法
test conditicon # test内部命令,检测表达式是否为真,返回TRUE or False
[ condition ] # 常用,注意条件左右距离 [] 有一个空格
[[ condition ]] # 比第二种更严谨,第二种有时候会出错
文件测试
文件测试主要是判断
- 文件是否存在
- 文件属性是否满足条件
常用操作如下:
-d file # 检测是否是目录,如果是。返回true
-f file # 检测是否是文件,如果是。返回true
-r file # 检测文件是否可读,如果是,返回true
-w file # 检测文件是否可写,如果是,返回true
-x file # 检测文件是否可执行,如果是,返回true
-s file # 检测文件是否为空,不为空返回true
-e file # 检测文件(包括目录)是否存在,如果存在返回true
【注意】在条件测试时,0为真、1为假,与大多数编程语言相反。
- 示例,判断某个文件是否为目录
[-e pp]命令的返回结果 自动复制给?
操作符,(?
操作符是一个特殊变量,作用是保存上一次命令执行的返回值)
,适用$
操作符引用并输出结果。
此时的输出结果为0,也就是真值,pp文件时一个目录。
字符串测试
常用的字符串操作符如下
< # ASCII顺序,前面的字符串小于后面的字符串为真
> # 大于
== # 字符串完全相同为真
= # 字符串相等为真
=~ # 前面的字符串包含后面的字符串为真
!= # 字符串不相等为真
-z # 字符串为空则为真
-m # 字符串非空为真
【注意】以上所有操作符在使用时,必须两侧都有空格才可以。在赋值时,两侧都不可以有空格。
- 示例 测试字符串比较操作符
-
示例 对比两个字符串类型的变量
整数值的测试
Shell对整数值的测试是使用字符操作符,而不是常用的算术运算符。
常用的操作符如下:
-eq # 两个数相等为真
-ge # 前者大于等于后者为真
-gt # 前者大于后者为真
-le # 前者小于等于后者为真
-lt # 前者小于后者为真
-ne # 两个数不相等为真
-
示例 整数值测试
分支语句
if语句
if语法
if [条件测试];then +
条件测试为真时执行语句
elif [条件测试];then
条件测试为真时执行语句
else 上述测试都为假时执行该语句
fi
同上,shell脚本中每一行一条语句即可执行。如果想将多条语句写在同一行,需要使用;
分割。
-
示例,if判断pp文件是否存在
![image-20230203174751984](2023-02-03-Shell.assets/image-20230203174751984.png
case语句
if结构中,如果条件测试多于一个,可以用elif进行判断,但是当条件测试太多了,结构会太复杂。可以使用case结构处理多分支的情况。
case语法
case "变量值" in
"var1")
语句
;;
"var2")
语句
;;
"var3")
语句
;;
*)
语句
;;
esac
变量值为需要判断的值,var1-var3为匹配条件,case结构在执行时,Shell会一次将变量值与后续的匹配条件进行匹配。如果有匹配,则执行匹配后的语句,如果全部不匹配,则执行*代表的分支,如果没有*分支,则直接退出执行。
case结构中,行尾必须是in
关键词,每个匹配条件后必须有一个)
,每个条件分支都有两个连续的;
,表示分支结束。
- 示例,case判断字符串
case匹配中,可以使用正则表达式。
-
示例,使用正则表达式的case匹配
![image-20230203181001936](2023-02-03-Shell.assets/image-20230203181001936.png
注意:正则表达式
[]
左右和-
均没有空格
循环语句
Shell支持多种循环结构,包括常用的for、while
,还有until
等
for循环
for循环语法
for var in list
do
循环语句
done
# var循环变量
# list元素组合 通常是数组变量
在执行for循环时,每次循环过程都将list中的相应位置的元素赋值给var,之后执行do…done之间的循环语句。var变量的值可以在循环语句中使用。
-
示例 for输出数组中的元素
while循环
理论上,for循环能够实现的while循环基本可以实现。
while语法
while 表达式 # 表达式为条件测试,测试结果为真执行循环语句,结果为假不执行.注意避免死循环
do
循环语句
done
如果表达式的结果一直为真,那么会一直执行,除以避免死循环
-
示例 while循环输出数组内容
while可以结合while实现与用户的交互。
until循环
until循环时条件测试为假时才执行。
语法
until 表达式 # 表达式为假时,执行do...done循环语句
do
循环语句
done
select循环
select是一个可以和用户进行交互的循环结构,在命令行Shell中非常实用。select循环结构中数组和变量是必须的。
语法
select var in list
do
循环语句
done
# var 必须的变量,由用户输入
# list 数组
注意,select结构是死循环,需要使用break、exit或Ctrl+C组合键等方法退出脚本
-
示例,select提供菜单选型
continue和break语句
continue结束本次循环
break结束整个循环
数组
定义数组
和变量的定义类似。在定义数组时,可以先声明用declare -a
数组,也可以不声明。
- 示例 定义数组
-
示例 unset销毁数组
数组占用空间,可以使用unset小会,可以一次销毁整个数组,也可以销毁其中一个值。
获取数据长度
Shell中使用#
操作符来获取数组的长度,可以使用两种方法进行获取。
语法
# 查看整个数组长度
${#array[@]}
${#array[*]}
# 查看变量长度
${#x}
# 查看数组中一个元素的长度
${#array[n]}
-
示例 使用
#
操作符获取数组长度 -
示例 使用
#
操作符获取变量长度 -
获取数组中某一位置的数据长度
数组切片
数组切片是获得数组中的部分元素。
语法
array1=${array[@]:m:n} # 获得原始array数组从m开始的n个元素
array1=${array[*]:m:n}
m和n可以忽略,可正可负。
注意,在赋值时,注意销毁之前定义的重名变量。不然会出错
数组字符串替换
通常,在数组中修改元素值,需要重新赋值。而在Shell中提供了一种简单的方式来替换数组元素中的字符串。
语法
${array[@]/from/to}
${array[@]/from/}
array是要替换元素的数组名称;from是数组元素中原来存在的字符串;to是需要替换的新的字符串。如果没有to参数,则删除from指定的内容。
-
示例 数组元素值的替换
关联数组
Shell中还有一种被称为关联数组的数组形式。普通数组使用数字作为索引,而关联数组的Index使用字符串作为索引,通过索引与值一一相对应。
语法
ass_array=( [index]=vall [index=val2])
# ass_array是关联数组的数组名称
-
示例 创建关联数组
函数
Shell支持函数的创建与调用,当脚本过于复杂时,可以提取程序的某些功能,形成一个函数模块,通过函数调用实现某个功能。
函数定义
语法
function 函数名()
{
函数体
}
-
示例,定义两个函数并进行调用
![image-20230203191757065](2023-02-03-Shell.assets/image-20230203191757065.png
函数的参数
函数的函数体内可以通过$n
的形式来获取参数的值。
除了传递进来的参数,每个函数还有一些内部参数。常见内部参数如下表:
# # 传递到脚本的参数个数
* # 以一个单字符串显示所有向脚本传递的参数
$ # 脚本运行时的当前进程ID号
! # 后台运行的最后一个进程ID号
@ # 与*号相同,但是使用时加引号,并在引号中返回参数个数
- # 显示Shell使用的当前选项
? # 显示最后命令的退出状态
脚本中传递参数语法
function 函数名()
{
$1
$2
}
函数名 参数1 参数2 # 在这里传入参数
-
示例
![image-20230203193300327](2023-02-03-Shell.assets/image-20230203193300327.png
$0
为固有参数,代表获取脚本名称
函数的返回值
在Shell编程中,每个函数都会有返回值。如果没有自定义返回值,那么会以最后一条命令执行结果作为返回值;如果用户自定义返回值,可以在函数的最后添加return n(n in [0-255])
,
-
示例,定义两个函数,分别实现默认返回值和自定义返回值
文本处理
格式化输出
格式化输出能够把输出的内容根据指定的格式输出,自动对齐、换行等操作。格式化输出内容使用的是printf
指令。
语法
printf format-string [arguments…]
# format-string 格式化字符串操作
# [arguments…] 参数字符串,待输出美容
【注意】转义字符只在格式字符串中会被特别对待,出现在参数字符串里的转移字符不会被解释、
常见格式化字符如下
\t # 水平tab 键
\v # 垂直tab 键
\n # 换行
\r # 回车, Enter键
\f # 清除屏幕
\b # 输出退格键
\a # 输出警告声音
%ns # 输出字符串: 输出n位的字符串
%ni # 输出整数: 输出n位的整数
%m.nf # 输出浮点数: m位整数 和 n位小数
-
示例
sed命令 未看
sed处理替换文本的需求。如,某个词首字母大写。
sed可以进行替换、删除、新增、选取等特定工作,sed命令常用选项及操作如下。
-n # 使用安静模式
-e # 直接在命令列模式上进行编辑
-f # 将sed命令输出到文件中,通过执行该文件执行对应的sed命令
awk命令 未看
diff文本内容比较
diff语法
diff ver1 ver2
其中,ver1和ver2是相同的恩建,但是版本不同。对比结果会输出到终端中,也可以使用重定向的方式,将结果输出到指定文件中,形成修补文件。
-
示例,使用diff查看文件不同
-
输出内容共两部分,以第一部分为例。
第一行,第一个1代表第一个文件中的第一行,c代表change,第二个1代表第二个文件中的第一行;意思为第一行内容改变了
第二行,第一个文件第一行的内容。
<
用来标记,说明后面的东西是第一个文件的内容第三行,—分隔符
第四行,第二个文件第一行的内容。
>
用来标记,说明后面的东西是第二个文件的内容
`