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#]

相对路径执行(要给予其执行权限)

bash编程初体验之正则_第1张图片

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#]

绝对路径执行(要给予执行权限)

 bash编程初体验之正则_第2张图片

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执行(不需要执行权限)

bash编程初体验之正则_第3张图片

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编程初体验之正则_第4张图片


算术逻辑运算

算术运算

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#]

bash编程初体验之正则_第5张图片

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#]

wKiom1euyTiza2aHAAA2_fRg0UI731.png

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#]

bash编程初体验之正则_第6张图片


条件测试与退出状态

退出状态

每一条命令的执行,要么成功,要么失败,要么成功一半,失败一半,(这种情况严格地说属于失败),对于我们的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"

练习

  1. 写一个脚本/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#]

bash编程初体验之正则_第7张图片

  1. 写一个脚本/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#]

bash编程初体验之正则_第8张图片

  1. 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#]

bash编程初体验之正则_第9张图片


  1. 编写脚本/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用户:

wKiom1euyDPCQyCpAAAslOHP2r4151.png

bash编程初体验之正则_第10张图片

bash编程初体验之正则_第11张图片

允许liansir用户登录:

wKiom1eux4filXDLAAAsYdft3t4788.png

bash编程初体验之正则_第12张图片

  1. 计算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#]

bash编程初体验之正则_第13张图片

  1. 计算从脚本第一参数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#]

bash编程初体验之正则_第14张图片


小探位置变量:

位置变量:用于让脚本在代码中调用通过命令行传递给它的参数

  • $1,$2.. $10,$11...MAX

  • $*,$@ 的区别

  • $#

位置变量知多少?

bash编程初体验之正则_第15张图片

既然参数的个数与所有参数的显示都正常,那为什么从第十个参数起位置变量就引用出错呢?

再看:

bash编程初体验之正则_第16张图片

至此,我们需要对比一下weizhi1.sh与weizhi2.sh的脚本了。

bash编程初体验之正则_第17张图片

于是,我们可得出以下结论:$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#]

bash编程初体验之正则_第18张图片

可见$0表示命令本身,这个并不难理解。但$@ 与$*的区别还是看不出来!

[root@centos7 ~/bin#]cat wzhi2.s
#!/bin/bash#wzhi.sh "$*"
echo ------------------------------
wzhi.sh "$@"
[root@centos7 ~/bin#]

bash编程初体验之正则_第19张图片

由此可见,$*中在参数会被看成是一个整体,$@中的每个参数是独立的,注意这是在被双引号引起的时候

如果把双引号换作单引号:

bash编程初体验之正则_第20张图片

bash编程初体验之正则_第21张图片

最终,我们有了这样的结论:表示位置变量的$* 与 $@ 在被双引号引起的时候是有差别的,$*中在参数会被看成是一个整体,$@中的每个参数是独立的;而如果是被单引号引起来,则二者并无任何差别

bash编程初体验(一)先到这儿!

2016.8.13

止战