ShellScript脚本编程

语法基础

脚本结构

我们先从这个小demo程序来窥探一下我们shell脚本的程序结构

#!/bin/bash# 注释信息echo_str="hello world"test(){echo $echo_str
}test echo_str

首先我们可以通过文本编辑器(在这里我们使用linux自带文本编辑神器vim),新建一个文件demo.sh,文件扩展名sh代表shell,表明该文件是一个shell脚本文件,并不影响脚本的执行,然后将上述代码片段写入文件中,保存退出

然后使用bash -n demo.sh命令可以检测刚才脚本文件的语法是否错误,如果没有回显结果就代表脚本文件没有语法错误

  • 脚本都以#!/bin/bash开头,#称为sharp!在unix行话里称为bang,合起来简称就是常见的shabang#!/bin/bash 指定了shell脚本解释器bash的路径,即使用bash程序作为该脚本文件的解释器,当然也可以使用其它的解释器/bin/sh等,根据具体环境进行相应选择
  • echo_str是字符串变量,通过$进行引用变量的值,
  • test是自定义函数名,通过函数名 传入参数格式进行函数的调用
  • echo是shell命令,相对于python中的print
  • #字符用来注释shell脚本的

最后可以使用下列两种方式执行上述脚本

  • 将脚本作为bash解释器的参数执行:此时首行的#!/bin/bash可以不用写

    • bash demo.sh:直接将脚本文件作为bash命令的参数
    • bash -x demo.sh:使用-x参数可以查看脚本的详细执行过程
  • 将脚本作为独立的可执行文件执行:此时首行的#!/bin/bash必须写,用来指定shell解释器路径;同时脚本必须可执行权限

    • chmod +x demo.sh:给脚本添加执行权限
    • ./demo.sh:执行脚本文件,在这里需要使用./demo.sh表明当前目录下脚本,因为PATH环境变量中没有当前目录,写成demo.sh系统会去/sbin、/sbin等目录下查找该脚本,无法找到该脚本文件执行,造成报错

数据结构

数据类型的本质:固定内存大小的别名

据类型的作用:

  • 确定对应变量分配的内存大小
  • 确定对应变量所能支持的运算或操作

shell脚本是弱类型解释型的语言,在脚本运行时由解释器进行解释变量在什么时候是什么数据类型

在bash中,变量默认都是字符串类型,都是以字符串方式存储,所以在本章主要是介绍各数据类型变量所支持的运算或操作

虽说变量默认都是字符串类型,但是按照其使用场景可将数据类型分为以下几种类型:

  • 数值型
  • 字符串型
  • 数组型
  • 列表型

数值型

首先我们来声明定义一个数值型变量:declare -i Var_Name

  • 虽说声明是一个数值型变量,但是存储依然是按照字符串的形式进行存储
  • 该种方式声明,变量默认是本地全局变量,可以通过local Var_Name关键字将变量修改为局部变量,可以通过export Var_Name关键字将变量导出为环境变量
  • 除了使用declare -i显式声明变量数据类型为数值型,还可以像Var_Name=1由解释器动态执行隐式声明该变量数据类型为数值型

数值型变量一般支持以下运算操作

  • 算术运算
  • 比较运算
  • 数组索引

算术运算

算数运算代码示例如下

#!/bin/bashdeclare -i val=5   # 显式声明数值变量
num=2              # 隐式声明数值变量# 使用[]运算符执行算术表达式$val+$num
# 使用$引用表达式执行结果
echo "val+num=$[$val+$num]"
echo "val++: $[val++]"  # 这里不需要加$,不是引用变量的值,而是修改变量的值
echo "val--: $[val--]"  # 这里不需要加$,不是引用变量的值,而是修改变量的值
echo "++val: $[++val]"  # 这里不需要加$,不是引用变量的值,而是修改变量的值
echo "--val: $[--val]"  # 这里不需要加$,不是引用变量的值,而是修改变量的值# 使用(())运算符执行算术表达式
# 使用$引用表达式执行结果
echo "val-num=$(($val-$num))"
echo "val%num=$(($val%$num))"# 使用let关键字执行算术表达式$val*$num
# 使用=运算符将执行结果赋值给变量
let ret=$val*$num
echo "var*num=$ret"# 使用expr命令执行算术表达式$val/$num但是$val / $num之间需要用空格隔开
# 此时该表达式中的各个部分将作为参数传递给expr命令,最后使用``运算符引用命令的执行结果
# 使用=运算符将命令引用结果赋值给变量
ret=`expr $val / $num`
echo "val/num=$ret"# 使用let关键字执行算术表达式+=、-=、*=、/=、%=
let val+=$num
echo "var+=num:$val"
let val-=$num
echo "var-=num:$val"
let val*=$num
echo "val*=num:$val"
let val/=$sum         # 貌似let不支持/=运算符
echo "val/=num:$val"
let val%=$num
echo "val%=num:$val"

运行结果为:

val+num=7
val++: 5
val--: 6
++val: 6
--val: 5
val-num=3
val%num=1
var*num=10
val/num=2
var+=num:7
var-=num:5
val*=num:10
test.sh: line 37: let: val/=: syntax error: operand expected (error token is "/=")
val/=num:10
val%=num:0

let 关键字用于执行算术运算。它主要用于对变量进行数学计算,并且可以在表达式中直接对变量进行操作,而不需要使用 $ 符号来引用变量值。

作用
  1. 执行算术运算let 可以对变量进行加、减、乘、除等基本运算。

  2. 更新变量值let 会根据运算结果更新变量的值。

  3. 支持复合运算符let 支持如 +=-=*=/= 等复合运算符

比较运算

比较运算有以下几种类型

  • 用于条件测试
  • 用于for循环

用于条件测试的示例代码如下

#!/bin/bashdeclare -i val=5   # 显式声明数值变量
num=2              # 隐式声明数值变量# -eq:判断val变量的值是否等于5
# []运算符用来执行条件测试表达式,其执行结果要么为真,要么为假
# []运算符和条件测试表达式之间前后有空格
if [ $val -eq 5 ]; thenecho "the value of val variable is 5"
fi# -ne:判断num变量的值是否不等于5
# [[]]运算符用来执行条件测试表达式,其执行结果要么为真,要么为假
# [[]]运算符和条件测试表达式之间前后有空格
if [[ $num -ne 5 ]];thenecho "the value of num variable is not 5"
fi# -le:判断num变量的值是否小于或等于val变量的值
# test命令关键字用来执行条件测试表达式,其执行结果要么为真,要么为假
if test $num -le $val ;thenecho "the value of num variable is lower or equal than val variable"
fi# -ge:判断val变量的值是否大于或等于num变量的值
# [[]]运算符用来执行条件测试表达式,其执行结果要么为真,要么为假
# [[]]运算符和条件测试表达式之间前后有空格
if [[ $val -ge $num ]];thenecho "the value of val variable is growth or equal than num variable"
fi# -gt:判断val变量的值是否大于5
# []运算符用来执行条件测试表达式,其执行结果要么为真,要么为假
# []运算符和条件测试表达式之间前后有空格
if [ $val -gt 5 ];thenecho "the value of val variable is growth than 5"
fi# -lt:判断num变量的值是否小于5
# [[]]运算符用来执行条件测试表达式,其执行结果要么为真,要么为假
# [[]]运算符和条件测试表达式之间前后有空格
if [[ $num -lt 5 ]];thenecho "the value of num variable is lower than 5"
fi

gt 的作用是,greater than

大于的意思

then 的作用

  • 表示条件满足后的执行起点:在 if 语句中,then 是一个分隔符,用于区分条件判断部分和条件满足时的执行部分。当条件表达式(如 [ $val -eq 5 ])的值为真(即条件满足)时,then 后面的语句将被执行。

  • 语法结构的必要组成部分:在 Shell 脚本的 if 语句中,then 是必须的,它标志着条件判断之后的代码块的开始。如果没有 then,脚本将无法正确解析 if 语句的结构。

fi 的作用

  • 表示 if 语句的结束fiif 的反向拼写,用于明确地标识 if 语句的结束。它告诉 Shell 解释器,if 语句的逻辑已经完成,后续的代码将不再属于这个 if 语句的范围。

  • 确保语法完整性和正确性:在 Shell 脚本中,if 语句必须以 fi 结尾,否则脚本会报错。fi 是确保脚本语法完整性和正确性的关键部分。

val+num=7
val++: 5
val--: 6
++val: 6
--val: 5
val-num=3
val%num=1
var*num=10
val/num=2
var+=num:7
var-=num:5
val*=num:10
test.sh: line 37: let: val/=: syntax error: operand expected (error token is "/=")
val/=num:10
val%=num:0
[root@localhost ~]# vim test.sh
[root@localhost ~]# bash test.sh
val+num=7
val++: 5
val--: 6
++val: 6
--val: 5
val-num=3
val%num=1
var*num=10
val/num=2
var+=num:7
var-=num:5
val*=num:10
test.sh: line 37: let: val/=: syntax error: operand expected (error token is "/=")
the value of val variable is 5
the value of num variable is not 5
the value of num variable is lower or equal than val variable
the value of val variable is growth or equal than num variable
the value of num variable is lower than 5

用于用于for循环的示例代码如下

#!/bin/bash# ==判断变量i的值是否等于1
for ((i=1; i==1; i++));doecho $i
done# !=判断变量i的值是否不等于3
for ((i=1; i!=3; i++)); doecho $i
done# <=判断变量i的值是否小于等于4
for ((i=1; i<=4; i++)); doecho $i
done# >=判断变量i的值是否大于等于1
for ((i=5; i>=1; i--));doecho $i
done# <判断变量i的值是否小于7
# >判断变量i的值是否大于0
# &&表示逻辑与
# ||表示逻辑或
# !表示逻辑非
# 非的优先级大于与,与的优先级大于或
for ((i=1; i>0 && i<7; i++)); doecho $i
done

运行结果为:

test.sh: line 1: 的值是否等于1: command not found
1
1
2
1
2
3
4
5
4
3
2
1
1
2
3
4
5
6

数组索引

数组是一种数据结构,也可以叫做数据序列,它是一段连续的内容空间,保存了连续的多个数据(数据类型可以不相同),可以使用数组index索引来访问操作数组元素

根据数组index索引的不同可将数组分为

  • 普通数组:数组index索引为整数型
  • 关联数组:数组index索引为字符串
普通数组

普通数组也可以称为整型索引数组,它的声明定义方式有以下几种

#!/bin/bash# 使用declare -a显式声明变量数据类型为整型索引数组型
# 数组中各元素间使用空白字符分隔
# 字符串类型的元素使用引号
declare -a array1=(1 'b' 3 'a')
# 依次引用数组的第一、二、三、四个元素
# 不加下标时默认引用第一个元素
# 引用时必须加上{},否则$array1[0]的值为1[0]
echo "the first element of array1 is ${array1[0]}"
echo "the second element of array1 is ${array1[1]}"
echo "the third element of array1 is ${array1[2]}"
echo "the fourth element of array1 is ${array1[3]}"
# 查看数组所有元素
echo "all elements of array1 is ${array1[*]}"
echo "all elements of array1 is ${array1[@]}"# 由解释器动态解释变量数据类型为整型索引数组型
# 如果数组中各元素间使用逗号,则它们将作为一个整体,也就是数组索引0的值
array2=(1,'b',3,'a')
echo "the first element of array2 is ${array2[0]}"# 由解释器动态解释变量数据类型为整型索引数组型
# 数组元素使用自定义下标赋值
# 以下数组定义中,第一个元素是1,第二个元素是'b',第3个元素为空,第4个元素为'a'
array3=(1 'b' [3]='a')
# 依次引用数组的第一、二、三、四个元素
# 不加下标时默认引用第一个元素
echo "the first element of array3 is ${array3[0]}"
echo "the second element of array3 is ${array3[1]}"
echo "the third element of array3 is ${array3[2]}"
echo "the fourth element of array3 is ${array3[3]}"
# 查看数组中所有有效元素(不为空)的整型索引号
echo "the index of effective element is ${!array3[*]}"
echo "the index of effective element is ${!array3[@]}"
# 查看数组中的有效元素个数(只统计值不为空的元素)
echo "the num of array3 is ${#array3[*]}"
echo "the num of array3 is ${#array3[@]}"

结果为:

the first element of array1 is 1
the second element of array1 is b
the third element of array1 is 3
the fourth element of array1 is a
all elements of array1 is 1 b 3 a
all elements of array1 is 1 b 3 a
the first element of array2 is 1,b,3,a
the first element of array3 is 1
the second element of array3 is b
the third element of array3 is 
the fourth element of array3 is a
the index of effective element is 0 1 3
the index of effective element is 0 1 3
the num of array3 is 3
the num of array3 is 3

在 Bash 脚本中,${#array4[1]} 中的 # 符号的作用是获取数组中指定元素的长度。

另外普通数组还支持以下运算操作

  • 返回数组长度(即有效元素的个数,不包括空元素)

    • ${#Array_Name[*]}
    • ${#Array_Name[@]}
  • 数组元素消除,该操作不会修改原数组元素,操作执行结果用数组来接收

    • Array_Name1=${Array_Name[*]#*word}:功能同下
    • Array_Name1=${Array_Name[*]##*word}:自左而右查找Array_Name数组中所有被匹配到的word匹配到的元素,并将所有匹配到的元素删除(并不会删除原数组中的元素),最后返回剩余的数组元素
    • Array_Name1=${Array_Name[*]%word*}:功能同下
    • Array_Name1=${Array_Name[*]%%word*}:自右而左查找Array_Name数组中所有被匹配到的word匹配到的元素,并将所有匹配到的元素删除(并不会删除原数组中的元素),最后返回剩余的数组元素
  • 数组元素提取,该操作不会修改原数组元素,操作执行结果用数组来接收

    • Array_Name1=${Array_Name[*]:offset}:返回Array_Name数组中索引为offset的数组元素以及后面所有元素;其中offset为整型数
    • Array_Name1=${Array_Name[*]:offset:length}:返回Array_Name数组中索引为offset的数值元素以及后面length-1个元素;其中offsetlength都为整型数

代码示例如下

#!/bin/basharray_test=(/usr/bin /root/bin /usr/apache/bin /usr/mysql /usr/apache/bin)# 返回数组长度(即有效元素的个数,不包括空元素)
echo "the length of array_test is ${#array_test[*]}"
echo "the length of array_test is ${#array_test[@]}"# 数组元素消除,该操作不会修改原数组元素,操作执行结果用数组来接收
array_test1=${array_test[*]#*/usr/apache/bin}
echo "array_test:${array_test[*]}"
echo "array_test1:${array_test1[@]}"
array_test2=${array_test[*]##*/usr/apache/bin}
echo "array_test:${array_test[*]}"
echo "array_test2:${array_test2[@]}"
array_test3=${array_test[*]%/usr/apache/bin*}
echo "array_test:${array_test[*]}"
echo "array_test3:${array_test3[@]}"
array_test4=${array_test[*]%%/usr/apache/bin*}
echo "array_test:${array_test[*]}"
echo "array_test4:${array_test4[@]}"# 数组元素提取,该操作不会修改原数组元素,操作执行结果用数组来接收
array_test5=${array_test[*]:2}
echo "array_test:${array_test[*]}"
echo "array_test5:${array_test5[@]}"
array_test6=${array_test[*]:2:2}
echo "array_test:${array_test[*]}"
echo "array_test6:${array_test6[@]}"# 数组元素替换,该操作不会修改原数组元素,操作执行结果用数组来接收
array_test7=${array_test[*]/\/usr\/apache\/bin/}   # 需要用\对/进行转义,替换值为空表示删除前面匹配到的
echo "array_test:${array_test[*]}"
echo "array_test7:${array_test7[@]}"
array_test8=${array_test[*]//\/usr\/apache\/bin/}  # 需要用\对/进行转义,替换值为空表示删除前面匹配到的
echo "array_test:${array_test[*]}"
echo "array_test8:${array_test8[@]}"

执行结果如下

the length of array_test is 5
the length of array_test is 5
array_test:/usr/bin /root/bin /usr/apache/bin /usr/mysql /usr/apache/bin
array_test1:/usr/bin /root/bin /usr/mysql
array_test:/usr/bin /root/bin /usr/apache/bin /usr/mysql /usr/apache/bin
array_test2:/usr/bin /root/bin /usr/mysql
array_test:/usr/bin /root/bin /usr/apache/bin /usr/mysql /usr/apache/bin
array_test3:/usr/bin /root/bin /usr/mysql
array_test:/usr/bin /root/bin /usr/apache/bin /usr/mysql /usr/apache/bin
array_test4:/usr/bin /root/bin /usr/mysql
array_test:/usr/bin /root/bin /usr/apache/bin /usr/mysql /usr/apache/bin
array_test5:/usr/apache/bin /usr/mysql /usr/apache/bin
array_test:/usr/bin /root/bin /usr/apache/bin /usr/mysql /usr/apache/bin
array_test6:/usr/apache/bin /usr/mysql
array_test:/usr/bin /root/bin /usr/apache/bin /usr/mysql /usr/apache/bin
array_test7:/usr/bin /root/bin /usr/mysql
array_test:/usr/bin /root/bin /usr/apache/bin /usr/mysql /usr/apache/bin
array_test8:/usr/bin /root/bin /usr/mysql

同时普通数组也可用于for循环遍历

代码示例如下

#!/bin/bash# 获取家目录下文件列表,转换成普通数组
array_test=(`ls ~`)
echo ${array_test[@]}
echo "----------------"# 以数组元素值的方式直接遍历数组
for i in ${array_test[*]};doecho $i
done
echo "----------------"# 以数组index索引的方式遍历数组
for i in ${!array_test[*]};doecho ${array_test[$i]}
done
echo "----------------"# 以数组元素个数的方式遍历数组
for ((i=0;i<${#array_test[*]};i++));doecho ${array_test[$i]}
done

执行结果如下

anaconda-ks.cfg crontest.txt ln1 test.sh
----------------
anaconda-ks.cfg
crontest.txt
ln1
test.sh
----------------
anaconda-ks.cfg
crontest.txt
ln1
test.sh
----------------
anaconda-ks.cfg
crontest.txt
ln1
test.sh
关联数组

关联数组也可以称为字符索引数组,它的声明定义方式有以下几种

#!/bin/bash# 声明定义字符索引数组时必须使用declare -A
# 数组中各元素间使用空白字符分隔
declare -A array1=([name1]=jack [name2]=anony)
# 依次引用name1和name2对应的值
echo "the value of name1 element is ${array1[name1]}"
echo "the value of name2 element is ${array1[name2]}"# 声明定义字符索引数组时必须使用declare -A
# 如果数组中各元素间使用逗号,则它们将作为一个整体
declare -A array2=([name1]=jack,[name2]=anony)
echo "the value of name1 element is ${array2[name1]}"
# 查看name1对应值的字符长度
echo "the length of name1 element is ${#array2[name1]}"# 声明定义字符索引数组时必须使用declare -A
declare -A array3=([name1]=jack [name2]=anony)
echo "the value of name1 element is ${array3[name1]}"
echo "the value of name2 element is ${array3[name2]}"
# 通过字符索引进行赋值
array3[name3]=zhangsan
echo "the value of name3 element is ${array3[name3]}"
# 通过字符索引进行赋值
array3[name5]=lisi
# 查看数组所有元素
echo "the all effective element is ${array3[*]}"
echo "the all effective element is ${array3[@]}"
# 查看数组中所有有效元素(不为空)的字符索引号,默认是对应值的排列顺序
echo "the index of all effective element is ${!array3[*]}"
echo "the index of all effective element is ${!array3[@]}"
# 查看数组中的有效元素个数(只统计值不为空的元素)
echo "the length of array is ${#array3[*]}"
echo "the length of array is ${#array3[@]}"

执行结果如下

the value of name1 element is jack
the value of name2 element is anony
the value of name1 element is jack,[name2]=anony
the length of name1 element is 18
the value of name1 element is jack
the value of name2 element is anony
the value of name3 element is zhangsan
the all effective element is zhangsan anony jack lisi
the all effective element is zhangsan anony jack lisi
the index of all effective element is name3 name2 name1 name5
the index of all effective element is name3 name2 name1 name5
the length of array is 4
the length of array is 4

和普通数组一样,关联数组也支持以下运算操作

  • 返回数组长度(即有效元素的个数,不包括空元素)

    • ${#Array_Name[*]}
    • ${#Array_Name[@]}
  • 数组元素消除,该操作不会修改原数组元素,操作执行结果用数组来接收

    • declare -A Array_Name1=${Array_Name[*]#*word}:功能同下
    • declare -A Array_Name1=${Array_Name[*]##*word}:自左而右查找Array_Name数组中所有被匹配到的word匹配到的元素,并将所有匹配到的元素删除(并不会删除原数组中的元素),最后返回剩余的数组元素
    • declare -A Array_Name1=${Array_Name[*]%word*}:功能同下
    • declare -A Array_Name1=${Array_Name[*]%%word*}:自右而左查找Array_Name数组中所有被匹配到的word匹配到的元素,并将所有匹配到的元素删除(并不会删除原数组中的元素),最后返回剩余的数组元素
  • 数组元素提取,该操作不会修改原数组元素,操作执行结果用数组来接收

    • declare -A Array_Name1=${Array_Name[*]:offset}:返回Array_Name数组中索引为offset的数组元素以及后面所有元素;其中offset为整型数
    • declare -A Array_Name1=${Array_Name[*]:offset:length}:返回Array_Name数组中索引为offset的数值元素以及后面length-1个元素;其中offsetlength都为整型数
#!/bin/bashdeclare -A array_test=([ele1]=/usr/bin [ele2]=/root/bin [ele3]=/usr/apache/bin [ele4]=/usr/mysql [ele5]=/usr/apache/bin)# 返回数组长度(即有效元素的个数,不包括空元素)
echo "the length of array_test is ${#array_test[*]}"
echo "the length of array_test is ${#array_test[@]}"# 数组元素消除,该操作不会修改原数组元素,操作执行结果用数组来接收
declare -A array_test1=${array_test[*]#*/usr/apache/bin}
echo "array_test:${array_test[*]}"
echo "array_test1:${array_test1[@]}"
declare -A array_test2=${array_test[*]##*/usr/apache/bin}
echo "array_test:${array_test[*]}"
echo "array_test2:${array_test2[@]}"
declare -A array_test3=${array_test[*]%/usr/apache/bin*}
echo "array_test:${array_test[*]}"
echo "array_test3:${array_test3[@]}"
declare -A array_test4=${array_test[*]%%/usr/apache/bin*}
echo "array_test:${array_test[*]}"
echo "array_test4:${array_test4[@]}"# 数组元素提取,该操作不会修改原数组元素,操作执行结果用数组来接收
declare -A array_test5=${array_test[*]:2}
echo "array_test:${array_test[*]}"
echo "array_test5:${array_test5[@]}"
declare -A array_test6=${array_test[*]:2:2}
echo "array_test:${array_test[*]}"
echo "array_test6:${array_test6[@]}"# 数组元素替换,该操作不会修改原数组元素,操作执行结果用数组来接收
declare -A array_test7=${array_test[*]/\/usr\/apache\/bin/}
echo "array_test:${array_test[*]}"
echo "array_test7:${array_test7[@]}"
declare -A array_test8=${array_test[*]//\/usr\/apache\/bin/}
echo "array_test:${array_test[*]}"
echo "array_test8:${array_test8[@]}"

执行结果如下

the length of array_test is 5
the length of array_test is 5
array_test:/usr/mysql /usr/apache/bin /usr/bin /root/bin /usr/apache/bin
array_test1:/usr/mysql  /usr/bin /root/bin 
array_test:/usr/mysql /usr/apache/bin /usr/bin /root/bin /usr/apache/bin
array_test2:/usr/mysql  /usr/bin /root/bin 
array_test:/usr/mysql /usr/apache/bin /usr/bin /root/bin /usr/apache/bin
array_test3:/usr/mysql  /usr/bin /root/bin 
array_test:/usr/mysql /usr/apache/bin /usr/bin /root/bin /usr/apache/bin
array_test4:/usr/mysql  /usr/bin /root/bin 
array_test:/usr/mysql /usr/apache/bin /usr/bin /root/bin /usr/apache/bin
array_test5:/usr/apache/bin /usr/bin /root/bin /usr/apache/bin
array_test:/usr/mysql /usr/apache/bin /usr/bin /root/bin /usr/apache/bin
array_test6:/usr/apache/bin /usr/bin
array_test:/usr/mysql /usr/apache/bin /usr/bin /root/bin /usr/apache/bin
array_test7:/usr/mysql  /usr/bin /root/bin 
array_test:/usr/mysql /usr/apache/bin /usr/bin /root/bin /usr/apache/bin
array_test8:/usr/mysql  /usr/bin /root/bin

关联数组和普通数组一样,也可用于for循环遍历

先创建test.log文件,内容如下

[root@localhost ~]# cat test.log
portmapper
portmapper
portmapper
portmapper
portmapper
portmapper
status
status
mountd
mountd
mountd
mountd
mountd
mountd
nfs
nfs
nfs_acl
nfs
nfs
nfs_acl
nlockmgr
nlockmgr
nlockmgr
nlockmgr
nlockmgr
nlockmgr

代码示例如下:统计文件中重复行的次数

#!/bin/bashdeclare -A array_testfor i in `cat ~/test.log`;dolet ++array_test[$i]  # 修改数组元素值
donefor j in ${!array_test[*]};doprintf "%-15s %3s\n" $j :${array_test[$j]}
done

执行结果如下

status           :2
nfs              :4
portmapper       :6
nlockmgr         :6
nfs_acl          :2
mountd           :6

列表型

列表型变量常用来for循环遍历,但是一般是在for循环中直接使用,当然也可以通过变量进行引用

代码示例如下

#!/bin/bash# 生成数字列表:使用{}运算符
for i in {1..4};doecho $i
done
echo "-------------------"# 生成数字列表:使用seq命令
for i in `seq 1 2 7`;doecho $i
done
echo "-------------------"# 生成文件列表:直接给出列表
for fileName in /etc/init.d/functions /etc/rc.d/rc.sysinit /etc/fstab;doecho $fileName
done
echo "-------------------"# 生成文件列表:使用文件名通配机制生成列表
dirName=/etc/rc.d
for fileName in $dirName/*.d;doecho $fileName
done
echo "-------------------"# 生成文件列表:使用``运算符引用相关命令的执行结果
for fileName in `ls ~`;doecho $fileName
done

代码执行结果

1
2
3
4
-------------------
1
3
5
7
-------------------
/etc/init.d/functions
/etc/rc.d/rc.sysinit
/etc/fstab
-------------------
/etc/rc.d/init.d
/etc/rc.d/rc0.d
/etc/rc.d/rc1.d
/etc/rc.d/rc2.d
/etc/rc.d/rc3.d
/etc/rc.d/rc4.d
/etc/rc.d/rc5.d
/etc/rc.d/rc6.d
-------------------
anaconda-ks.cfg
crontest.txt
ln1
test.log
test.sh

字符串型

首先我们来声明定义一个字符串型变量:Var_Name="anony"

  • 在bash中,变量默认都是字符串类型,也都是以字符串方式存储,所以字符串可以不需要使用"",除非特殊声明,否则都会解释成字符串
  • 该种方式声明,变量默认是本地全局变量,可以通过local Var_Name关键字将变量修改为局部变量,可以通过export Var_Name关键字将变量导出为环境变量
  • 该种声明定义方式是由shell解释器动态执行隐式声明该变量数据类型为字符串型

字符串型变量一般支持以下运算操作

  • 返回字符串长度:${#Var_Name}(长度包括空白字符)

  • 字符串消除

    • ${var#*word}:查找var中自左而右第一个被word匹配到的串,并将此串及向左的所有内容都删除;此处为非贪婪匹配
    • ${var##*word}:查找var中自左而右最后一个被word匹配到的串,并将此串及向左的所有内容都删除;此处为贪婪匹配
    • ${var%word*}:查找var中自右而左第一个被word匹配到的串,并将此串及向右的所有内容都删除;此处为非贪婪匹配
    • ${var%%word*}:查找var中自右而左最后一个被word匹配到的串,并将此串及向右的所有内容都删除;此处为贪婪匹配
  • 字符串提取

    • ${var:offset}:自左向右偏移offset个字符,取余下的字串;例如:name=jerry,${name:2}结果为rry
    • ${var:offset:length}:自左向右偏移offset个字符,取余下的length个字符长度的字串。例如:``name=’hello world’ ${name:2:5}结果为llo w``

函数

在编程语言中,函数是能够实现模块化编程的工具,每个函数都是一个功能组件,但是函数必须被调用才能执行

函数存在的主要作用在于:最大化代码重用,最小化代码冗余

在shell中,函数可以被当做命令一样执行,它的本质是命令的组合结构体,即可以将函数看成一个普通命令或一个小型脚本。接下来本章内容将从以下几个方面来介绍函数

  • 函数定义
  • 函数调用
  • 函数退出
  • 示例代码

0x00 函数定义

在shell中函数定义的方法有两种(使用help function命令可以查看)

# 方法一
function FuncName {COMMANDS_LIST
} [&>/dev/null]# 方法二
FuncName() {COMMANDS_LIST
} [&>/dev/null]

上面两种函数定义方法定义了一个名为FuncName的函数

  • 方法一中:使用了function关键字,此时函数名FuncName后面的括号可以省略
  • 方法二中:省略了function关键字,此时函数名FuncName后面的括号不能省略

COMMANDS_LIST是函数体,它与以下特点

  • 函数体通常使用大括号{}包围,由于历史原因,在shell中大括号本身也是关键字,所以为了不产生歧义,函数体和大括号之间必须使用空格、制表符、换行符分隔开来;一般我们都是通过换行符进行分隔
  • 函数体中的每一个命令必须使用;换行符进行分隔;如果使用&结束某条命令,则表示该条命令会放入后台执行

需要注意的是

  • &>/dev/null表示将函数体执行过程中可能输出的信息重定向至/dev/null中,该功能可选
  • 定义函数时,还可以指定可选的函数重定向功能,这样当函数被调用的时候,指定的重定向也会被执行
  • 当前shell定义的函数只能在当前shell使用,子shell无法继承父shell的函数定义,除非使用export -f将函数导出为全局函数;如果想取消函数的导出可以使用export -n
  • 定义了函数后,可以使用unset -f移除当前shell中已定义的函数
  • 可以使用typeset -f [func_name]declare -f [func_name]查看当前shell已定义的函数名和对应的定义语句;使用typeset -Fdeclare -F则只显示当前shell中已定义的函数名
  • 只有先定义了函数,才可以调用函数;不允许函数调用语句在函数定义语句之前
  • 在shell脚本中,函数没有形参的概念,使用方法二定义函数时,括号里什么都不用写,只需要在函数体内使用相关的调用机制调用接收参数即可

0x01 函数调用

函数的调用格式如下

FuncName ARGS_LIST

其中

  • FuncName:表示被调用函数的函数名,需要注意的是在shell中函数调用时函数名后面没有()操作符
  • ARGS_LIST:表示被调用函数的传入参数,在shell中给函数传入参数和脚本接收参数的方法相似,直接在函数名后面加上需要传入的参数即可

函数调用时需要注意以下几点

  • 如果函数名和命令名相同,则优先执行函数,除非使用command命令。例如:定义了一个名为rm的函数,在bash中输入rm执行时,执行的是rm函数,而非/bin/rm命令,除非使用command rm ARGS,表示执行的是/bin/rm命令
  • 如果函数名和命令别名相同,则优先执行命令别名,即在优先级方面:别名别名>函数>命令自身

当函数调用函数被执行时,它的执行逻辑如下

  • 接收参数:shell函数也接受位置参数变量,但函数的位置参数是调用函数时传递给函数的,而非传递给脚本的参数,所以脚本的位置变量和函数的位置变量是不同的;同时shell函数也接收特殊变量。函数体内引用位置参数和特殊变量方式如下

    • 位置参数

      • $0:和脚本位置参数一样,引用脚本名称
      • $1:引用函数的第1个传入参数
      • $n:引用函数的第n个传入参数
    • 特殊变量

      • $?:引用上一条命令的执行状态返回值,状态用数字表示0-255

        • 0:表示成功
        • 1-255:表示失败;其中1/2/127/255是系统预留的,写脚本时要避开与这些值重复
      • $$:引用当前shell的PID。除了执行bash命令和shell脚本时,$$不会继承父shell的值,其他类型的子shell都继承

      • $!:引用最近一次执行的后台进程PID,即运行于后台的最后一个作业的PID

      • $#:引用函数所有位置参数的个数

      • $*:引用函数所有位置参数的整体,即所有参数被当做一个字符串

      • $@:引用函数所有单个位置参数,即每个参数都是一个独立的字符串

  • 执行函数体:在函数体执行时,需要注意的是

    • 函数内部引用变量的查找次序:内层函数自己的变量>外层函数的变量>主程序的变量>bash内置的环境变量

    • 函数内部引用变量的作用域

      • 本地变量:函数体引用本地变量时,重新赋值会覆盖原来的值,如果不想覆盖值,可以使用local进行修饰
      • 局部变量:函数体引用局部变量时,函数退出,将会被撤销
      • 环境变量:函数体引用环境变量时,重新赋值会覆盖原来的值,如果不想覆盖值,可以使用local进行修饰
      • 位置变量:函数体引用位置变量表示引用传递给函数的参数
      • 特殊变量
  • 函数返回:函数返回值可分为两类

    • 执行结果返回值:正常的执行结果返回值有以下几种

      • 函数中的打印语句:如echoprint
      • 最后一条命令语句的执行结果值
    • 执行状态返回值:执行状态返回值主要有以下几种

      • 使用return语句自定义返回值,即return n,n表示函数的退出状态码,不给定状态码时默认状态码为0
      • 取决于函数体中最后一条命令语句的执行状态返回值

在shell中不仅可以调用本脚本文件中定义的函数,还可以调用其它脚本文件中定义的函数

  • 先使用. /path/to/shellscriptsource /path/to/shellscript命令导入指定的脚本文件
  • 然后使用相应的函数名调用函数即可

0x02 函数退出命令

函数退出命令有

  • return [n]:可以在函数体内的任何地方使用,表示退出整个函数;数值n表示函数的退出状态码
  • exit [n]:可以在脚本的任何地方使用,表示退出整个脚本;数值n表示脚本的退出状态码

此处需要注意的是:return并非只能用于function内部

  • 如果returnfunction之外,但在.或者source命令的执行过程中,则直接停止该执行操作,并返回给定状态码n(如果未给定,则为0)
  • 如果returnfunction之外,且不在source.的执行过程中,则这将是一个错误用法

可能有些人不理解为什么不直接使用exit来替代这时候的return。下面给个例子就能清楚地区分它们

先创建一个脚本文件proxy.sh,内容如下,用于根据情况设置代理的环境变量

#!/bin/bashproxy="http://127.0.0.1:8118"
function exp_proxy() {export http_proxy=$proxyexport https_proxy=$proxyexport ftp_proxy=$proxyexport no_proxy=localhost
}case $1 inset) exp_proxy;;unset) unset http_proxy https_proxy ftp_proxy no_proxy;;*) return 0
esac

首先我们来了解下source的特性:即source是在当前shell而非子shell执行指定脚本中的代码

当进入bash

  • 需要设置环境变量时:使用source proxy.sh set即可
  • 需要取消环境变量时:使用source proxy.sh unset即可

此时如果不清楚该脚本的用途或者一时手快直接输入source proxy.sh,就可以区分exitreturn

  • 如果上述脚本是return 0,那么表示直接退出脚本而已,不会退出bash
  • 如果上述脚本是exit 0,则表示退出当前bash,因为source是在当前shell而非子shell执行指定脚本中的代码

可能你想象不出在source执行中的return有何用处:从source来考虑,它除了用在某些脚本中加载其他环境,更主要的是在bash环境初始化脚本中使用,例如/etc/profile~/.bashrc等,如果你在/etc/profile中用exit来替代function外面return,那么永远也登陆不上bash

0x03 示例代码

  • 随机生成密码
#!/bin/bashgenpasswd(){local l=$1[ "$l" == ""  ]&& l=20tr -dc A-Za-z0-9_</dev/urandom | head -c ${l} | xargs
}genpasswd $1   # 将脚本传入的位置参数传递给函数,表示生成的随机密码的位数
  • 写一个脚本,完成如下功能:

    • 1、脚本使用格式:mkscript.sh [-D|--description "script description"] [-A|--author "script author"] /path/to/somefile

    • 2、如果文件事先不存在,则创建;且前几行内容如下所示:

      • #!/bin/bash
      • # Description: script description
      • # Author: script author
    • 3、如果事先存在,但不空,且第一行不是#!/bin/bash,则提示错误并退出;如果第一行是#!/bin/bash,则使用vim打开脚本;把光标直接定位至最后一行

    • 4、打开脚本后关闭时判断脚本是否有语法错误;如果有,提示输入y继续编辑,输入n放弃并退出;如果没有,则给此文件以执行权限

#!/bin/bash
read -p "Enter a file: " filename
declare authname
declare descroptions(){
if [[ $# -ge 0 ]];thencase $1 in-D|--description)authname=$4descr=$2;;-A|--author)descr=$4authname=$2;;esac
fi
}command(){
if  bash -n $filename &> /dev/null;thenchmod +x $filename
elsewhile true;doread -p "[y|n]:" optioncase $option iny)vim + $filename;;n)exit 8;;esacdone
fi
exit 6
}oneline(){
if [[ -f $filename ]];thenif [ `head -1 $filename` == "#!/bin/bash" ];thenvim + $filenameelseecho "wrong..."exit 4fi
elsetouch $filename && echo -e "#!/bin/bash\n# Description: $descr\n# Author: $authname" > $filenamevim + $filename
fi
command
}options $*
oneline
  • 写一个脚本,完成如下功能:

    • 1、提示用户输入一个可执行命令
    • 2、获取这个命令所依赖的所有库文件(使用ldd命令)
    • 3、复制命令至/mnt/sysroot/对应的目录中;如果复制的是cat命令,其可执行程序的路径是/bin/cat,那么就要将/bin/cat复制到/mnt/sysroot/bin/目录中,如果复制的是useradd命令,而useradd的可执行文件路径为/usr/sbin/useradd,那么就要将其复制到/mnt/sysroot/usr/sbin/目录中
    • 4、复制各库文件至/mnt/sysroot/对应的目录中
#!/bin/bashoptions(){for i in $*;dodirname=`dirname $i`[ -d /mnt/sysroot$dirname ] || mkdir -p /mnt/sysroot$dirname[ -f /mnt/sysroot$i ]||cp $i /mnt/sysroot$dirname/done
}while true;doread -p "Enter a command : " pidname[[ "$pidname" == "quit" ]] && echo "Quit " && exit 0bash=`which --skip-alias $pidname`if [[ -x $bash ]];thenoptions `/usr/bin/ldd $bash |grep -o "/[^[:space:]]\{1,\}"`options $bashelseecho "PLZ a command!"fi
done# 说明
# 将bash命令的相关bin文件和lib文件复制到/mnt/sysroot/目录中后
# 使用chroot命令可切换根目录,切换到/mnt/sysroot/后可当做bash执行复制到该处的命令,作为bash中的bash
  • 写一个脚本,用来判定172.16.0.0网络内有哪些主机在线,在线的用绿色显示,不在线的用红色显示
#!/bin/bash
Cnetping(){for i in {1..254};doping -c 1 -w 1 $1.$iif [[ $? -eq 0 ]];thenecho -e -n "\033[32mping 172.16.$i.$j ke da !\033[0m\n"elseecho -e -n "\033[31mping 172.16.$i.$j bu ke da !\033[0m \n"fidone
}Bnetping(){for j in {0..255};doCnetping $1.$jdone
}Bnetping 172.16
  • 写一个脚本,用来判定随意输入的ip地址所在网段内有哪些主机在线,在线的用绿色显示,不在线的用红色显示
#!/bin/bash
Cnetping(){for i in {1..254};doping -c 1 -w 1 $1.$iif [[ $? -eq 0 ]];thenecho -e -n "\033[32mping 172.16.$i.$j ke da !\033[0m\n"elseecho -e -n "\033[31mping 172.16.$i.$j bu ke da !\033[0m \n"fidone
}Bnetping(){for j in {0..255};doCnetping $1.$jdone
}Anetping(){for m in {0.255};doBnetping $1.$mdone
}netType=`echo $1 | cut -d'.' -f1`if [ $netType -gt 0 -a $netType -le 126 ];thenAnetping $1
elif [ $netType -ge 128 -a $netType -le 191 ];thenBnetping $1
elif [ $netType -ge 192 -a $netType -le 223 ];thenCnetping $1
elseecho "Wrong"exit 3
fi

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/79235.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

你了解哪些Java限流算法?

大家好&#xff0c;我是锋哥。今天分享关于【你了解哪些Java限流算法?】面试题。希望对大家有帮助&#xff1b; 你了解哪些Java限流算法? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 Java 中&#xff0c;限流算法广泛用于控制流量、避免过载和保护系统的稳…

prime-2 靶场笔记(vuInhub靶场)

前言&#xff1a; 在本次靶场环境中涉及的知识点&#xff0c;主要包含LFI和SMB以及Lxd组提权&#xff0c;具体内容包括主机探测、端口扫描、目录扫描、wpscan扫描、反弹shell、一句话木马、容器、linux各种提权和维持。 环境介绍&#xff1a; 本靶场使用了kali&#xff08;192…

SparseDrive---论文阅读

纯视觉下的稀疏场景表示 算法动机&开创性思路 算法动机&#xff1a; 依赖于计算成本高昂的鸟瞰图&#xff08;BEV&#xff09;特征表示。预测和规划的设计过于直接&#xff0c;没有充分利用周围代理和自我车辆之间的高阶和双向交互。场景信息是在agent周围提取&#xff…

旅游特种兵迪士尼大作战:DeepSeek高精准路径优化

DeepSeek大模型高性能核心技术与多模态融合开发 - 商品搜索 - 京东 随着假期的脚步日渐临近&#xff0c;环球影城等备受瞩目的主题游乐场&#xff0c;已然成为大人与孩子们心中不可或缺的节日狂欢圣地。然而&#xff0c;随之而来的庞大客流&#xff0c;却总让无数游客在欢乐的…

android rtsp 拉流h264 h265,解码nv12转码nv21耗时卡顿问题及ffmpeg优化

一、 背景介绍及问题概述 项目需求需要在rk3568开发板上面&#xff0c;通过rtsp协议拉流的形式获取摄像头预览&#xff0c;然后进行人脸识别 姿态识别等后续其它操作。由于rtsp协议一般使用h.264 h265视频编码格式&#xff08;也叫 AVC 和 HEVC&#xff09;是不能直接用于后续处…

运维面试题(十四)

6.将日志从一台服务器保存到另一台服务器中的方法 1.使用 rsync 同步日志文件 2.使用 scp 手动或脚本化传输 3.配置日志服务&#xff08;如 syslog 或 rsyslog &#xff09;远程传输  4.编写脚本定时上传&#xff1a;结合 cron 定时任务和传输工具&#xff0c;编…

永磁同步电机控制中,滑模观测器是基于反电动势观测转子速度和角度的?扩展卡尔曼滤波观测器是基于什么观测的?扩展卡尔曼滤波观测器也是基于反电动势吗?

滑模观测器在PMSM中的应用&#xff1a; 滑模观测器是一种非线性观测器&#xff0c;利用切换函数设计&#xff0c;使得状态估计误差迅速趋近于零&#xff0c;实现快速响应和对外部干扰的鲁棒性。 在永磁同步电机&#xff08;PMSM&#xff09;无传感器控制中&#xff0c;滑模观测…

【前端】Vue一本通 ESLint JSX

近几天更新完毕&#xff0c;不定期持续更新&#xff0c;建议关注收藏点赞。 目录 工具推荐vscode插件vue-devtoolsESLint JSX语法扩展简介设计模式快速入门 vue/cli脚手架使用vue指令 工具推荐 工欲善其事&#xff0c;必先利其器。 vscode插件 Vetur&#xff1a;vue代码高亮…

【adb】bat批处理+adb 自动亮屏,自动解锁屏幕,启动王者荣耀

准备adb 下载 需要确认是否安装了adb.exe文件,可以在: 任务管理器 -->详细信息–>找一下后台运行的adb 安装过anroid模拟器,也存在adb,例如:雷电安装目录 D:\leidian\LDPlayer9 单独下载adb 官方下载地址:[官方网址] 下载目录文件: 测试adb USB连接手机 首先在设置界…

微信小程序转为App实践篇 FinClip

参考下面链接先 开始实践 微信小程序转为App并上架应用市场_微信小程序生成app-CSDN博客 首先在FinClip 官网上下载应用 小程序开发工具下载_小程序sdk下载资源-FinClip资源下载|泰坪小程序开放平台 下载到本地安装 打开导入自己的小程序项目&#xff1b;导入时会解析自己的…

arco design框架中的树形表格使用中的缓存问题

目录 1.问题 2.解决方案 1.问题 arco design框架中的树形表格使用中的缓存问题&#xff0c;使用了树形表格的load-more懒加载 点击展开按钮后&#xff0c;点击关闭&#xff0c;再次点击展开按钮时&#xff0c;没有调用查询接口&#xff0c;而是使用了缓存的数据。 2.解决方…

100个GEO基因表达芯片或转录组数据处理023.GSE24807

100个GEO基因表达芯片或转录组数据处理 写在前边 虽然现在是高通量测序的时代&#xff0c;但是GEO、ArrayExpress等数据库储存并公开大量的基因表达芯片数据&#xff0c;还是会有大量的需求去处理芯片数据&#xff0c;并且建模或验证自己所研究基因的表达情况&#xff0c;芯片…

SAP ECCS标准报表在报表中不存在特征CG细分期间 消息号 GK715报错分析

ECCS报表执行报错&#xff1a; 在报表中不存在特征CG细分期间 消息号 GK715 诊断 未在报表中指定特征CG细分期间。但是&#xff0c;同时需要特征CG细分期间和其它特征。例如&#xff1a; 期间’需要用于扩展合并组。 系统响应 处理终止 步骤 调整报表定义。 报这个错。 业务背景…

spring boot 文件下载

1.添加文件下载工具依赖 Commons IO is a library of utilities to assist with developing IO functionality. <dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version> </depe…

FastAPI 中定义接口函数参数,包含请求体参数、查询参数、依赖注入参数的组合

FastAPI 中定义接口函数参数&#xff0c;包含请求体参数、查询参数、依赖注入参数的组合。 ✅ 示例结构 async def chat(request: Request,data: ChatData,conversation_id: Optional[str] Query(None),current_user: User Depends(get_current_user), ):这表示你定义了一个…

实用类题目

1. 密码强度检测 题目描述&#xff1a;生活中&#xff0c;为保证账户安全&#xff0c;密码需要有一定强度。编写一个方法&#xff0c;接收一个字符串作为密码&#xff0c;判断其是否符合以下强度要求&#xff1a;长度至少为 8 位&#xff0c;包含至少一个大写字母、一个小写字…

MATLAB学习笔记(二) 控制工程会用到的

MATLAB中 控制工程会用到的 基础传递函数表达传递函数 零极点式 状态空间表达式 相互转化画响应图线根轨迹Nyquist图和bode图现控部分求约旦判能控能观极点配置和状态观测 基础 传递函数表达 % 拉普拉斯变换 syms t s a f exp(a*t) %e的a次方 l laplace(f) …

基于YOLOv9的课堂行为检测系统

基于YOLOv9的课堂行为检测系统 项目概述 本项目是一个基于YOLOv9深度学习模型的课堂行为检测系统&#xff0c;旨在通过计算机视觉技术自动识别和监测课堂中学生的各种行为状态&#xff0c;帮助教师更好地了解课堂教学效果。 项目结构 课堂行为检测/ ├── data/ │ ├──…

C 语言中的 volatile 关键字

1、概念 volatile 是 C/C 语言中的一个类型修饰符&#xff0c;用于告知编译器&#xff1a;该变量的值可能会在程序控制流之外被意外修改&#xff08;如硬件寄存器、多线程共享变量或信号处理函数等&#xff09;&#xff0c;因此编译器不应对其进行激进的优化&#xff08;如缓存…

java 洛谷题单【算法2-1】前缀和、差分与离散化

P8218 【深进1.例1】求区间和 解题思路 前缀和数组&#xff1a; prefixSum[i] 表示数组 a 的前 (i) 项的和。通过 prefixSum[r] - prefixSum[l - 1] 可以快速计算区间 ([l, r]) 的和。 时间复杂度&#xff1a; 构建前缀和数组的时间复杂度是 (O(n))。每次查询的时间复杂度是 …