shell编程之变量
一、变量
1.变量的命名
shell中的变量必须以字母或者下划线开头,后面可以跟数字、字母和下划线,变量长度没有限制。
#正确的变量命名
firstname
FIRSTNAME
_helloworld
Fullname
big_data
Fullname
Person01
#错误的变量命名
51paly #变量不能以数字开头
*badname #变量不能以特殊字符开头
PS1 #变量不能以特殊字符开头
for #变量不能使用shell关键字
按照以上的变量命名规则定义变量abc,从理论上来说是可行的,但是一个好的习惯是变量最好能表明它代表的含义。比如说Student_ID,一看就知道它所表达的是“学号"的意思,绝对比number这种模棱两可的变量要清晰得多名,不仅看代码的人觉得简单明了,而且有利于后期的代码维护。更好地习惯则是加上一些注释,但是也不要太过拘泥,如下所示:
#定义学号 #使用注释解释变量使后期阅读更为清晰
Student_ID
#定义日期 #这种注释就显得有所拘泥
DATE
2.变量的赋值和取值
#定义变量:变量名=变量值
#注意点一:变量名和变量值之间用等号紧紧相连,之间没有任何空格
[root@Cfhost-170820-UCNK ~]# name=john
[root@Cfhost-170820-UCNK ~]# name = john
-bash: name: command not found
[root@Cfhost-170820-UCNK ~]# name="john"
#注意点二:当变量中有空格时必须用引号括起,否则会出现错误
#其中的引号可以是双引号,也可以是单引号
[root@Cfhost-170820-UCNK ~]# name="john wang"
[root@Cfhost-170820-UCNK ~]# name=john wang
-bash: wang: command not found
变量取值很简单,只需要在变量名前加上$符号即可,严谨一点的写法是${},如下所示:
[root@Cfhost-170820-UCNK ~]# echo $name
john wang
[root@Cfhost-170820-UCNK ~]# echo ${name}
john wang
#使用${}获取变量值是一种相对比较保险的方式
[root@Cfhost-170820-UCNK ~]# name=john
[root@Cfhost-170820-UCNK ~]# name1="$name"
[root@Cfhost-170820-UCNK ~]# echo $name1
john
[root@Cfhost-170820-UCNK ~]# name1='$name'
[root@Cfhost-170820-UCNK ~]# echo name1
name1
由于Shell具有“弱变量"的特性,因此即便在没有预先声明变量的时候也可以引用的,而且没有任何报错或提醒,这可能会造成脚本中引用不正确的变量,从而导致脚本异常,但是却很难找出原因。在这种情况下,可以设置脚本运行时必须遵循“先声明再使用"的原则,这样一旦脚本中出现使用未声明的变量的情况则立刻报错。
[root@Cfhost-170820-UCNK ~]# echo $unDefinedVar
#因为该变量未声明,所以值为空,但没有任何报错
#设置变量必须先声明再使用
[root@Cfhost-170820-UCNK ~]# shopt -s -o nounset
[root@Cfhost-170820-UCNK ~]# echo $unDefinedVar
-bash: unDefinedVar: unbound variable
3.取消变量
[root@Cfhost-170820-UCNK ~]# name=john
[root@Cfhost-170820-UCNK ~]# echo $name
john #此时变量有值
[root@Cfhost-170820-UCNK ~]# unset name
[root@Cfhost-170820-UCNK ~]# echo $name
-bash: name: unbound variable
#报错为没有绑定变量
#取消函数
unset_function(){
echo "Hello,World"
}
unset unset_function
unset_function #由于函数已经被取消,这里调用会出错
4.特殊变量
Shell中还有一些预先定义的特殊只读变量,它们的值只有在脚本运行时才能确定。首先是”位置参数“,位置参数的命名简单直接,比如,脚本本身为$0,第一个参数为$1,第二个参数为$2,第三个参数为$3,以此类推。当位置参数的个数大于9时,需要用${}括起来标识,比如说第10个位置参数应该记为${10}。另外,$#表示脚本参数的个数总和,$@或$*表示脚本的所有参数。请看下面示例:
[root@Cfhost-170820-UCNK ~]# cat posion.sh
#!/bin/bash
echo "This script is name is: $0"
echo "$# Parameters in total"
echo "All parameters list as: $@"
echo "The first parameter is $1"
echo "The second parameter is $2"
echo "The third parameter is $3"
[root@Cfhost-170820-UCNK ~]# bash posion.sh a b c
This script is name is: posion.sh
3 Parameters in total
All parameters list as: a b c
The first parameter is a
The second parameter is b
The third parameter is c
脚本或命令返回值:$?
在管理员登录到系统中交互式地输入命令时,系统也会及时在屏幕上输出内容给予反馈。比如说本想使用ifconfig查看网卡状态,但是将命令错写成ifconfi,系统会立刻给出command not found 的提示,这种提示确实能让管理员感觉到系统非常友好。
[root@Cfhost-170820-UCNK ~]# ifconfi
-bash: ifconfi: command not found
但是在很多后台脚本是需要每天自动运行的,比如说每天凌晨两点的数据库备份。在这种情况下一旦出错是不可能在第一时间知道的。那靠什么判断出错呢?
再考虑一个场景:有些自动备份脚本在按时完成本地数据备份后,还会复制一份放到远程主机上(通过scp就可以做到)。不过在复制前需要先确认远程主机是否还”活着",这可以通过ping远程主机做到。如果能ping通则进行复制,如果ping不通则采取其他动作。这里又如何判断是否ping成功了呢?
这时就需要借助命令的返回值来判断了。Linux中规定正常退出的命令和脚本应该以0作为返回值,任何非0的返回值都表示命令未正确退出或未正常执行。
在第一个例子中,输错命令后立即查看当时特殊变量$?的值为127;第二个例子中,ping不通某个地址时查看当时的$?值为1.注意,$?永远是上一个命令的返回值,所以要查看某个
命令的返回值必须在运行该命令后立即查看$?。在自动化脚本中,也可以通过$?变量的值判断之前命令的执行状态,从而采取不同的动作。
#输入错误的命令时的返回值
[root@Cfhost-170820-UCNK ~]# ifconfi
-bash: ifconfi: command not found
[root@Cfhost-170820-UCNK ~]# echo $?
127
#尝试ping主机ping不通时的返回值
[root@Cfhost-170820-UCNK ~]# ping 192.168.160.66 -c 1
PING 192.168.160.66 (192.168.160.66) 56(84) bytes of data.
--- 192.168.160.66 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 10000ms
[root@Cfhost-170820-UCNK ~]# ^C
[root@Cfhost-170820-UCNK ~]# echo $?
130
5.数组
数组是一种特殊的数据结构,其中每一项被称为一个元素,对于每个元素,都可以用索引方式取出元素的值。使用数组的典型场景是一次性要记录很多类型相同的数据时(但不是说一定要相同,因为Shell变量是弱类型的,并不要求数组的每个元素都是相同类型)。比如,为了记录班级中所有人的计算机成绩,如果不用数组来处理就只能定义所有人成绩的变量,这就会显得非常繁琐。Shell中的数组对元素个数没有限制,但是只支持一维数组,这一点和许多语言不同。
a.数组的定义
数组的定义方法如下:用declare命令定义数组Array,并将第一个元素赋值为0,第二个元素赋值为1,其下标(也就是索引)则分别是0和1(记住数组的索引从0开始计数的),然后打印出第一个元素的值
#定义名为Array的索引数组
[root@Cfhost-170820-UCNK ~]# declare -a Array
#数组的下标从0开始计数,定义了第一个元素值为0,第二个元素值为1
[root@Cfhost-170820-UCNK ~]# Array[0]=0
[root@Cfhost-170820-UCNK ~]# Array[1]=1
如果说数组Array的前两个元素“类型相同"(严格意义上这么说是不对的),那么第三个元素就显得”另类"了:赋值为一个字符串。这又一次验证了Shell变量是弱类型的,这在很多语言中是不可能的。
[root@Cfhost-170820-UCNK ~]# Array[2]="HelloWorld"
和其他变量一样,Shell对于数组变量的声明也非常宽松,而且随时都可以根据需要增加变量中的元素。相比其他语言,Shell的数据更为灵活。在很多语言中,一旦对数组进行初始化就不能改变其大小了。
[root@Cfhost-170820-UCNK ~]# declare -a Array
[root@Cfhost-170820-UCNK ~]# Array[0]=0
[root@Cfhost-170820-UCNK ~]# Array[1]=1
[root@Cfhost-170820-UCNK ~]# Array[2]"HelloWorld"
-bash: Array[2]HelloWorld: command not found
[root@Cfhost-170820-UCNK ~]# Array[2]="HelloWorld"
[root@Cfhost-170820-UCNK ~]# declare -a Name=('john','sue')
[root@Cfhost-170820-UCNK ~]# Name[2]='wang'
[root@Cfhost-170820-UCNK ~]# Name=('john' 'sue')
[root@Cfhost-170820-UCNK ~]# Score=([3]=3 [5]=5 [7]=7)
#数组在创建的时候同时赋值
[root@Cfhost-170820-UCNK ~]# declare -a Name=('john' 'sue')
#增加元素
[root@Cfhost-170820-UCNK ~]# Name[2]='wang'
#更简单的创建数组的方式--不使用declare关键字
[root@Cfhost-170820-UCNK ~]# Name=('john' 'sue')
还可以给特定的元素赋值。下面的实例就是只对第四个、第六个、第八个元素进行赋值
#跳号赋值
[root@Cfhost-170820-UCNK ~]# Score=([3]=3 [5]=5 [7]=7)
#数组取值:知道了如何定义数组和赋值元素后,下面就要了解数组一些常见操作。最简单的操作就是数组取值,其格式为:${数组名[索引]}.比之前定义的数组Array、Name为例,取值演示如下·:
[root@Cfhost-170820-UCNK ~]# echo ${Array[0]}
0
[root@Cfhost-170820-UCNK ~]# echo ${Array[2]}
HelloWorld
[root@Cfhost-170820-UCNK ~]# echo ${Name[0]}
john
[root@Cfhost-170820-UCNK ~]# echo
[root@Cfhost-170820-UCNK ~]# echo ${Name[1]}
sue
#指定索引,只能取单个值,要是想一次性取出所有元素的值,可以采取以下两种方式:
[root@Cfhost-170820-UCNK ~]# echo ${Array[@]}
0 1 HelloWorld
[root@Cfhost-170820-UCNK ~]# echo ${Array[*]}
0 1 HelloWorld
从表面上看两者没有什么区别,但是${Array[@]}得到的是以空格隔开的元素值,而${Array[*]}的输出是整个字符串。
数组长度:即数组个数。利用"@"或"*“字符,可以将数组扩展成列表,然后使用"#"来获取数组元素的个数,如下所示:
[root@Cfhost-170820-UCNK ~]# echo ${#Array[@]}
3
[root@Cfhost-170820-UCNK ~]# echo ${#Array[*]}
3
如果某个元素是字符串,还可以通过指定索引的方式获得该元素长度,如下所示:
[root@Cfhost-170820-UCNK ~]# echo ${#Array[2]}
10
数组截取:可以截取某个元素的一部分,对象可以是整个数组或某个元素。
#取出数组的第一、第二个元素
[root@Cfhost-170820-UCNK ~]# echo ${Array[@]:1:2}
1 HelloWorld
#取出第二个元素从第0个字符开始连续5个字符
[root@Cfhost-170820-UCNK ~]# echo ${Array[2]:0:5}
Hello
#l连接数组:将n个数组进行拼接操作
[root@Cfhost-170820-UCNK ~]# Conn=($Array[@] ${Name[@]})
[root@Cfhost-170820-UCNK ~]# echo ${Conn[@]}
0[@] john sue
#取消数组或元素:和取消一般变量一样,取消一个数组的方式也可以使用unset命令
#取消数组中的一个元素
[root@Cfhost-170820-UCNK ~]# unset Array[1]
#取消整个数组
[root@Cfhost-170820-UCNK ~]# echo ${Array[@]}
0 HelloWorld
[root@Cfhost-170820-UCNK ~]# unset Array
[root@Cfhost-170820-UCNK ~]# echo
[root@Cfhost-170820-UCNK ~]# 4{Array[@]}
-bash: 4{Array[@]}: command not found
[root@Cfhost-170820-UCNK ~]# echo ${Array[@]}
6.只读变量
只读变量又称常量,是通过readonly内建命令创建的变量。这种变量在声明时就·要求赋值,并且之后无法修改,这和之前讲的使用declare -r 声明只读变量的效果是一致的。
[root@Cfhost-170820-UCNK ~]# readonly RO=100
[root@Cfhost-170820-UCNK ~]# RO=200
-bash: RO: readonly variable
7.变量的作用域
变量的作用域又叫命名空间,表示变量的上下文。相同的变量可以在多个命名空间中定义,并且彼此之间互不干涉,所以在一个新的命名空间中可以自定义任何变量,因为所定义的变量都只在各自命名空间中。
在Linux系统中,不同进程ID的shell默认为一个不同的命名空间。
Shell变量的作用域是在本Shell内,属于本Shell的全局变量,也就是从定义该变量的地方开始到结束,或到主动使用unset删除该变量的地方为止。在变量的作用域内,该变量是可见的,在函数内对变量也是可以访问的、可修改的,这和C语言不同。