03-Shell脚本编程

nobility 发布于 2020-09-20 1655 次阅读


Shell脚本编程

  • 需要在脚本文件的第一行写上#!/bin/bash用来指定解析的shell引擎

  • 注释使用#开头,属于单行注释

  • 给脚本添加可执行权限,chmod +x script.sh

  • 在脚本所在目录使用./形式运行脚本,也可以使用bash -x script.sh命令调试模式运行脚本

  • 多条命令可使用;隔开在同一行上执行,这时的多个命令没有任何关联

  • 多条命令可以使用&&在同一行执行,表示前面命令执行成功后面命令才执行

  • 多条命令可以使用||在同一行执行,表示前面命令执行失败后面命令才执行

变量

自定义变量

  • 无需像其他编程语言一样需要声明变量,直接使用变量=值的形式就是定义了一个变量,要注意等号两端是不能有空格的,否则会认为变量是命令;使用变量时需要在变量前增加$前缀
  • 在shell脚本中的变量都是字符串形式的
    • 单引号:不会解析变量的字符串
    • 双引号:会解析变量字符串
    • 反引号:会将包裹内容当作命令执行,将执行的结果返回,与$()包裹有相同效果
  • 在shell脚本中的变量无法被外部使用,若想让外部是由需要使用export导出变量
  • 可以使用unset 删除变量,这与给变量赋值为空字符串是相同的效果
  • 可以使用readonly 声明常量

参数变量

这多个参数就想队列一样,所以可以使用shift命令来使这这些参数挨个弹出

变量名 描述
$# 参数的个数
$0 脚本的名称
$n n个参数,从1开始
$_ 最后一个参数
$? 上一个命令执行成功返回0

数组

数组定义

  • 使用赋值的方式为每个元素赋值
arr[0]=val0
arr[1]=val1
  • 使用括号获取来并且以空格或回车分开的各个元素
arr=(val0 val1)

#
arr=(
	'val1'
	"val2"
)
  • 使用map结构赋值方式,这样顺序就不重要了
arr=([0]=val0 [1]=val1)

数组使用

  • 与其他编程语言一样,使用变量后跟方括号去变量的值,但是需要使用${arr[index]}形式,否则会将方括号前当作一个普通非数组变量的字符串,只会输出第一个元素和方括号字符串,当然普通变量也可以放到该包裹中使用
    • 下标从0开始
    • 像js一样可以跳格赋值,空位是空字符串
    • 若想根据数组元素的值获取索引下标则使用${!arr[val]}的形式即可
    • 若取出所有变量则方括号中是*号,如arr[*]
    • 数组的长度(数组的元素个数):${#arr[*]}

数组操作

  • 截取子数组:${arr[*]:start:length}
    • arr:是要截取的数组,先将所有元素取出来
    • index:开始截取的下标位置,负数代表倒数
    • legth:截取的长度,负数会倒着截取,省略会截取到字符串结尾
  • 合并数组:arr1+=arr2,将arr2中的元素复制一份到arr1
  • 删除数组元素:可以使用unset 删除数组元素,这与给变量赋值为空字符串是相同的效果

运算符

  • let:用于执行拥有整数算数运算、逻辑运算、关系运算、赋值运算、位运算,这与其他编程语言是一致的,而且还有**乘方运算符,与$(())包裹和expr命令有相同效果
  • bc:用于执行拥有浮点数的数学运算语句,由于该命令是交互式的所以需要echo "浮点数算数语句" | bc的形式,这样会立刻输出,可以采用变通的方式赋值,比如先将结果重定向到一个临时文件,之后在读取出来赋值给其他变量,然后再删除临时文件

输入输出语句

  • echo:输出内容
    • e:会解析转义字符
    • n:取消输出内容后的默认换行
  • read:读取内容赋给变量,可同时为多个变量赋值,都是以空格隔开的
    • e:自动补全
    • p:显示提示信息
    • a:输入赋值给一个数组
    • n:限制输入的最大字符数量,到最大数量后会自动结束输入,由于没有输入回车会导致下面输出内容直接显示
    • t:限制输入时间,超过时间会自动结束输入
    • s:隐藏输入内容,通常用于输入密码

字符串处理

  • 字符串的长度需要使用${#str}的形式获取,大括号不能省略,否则会将$#当作参数变量
  • 截取字符串:${str:index:length}
    • str:是要截取的字符串变量,要注意只能是变量
    • index:开始截取的下标位置,负数代表倒数
    • legth:截取的长度,负数会倒着截取,省略会截取到字符串结尾
  • 替换字符串:要注意字符串只能是变量
    • ${str#substr}:从左向右非贪婪匹配,替换为空字符串
    • ${str##substr}:从左向右贪婪匹配,替换为空字符串
    • ${str%substr}:从右到左非贪婪匹配,替换为空字符串
    • ${str%%substr}:从右到左贪婪匹配,替换为空字符串
    • ${str/oldstr/newstr}:从左向右贪婪匹配,将第一个oldstr替换为newstr
    • ${str//oldstr/newstr}:从左向右贪婪匹配,将字符串中全部的oldstr替换为newstr
  • 大小写转化:要注意字符串只能是变量
    • ${str^^}:转大写
    • ${str,,}:转小写

控制流程

条件分支

  • 要注意if后需要有空格分隔,then...fi就像其他编程语言中的大括号,若then写在后面需要在条件测试后需要加一个分号
if [ 条件测试 ]
then

	#code
fi

#也可以
if [ 条件测试 ]; then

	#code
fi



#配合else
if [ 条件测试 ]
then

	#code
else

	#code
fi


#配合else if
if [ 条件测试 ]; then

	#code
elif [ 条件测试 ]; then

	#code
elif [ 条件测试 ]; then

	#code
else

	#code
if

开关语句

  • 匹配项可以有多种形式
    • a):单个匹配为a字母
    • a|b):匹配ab
    • 也可以使用基础命令中的哪些扩展特殊字符,如:*?[]
case $var in	#var是变量
	match1 ) #code
	;;	#相当于brack,如果是 ;;& 才是没写brack的效果,若这两种形式都不写则会报错
	match2 ) #code
	;;	#相当于brack
	* ) #default,其实使用的是基础命令中扩展的特殊字符中的*号,代表任意匹配
esac

循环

循环中都可以使用brackconrinue语句

while循环
  • 要注意while后需要有空格分隔,do...done就像其他编程语言中的大括号,若do写在后面需要在条件测试后x需要加一个分号
while [ 条件测试 ]
do

#loop code
done

#也可以
while [ 条件测试 ]; do

#loop code
done
until循环
  • 与while循环相反,条件为假时才会循环,为真时跳出循环
until [ 条件测试 ]
do

#loop code
done

#也可以
until [ 条件测试 ]; do

#loop code
done
for...in循环
  • seq:序列,该命令可以返回一个数字列表,可以用于该类型的循环中的列表
    • 单个参数:默认从1开始,生成到该参数的数字连序的序列
    • 两个参数:第一个参数指定起始位置(可以为负数),第二个参数指定结束位置(可以为负数),生成该范围内数字连序的序列
    • 三个参数:第一个参数指定起始位置,第二个参数指定步长,第三个参数指定结束位置,生成该范围内的按照步长的连序序列
for $var in list	#var是变量,是将in后的list列表挨个取出赋给var变量,list是 val1 val2 ... 的形式
do						  #所以遍历数组时需要使用 arr[*] 取出所有元素

#loop code			 #所以可以使用基础命令中的哪些扩展特殊字符,如{1..5}
done					  #一些命令也会返回列表形式,如ls,所以可以直接遍历

#也可以
for $var in list; do

#loop code
done
for循环
  • 与其他编程语言一样都有相同的for循环结构,只不过是形式不同,是两重小括号
  • 这种形式同样可以用于while循环
for ((i=1;i<5;i++))
do
  echo $i
done

#也可以
for ((i=1;i<5;i++)); do
  echo $i
done

条件测试

条件测试的三种形式

多个测试条件可以使用&&||来连接,条件测试也可以使用!取反

  • [ 条件测试 ]:注意方括号两边有空格
  • test 条件测试test是一个命令
  • [[ 条件测试 ]]:支持正则匹配,等于正则时使用=~号是正则匹配
字符串测试
条件测试 描述
[ str=str ][ str==str ] 相等
[ str!=str ] 不相等
[ -z str ] 空字符串
[ -n str ][ str ] 非空字符串
整数测试

可以使用(())代替以下的大小判断,比如:((2 > 1)),此时就不需要注意括号两端的空格了,这就更像主流的编程语言了,for和while循环使用该形式也是主要用了(())表达式

条件测试 描述
[ int -eq int ] 数值相等
[ int -ne int ] 数值不等
[ int -le int ] 数值小于等于
[ int -lt int ] 数值小于
[ int -ge int ] 数值大于等于
[ int -gt int ] 数值大于
文件测试
条件测试 描述
[ -e file ] 文件存在
[ -d file ] 文件存在且是目录
[ -f file ] 文件存在且是普通文件
[ -L file ] 文件存在且是连接文件
[ -r file ] 文件存在且有可读权限
[ -w file ] 文件存在且有可写权限
[ -x file ] 文件存在且有可执行权限
[ file1 -nt file2 ] file1更新时间更新于file2,或file1存在file2不存在
[ file1 -ot file2 ] [ file1 -nt file2 ]相反
[ file1 -ef file2 ] file1与file2是否互为硬连接

函数

函数声明

  • 函数声明必须在调用前
  • 不能像其他函数一样括号中带有参数,而是于执行命令般的传递参数,所以可以使用内置参数$0是函数名,$n就是第n个参数那样
  • 函数的返回值只能返回函数的状态,不能返回具体的值,通常执行成功返回0,没成功返回非0,通过函数执行后的$?获得
fun() {

	#code
}

#也可以
function fun() {

}

函数调用

  • 就想使用命令一样调用函数
  • 函数后面可以跟参数
fun() {

	#code
}
fun val	#调用并传递参数val
echo $?	#查看函数的返回值

作用域

  • 默认所有变量都是全局作用域下的,若想在函数中声明只有本函数作用域的变量则需要使用local声明即可
  • 当定义函数名和系统命令相同时会就近原则选择自定义的函数作为命令,恰巧该函数中又使用了那个冲突的系统命令就会陷入无限递归循环,所以只需要使用command声明使用的是系统命令即可
ls() {
    ls	#会陷入无限递归
}
ls

ls() {
	command ls	#声明使用的是系统命令即可
}
ls

ls() {
	echo 'fun ls'
}
command ls	#不会调用自定义的函数,而是调用系统命令
此作者没有提供个人介绍
最后更新于 2020-09-20