语法基础
脚本结构
我们先从这个小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
关键字用于执行算术运算。它主要用于对变量进行数学计算,并且可以在表达式中直接对变量进行操作,而不需要使用 $
符号来引用变量值。
作用
-
执行算术运算:
let
可以对变量进行加、减、乘、除等基本运算。 -
更新变量值:
let
会根据运算结果更新变量的值。 -
支持复合运算符:
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
语句的结束:fi
是if
的反向拼写,用于明确地标识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
个元素;其中offset
和length
都为整型数
代码示例如下
#!/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
个元素;其中offset
和length
都为整型数
#!/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 -F
或declare -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
进行修饰 - 位置变量:函数体引用位置变量表示引用传递给函数的参数
- 特殊变量
- 本地变量:函数体引用本地变量时,重新赋值会覆盖原来的值,如果不想覆盖值,可以使用
-
-
函数返回:函数返回值可分为两类
-
执行结果返回值:正常的执行结果返回值有以下几种
- 函数中的打印语句:如
echo
、print
等 - 最后一条命令语句的执行结果值
- 函数中的打印语句:如
-
执行状态返回值:执行状态返回值主要有以下几种
- 使用
return
语句自定义返回值,即return n,n表示函数的退出状态码,不给定状态码时默认状态码为0 - 取决于函数体中最后一条命令语句的执行状态返回值
- 使用
-
在shell中不仅可以调用本脚本文件中定义的函数,还可以调用其它脚本文件中定义的函数
- 先使用
. /path/to/shellscript
或source /path/to/shellscript
命令导入指定的脚本文件 - 然后使用相应的函数名调用函数即可
0x02 函数退出命令
函数退出命令有
return [n]
:可以在函数体内的任何地方使用,表示退出整个函数;数值n表示函数的退出状态码exit [n]
:可以在脚本的任何地方使用,表示退出整个脚本;数值n表示脚本的退出状态码
此处需要注意的是:return
并非只能用于function内部
- 如果
return
在function之外
,但在.
或者source
命令的执行过程中,则直接停止该执行操作,并返回给定状态码n(如果未给定,则为0) - 如果
return
在function之外
,且不在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
,就可以区分exit
和return
- 如果上述脚本是
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