shell变量类型和条件判断
1. 变量
1.1 变量的分类
全局变量:所有的用户都可以使用,保存在 /etc/profile、/etc/bashrc文件中。在开机之后,用户登陆之前,把前面文件中的变量加载到内存中,等待用户使用。
本地变量:用户私有变量,只有本用户可以使用,保存在用户家目录下的 .bash_profile、.bashrc文件中。在用户登录成功之后才会加载到内存中。
用户自定义变量:由用户自定义,比如脚本中的变量。
1.2 定义变量
变量格式: 变量名=值 (注意:在shell编程中的变量名和等号之间不能有空格)
变量命名规则:
命名只能使用英文字母,数字和下划线,首个字符不能以数字开头;
中间不能有空格,可以使用下划线(_);
不能使用标点符号;
不能使用bash里的关键字(可用help命令查看保留关键字);
建议变量名全部用大写,和命令区分开
定义全局变量 -- export
[root@haha ~]# export UNAME="xiaoming"
[root@haha ~]# echo $UNAME
xiaoming
这种方式设置的变量是一次性变量,系统重启后就会丢失。如果希望可以永久使用,可以将需要定义的变量写入变量文件中即可。
定义永久变量 -- 变量文件
定义全局的永久变量:vim /etc/profile,添加全局变量 export UNAME="xiaoming",重启配置文件生效 source /etc/profile
定义本地的永久变量:vim ~/.bash_profile,添加本地变量 UNAME="xiaoming",重启配置文件生效 source ~./bash_profile
定义普通变量 -- 变量赋值
[root@www ~]# UNAME="xiaoming"
[root@www ~]# SCHOOL='nanjing'
[root@www ~]# AGE=25
[root@www ~]# SCORE=99
预定义变量
$0:脚本名
$*:所有的参数,具体区别见下
$@:所有的参数,具体区别见下
$#:参数个数
$$:当前进程的pid
$!:上一个后台进程的pid
$0:上一个命令的返回值,0表示执行成功
预定义变量里, $@ 和 $* 是有区别的:
$* 和 $@ 都表示传递给函数或脚本的所有参数。
当 $* 和 $@ 不被双引号" "包围时,它们之间没有任何区别,都是将接收到的每个参数看做一份数据,彼此之间以空格来分隔。
但是当它们被双引号" "包含时,就会有区别了:
"$*"会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据。
"$@"仍然将每个参数都看作一份数据,彼此之间是独立的。
比如传递了5个参数,那么对于"$*"来说,这5个参数会合并到一起形成一份数据,它们之间是无法分割的;而对于"$@"来说,这5个参数是相互独立的,它们是5份数据。
这种区别在使用函数传入数组参数时就显现出来了(详细参考 https://i-beta.cnblogs.com/posts/edit-done;postId=12832432 ,1.4.2 shell函数传参)。
预定义变量的使用
#!/bin/bash
#ping一个文本中的所有ip
if [ $# -eq 0 ]; then
echo "error info: `basename $0` need detail arguments!"
exit
fi
if [ ! -f $1 ]; then
echo "error info: it mast be a file, not directory!"
exit
fi
for ip in `cat $1`
do
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ]; then
echo "$ip is up"
else
echo "$ip is down"
fi
done
结果如下:
[root@haha shell_0502]# sh ping_txt.sh
error info: ping_txt.sh need detail arguments!
[root@haha shell_0502]#
[root@haha shell_0502]# sh ping_txt.sh ./test.txt
1.3 引用变量
$变量名 或 ${变量名}
1.4 操作变量
读取变量: echo $变量名 或 echo ${变量名}
[root@haha ~]# UNAME="xiaoming"
[root@haha ~]# echo $UNAME
xiaoming
查看变量长度:echo ${#变量名}
[root@haha shell_0503]# NAME='alibaba' [root@haha shell_0503]# echo ${#NAME} 7
切片: echo ${变量名: 开始位置: 切片个数}
[root@haha shell_0503]# NAME='alibaba'
[root@haha shell_0503]#
[root@haha shell_0503]# echo ${NAME:1:2}
li
内容替换:echo ${变量名/替换前内容/替换后内容}, 原来的变量不发生变化
[root@haha shell_0503]# name="alibaba"
[root@haha shell_0503]#
[root@haha shell_0503]# echo ${name}
alibaba
[root@haha shell_0503]#
[root@haha shell_0503]# echo ${name/b/B}
aliBaba
[root@haha shell_0503]# echo ${name//a/A}
AlibAbA
[root@haha shell_0503]# echo $name
alibaba
变量替代:原来的变量不发生变化
${变量名-新值}:变量没有被赋值,就会使用新变量重新赋值;如果变量赋值(包括空值),则不会重新赋值
[root@haha shell_0503]# unset var1 [root@haha shell_0503]# echo ${var1-aaa} aaa [root@haha shell_0503]# var2= [root@haha shell_0503]# echo ${var2-bbb} # 还是空值 [root@haha shell_0503]# var3=333 [root@haha shell_0503]# echo ${var3-ccc} 333 [root@haha shell_0503]# var4='' [root@haha shell_0503]# echo ${var4-ddd} # 还是空值 [root@haha shell_0503]#
${变量名:-新值}:变量没有被赋值(空值也算没有赋值),就会使用新变量重新赋值;如果变量赋值(不为空),则不会重新赋值
[root@haha shell_0503]# var1= [root@haha shell_0503]# echo ${var:-aaa} aaa [root@haha shell_0503]# var2=222 [root@haha shell_0503]# echo ${var2:-bbb} 222
1.5 取消变量 -- unset
unset取消不了永久变量,在重启之后又会恢复,除非在变量文件中删除。
[root@haha ~]# unset UNAME
[root@haha ~]#
[root@haha ~]# echo $UNAME
2. 数组
2.1 基本数组
基本数组,即数组索引从0开始,不允许用户自定义索引的数组。
1. 基本数组语法: 数组名称=(元素1 元素2 元素3 元...)
2. 基本数组查询: echo ${数组名称[index]}
3. 基本数组赋值:数组名[index]=元素值
4. 查看所有数组: declare -a
[root@haha ~]# NAME_ARRAY=('a' 'b' 'c' 'd')
[root@haha ~]# declare -a
declare -a ARRAY1='([0]="a" [1]="b" [2]="c" [3]="d" [4]="e" [5]="f")'
declare -a NAME_ARRAY='([0]="a" [1]="b" [2]="c" [3]="d")'
...
5. 访问数组元素:
echo ${NAME_ARRAY[0]} # 访问数组第一个元素
echo ${NAME_ARRAY[@]} 或 echo ${NAME_ARRAY[*]} # 访问数组中的所有元素, 当用双引号引起来时("${NAME_ARRAY[@]}" 和 "${NAME_ARRAY[*]}")还是有很大区别的。
echo ${#NAME_ARRAY[@]} # 统计数组的长度
echo ${!NAME_ARRAY[@]} # 获取数组的元素索引
echo ${NAME_ARRAY[@]:2} # 数组切片,从索引为2到结束的元素
echo ${NAME_ARRAY[@]:1:2} # 从索引为1开始获取两个元素
6. for循环遍历数组 ---- 根据数组的索引遍历索引对应的值(推荐使用,没有局限性)
第一种遍历: 直接遍历数组的值(有局限性: 当数组的元素有空格时, 遍历会出错)
ME_ARRAY=('a' 'b' 'c' 'd') for i in ${ME_ARRAY[@]} do echo $i done
第二种遍历:根据索引遍历索引对应的值
ME_ARRAY=('a' 'b' 'c' 'd') for i in ${!ME_ARRAY[@]} do echo ${ME_ARRAY[$i]} done
练习: 把hosts文件组成一个数组
注意: host_array[i++]=$line, 这块不用事先声明数组或变量i(默认i=0),不像其他语言那样严格。
#!/bin/bash # 把hosts文件内容组成一个数组,并遍历输出 while read line do host_array[i++]=$line done < /etc/hosts
# 如果这块直接遍历数组值的话, 由于每一个元素有空格存在,所以每个有空格的元素会被多次遍历,导致出错。 for i in ${!host_array[@]} do echo "$i: ${host_array[i]}" done
2.2 关联数组
关联索引,即允许用户自定义数组索引,使用起来更方便高效。
1. 关联数组语法:
# 声明一个关联数组变量
declare -A ASS_ARRAY
ASS_ARRAY=([index1]='元素1' [index2]='元素2' [index3]='元素3' [index]=...)
2. 关联数组查询:
[root@haha ~]# declare -A ASS_ARRAY
[root@haha ~]#
ASS_ARRAY=([name]='xiaoming' [age]=25 [gender]='male')
[root@haha ~]# echo ${ASS_ARRAY[name]}
xiaoming
[root@haha ~]# echo ${ASS_ARRAY[gender]}
male
3. 访问关联数组元素的长度、索引、切片等方法同基本数组一样。
4. 关联数组赋值(同基本数组):关联数组[index]=元素
[root@haha ~]# ASS_ARRAY[score]=100
[root@haha ~]# echo ${ASS_ARRAY[score]}
100
2.3 数组的追加
对于基本数组:arr+=(要追加的值)
[root@haha array_gender]# arr1=("a" "b" "c")
[root@haha array_gender]# arr1+=("d")
[root@haha array_gender]# echo ${arr1[@]}
a b c d
对于关联数组:arr+=([索引]=值)
[root@haha array_gender]# declare -A arr
[root@haha array_gender]# arr=([name]='xiaoming' [age]=25 [gender]='male')
[root@haha array_gender]# arr+=([location]='shanghai')
[root@haha array_gender]# echo ${arr[@]}
male xiaoming 25 shanghai
2.4 数组练习
例1: 对info信息进行性别统计,男女各多少人(用关联数组,把要统计的性别作为索引,把累加的个数作为索引值)
如果直接命令行查看: awk '{print $2}' info.txt |sort | uniq -c
用脚本实现:
xiaofang female
yuanfang male
yingzheng male
xiaoqiao female
diaochan female
lvbu male
mengqi xxx
#!/bin/bash
#统计info.txt中男女各多少人
declare -A gender_array
while read line
do
gender=`echo $line | awk '{print $2}'`
let gender_array[$gender]++
done < info.txt
for i in ${!gender_array[@]}
do
echo "$i: ${gender_array[$i]}"
done
3. shell的条件运算
3.1 数学比较运算
注意:这种比较运算只能支持整型,不支持浮点型,可以先将浮点型*10转成整型然后再判断。
运算符解释
-eq 等于
-gt 大于
-lt 小于
-ge 大于或等于
-le 小于或等于
-ne 不等于
3.2 字符串比较运算
运算符解释,注意字符串一定别忘了使用引号引起来
== 等于
!= 不等于
-n 检查字符串的长度是否大于0
-z 检查字符串的长度是否为0
3.3 逻辑运算
注意: 逻辑运算符不能用于 [ ] 中,但是可以用在 [[ ]] 里;[ ] 中 -a 表示 and, -o 表示 or
逻辑与运算 &&
逻辑或运算 ||
逻辑非运算 !
注意:
逻辑与 或 运算都需要两个或以上条件,逻辑非运算只能一个条件。
口诀: 逻辑与运算 真真为真 真假为假 假假为假
逻辑或运算 真真为真 真假为真 假假为假
逻辑非运算 非假为真 非真为假
3.4 文件比较与检查
-d 检查文件是否存在且为目录
-e 检查文件或目录是否存在
-f 检查文件是否存在且为文件
-r 检查文件是否存在且可读
-s 检查文件是否存在且不为空
-w 检查文件是否存在且可写
-x 检查文件是否存在且可执行
-O 检查文件是否存在并且被当前用户拥有
-G 检查文件是否存在并且默认组为当前用户组
file1 -nt file2 检查file1是否比file2新
file1 -ot file2 检查file1是否比file2旧
file1 -ef file2 检查两个文件的索引位置是否相同,例如文件和该文件的硬链接索引位置相同
4. if语法
4.1 if基础语法
if [ condition ] # condition 值为 true or false,必须和 [ ] 之间有空格, 否则报错,可以使用 " == " 或 " != "
then
commands
fi # if 代码块结束的标志,必须得有,不然报错
下面两个脚本中的 $1,$2 在 shell 中称为位置参数,表示命令行传入的第1个参数, 第2个参数...第10个位置参数表示 ${10}
如何执行含有参数的脚本? bash judge_num.sh 2 5,即表示执行这个脚本,第1个位置参数为2,第2个位置参数为5
#!/bin/bash
#判断两个数的大小,if嵌套。$1表示第一个位置参数,$2表示第二个位置参数
if [ $1 -eq $2 ]
then
echo "$1 = $2"
else
if [ $1 -gt $2 ]
then
echo "$1 > $2"
else
echo "$1 < $2"
fi
fi
#!/bin/bash
#判断两个数大小,if - elif -else
if [ $1 -eq $2 ]
then
echo "$1 = $2"
elif [ $1 -gt $2 ]
then
echo "$1 > $2"
else
echo "$1 < $2"
fi
注意:如果是 if 嵌套的话一定记得每个 if 代码块都要写 fi
4.2 if 高级用法
1. 条件语句中使用 (( 条件...)),可以在条件中植入数学表达式,用来进行数学运算。就可以使用传统的比较运算符 >、<、 >=、<= 等,不用再使用 -eq,-gt,-lt等
注意: if非数学运算的判断条件尽量使用[ ],(()) 只能用于数学运算,不能用于判断字符串和字符串是否相等(使用(())判断出来的结果是相等),字符串和数字是否相等(使用(())判断出来的结果也是相等)。
#!/bin/bash
# 正确使用
if (( 100%3+1>5 ));then
echo "yes"
else
echo "no"
fi
#!/bin/bash
# 错误使用,具体原因看下图红色注释说明
read -p "输入字符>>> " ch
#if (( $ch == "a" ));then
if [ $ch == "a" ];then
echo "字母:$ch"
#elif (( $ch == 0 ));then
elif [ $ch == 0 ];then
echo "数字:$ch"
fi
2. 条件语句中使用 [[条件...]],可以放多个条件,也条件中使用正则。
放多个条件:[[ 2 -eq 2 && 5 -lt 9 ]]
使用正则:[[ "abcde" =~ ^[a-z]+$ ]]
#!/bin/bash
# 如果 jk jm an ae js aw 中,有 a 开头的,就输出
for var in jk jm an ae js aw
do
if [[ "$var" =~ a* ]]
then
echo "$var"
fi
done
用脚本实现: if判断磁盘使用率是否超过90%
注意:awk 参数 -F("%"),表示用 "%" 作为分隔符
#!/bin/bash
#磁盘使用率脚本
disk_use=`df -h | grep "/$" | awk '{print $(NF-1)}' | awk -F"%" '{print $1}'`
if [ $disk_use -ge 90 ]
then
echo "`date +"%F %H:%M:%S"` 磁盘使用率:${disk_use}%, 未超过90%"
else
echo "`date +"%F %H:%M:%S"` 磁盘使用率:${disk_use}%, 未超过90%"
fi
以调试模式运行脚本: sh -vx disk_use.sh(可以看到每一步的结果)
5. case多条件分支语句(用法相当于 if...elif...elif...elif...elif...else...fi)
在生产环境中,我们总会遇到一个问题需要根据不同的状况来执行不同的预案,那么我们要处理这样的问题就要首先根据可能出现的情况写出对应预案,根据出现的情况来加载不同的预案。
特点:根据给予的不同条件执行不同的代码块
5.1 case语法
case 变量 in
条件1)
执行代码块1
;;
条件2)
执行代码块2
;;
......
esac
注意:每个代码块执行完毕要以;;结尾代表结束,case结尾要以倒过来写的esac来结束。
#!/bin/bash #case使用语法 web1=192.168.12.60 web2=192.168.12.56 web3=192.168.17.125 cat << EOF 1. web1:$web1 2. web2:$web2 3. web3:$web3 EOF read -p "input the number that you need to collect: " input_ip case "$input_ip" in 1) ssh alice@$web1 ;; 2) ssh alice@$web2 ;; 3) ssh alice@$web3 ;; "") echo "input do not empty!" ;; *) echo "error input!" ;; esac