bash编程初体验之正则
认识bash编程
变量与赋值
算术与逻辑运算
条件测试与退出状态
认识bash编程
Bash(GNU Bourne-Again Shell)是许多Linux发行版的默认Shell,我们要认识的bash中,就是在bash的环境下的一种编程。
众所周知,程序=指令+数据,由此也决定了两种不同的编程风格,过程过与对象式;
过程式:以指令为中心,数据服务于指令 对象式:以数据为中心,指令服务于数据
而shell也给我们提供了一种编程能力,在shell上编写的脚本程序,都是解释执行,而不是通过编译,因为我们Bash自身就是解释器。
所有的编程语言都有三种基本的逻辑处理方式:顺序结构、选择结构、循环结构,传说中,只要你掌握这三种结构,你就可以编织一个属于自己的星球了,这就是《***帝国》的故事!
编程语言的基本结构:数据+表达式+语句,在Linux中,一切皆文件,而shell脚本是一个包含命令或声明的文本文件,有一定的格式要求,# 表示注释,且首行要符合shebang机制:
#!/bin/bash #!/usr/bin/python #!/usr/bin/perl
对于运维来说,shell脚本是一个解放我们生产力的工具,可自动化常用的命令;执行系统管理和故障排除;创建简单的应用及处理文本与文件。
当写完一个脚本之后,就需要运行,shell脚本的运行主要有两种方法:
1.给予权限执行,在命令行上指定脚本的相对或绝对路径 2.直接运行bash解释器,将脚本作为解释器程序的参数来运行
在以bash直接运行脚本时,有如下两种调试方法:
1. bash -n /PATH/TO/SOME_SCRIPT 检测脚本有无语法错误 2. bash -x /PATH/TO/SOME_SCRIPT 调试执行
一个简单的shell脚本:
#!/bin/bash #author: liansir #Version: 1.0 #Description: display a Hello World! echo "Hello World!"
变量与赋值
变量一词,小学数学中应该就接触到了,如常量,变量,常量就是指固定不变的量,在数学中就是一个给定的数值;变量就是指一个变化的量,形如x, y之类的;而编程中的常量与变量与数学中的常量与变量其内含是极其相似的,只是它们存在的环境变了而已,在shell脚本编程中,变量就是一段命名的地址空间,用变量可以为代表你想要表达的东西,譬如把变量看作“水果”的话,它可代表苹果、桃子等。
在shell脚本中,变量有两种类型:
强类型:定义变量时必须指定数据类型 弱类型:定义变量时无需指定数据类型
说到数据类型,就涉及到了数据的存储格式,数据的存储主要有字符型与数值型,而数值型主要有×××与浮点型,但是bash是不支持浮点型数据的。
再回到我们的变量,强类型的变量参与运算时一定得符合其类型要求;而弱类型的变量在参与运算时会自动进行隐式转换。既然如此,变量的类型也就会有如下作用:
1.数据存储格式 2.参与的运算 3.表示的数据范围
变量的命名法则:
1.不能程序中的保留字,如if, for 2.只能使用数字、字母及下划线,且不能以数字开头 3,驼峰命名法 4.见名知义
在bash中,根据变量的生效范围主要有如下几种类型的变量:
本地变量:生效范围为当前shell进程,对当前shell的子shell及其它shell均无效 环境变量:生效范围为当前shell进程及其子进程 局部变量:生效范围为当前shell进程中的某代码片断(通常指函数) 位置变量:$1, $2, $3...来表示,用于让脚本在代码中调用通过命令行传递给它的参数 特殊变量:$?, $0, $*, $@, $# 只读变量:只能声明,不能修改与删除, 可用readonly name或declare -r name来声明。
在bash中,变量的赋值有直接赋值与引用赋值两类,所谓直接赋值,类似于name=value,即直接给定一个变量名对其进行赋值,注意等号=两边无空格;引用赋值又分为变量引用与命令:
变量引用:name="$USER" 命令引用:name=`COMMAND` 或 name=$(COMMAND)
再来看看命令行中的变量引用:$name, ${name}!
而引用也有强弱之别:
" ":弱引用,变量会被替换为变量值 ' ':强引用,变量会被当作原字符串
当我们要查看已经定义的所有变量时,可直接使用 set命令,而删除变量则使用unset name.
环境变量,在bash中占有重要的一席之地,其声明与赋值格式为:
export name=VALUE declare -x name=VALUE
通过以下命令来显示环境变量:
export: 可声明与显示环境变量 env:显示系统中已存在的环境变量 printenv:类似于env
小结下bash中的环境变量:
USER, UID, PATH, SHELL, HOME, HISTSIZE, HISTFILE, HISTFILESIZE, HISTCONTROL, HISTTIMEFORMAT, PS1, PWD, OLDPWD
另外,位置变量也不容我们小觑啊!
$0: 命令本身 $1,$2:对应第1与第2个参数,shift [n]变换位置 $*: 传递给脚本的所有参数,且全部参数合为一个字符串 $@: 传递给脚本的所有参数,每个参数为独立字符串 $#: 传递给脚本的参数个数 注:$@ 与 $* 只在被双引号引起来的时候有差异
练习
1、编写脚本/root/bin/systeminfo.sh,显示当前主机系统信息,包括主机名,IPv4地址,操作系统版本,内核版本,CPU型号,内存大小,硬盘大小。
[root@centos7 ~/bin#]cat systeminfo.sh #!/bin/bash # echo -e "The following is systeminfo:\n" echo "The hostname is `hostname`" echo "The IP is `ifconfig |sed -n '2p' |sed -n -r 's@.*inet.(.*) net.*@\1@p'`" echo "The OS version is `cat /etc/redhat-release`" echo "The kernel version is `uname -r`" echo "The CPU is `cat /proc/cpuinfo |grep 'model name' |sed -n '1p' |cut -d: -f2 |tr -d ' 'i`" echo "The memory size is `cat /proc/meminfo |sed -n '1p' |cut -d: -f2 |tr -d ' '`" echo "The disk size is `fdisk -l |sed -n '2p' |cut -d: -f2 |cut -d, -f1`" [root@centos7 ~/bin#]
相对路径执行(要给予其执行权限)
2、编写脚本/root/bin/backup.sh,可实现每日将/etc/目录备份到/root/etcYYYY-mm-dd中.
[root@centos7 ~/bin#]cat baskup.sh #!/bin/bash # cp -r /etc/ /root/etc`date +%F` [root@centos7 ~/bin#]
绝对路径执行(要给予执行权限)
3、编写脚本/root/bin/disk.sh,显示当前硬盘分区中空间利用率最大的值
[root@centos7 ~/bin#]cat checkdisk.sh #!/bin/bash # NumDisk=`df |grep 'sd' |tr -s ' ' |cut -d' ' -f5 |cut -d% -f1` [[ $NumDisk -gt 50 ]] && wall disk will be full!;exit [root@centos7 ~/bin#]
直接利用bash执行(不需要执行权限)
4、编写脚本/root/bin/links.sh,显示正连接本主机的每个远程主机的IPv4地址和连接数,并按连接数从大到小排序.
[root@centos7 ~/bin#]cat links.sh #!/bin/bash # echo -e "远程主机的IP地址及连接数:\n"netstat -nt | tr -s ' ' |cut -d' ' -f5 |tail -n +3 |cut -d: -f1 |sort |uniq -c [root@centos7 ~/bin#]
算术逻辑运算
算术运算
bash中的运算:命令let
help let:
id++, id-- variable post-increment, post-decrement ++id, --id variable pre-increment, pre-decrement -, + unary minus, plus !, ~ logical and bitwise negation ** exponentiation *, /, % multiplication, division, remainder +, - addition, subtraction <<, >> left and right bitwise shifts <=, >=, <, > comparison ==, != equality, inequality & bitwise AND ^ bitwise XOR | bitwise OR && logical AND || logical OR expr ? expr : expr conditional operator =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |= assignment
实现算术运算的几种方式:
1.let var=算术表达式 2.var=$[算术表达式] 3.var=$((算术表达式)) 4.var=$(expr arg1 arg2 arg3...) 5.declare -i var = 数值6.echo '算术表达式' |bc 注:乘法 * 在有些场合中需要转义 \* bash内有内建的随机数生成器:$RANDOM (1-32767) echo $[$RANDOM%50]: 0-49之间的随机数
除此,还有以下增强型赋值:
+=, -=, *=, %= /= let var+=3 自加3后自赋值 let var++ 自加1 let var-=1 自减1后自赋值 let var-- 自减1
逻辑运算
逻辑运算,又称布尔运算,就是关于一个事件真或假的运算,通常用来测试真假值。
true false 1 0 与:同真为真,一假为假 或:同假为假,一真为真 非:非真为假,非假为真 短路运算: 短路与: 第一个为0,结果必定为0 第一个为1,第二个必须要参与运算 短路或: 第一个为1,结果必定为1 第一个为0,结果必须要参与运算 异或:^ 不同为真,相同为假
练习
1:写一个脚本/root/bin/sumid.sh,计算/etc/passwd文件中的第10个用户和第20用户的ID之和。
[root@centos7 ~/bin#]cat sumid.sh #!/bin/bash # num1=`cat /etc/passwd |sed -n -e '10p' |cut -d: -f3` num2=`cat /etc/passwd |sed -n -e '20p' |cut -d: -f3` let var=$num1+$num2echo "第十个用户与第二十个用户的UID之和为:"echo $var [root@centos7 ~/bin#]
2:写一个脚本/root/bin/sumspace.sh,传递两个文件路径作为参数给脚本,计算这两个文件中所有空白行之和.
[root@centos7 ~/bin#]cat sumspace.sh #!/bin/bash # fSpace1=`grep '^$' $1 |wc -l` fSpace2=`grep '^$' $2 |wc -l` sumSpace=$[ $fSpace1+$fSpace2 ]echo "两个文件的空白行之和为:$sumSpace" [root@centos7 ~/bin#]
3:写一个脚本/root/bin/sumfile.sh,统计/etc, /var, /usr目录中共有多少个一级子目录和文件.
[root@centos7 ~/bin#]cat sumfile.sh #!/bin/bash # #统计/etc, /var, /usr目录中共有多少个一级子目录和文件 Detc=`tree -L 1 /etc/ |wc -l` Fetc=`ls -lR /etc/ |wc -l` Dvar=`tree -L 1 /var/ |wc -l` Fvar=`ls -lR /var/ |wc -l` Dusr=`tree -L 1 /usr/ |wc -l` Fusr=`ls -lR /usr/ |wc -l` echo "/etc目录中共有一级子目录$Detc 个,文件 $Fetc 个" echo "/var目录中共有一级子目录$Dvar 个,文件 $Fvar 个" echo "/usr目录中共有一级子目录$Dusr 个,文件 $Fusr 个" [root@centos7 ~/bin#]
条件测试与退出状态
退出状态
每一条命令的执行,要么成功,要么失败,要么成功一半,失败一半,(这种情况严格地说属于失败),对于我们的shell脚本而言,也是一样的,shell脚本不就是把简单的命令通过有效的组织,以编程的思想而实现的么!这也不就是Linux的哲学之一,将单一用途的命令通过组合而完成复杂的任务!
对于我们用户而言,一个脚本执行成功与否,一般情况下通过肉眼凡胎也能看见,大不了 echo $? 一下,返回值为0,则表示执行成功,为1-255之间的数则表示错误。至此,脑袋稍微一转便知Linux系统就是以返回值为0或为1而判断命令是否执行成功的。一个程序的发起就是一个进程,程序的运行有期寿命,进程自然也有其退出的状态,而进行就是通过退出状态来报告成功与失败的。
0 代表成功,1-255代表失败 $? 变量保存最近命令的退出状态 另外,有两种聚焦命令的方法: 复合式:date; who |wc -l 命令会一个接一个地运行 子shell: (date; who |wc -l) >> /tmp/trace 所有的输出都被发送给单个STDOUT和STDERR
既然 echo $? 可以查找最近一个命令的退出状态码,灵活的LInux也允许我们自定义退出状态码:exit [n]
注意:脚本中一旦遇到exit命令,脚本会立即终止,终止退出状态取决于exit命令 后面的数字;如果未给脚本指定的退出状态码,整个脚本状态执行码取决于脚本中 执行的最后一条命令的状态码。
条件测试
所谓条件测试,就是判断某需求是否满足,需要专门的测试机制来实现,测试机制必然有相应的表达式与测试命令。
测试命令:
test EXPRESSION [ EXPRESSION ][[ EXPRESSION ]] 注:EXPRESSION前后必须要有空白字符
test
test - check file types and compare values ( EXPRESSION ) EXPRESSION is true ! EXPRESSION EXPRESSION is false 组合条件测试: COMMAND1 && COMMAND2 COMMAND1 || COMMAND2 ! COMMAND 如:[ -e FILE ] && [-r FILE ] EXPRESSION1 -a EXPRESSION2 both EXPRESSION1 and EXPRESSION2 are true EXPRESSION1 -o EXPRESSION2 either EXPRESSION1 or EXPRESSION2 is true ! EXPRESSION 必须使用测试命令进行: ~#] [ -z "$HOSTNAME" -o "$HOSTNAME"=="localhost.localdomain" ] && hosname liansir.com ~#] [ -f /etc/cat -a -x /etc/cat ] cat /etc/issue 注:&& 与 || 可视为“条件性”操作符,灵活运行这两个操作符,可达到简单if语句 的作用; && 代表条件性的 AND THEN || 代表条件性的 OR ELSE ------------------------------------------------------------------------------ -n STRING the length of STRING is nonzero -z STRING the length of STRING is zero ==: 是否等于 >: 是否大于,比较的是ascii码 <: 是否小于 =~ 左侧字符串是否能够被右侧的PATTERN所匹配 注:此表达式一般用于[[ ]] 中; 用于字符串比较时用到的操作数都应该使用引号; ------------------------------------------------------------------------ 数值测试: INTEGER1 -eq INTEGER2 INTEGER1 is equal to INTEGER2 INTEGER1 -ge INTEGER2 INTEGER1 is greater than or equal to INTEGER2 INTEGER1 -gt INTEGER2 INTEGER1 is greater than INTEGER2 INTEGER1 -le INTEGER2 INTEGER1 is less than or equal to INTEGER2 INTEGER1 -lt INTEGER2 INTEGER1 is less than INTEGER2 INTEGER1 -ne INTEGER2 INTEGER1 is not equal to INTEGER2 ------------------------------------------------------------------------ 文件测试: 双目测试: FILE1 -ef FILE2 FILE1 and FILE2 have the same device and inode numbers # FILE1与FILE2是否指向同一设备上的相同inode号 FILE1 -nt FILE2 FILE1 is newer (modification date) than FILE2 # FILE1是否新于FILE2 FILE1 -ot FILE2 FILE1 is older than FILE2 # FILE1是否旧于FILE2 存在性及类别测试 -b FILE FILE exists and is block special -c FILE FILE exists and is character special -d FILE FILE exists and is a directory -e FILE FILE exists -f FILE FILE exists and is a regular file -h FILE FILE exists and is a symbolic link (same as -L) -L FILE FILE exists and is a symbolic link (same as -h) -p FILE FILE exists and is a named pipe -S FILE FILE exists and is a socket 文件权限测试 -r FILE FILE exists and read permission is granted -w FILE FILE exists and write permission is granted -x FILE FILE exists and execute (or search) permission is granted 文件特殊权限测试 -u FILE FILE exists and its set-user-ID bit is set -g FILE FILE exists and is set-group-ID -k FILE FILE exists and has its sticky bit set 文件大小测试 -s FILE FILE exists and has a size greater than zero 是否存在且非空 文件是否打开: -t FD file descriptor FD is opened on a terminal 文件描述符是否已经被打开且与某终端相关 -O FILE FILE exists and is owned by the effective user ID 当前有效用户是否为文件属主 -G FILE FILE exists and is owned by the effective group ID 当前有效用户是否为文件属组 -N FILE 文件自上一次被读取之后是否被修改过
对于test命令,有以下两种格式:
长格式: test "$A" == "$B" && echo "String are equal" test "$A" -eq "$B" && echo "String are equal" 简写: [ "$A" == "$B" ] && echo "String are equal" [ "$A" -eq "$B" ] && echo "String are equal"
练习
写一个脚本/root/bin/argsnum.sh,接受一个文件路径作为参数;如果参数个数小于1,则提示用户“至少应该给一个参数”,并立即退出;如果参数个数不小于1,则显示第一个参数所指向的文件中的空白行数。
[root@centos7 ~/bin#]cat argsnum.sh #!/bin/bash # [[ $# -lt 1 ]] && echo "至少应该给一个参数" [[ $# -ge 1 ]] && grep '^$' $1 |wc -l [root@centos7 ~/bin#]
写一个脚本/root/bin/hostping.sh,接受一个主机的IPv4地址做为参数,测试是否可连通。如果能ping通,则提示用户“该IP地址可访问”;如果不可ping通,则提示用户“该IP地址不可访问”.
[root@centos7 ~/bin#]cat hostping.sh #!/bin/bash # [[ $# -eq 1 ]] && (echo "$1" |grep -q -E '\<((([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]))\>' && ( ping -c1 -W1 $1 &> /dev/null && echo "该IP地址可访问!" || echo \ "该IP地址不可访问!" )) || echo "请给出一个合法IP地址!"; exit 2[root@centos7 ~/bin#]
chmod -rw /tmp/file1,编写脚本/root/bin/per.sh,判断当前用户对/tmp/fiile1文件是否不可读且不可写。
[root@centos7 ~/bin#]cat per.sh #!/bin/bash #[ ! -r $1 ] && [ ! -w $1 ] && echo "当前用户对$1 文件不可读且不可写!" [root@centos7 ~/bin#]
编写脚本/root/bin/nologin.sh和login.sh,实现禁止和充许普通用户登录系统。
[root@centos7 ~/bin#]cat nologin.sh #!/bin/bash # [ -e /etc/nologin ] && echo "已禁用普通用户登录系统" || touch /etc/nologin && echo "已禁用普通用户登录系统" [root@centos7 ~/bin#] [root@centos7 ~/bin#]cat login.sh #!/bin/bash # [ -e /etc/nologin ] && rm -f /etc/nologin && echo "已允许普通用户登录系统!" [root@centos7 ~/bin#]cat nologin.sh #!/bin/bash # [ -e /etc/nologin ] && echo "已禁用普通用户登录系统" || touch /etc/nologin && echo "已禁用普通用户登录系统" [root@centos7 ~/bin#]
先禁用liansir用户:
允许liansir用户登录:
计算1+2+3+...+100的值
[root@centos7 ~/bin#]cat 100sum.sh #!/bin/bash # # 计算1-100或100-1之间的和 echo "1-100之和为:"echo {1..100} |tr ' ' '+' |bc [root@centos7 ~/bin#]
计算从脚本第一参数A开始,到第二个参数B的所有数字的总和,判断B是否大于A,否提示错误并退出,是则计算之。
[root@centos7 ~/bin#]cat sumAB.sh #!/bin/bash # [[ $# -eq 2 ]] && ( [[ $1 -lt $2 ]] && seq $1 $2 |tr '\n' '+' |grep -o '.*[^+]' |bc \ || echo "$1 大于 $2,无法计算!") || echo "请给出两个数值!" [root@centos7 ~/bin#]
小探位置变量:
位置变量:用于让脚本在代码中调用通过命令行传递给它的参数
$1,$2.. $10,$11...MAX
$*,$@ 的区别
$#
位置变量知多少?
既然参数的个数与所有参数的显示都正常,那为什么从第十个参数起位置变量就引用出错呢?
再看:
至此,我们需要对比一下weizhi1.sh与weizhi2.sh的脚本了。
于是,我们可得出以下结论:$1-$9代表第一到第九个参数,但第十及以上的参数需要用大括号表示,如${10}。
$* 与 $@ 都表示所有参数,那二者又有何区别?
[root@centos7 ~/bin#]cat wzhi.sh #!/bin/bash #e cho "命令本身: $0" echo "1st is $1" echo "2st is $2" echo "all agrs are $*" echo "all agrs are $@" [root@centos7 ~/bin#]
可见$0表示命令本身,这个并不难理解。但$@ 与$*的区别还是看不出来!
[root@centos7 ~/bin#]cat wzhi2.s #!/bin/bash#wzhi.sh "$*" echo ------------------------------ wzhi.sh "$@" [root@centos7 ~/bin#]
由此可见,$*中在参数会被看成是一个整体,$@中的每个参数是独立的,注意这是在被双引号引起的时候。
如果把双引号换作单引号:
最终,我们有了这样的结论:表示位置变量的$* 与 $@ 在被双引号引起的时候是有差别的,$*中在参数会被看成是一个整体,$@中的每个参数是独立的;而如果是被单引号引起来,则二者并无任何差别。
bash编程初体验(一)先到这儿!
2016.8.13
止战