脚本编程(一)
啰里啰唆:每周的最后一天都特别难受。墨迹扣不出文章。
一、概况
注释是以#开都的,#开头不一定都是注释
SHELL是解释型语言
SHELL脚本第一句以#!/bin/bash开头
SHELL脚本需要具有执行权限
一般以.sh结尾
别名在脚本中无效。在脚本中不能使用别名。
bash使用技巧:
-n 检查语法,无法无法检测处命令错误,同时只是检查语法不会真正执行脚本。
-x 逐行执行,逐行显示执行结果
脚本中的错误:
1、语法错误,会导致后续的命令无法继续执行。可以通过bash -n选项来检查
提示的错误行不一定是准确的。
2、命令错误,后续的命令还可以继续执行。无法通过bash -n 选项来检查错误,可以使用set -e或者set -o errexit来设定遇到错误命令后退出
3、逻辑错误,只能使用bash -x选项来检查错误。
变量:
变量表示命名的内存空间,讲数据放在内存空间中,通过 "$变量名" 引用,从而获取数据
内置环境变量:
PS1 SHELL HASTNAME $$ $?
自定义变量:
[set] NAME=VALUE set可以省略
变量类型:
字符型:默认都是字符型
数值:整型、 bash不支持浮点型
静态编译语言,使用变量前,先声明变量类型,之后类型不能改变,在编译时执行检查,JAVA C
动态编译语言:不用事先声明,可以随时改变变量类型,SHELL PYTHON
强类型语言:不同类型的操作数,必须经过强制转换成同一类型的变量后才能运算
弱类型语言:不同类型的操作数,会隐式转换数据类型,如SHELL
shell中变量命令规则
变量名不用实现声明
变量名不能和系统已经定义好的变量同名
变量名不能和系统的内外部命令同名
变量名不能和系统定义好的关键字同名,如:if else case for 等
变量名不能以数字开头
变量名不支持短横线-
变量名需要见名知意,最好用英语名,最好不要命拼音,不要用拼英缩写
统一命名规则:驼峰命名法,大驼峰studentName 小驼峰studentName
变量名大写
局部变量小写
函数名小写
普通变量,生效范围是当前shell进程;对当前shell之外的其它进程(包括当前shell的子进程)均无效。
环境变量,生效范围是当前shell进程及其子进程
本地变量,生效范围进士当前shell进程中某个代码片段,通常指函数。
变量赋值的时候等号左右不要有空格: NAME="value" 如果赋值中有空格需要使用引号引起来。
可以把文件路径赋值给变量。
特殊用法参考以下示例:
[root@CentOS8 /]# FILE=/* [root@CentOS8 /]# echo $FILE /bin /boot /data /dev /etc /home /lib /lib64 /media /mnt /opt /proc /root /run /sbin /srv /sys /@System.solv /tmp /usr /var /* [root@CentOS8 /]# ls -d $FILE /bin /data /etc /lib /media /opt /root /sbin /sys /tmp /var /boot /dev /home /lib64 /mnt /proc /run /srv /@System.solv /usr [root@CentOS8 /]# ls -d "$FILE"
变量引用 $NAME ${NAME}
变量引用的时候,加上双引号会保留原始的回车符等隐式字符,
不加上双引号默认会去掉回车符并以空格代替。
看以下示例 {
[root@CentOS8 ~]# NUM=`seq 10` [root@CentOS8 ~]# echo "$NUM" 1 2 3 4 5 6 7 8 9 10 [root@CentOS8 ~]# echo $NUM 1 2 3 4 5 6 7 8 9 10 [root@CentOS8 ~]#
注意:变量赋值时临时生效,当退出终端后,变量会自动删除;脚本中的变量会随着脚本结束自动删除。
env可以显示所有已经定义好的环境变量
export不跟任何选项,也可以显示所有已经定义的环境变量
declare -x 也可以显示所有已经定义的环境变量
set可以显示所有已经定义好的所有变量
unset NAME可以取消变量定义
unset NAME1 NAME2 NAME3...取消多个变量
无法在子shell中使用父shell中定义的本地变量,可以使用export或declare -x把变量声明成环境变量,就可以在子shell(包括孙子进程)中调用父shell中定义的环境变量。但是无法让父进程继承子进程的
变量,孙子进程可以继承子进程的环境变量。
$BASHPID 显示当前进程的PID
$PPID 显示父进程的PID
LANG 语言系环境变量
MAIL 邮箱
SHLVL shell的嵌套深度
变量除了可以存字符,还可以存命令。$CMD的结果SHELL会当成命令执行。
只读变量,定义后的只读变量不能修改和删除,只能退出重新登
readonly NAME 定义只读变量
declare -r NAME 定义只读变量
位置变量:
$0 表示命令本身,会包括路径
$1 表示对应第一个参数,使用shift n替换
$2 表示对应第二个参数,使用shift n替换
...
${10} 表示对应的第十个参数,不能直接写成$10
${11} 表示对应的第十一个参数,不能直接写成$11
$* 表示所有参数,会把各个变量看成统一整体,只有双引号”$*"”$@"才有区别
$@ 表示所有参数,会把各个变量看成独立个体,只有双引号”$*"”$@"才有区别
$# 表示参数个数
$? 判断上一条执行状态的执行结果
$_ 前一个命令的最后一个参数
set -- 清楚所有位置变量
[root@CentOS7 2]# cat arg.sh #!/bin/bash # echo "1st is $1" echo "2st is $2" echo "3st is $3" echo "10st is ${10}" echo "10st is $10" echo "11st is ${11}" echo "11st is $11" echo "The number of `basename $0` is $#" echo "All args are $*" echo "All args are $@"
./arg2.sh $* ;echo '$*' ./arg2.sh $@ ;echo '$@' ./arg2.sh "$*" ;echo '"$*"' ./arg2.sh "$@" ;echo '"$@"'
[root@CentOS7 2]# cat arg2.sh #!/bin/bash # echo -n "$1 " [root@CentOS7 2]# ./arg.sh {a..z} 1st is a 2st is b 3st is c 10st is j 10st is a0 11st is k 11st is a1 The number of arg.sh is 26 All args are a b c d e f g h i j k l m n o p q r s t u v w x y z All args are a b c d e f g h i j k l m n o p q r s t u v w x y z a $* a $@ a b c d e f g h i j k l m n o p q r s t u v w x y z "$*" a "$@" [root@CentOS7 2]#
命令行展开
命令行展开优先级
1、把命令行分成单个命令词
2、展开别名
3、展开大括号的声明{}
4、展开波浪线声明~
5、命令替换$()或··
6、再次把命令行分成命令词
7、展开文件通配符
8、准备I/O重定向
9、运行命令
防止扩展
使用反斜线会是随后的字符按愿意执行
脚本安全和set
she命令,地址shell环境
$- 以关键字形式显示系统启用功能
+表示禁用,-表示启用
h 表示hash,可以set +h选项关闭hash
i 交互式,说明当前shell是个交互是shell
m 表示启用job control来控制进程的停止
B 表示启用大括号扩展
H HISTORY,表示可以展开命令历史列表
$_ 前一个命令的最后一个参数
set命令 修改环境变量
-o 打开关闭某些选项,如果后面不跟任何选项表示显示以shell启用的某些功能
set -o 表示启用
set +o 表示禁用
-e 等同于 -o errexit遇到命令出错就退出
-u 是否使用没有定义的变量,相当于-o nounset
-x 单步执行
exit命令
用户可以在脚本中使用以下命令自定义退出状态码
exit [n]
脚本中一旦遇到exit命令,脚本会立即终止,然后返回状态码
如果exit未指定退出码,整个脚本的最终状态码取决于最后一条命令的状态码。
例:{示例说明演示未指定退出码的情况
[root@CentOS7 2]# cat exit.sh #!/bin/bash # echo haha ech haha exit [root@CentOS7 2]# ./exit.sh haha ./exit.sh: line 4: ech: command not found [root@CentOS7 2]# echo $? 127
printf命令
printf "指定的格式" 文本1 文本2.。。 按照指定格式显示指定文本
%s 字符串格式,%-#.##s中,#表示显示字符宽度,数字不足的用空格补足,##表示显示小数个数,-表示左对齐
%f 浮点型格式
%b 相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义
%d | %i 十进制整数
%c ASCII字符,即显示对应参数的第一个字符
%o 八进制显示
%u 不带正负号的十进制显示
%x 小写的十六进制显示,即a-f
%X 大写的十六进制显示,即A-F
%% 表示%本身
转义字符:
a 警告字符,会发一声响
后退一格
f 换页
换行
回车
横向制表符
v 纵向制表符
\ 表示本身
二、算数运算
算数运算符
+
-
*
/
% 取模,求余数
** 乘方
进行算数表达式
let $M+$N 不用带$,带上也没问题
let M++ 让M自增,与++M的区别是前者是先引用后自增,后者是先自增后引用
let N-- 让N自减,与--N的区别是前者是先引用后自减,后者是先自减后引用
$[$M+$N] 不用带$,带上也没问题
$(($M+$N)) 不用带$,带上也没问题
expr $M + $N 在运算符两边要有空格,在运算乘法的时候需要转移即*
declare -i VAR 可以使用declare -i把变量声明成整数,然后就可以直接运算了
三、逻辑运算
与 1&&1=1 1&&0=0 0&&1=0 0&&0=0 任何数与0相与都为假
或 1||1=1 1||0=1 0||1=1 0||0=0 任何数与1相或都为真
非 !1=0 !0=1
异或 1^0=1 1^1=0 0^0=0 0^1=0 异或是用二进制进行运算。异或的两个值,相同为假,不同为真
两个运算数异或得出的值,此值在与两个运算数其中任何一个再异或,必定得出另一个运算数
示例演示用异或互换两个变量的值
#!/bin/bash
# i=10 j=20 echo "i=$i;j=$j" #i中的值现在已经是i与j的异或值 i=$[i^j] #下句的意思是用上一句得出的异或值i,与运算数j 再次进行异或,会得出另一个运算数 j=$[i^j] #下句的意思是用上一句得出的异或值j再次与第一句中得出的异或值i进行异或,会得出另一个 运算数 i=$[i^j] echo "i=$i;j=$j"
短路运算
短路与:CMD1&&CMD2
第一个CMD1结果为0,总的结果必定为0,因此不需要执行CMD2
第一个CMD1结果为1,第二个CMD2必须要参与运算,才能得到最终结果
短路或:CMD1||CMD2
第一个CMD1结果为1,总的结果必定为1,因此不需要执行CMD2
第一个CMD1结果为0,第二个CMD2必须要参与运算,才能得到最终结果
四、条件测试
条件测试命令
test EXPRESSION
[ EXPRESSION ] 判断式必须有空格。等同于test
[[ EXPRESSION ]]
变量测试
[ -v VAR ] 判断变量VAR是否已经定义,True if the shell variable VAR is set.
[ -R VAR ] 判断变量VAR是否已经定义并且已经引用,True if the shell variable VAR is set and is a name reference.
数值测试 进行数值判断的时候不能有非数字
-eq 相等
-ne 不相等
-lt 小于
-le 小于等于
-gt 大于
-ge 大于等于
字符串测试
-z 测试变量是否为空或者没赋值,变量未定义 或 变量里面是空值 都为真,但是在以用变量的时候需要加上双引号。True if string is empty.例:n="";[ -z "$n" ];echo $?
-n 判断变量是否为非空,在引用变量的时候必须加上双引号 True if string is not empty.例: [ -n "$mm" ]
[STRING] 判断式中什么也不见,默认是指判断变量是否为非空。True if string is not empty.
= 判断字符串是否相等,等号两边都有空格,True if the strings are equal.
!= 判断字符串是否不相等,不等号两边都有空格,True if the strings are not equal.
> 前一个字符串的长度大于后一个字符串,True if STRING1 sorts after STRING2 lexicographically.
< 前一个字符串的长度小于后一个字符串,True if STRING1 sorts before STRING2 lexicographically.
[ $mm ] 如果什么也不跟,是为了判断变量mm长度是否非零,True if string is not empty.
示例演示[ $mm ]
[root@CentOS8 7]# mm="" [root@CentOS8 7]# [ $mm ] && echo haha [root@CentOS8 7]# mm="123" [root@CentOS8 7]# [ $mm ] && echo haha haha}
[[ ]] 双中括号的时候里面可以用正则表达式,双中括号也支持通配符,一般情况下使用单中括号
[[ "$FILE" == *.log ]] 在双中括号中== 后面接通配符,判断FILE文件是不是log结尾
[[ "$IP" =~ ^([0-9]{1,3}.){3}[0-9]{1,3}$ ]] 在双中括号中=~ 后面接正则表达式
结论:在[[ == ]]这种写法中,==右侧的* 如果想做通配符,不要加"",只想做*本身含义,需要转移或者加上双引号
示例演示通配符 {[root@CentOS8 7]# mm=hh.tt
[root@CentOS8 7]# [ "$mm" == "*.tt" ] && echo haha [root@CentOS8 7]# [ $mm == "*.tt" ] && echo haha [root@CentOS8 7]# [[ $mm == "*.tt" ]] && echo haha [root@CentOS8 7]# [[ "$mm" == "*.tt" ]] && echo haha [root@CentOS8 7]# [[ "$mm" == "*.tt" ]] && echo haha [root@CentOS8 7]# [[ "$mm" == *.tt ]] && echo haha haha
=~和==详细使用介绍{
#通配符 [root@centos8 ~]#FILE=test.log [root@centos8 ~]#[[ "$FILE" == *.log ]] [root@centos8 ~]#echo $? 0 [root@centos8 ~]#FILE=test.txt [root@centos8 ~]#[[ "$FILE" == *.log ]] [root@centos8 ~]#echo $? 1 [root@centos8 ~]#[[ "$FILE" != *.log ]] [root@centos8 ~]#echo $? 0 #正则表达式 [root@centos8 ~]#[[ "$FILE" =~ .log$ ]] [root@centos8 ~]#echo $? 1 [root@centos8 ~]#FILE=test.log [root@centos8 ~]#[[ "$FILE" =~ .log$ ]] [root@centos8 ~]#echo $? 0 [root@centos8 ~]#N=100 [root@centos8 ~]#[[ "$N" =~ ^[0-9]+$ ]] [root@centos8 ~]#echo $? 0 [root@centos8 ~]#N=Magedu10 [root@centos8 ~]#[[ "$N" =~ ^[0-9]+$ ]] [root@centos8 ~]#echo $? 1 [root@centos8 ~]#IP=1.2.3.4 [root@centos8 ~]#[[ "$IP" =~ ^([0-9]{1,3}.){3}[0-9]{1,3}$ ]] [root@centos8 ~]#echo $? 0 [root@centos8 ~]#IP=1.2.3.4567 [root@centos8 ~]#[[ "$IP" =~ ^([0-9]{1,3}.){3}[0-9]{1,3}$ ]] [root@centos8 ~]#echo $? 1 [root@centos8 ~]#[[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3} ([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]] [root@centos8 ~]#echo $? 1 #通配符 [root@centos8 ~]#NAME="linux1" [root@centos8 ~]#[[ "$NAME" == linux* ]] [root@centos8 ~]#echo $? 0 [root@centos8 ~]#[[ "$NAME" == "linux*" ]] [root@centos8 ~]#echo $? 1 [root@centos8 ~]#NAME="linux*" [root@centos8 ~]#[[ "$NAME" == "linux*" ]] [root@centos8 ~]#echo $? 0
#结论:[[ == ]] == 右侧的 * 做为通配符,不要加“”,只想做为*, 需要加“” 或转义
文件测试
-a 文件是否存在,True if file exists.
-e 文件是否存在 等同于-a, -e选项常用,True if file exists.
-b 文件是否是块文件,True if file is block special.
-c 文件是否是字符文件,True if file is character special.
-d 判断是不是目录,True if file is a directory.
-f 判断是不是普通文件,在判断软连接的时候,会追踪软连接指向的文件,True if file exists and is a regular file.
-h 或 -L 判断是不是软连接,True if file is a symbolic link.
-p 判断是不是管道文件,True if file is a named pipe.
-S 大写,判断是不是套接字文件
文件新旧比较
-ot old than,True if file1 is older than file2.
-nt new than,True if file1 is newer than file2
-ef 判断是不是硬链接,True if file1 is a hard link to file2.
文件权限测试
-w 判断文件是否有写权限,判断最终的权限,而不是看表面ll的显示结果,True if the file is writable by you.
-x 判断文件是否有执行权限,判断最终的权限,而不是看表面ll的显示结果,True if the file is executable by you.
-r 判断文件是否有读权限,判断最终的权限,而不是看表面ll的显示结果,True if file is readable by you.
-u FILE: 是否存在且拥有suid权限
-g FILE: 是否存在且拥有sgid权限
-k FILE: 是否存在且拥有sticky权限
文件属性测试
-s 小写,是否存在且非空,True if file exists and is not empty.
-t 判断文件的文件描述符是否已经打开,True if FD is opened on a terminal.
-N FILE 文件自从上一次被读取之后是否被修改过
-O FILE 当前有效用户是否为文件属主
-G FILE 当前有效用户是否为文件属组
() 小括号中变量会在子进程中运行,同时会继承父进程中的变量值,但是起产生的结果不会影响父进程中的值
{} 花括号中的变量不会在子进程中运行,会在当前进程中执行,其结果会影响话括号外面的变量,花括号内的最后公式需要以分号;结尾,花括号内两边有空格
()和{}都能将多个命令组合在一起,批量执行.
组合测试条件
[ EXPRESSION1 -a EXPRESSION2 ] 并且,表达式1和表达式2,结果才为真,True if both expr1 AND expr2 are true.
[ EXPRESSION1 -o EXPRESSION2 ] 或者,表达式1和表达式2只要一个为真,结果就为真,会进行短路运算,True if either expr1 OR expr2 is true.
[ ! EXPRESSION ] 取反,True if expr is false.
-a和-o选项只能在单中括号内使用,不能再[[ ]]内使用。
COMMAND1 && COMMAND2 并且,短路与,代表条件性的AND THEN。如果COMMAND1 成功,将执行COMMAND2,否则,将不执行COMMAND2
COMMAND1 || COMMAND2 或者,短路或,代表条件性的OR ELSE。如果COMMAND1 成功,将不执行COMMAND2,否则,将执行COMMAND2
! COMMAND 非,取反
如果&&和||同时使用,一般情况下&&放在前面给,||放在后面。
六、条件判断
顺序执行:
;分号
if语句
单分支:
if COMMANDS; then
COMMANDS
fi
双分支:
if EXPRIESSION;then
COMMANDS
else
COMMANDS
fi
多分支:
if EXPRIESSION;then
COMMANDS
elif EXPRIESSION; then
COMMANDS
elif EXPRIESSION; then
COMMANDS
fi
case语句
case WORD in
[PATTERN])
COMMAND
;;
[PATTERN])
COMMAND
;;
*)
COMMAND
;;
esac
PATTER式通配符模式
* 任意长度任意字符
? 任意一个字符
[] 匹配中括号中的单个字符
^ 取反