本章主要介绍函数的基础,变量赋值,逻辑运算,条件表达式,测试
一,编程基础的介绍
1.1 编程基础
程序是由指令和数据组成的
程序编程风格可分为面向对象和面向过程编程
面向过程:以指令为中心,数据服务于指令
面向对象:以数据为中心,指令服务于数据
sehll程序提供了编程能力,是通过解释器解释执行的
1.2 程序的执行方式
计算机只能运行二进制指令;
编程语言:
低级:汇编
高级:
编译:高级语言 --> 编译器 --> 目标代码 比如 java,C# 等
解释:高级语言 --> 解释器 --> 机器代码 比如 shell perl python 等
1.3 编程基本概念
编程逻辑处理方式:
顺序执行
循环执行
选择执行
shell编程:过程式,解释执行
编程语言的基本结构:
数据存储:变量,数组 等
表达式:a+b 等
语句:if while 等
二,shell脚本编程
2.1 shell脚本基础
shell脚本是包含一些命令或声明,并符合一定格式的文本文件
格式要求:首行shebang机制
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl
shell脚本的用途:
自动化常用命令
执行系统管理和故障排除
创建简单的应用程序
处理文本或文件
2.2 创建shell脚本
第一步:使用文本编辑器来创建一个以 .sh 为结尾的文本文件,第一行必须包括shell声明序列:#!/bin/bash ;添加适当的注释,注释以 # 开头
第二步:运行脚本,首先,先给脚本赋予执行权限,在命令行上指定脚本的绝对或相对路径;也可以直接运行解释器,将脚本作为解释器程序的参数运行
脚本范例如下:
#!/bin/bash #author: nineven #Version: 1.0 #Description:This script displays some information about your # environment echo "Greetings. The date and time are $(date)" echo "Your working directory is: $(pwd)"
2.3 脚本调试
bash -n /path/to/some_script :检查脚本中的语法错误(这个功能好像不怎么好用)
bash -x /path/to/some_script :调试执行,感觉很强大
2.4 编程程序语言分类
强类型:定义变量时必须指定类型,参与运算必须符合类型要求,调用未声明变量会产生错误,比如 java python
弱类型:无须指定类型,默认均为字符型,参与运算会自动进行隐式类型转换,变量无须事先定义可直接调用,比如 bash 不支持浮点数
变量命令法则:
不能使用程序中的保留字:比如 if for 等
只能使用数字,字母及下划线,且不能以数字开头
见名知义
2.5 变量
变量:命名空间的内存空间
变量的数据存储方式:
字符
数值,比如 整形,浮点型
变量的类型:
字符
数值:整形,浮点型
变量的作用:
数据存储格式
参与的运算
表示的数据范围
2.5.2 bash中的变量的种类
根据变量的生效范围等标准可分为以下几类:
本地变量:生效范围为当前shell进程,对当前shell之外的其他shell进程,包括当前shell的子shell进程均无效
环境变量:生效范围为当前shell进程及其子进程
局部变量:生效范围为当前shell进程中某代码片段(通常指函数)
位置变量:$1,$2,...来表示,用于让脚本在脚本代码中调用通过命令行传递给它的参数
特殊变量:$?,$0,$*,$@,$#
2.5.2.1 本地变量
变量赋值: name='value'
可以使用引用value:
可以直接是字符串:name="nineven"
变量引用:name="$USER"
命令引用:name=`command`,name=$(command)
变量引用:${name},$name
"":弱引用,其中的变量引用会被替换为变量值
'':强引用,其中的变量引用不会被替换为变量值,而是保持原字符串
显示已定义的所有变量:set
删除变量: unset name (删除变量时千万不要加 $ 符号)
实战训练1
1、编写脚本/root/bin/systeminfo.sh,显示当前主机系统信 息,包括主机名,IPv4地址,操作系统版本,内核版本, CPU型号,内存大小,硬盘大小。
#!/bin/bash echo -e "The computer's info is" name=$(hostname) ip=$(ifconfig | sed -rn "s@^[[:space:]]+inet[[:space:]]+(.*)net.*@\1@p"|head -1) sysinfo=$(cat /etc/centos-release) kernav=$(uname -r) cpuinfo=$(lscpu | sed -r -n 's@Model name.*:[[:space:]]+(.*)@\1@p') meminfo=$( cat /proc/meminfo | head -1) hhdinfo=$(fdisk -l | head -2|tail -1) echo -e "\033[31m------主机名----------\033[0m" echo $name echo -e "\033[31m--------IPv4地址-----------------\033[0m" echo -e $ip echo -e "\033[31m---------操作系统版本-------------\033[0m" echo $sysinfo echo -e "\033[31m--------内核版本-----------------\033[0m" echo $kernav echo -e "\033[31m-------CPU型号------------------\033[0m" echo $cpuinfo echo -e "\033[31m----------内存大小---------------\033[0m" echo $meminfo echo -e "\033[31m-----硬盘大小--------------------\033[0m" echo $hhdinfo echo -e "\033[31m-------------------------\033[0m"
2、编写脚本/root/bin/backup.sh,可实现每日将/etc/目录 备份到/root/etcYYYY-mm-dd中
#!/bin/bash cp -a /etc /root/etc`date +%F`
3、编写脚本/root/bin/disk.sh,显示当前硬盘分区中空间利 用率最大的值
#!/bin/bash echo "当前硬盘分区利用率最大值是 : `df | egrep '\/dev\/sda*' | tr -s ' '|cut -d' ' -f5|sort -n|tail -1`"
4、编写脚本/root/bin/links.sh,显示正连接本主机的每个远 程主机的IPv4地址和连接数,并按连接数从大到小排序
#!/bin/bash echo "the links number is:" netstat -nt |tr -s ' ' |cut -d ' ' -f5 |cut -d: -f1 |grep [0-9]|sort |uniq -c|sort -nr
2.5.2.2 环境变量
变量声明,赋值:
export name=VALUE
declare -x name=VALUE
变量引用:$name,${name}
显示所有环境变量:
export
env
printenv
删除:unset name
bash有许多内建的环境变量:PATH,SHELL,USER,UID,HISTSIZE,HOME,PWD,OLDPWD,HISTFILE,PS1等
2.5.2.3 只读和位置变量
只读变量:只能声明,但不能修改和删除
readonly name
declear -r name
位置变量:在脚本代码中调用通过命令行传递给脚本的参数
$1,$2,...:对应第1,第2等参数,shift [n] 换位置
$0:命令本身
$*:传递给脚本的所有参数,全部参数合为一个字符串
$@:传递给脚本的所有参数,每个参数为独立字符串
$#:传递给脚本的参数的个数
$@ $* 只在被双引号包起来的时候才会有差异
比如:判断给出的文件的行数
#!/bin/bash linecount="$(wc -l $1| cut -d' ' -f1)" echo "$1 has $linecount lines."
三,shell脚本运算
3.1 算术运算
bash中的算术运算可以通过 help let 命令查看
+ - * / %取模(取余) **(乘方)
实现算术运算:
let var=算术表达式
var=$[算术表达式]
var=$((算术表达式))
var=$(expr arg1 arg2 arg3 ...)
declare -i var = 数值
echo `算术表达式`|bc
乘法符号有些场景中需要转义,如*
bash中有内建的随机数生成器:$RANDOM 可以生成 1-32767 ,比如
echo $[$RANDOM%80] :表示0-79之间的随机数 [root@centos7 ~]# echo $[$RANDOM%81] 73 [root@centos7 ~]# echo $[$RANDOM%81] 9 [root@centos7 ~]# echo $[$RANDOM%81] 80
3.2 赋值
增强型赋值:
+= ,-= ,*= ,/= ,%=
let varOPERvalue
例如: let count+=3 自加3后的值重新赋给自己
自增,自减:
let var+=1
let var++
let var-=1
let var--
实战训练2
1:写一个脚本/root/bin/sumid.sh,计算/etc/passwd 文件中的第10个用户和第20用户的ID之和
#!/bin/bash echo "ID sum is : `sed -n '10p;20p' /etc/passwd | cut -d: -f3 | paste -s -d '+'| bc`"
2:写一个脚本/root/bin/sumspace.sh,传递两个文件 路径作为参数给脚本,计算这两个文件中所有空白行之和
#!/bin/bash echo "文件空白行总共有: `cat $1 $2 | grep -c '^[[:space:]]*$'` 行"
3:写一个脚本/root/bin/sumfile.sh,统计/etc, /var, /usr目录中共有多少个一级子目录和文件
#!/bin/bash echo $[$(ls -a /etc /var /usr |wc -l)-6]
我感觉 . .. 应该不是子目录吧,所以我在得到的结果减去6
3.3 逻辑运算
true ,false
1 0
与:
同一为一,其余为零
1 与 1 = 1
1 与 0 = 0
0 与 1 = 0
0 与 0 = 0
或:
同零为零,其余为一
1 或 1 =1
1 或 0 =1
0 或 1 =1
0 或 0 =0
非:!
零一互换
!1 = 0
!0 = 1
短路运算:
短路与;
第一个为0,结果必定为0
第一个为1,第二个必须要参与运算
短路或:
第一个为1,结果必定为1
第一个为0,第二个必须要参与运算
异或:^
异或的两个值,相同为假,不同为真
3.4 聚集命令
有两种聚集命令的方法:
复合式:date;who |wc -l 命令会一个接一个地运行
子shell:(date;who |wc -l) >> /tmp/trace 所有的输出都被发送给单个STDOUT和STDERR
3.5 退出状态及退出码
进程使用退出状态来报告成功或失败
0 代表成功,1-255代表失败
$? 变量保存最近的命令退出状态
例如
[root@centos7 testdir]# ping -c1 -W1 10.1.0.1 &> /dev/null [root@centos7 testdir]# echo $? 0 [root@centos7 testdir]# ping -c1 -W1 10.1.2.1 &> /dev/null [root@centos7 testdir]# echo $? 1
成功返回0 失败返回非0
bash自定义退出状态码
exit [n] :自定义退出状态码
注意:脚本中一旦遇到exit命令,脚本会立即终止,终止退出状态取决于exit命令后面的数字
如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
四,测试
4.1 条件测试
判断某需求是否满足,需要由测试机制来实现
专用的测试表达式需要由测试命令辅助完成测试过程
评估布尔声明,以便用在条件性执行中
若真,则返回0
若假,则返回1
测试命令
test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]
注意:EXPRESSION前后必须有空白字符
4.2 条件性的执行操作符
根据退出状态而定,命令可以有条件地运行
&& 代表条件性的AND THEN
|| 代表条件性的OR ELSE
比如:
[root@centos7 testdir]# grep -q root /etc/passwd && echo 'user is exist' || echo 'no user' user is exist [root@centos7 testdir]# grep -q roota /etc/passwd && echo 'user is exist' || echo 'no user' no user
[root@centos7 testdir]# ip="10.1.0.122" [root@centos7 testdir]# ping -c1 -W1 $ip &> /dev/null && echo 'ip is up' || echo 'ip is down' ip is down [root@centos7 testdir]# ip="10.1.0.1" [root@centos7 testdir]# ping -c1 -W1 $ip &> /dev/null && echo 'ip is up' || echo 'ip is down' ip is up
4.3 test命令
长格式的例子如下:
[cent@centos7 ~]$ A=abcd [cent@centos7 ~]$ B=abcd [cent@centos7 ~]$ test $A == $B && echo 'strings are equal' || echo 'strings are not equal' strings are equal [cent@centos7 ~]$ B=abcde [cent@centos7 ~]$ test $A == $B && echo 'strings are equal' || echo 'strings are not equal' strings are not equal [cent@centos7 ~]$ test $A -eq $B && echo 'strings are equal' || echo 'strings are not equal' -bash: test: abcd: integer expression expected #字符串不能做 -eq 运算 strings are not equal [cent@centos7 ~]$ A=18 [cent@centos7 ~]$ B=18 [cent@centos7 ~]$ test $A -eq $B && echo 'Integers are equal' || echo 'Integers are not equal' Integers are equal [cent@centos7 ~]$ B=19 [cent@centos7 ~]$ test $A -eq $B && echo 'Integers are equal' || echo 'Integers are not equal' Integers are not equal
简写格式的例子如下:
[cent@centos7 ~]$ [$A -eq $B] && 'Integers are equal' || echo 'Integers are not equal' bash: [18: command not found... # [ EXPRESSION ] ,EXPRESSION表达式前后必须有空白字符 [cent@centos7 ~]$ [ $A -eq $B ] && 'Integers are equal' || echo 'Integers are not equal' Integers are not equal [cent@centos7 ~]$ B=18 [cent@centos7 ~]$ [ $A -eq $B ] && echo 'Integers are equal' || echo 'Integers are not equal' Integers are equal [cent@centos7 ~]$ A=abcd [cent@centos7 ~]$ B=abcd [cent@centos7 ~]$ [ $A == $B ] && echo 'strings are equal' || echo 'strings are not equal' strings are equal
4.4 bash的测试类型
4.4.1 数值测试
-gt :是否大于
-ge :是否大于等于
-eq :是否等于
-ne :是否不等于
-lt :是否小于
-le :是否小于等于
[cent@centos7 ~]$ A=1;B=4 [cent@centos7 ~]$ [ $A -gt $B ] && echo '成立' || echo '不成立' 不成立 [cent@centos7 ~]$ [ $A -ge $B ] && echo '成立' || echo '不成立' 不成立 [cent@centos7 ~]$ [ $A -lt $B ] && echo '成立' || echo '不成立' 成立 [cent@centos7 ~]$ [ $A -ne $B ] && echo '成立' || echo '不成立' 成立 [cent@centos7 ~]$ [ $A -eq $B ] && echo '成立' || echo '不成立' 不成立 [cent@centos7 ~]$ [ $A -le $B ] && echo '成立' || echo '不成立' 成立
4.4.2 字符串测试
== :是否等于
> :左面的ascii 码是否大于右面字符串的ascii码
< :左面的ascii 码是否小于右面字符串的ascii码
!= :是否不等于
=~ :左侧字符串是否能够被右侧的PATTERN所匹配;注意:此表达式一般用于 [[ ]] 中
-z "STRING" :字符串是否为空,空为真,不空为假
-n "STRING" :字符串是否非空,非空为真,空为假
注意:用于字符串比较时的用到的操作数都应该使用引号
参数 | 功能 |
数值测试 | |
-gt | 是否大于 |
-ge | 是否大于等于 |
-eq | 是否等于 |
-ne | 是否不等于 |
-lt | 是否小于 |
-le | 是否小于等于 |
字符串测试 | |
== | 是否等于 |
> | ascii码是否大于ascii码 |
< | 是否小于 |
!= | 是否不等于 |
=~ | 左侧字符串是否能够被右侧的PATTERN所匹配 注意: 此表达式一般用于[[]] 中 |
-z "string" | 字符串是否为空,空为真,不空为假 |
-n "string" | 字符串是否不空,不空为真,空为假 |
注意:用于字符串比较时的用到的操作数都应该使用引号 |
实战训练3
1、写一个脚本/root/bin/argsnum.sh,接受一个文件路径作 为参数;如果参数个数小于1,则提示用户“至少应该给一个 参数”,并立即退出;如果参数个数不小于1,则显示第一个 参数所指向的文件中的空白行数
#!/bin/bash [ $# -ge 1 ] && echo "文件空白行数为: `grep -c '^[[:space:]]*$' $1`" || echo '至少应该给一个参数'
[cent@centos7 scripts]$ bash argsnum.sh /etc/passwd 文件空白行数为: 0 [cent@centos7 scripts]$ bash argsnum.sh /testdir/aa 文件空白行数为: 42 [cent@centos7 scripts]$ bash argsnum.sh 至少应该给一个参数
2、写一个脚本/root/bin/hostping.sh,接受一个主机的 IPv4地址做为参数,测试是否可连通。如果能ping通,则提 示用户“该IP地址可访问”;如果不可ping通,则提示用户“ 该IP地址不可访问”
#!/bin/bash ping -c1 -W1 $1 &> /dev/null && echo "该ip可以访问" || echo "该ip地址不可访问"
[cent@centos7 scripts]$ bash hostping.sh 10.1.0.1 该ip可以访问 [cent@centos7 scripts]$ bash hostping.sh 10.1.0.3 该ip地址不可访问
4.4.3 文件测试
参数 | 功能 |
文件存在性测试 | |
-e FILE | 文件存在性测试,存在为真,否则为假 |
-a FILE | 文件存在性测试,存在为真,否则为假 |
存在性及类别测试 | |
-b FILE | 是否存在且为块设备文件 |
-c FILE | 是否存在且为字符文件 |
-d FILE | 是否存在且为目录文件 |
-f FILE 或 -L FILE | 是否存在且为普通文件 |
-h FILE | 是否存在且为符号链接文件 |
-p FILE | 是否存在且为管道文件 |
-S FILE | 是否存在且为套接字文件 |
文件权限测试 | |
-r FILE | 是否存在且可读 |
-w FILE | 是否存在且可写 |
-x FILE | 是否存在且可执行 |
文件特殊权限测试 | |
-g FILE | 是否存在且拥有sgid权限 |
-u FILE | 是否存在且拥有suid权限 |
-k FILE | 是否存在且拥有sticky权限 |
文件大小测试 | |
-s FILE | 是否存在且非空 |
文件是否打开 | |
-t fd | fd表示文件描述符是否存在已经打开且与某终端相关 |
-N FILE | 文件自动上一次被读取之后是否被修改过 |
-O FILE | 当前有效用户是否为文件属主 |
-G FILE | 当前有效用户是否为文件属组 |
双目测试 | |
FILE1 -ef FILE2 | FILE1与FILE2是否指向同一个设备上的相同的inode |
FILE1 -nt FILE2 | FILE1是否新于FILE2 |
FILE1 -ot FILE2 | FILE1是否旧于FILE2 |