教你如何编写shell脚本

一、shell脚本编程规范

在一些复杂的Linux维护工作,大量重复性的输入与交互操作不但费时费力,而且容易出错,而编写一个恰好片的shell脚本程序,可以批量处理、自动化地完成一系列维护任务,大大减轻管理员的负担。

1、shell脚本应用场景

Shell脚本(shell scrip)就是将要执行的命令按顺序保存到一个文件文件,并给该文件可执行权限,方便一次性执行的一个程序文件。主要是方便管理员进行设置或管理,可结合各种shell控制语句以完成更复杂的操作。常用于重复性操作、批量事务处理、自动化运维、服务运行状态监控、定时任务执行等。

2、shell编程规范

Linux系统中的shell脚本是一个特殊的应用程序,它介于 操作系统内核与用户之间,充当一个“命令解释器”的角色,负责接收用户输入的操作指令并进行解释,将需要执行的操作传递给内核执行,并输出执行结果。

常见的shell解释器程序有很多,使用用不同的shell脚本时,其内部指令、命令行提示等方面会存在一些区别。通过/etc/shells文件可以了解当前系统所支持的shell脚本类型。
其中,/bin/bash是目前大多数Linux版本采用的默认shell脚本。Bash的全称为Bourne Agin Shell,是最受欢迎的开源软件之一。那么什么是“shell脚本”呢?

简单地说,只要将平时使用的各种Linux命令按顺序保存到一个文本文件,然后添加可执行权限,这个文件就成为一个Shell脚本了。

举例:创建第一个脚本文件
# vim first.sh
    cd /boot/
    pwd
    ls -lh vml*
# chmod +x first.sh //为脚本加上执行权限
#./first.sh //(前提是要加上执行权限) (相当于 #/bin/bash first.sh 或# /bin/sh first.sh)

上述first.sh脚本文件中,包括三条命令:cd/boot、pwd、ls -lh vml*。执行此脚本文件后,输出结果与依次单独执行这三条命令是相同的,从而实现了‘批量处理’的自动化过程。

添加注释

当然,一个合格的shell脚本程序应该遵循标准的脚本结构,而且能够输出友好的提示信息、更加容易读懂。对于代码较多、结构复杂的脚本,应添加必要的注释文字。改写后的first.sh脚本内容如下所示:

#vim first.sh
	#!/bin/bash      //特殊的脚本声明,表示此行以后的语句通过/bin/bash程序来解释执行
    #This is my first Shell-Script.
    cd /boot/
    echo “当前的目录位于:”
    pwd
    echo “其中以vml开头的文件包括:”
	ls -lh vml*

输出结果如下所示:

# ./first.sh 
当前的目录位于:
/boot
其中以vml开头的文件包括:
-rwxr-xr-x. 1 root root 6.4M 99 18:40 vmlinuz-0-rescue-396e2077721a48da8ea2738c4bd8e831
-rwxr-xr-x. 1 root root 6.4M 119 2018 vmlinuz-3.10.0-957.el7.x86_64
脚本的执行方式

直接通过文件路径”./first.sh”的方式执行脚本,要求文件本身具有X权限,在某此安全系统中可能无法满足此条件。鉴于此,Linux操作系统还提供了执行shell脚本的其他方式----指定某个shell来解释脚本语句,或者通过内部命令source(或点号”.”)来加载文件中的源代码执行。

举例:

# source first.sh 
# sh first.sh
# bash first.sh
# ./first.sh

3、管道与重定向

由于shell脚本”批量处理”的特殊性,其大部份操作过程位于后台,不需要用记进行干预。因此学会提取、过滤执行信息变得十分重要。

(1)管道操作

管道操作为不同命令之间的协同工作提供了一种机制,位于管道符号”|”左边的命令输出的结果,将作为右侧命令的输入(处理对象),同一行命令中可以使用多个管道。
在shell脚本应用中,管道操作通常用来过滤所需要的关键信息。

例如,使用grep命令查询使用”/bin/bash”作为shell的用户名称时,会输出符合条件的整行内容,在此基础上可以结合管道操作与awk命令做进一步过滤

【例】只输出用户名和登录shell

# grep "/bin/bash$" /etc/passwd  
    root:x:0:0:root:/root:/bin/bash
    student:x:1000:1000:student:/home/student:/bin/bash

【例】 以冒号”:”作为分隔,输出第1个和第7个区域的字符串

其中”-F”部份用来指定分隔符号(未指定时,默认以空格或制表符分隔)。

# grep "/bin/bash$" /etc/passwd | awk -F: '{print $1,$7}'
    root /bin/bash
    student /bin/bash

【 例】 以冒号”:”作为分隔,输出第1个和第3个区域的字符串

grep "/bin/bash$"  /etc/passwd | awk -F: '{print $1,$3}'  
   root 0
   lying 1000

【例】提取/的磁盘使用率信息。

#df -hT
    文件系统         类型    容量  已用  可用 已用% 挂载点
    /dev/mapper/centos-root xfs     17G  4.0G  13G  24% /
    devtmpfs         devtmpfs  470M   0  470M   0% /dev
    tmpfs          tmpfs   487M   0  487M   0% /dev/shm

#df -hT | grep “/$” | awk ‘{print $6}’
	24%
(2)重定向操作

Linux系统使用文件来描述各种硬件、设备等资源,如以前尝过的硬盘和分区、光盘等设备文件。用户通过操作系统处理信息的过程中,包括以下几类交互设备文件。

标准输入(STDIN):默认的设备是键盘,文件编号为0,命令将从标准输入文件中读取在执行过程中需要的输入的数据。
标准输出(STDOUT):默认的设备是显示器,文件编号为1,命令将执行后的输出结果发送到标准输出文件。
标准错误(STDERR):默认的设备是显示器,文件编号为2,命令执行期间的各种错误信息发送到标准错误文件。
在实际的Linux系统维护中,可以改变输入、输出内容的方向,而不使用默认的标准输入、输出设备,这种操作称为”重定向”

重定向输入:将命令中接收输入的路径由默认的键盘改为指定的文件,使用”<”操作符。

举例:给用户设置密码

#vim pass.txt
	 123456
#passwd --stdin jerry < pass.txt  //将jerry的密码改成123456

重定向输出:将命令的正常输出结果保存到指定的文件,使用”>”或”>>”操作符。

举例:将输出结果保存到指定的文件
#uname -p >kernel.txt   查看CPU类型
#uname -r >kernel.txt  查看内核版本信息
(3)错误重定向:2>

将执行命令过程中出现的错误保存到指定文件,使用”2>”操作符。在实际应用中,错误重定向可用来收集程序执行的错误信息,为排错提供依据。

2>:将错误的信息保存到指定文件
2>>:追加到指定文件
&>:将正常和错误信息保存到同一文件

【例】:将备份时出现的错误信息进行保存

# tar jcf /nonedir/etc.tgz/etc 2>error.log
# tar jcf /nonedir/etc.tgz/etc 2>>error.log

【例】 如在编译源码包的自动化脚本中,若要忽略make、make install等操作过程信息,可将其定向到空文件/dev/null。

#vim httpd_install.sh
    #!/bin/bash
    #自动编译安装httpd服务器的脚本
    cd /usr/src/httpd-2.2.17
    ./configure --prefix=/usr/local/httpd --enable-so &>/dev/null
    make &>/dev/null
    make install &>/dev/null
    ......   //省略内容
# chmod +x httpd_install.sh
#./httpd_install.sh
(4)编译安装软件的步骤:

解压文件—进入到解压文件后执行. /configure进行环境配置— make 编译— makeinstall 安装软件

二、shel脚本变量

Shell变量用来存放系统和用户需要使用的特定参数(值),而且这些参数可以根据用户的设定或系统环境的变化而相应变化。通过使用变量,shell程序能够提供更加灵活的功能,适应性更强。

0、常见shell变量的类型

自定义变量、环境变量、位置变量、预定义变量

1、定义新的变量

格式:变量名=变量值

  注意:等号两边没有空格。变量名称需以字母或下划线开头,名称中不要包含特殊字符:
(+ - * / . ? % & #)

【例】定义变量Product(值为Weixin),Version(值为6.0)

# Product=Weixin
# Version=6.0
(1)查看和应变量的值

引用变量值:在变量名称前添加”$”

查看变量值:使用echo命令,可以在一条echo命令同时查看多个变量值。

# echo $Product
	Weixin
# echo $Product$Version
	Weixin6.0  因为输出变量时是连在一起的所以结果也挨在一起

当变量名称容易和紧跟其后其他字符相混淆时,需要添加”{}”将其括起来,否则将无法确定正确的变量名称。对未定义的变量,将显示空值。

# echo $Prouduct4.5
# echo ${Product}4.5
(2)变量赋值的特殊操作
(a)双引号(””)-空格、引用其他变量

双引号主要起界定字符串的作用,特别是当要赋值的内容包含空格时,必须以双引号括起来;其他情况下双引号通常可以省略。

【例】变量值包含空格的正确赋值方法

# Weixin=weixin 4.5
	bash: 4.5: 未找到命令...
# Weixin="weixin 4.5"

在双引号范围内,使用$符号可以引用其他变量的值(变量引用)。

# Version=8.0
# QQ="QQ$Version"
# echo $QQ
	QQ8.0
(b)单引号(’ ’)–特殊意义

当要赋值的内容包含$ “ \等具有特殊含义的字符时,应使用单引号括起来。在单引号的范围内,将无法引用其他变量的值,任何字符均作为普通字符看待。但赋值内容中包含单引号时,需要使用\进行转义,以免冲突。

举例:使用单引号对变量赋值

# A='ABC$Version'
# echo $A
	ABC$Version
# AA='AA'\'$Version\'
# echo $AA
	AA'6.0'
# AA=AA\''$Version'\'
# echo $AA
	AA'$Version'
©反撇号(`)-将命令结果赋值给变量,嵌套以$()代替

主要用于命令替换,允许执行某个命令的屏幕输出结果赋值给变量。反撇号括起来的范围内必须是能够执行的命令行,否则会出错。

【例】在一行命令中查找tar命令程序的位置并列出其详细属性。

# ls -lh `which tar`
	-rwxr-xr-x. 1 root root 339K 1031 2018 /usr/bin/tar

【例】提取vsftpd服务的封禁用户列表,并将其赋值给变量Denylist

# Denylist=`grep -v "^#" /etc/vsftpd/ftpusers`  //-v表示反向选择,^#表示以#开头的 ^$表示空行
# echo $Denylist
	root bin daemon adm lp sync shutdown halt mail news uucp operator games nobody

注意:使用反撇号难以在一行命令中实现嵌套命令替换操作,这时可以使用$()来代替反撇号操作,以解决嵌套的问题。

【例】查询提供useradd命令程序的软件包所安装的配置文件位置

# rpm -qc $(rpm -qf $(which useradd))
    /etc/default/useradd
    /etc/login.defs
(d)read 参数
参数 含义
-t 输入等待时间(单位:秒)
-p 提示信息

read命令用来提示用户输入信息,从而实现简单的交互过程。执行时将从标准输入设备读入一行内容,并以空格为分隔符,将读入的各字段挨个赋值给指定的变量(多余的内容赋值给最后一个变量)。若指定的变量只有一个,则将整行内容赋值给此变量。

举例:使用read命令,以输入的方式给变量赋值(相当于input -print)

# read ToDir1
	输入:123456
# echo $ToDir1
	123456

read命令可以结合-p和-t选项来设置提示信息和输入等待时间(单位默认为秒)

# read -p "please input directory:" -t 3 AA   //等待3秒钟,变量名为AA 输入的值将赋值给AA
	please input directory:/mnt/bak  #输入一个值
# echo $AA
	/mnt/bak
(3)设置变量的作用范围-默认为局部变量

默认情况下,新定义的变量只在当前的shell环境中有效,因此称为局部变量。当进入子程序或新的子shell环境时,局部变量将无法再使用。

# Weixin=weixin
# Version=6.0
# echo "$weixin $Version"
	weixin 6.0

#bash
echo "$weixin $Version"
(4)全局变量 export

为了使用定义的变量在所有的子shell环境中能够继续使用,减少重复设置工作,可以通过内部命令export将指定的变量导出为”全局变量”。可以同时指定多个变量名称作为参数(不需要使用$),变量名之间以空格分隔。

# export weixin Version
# bash
# echo "$weixin $Version"
	weixin 6.0
使用export导出全局变量的同时,也可以为变量进行赋值。

#export KGC=”www.kgc.cn”
# echo $KGC
	www.kgc.cn
(4)数值变量的运算

shell变量的数值运算多用于脚本程序的过程控制(如循环次数、使用量比较等)。在Bash Shell环境中,只能进行简单的整数运算,不支持小数运算。

整数值的运算主要通过expr进行,基本格式如下:

expr 变量1 运算符 变量2 [运算符 变量3]…

其中,变量对应需要计算的数值变量(需要以”$”符号调用),常用的几种运算符如下:

运算符 意义
++ – 增加及减少,可前置也可放到结尾
* / % 乘法、除法、取余
+ - 加法、减法
< <= > >= 比较符号
== != 等于与不等于
& 位的与
^ 位的异或
| 位的或
&& 逻辑的与
|| 逻辑的或
?: 条件表达式
= += -= *= /= %= &= ^= <<= >>= |= 赋值运算符a+=相当于a=a+1

【例】两个整数运算 X=35 Y=16 整数运算 expr

#X=35
#Y=16
#expr $X + $Y
#expr $X - $Y
#expr $X \* $Y
#expr $X / $Y
#expr $X % $Y
#Ycube=`expr $Y \* $Y \* $Y`

使用expr进行计算的时候,变量必须是整数,不能是字符串,也不能含小数,否则会出错

除了expr命令外,变量数值常见的命令还包括:(())、let等。如果要执行简单的整数运算,只需要将特定对的算术表达式用$(())括起来即可。

【例】简单的算术运算

#bb=$((1+2**3-4))
#echo $((1+2**3-4))

【例】set 查看定义的变量

# unset Serversion  //取消变量
# readonly 变量名    //设置只读变量,unset不能删除只读变量

2、特殊变量

(1)环境变量

环境变量指的是出于运行需要而由Linux系统提前创建的一类变量,主要用于设置用户的工作环境,包括用户主目录、命令查找路径、用户当前目录、登录终端等。环境变量的值由Linux系统自动维护,随用户状态的改变而改变。

使用env命令可以查看到当前工作环境下的环境变量,对于常见的一些环境变量就了解其各自的用途。

PATH变量用于设置可执行程序的默认搜索路径,当仅指定文件名称来执行命令程序时,Linux系统将在PATH变量指定的目录范围查找对应的可执行文件,如果找不到则会提示”command not found”,此时修改PATH变量或将脚本文件复制到环境变量搜索目录下。

举例:向PATH环境变量添加脚本文件目录

# echo $PATH
	/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/root/bin
	/root/first.sh
# first.sh
	bash: first.sh: 未找到命令...
# PATH=”$PATH:/root”
# first.sh  #因为配置了PATH所以可以直接执行命令

在Linux系统中,环境变量的全局配置文件为/etc/profile,在此文件中定义的变量

作用于所有用户。除此外,每个用户还有自己的独立配置文件(~/.bash_profile)。若要长期变更或设置某个环境变量,应在上述文件中进行设置。

【例】修改历史记录命令条数

# history | wc -l
# vim /root/.bash_profile 
	export HISTSIZE=50
# source /root/.bash_profile 
# history | wc -l
(2)数值变量–bc运算符
1-直接定义成数值变量:
# declare -i a=100
# a=$a2
2-使用双圆括号:
# a=100
# a=$(($a/2))
3-使用expr命令:
# a=100
# a=`expr $a / 100`
4-bc运算符
# [root@localhost lying]# echo 100 /10|bc
# 10

# bc表示运算符,不加bc就会原样输出

【例】整数运算

#  a=100
#  b=33
# [root@localhost ~]#echo "$a/$b"| bc
#  3

【例】保留两位小数运算

#  [root@localhost ~]# echo "scale=2;$a/$b" | bc
(3)位置变量

为了在使用shell脚本程序时, 方便通过命令行为程序提供操作参数,bash引入了位置变量的概念。当执行命令时,第一个字段表示命令表示命令名或脚本程序名,其余的字符串参数按照从左到右的顺序依次赋值给位置变量。

位置变量也称为位置参数,使用$1 $2 $3 …$9表示。

#!/bin/bash
    SUM=`expr $1 + $2`
    echo "$1+$2"= $SUM
# chmod +x addr2num.sh 
# ./addr2num.sh 15 20
    15 +  20= 35
    
set 1 2 3  #指定$1=1 $2=2 $3=3
(4)预定义变量

预定义变量是由Bash程序预先定义好的一类特殊变量,用户只能使用预定义变量,而不能创建新的预定义变量,也不能直接为预定义变量赋值。预定义变量使用$符号和另一个符号组合表示,常见的有:

$#:表示命令行中位置参数的个数
$*:表示所有位置参数的内容
$?:表示前一条命令执行后的返回状态,返回值为0表示正确执行,返回值为不为0则表示执行异常
$0:表示当前执行的脚本或程序名称

$0       相当于C语言main函数的argv[0]
$1 $2 $3....位置参数,相当于argv[1],argv[2],argv[3]
$#       相当于argv -1
$@      表示参数列表(可以用做for的遍历)
$*        表示参数列表
$?       上一条命令的exit status(0为真  非0为假)
$$       当前进程号
shift     会使参数列表右移一位

#!/bin/bash
T=`date "+%Y%m%d-%H%M"`  #设置时间 
TARFILE=beifen-${T}.tgz  #备份后的文件名 
tar zcf $TARFILE $* &>/dev/null  #将所有文件备份名为$TARFILE 的打包正确错误信息设置为不显示
echo "已执行$0脚本"
echo "共完成$#个对象的备份"
echo "具体内容包括:$*"
chmod +x mybak.sh
# ./mybak.sh /boot/grub /mnt/public /root/aa
    已执行./mybak.sh脚本
    共完成1个对象的备份
    具体内容包括:/boot/grub

3、整数值比较

整数值比较指的是根据给定的两个整数值,判断第一个数与第二个数的关系,如是否是大于、等于、小于第二个数。整数值比较的常用操作选项如下:

-eq  a等于b
-ne  a不等于b
-gt  a大于b
-lt  a小于b
-le  a小于或等于b
-ge  a大于或等于b

​ 整数值比较在shell脚本编写中的应用较多,例如用来判断已登录用户数量、开启进程数、磁盘使用率是否超标,以及软件版本号是否符合要求等。实际使用时,往往会通过变量引用、命令替换等方式来获取一个数值。

【例】判断当前已登录用户数,当超过5个时输出“Too many”

# Unum=`who|wc -l`
# [ $Unum -gt 5 ]&&echo "too many."
too many.

【例】判断当前可用空闲内存(free)大小,当低于1024MB时输出具体数值

# FreeCC=$(free -m|grep Mem|awk '{print $4}')
# [ $FreeCC -lt 1024 ]&&echo ${FreeCC}MB

4、字符串比较

字符串比较通常用来检查用户输入、系统环境变量等是否满足条件,在提供交互式操作的shell脚本中,也可用来判断用户输入的位置参数是否符合要求。字符串比较常用的操作选项如下:

=   第一个字符串与第二个字符串相同
!=  第一个字符串与第二个字符串不相同
-z   检查字符串为空(zero),对于未定义或赋予空值的变量将视为空串

三、任务练习

任务一:对apache进行日志备份?要求在每天晚上12:00进行一次备份,每天一个文件?
apache日志文件:/var/log/httpd
有两个配置文件: access_log  访问日志
			   error_log  错误日志
每天晚上12:00进行一次备份-周期性计划任务-crontab 

1)编写shell脚本使之可以备份
vim /home/lying/first.sh
    #! /bin/bash
    T=`date "+%Y%m%d-%H:%M"`
    cp /var/log/httpd/error_log /var/log/httpd/error_log_${T}.bak
    cp /var/log/httpd/access_log /var/log/httpd/access_log_${T}.bak
    echo "已执行$0 脚本"
    echo "共完成$# 个对象的备份"
    echo "具体内容为$* "

2)编写周期性计划任务执行shell脚本
crontab -u root -e 
	00 00 * * * /home/lying/first.sh
	此处为了测试将时间设置为当前时间 
	39 16 * * * /home/lying/first.sh
3)查看邮件--成功
mail -u root
    Heirloom Mail version 12.5 7/5/10.  Type ? for help.
    "/var/mail/root": 1 message 1 new
    >N  1 (Cron Daemon)         Tue Jun 16 16:39  27/935   "Cron "
任务二:写一个脚本,判断apache是否已经安装?如果已安装,请打印“apache软件已安装”,并启动服务。如果没有安装?请安装并启动服务?
if语句的使用?

是否安装怎么判断? rpm -qa | grep httpd-----$?  

如果是0:表示已安装,如果是1表示未安装。

1)编辑shell脚本程序
vim /home/lying/apa.sh
    #!/bin/bash
    /usr/bin/rpm  -qa |grep httpd &>/dev/null
    if [ $? -eq 0 ]
    then
    echo "apache已经安装可以使用啦"
    else
    echo "没有安装apache,将安装"
    yum install httpd
    fi


2)运行程序
 cd /home/lying
 ./apa.sh
任务三:写一个脚本对服务器的CPU、内存、硬盘使用空间进行监测?
1)vim /home/lying/cpu.sh
    #!/bin/bash
    free -m |grep Mem | awk '{print "当前系统内存哦:" $2"MB " "已使用:" int ($3/$2*100)"%"}'
    top -n 1 |grep Cpu | awk '{print "当前系统的cpu使用率:" (100-$8)"%"}'
    df -hT | awk 'NR == 2 { print $1 " 分区容量:"$3" 已使用:"$6}'
2)让cpu.sh有执行的权限 
chmod +x /home/lying/cpu.sh
3)运行
./cpu.sh

top命令可以看到总体的系统运行状态和cpu的使用率

%us:表示用户空间程序的cpu使用率(没有通过nice调度)
%sy:表示系统空间的cpu使用率,主要是内核程序。
%ni:表示用户空间且通过nice调度过的程序的cpu使用率。
%id:空闲cpu
%wa:cpu运行时在等待io的时间
%hi:cpu处理硬中断的数量
%si:cpu处理软中断的数量
%st:被虚拟机偷走的cpu
任务四:定一个一键配置本地yum源的shell脚本

四、条件语句

一、条件测试

使用shell脚本程序具备一定的“智能”,面临的第一个问题就是如何区分不同的情况以确定执行何种操作。例如,当磁盘使用率超过95%时发送警告信息;当备份目录存在时能够自动创建;当源码编译程序时若配置失败则不再继续安装等。
shell环境根据命令执行后的返回状态($?)来判断是否执行成功:
当返回0时表示成功
当返回1时表示失败或异常
test命令–对特定条件进行测试,并根据返回值来判断是否成立。
 test命令的使用:test 条件表达式  或  [ 条件表达式 ]

 	这两种方式的作用完全相同,但通常后一种形式更为常用,也更贴近编程习惯。需要注意的是,方括号与条件表达式之间需要至少一个空格进行分隔。
	
根据需要测试的条件类别不同,条件表达式也不同。比较常用的条件操作包括文件测试、整数值比较、字符串比较、以及针对多个条件的逻辑测试。
1-文件测试-test

​ 文件测试指的是根据给定的路径名称,判断对应的是文件还是目录,或者判断文件是否可读、可写、可执行等。文件测试的常见操作选项如下,使用时将测试对象放在操作选项之后即可。

-d 测试是否为目录(Directory)
-e 测试目录或文件是否存在(Exist)
-f 测试是否为文件(File)
-r 测试当前用户是否有权限读取(Read)
-w 测试当前用户是否有权限写入(Write)
-x 测试是否设置有可执行(Excute)权限
-s 如果文件存在且文件大小(Size)大于零,则为True

-r File 如果文件File存在且是可读的(Readable),则为True
-w File 如果文件File存在且是可写的(Writable),则为True
-x File 如果文件File存在且是可执行的(Executable),则为True
-O File 如果文件File存在且属于当前用户(Owner),则为True
-G File 如果文件File存在且属于当前用户组(Group),则为True
-b File 如果文件File存在且是块(Block)特殊文件,则为True
-c File 如果文件File存在且是字符(Character)特殊文件,则为True
-L File 如果文件File存在且是符号链接(Link)文件,则为True

【例】判断/mnt/cdrom目录是否存在?

# test -e /mnt/cdrom
# echo $?
0

【例】判断/mnt/cdrom是不是目录?

# test -d /mnt/cdrom
# echo $?
0

//或
# [ -d /mnt/cdrom ]
# echo $?
0

【例】查看目录是否存在

#[ -e /media/cdrom ]&&echo “YES”
           //无输出表示该目录不存在
YES       //输出“YES”表示该目录存在

【例】查看是否为目录

# [ -d /media/cdrom ]&&echo “YES” || echo “NO”
1-2使用[[ …… ]]条件测试
# [[ -d /media/cdrom ]]&&echo “YES” || echo “NO”
1-3 test两个文件的比较

test命令中用于判断文件的选项有很多,从文件个数上可分为单个文件的判断和两个文件之间的比较。其-中判断单个文件最常用的选项就-f选项,在比较两个文件时,常用的选项有:

-nt 判断文件A是否比文件B新
-ot 判断文件A是否比文件B旧
-ef 判断两个文件是否为同一个文件,用来判断两个文件是否指向同一个inode

【例】

#touch a
#touch b
#test a -ot b&&echo “YES” || echo “NO”
#[ a -ot b ]&&echo “YES” || echo “NO”
#test a -ef b &&echo “YES” || echo “NO”
#ln a c
#test a -ef c &&echo “YES” || echo “NO”
2、整数值比较

整数值比较指的是根据给定的两个整数值,判断第一个数与第二个数的关系,如是否是大于、等于、小于第二个数。整数值比较的常用操作选项如下:

-eq  a等于b
-ne  a不等于b
-gt  a大于b
-lt  a小于b
-le  a小于或等于b
-ge  a大于或等于b

​ 整数值比较在shell脚本编写中的应用较多,例如用来判断已登录用户数量、开启进程数、磁盘使用率是否超标,以及软件版本号是否符合要求等。实际使用时,往往会通过变量引用、命令替换等方式来获取一个数值。

【例】判断当前已登录用户数,当超过5个时输出“Too many”

# Unum=`who|wc -l`
# [ $Unum -gt 5 ]&&echo "too many."
too many.

【例】判断当前可用空闲内存(free)大小,当低于1024MB时输出具体数值

# FreeCC=$(free -m|grep Mem|awk '{print $4}')
# [ $FreeCC -lt 1024 ]&&echo ${FreeCC}MB
3、字符串比较

字符串比较通常用来检查用户输入、系统环境变量等是否满足条件,在提供交互式操作的shell脚本中,也可用来判断用户输入的位置参数是否符合要求。字符串比较常用的操作选项如下:

=   第一个字符串与第二个字符串相同
!=  第一个字符串与第二个字符串不相同
-z   检查字符串为空(zero),对于未定义或赋予空值的变量将视为空串

【例】判断当前系统的语言环境,不是en.US时输出“Not en.US”

# echo $LANG
zh_CN.UTF-8
# [ $LANG != "en.US" ]&&echo "Not en.US"
Not en.US

【例】在shell脚本应用中,经常需要用户输入yes或no来确认某个任务

# read -p "是否覆盖现有文件(yes/no)?" ACK
是否覆盖现有文件(yes/no)?yes
# echo $ACK
yes
# [ $ACK = "yes" ]&&echo "覆盖"
# read -p "是否覆盖现有文件(yes/no)?" ACK
是否覆盖现有文件(yes/no)?no
# [ $ACK = "no" ]&&echo "不覆盖"
不覆盖
4、逻辑测试

逻辑测试指的是判断两个或多个条件之间的依赖关系。当系统任务取决于多个不同的条件时,判断是根据这些条件同时成立还是只要有其中一个成立等情况,需要有一个测试的过程,常用的逻辑测试操作如下,使用时放在不同的测试语句或命令之间。

	&&   逻辑与,表示而且,只有当前后两个条件都成立时,整个测试的命令返回值才为0(结果成立)。使用test命令测试时可改为-a
	||   逻辑或,表示或者,只要前后两个条件有一个成立,则整个测试的命令返回值为0(结果成立)。使用test命令测试时可改为-o
	!    逻辑否,表示不,只有当指定的条件不成立时,整个测试命令的返回值才为0(结果成立)

举例:判断Linux系统的内核版本是否大于2.4

# Mnum=$(uname -r|awk -F. '{print $1}')
# Snum=$(uname -r|awk -F. '{print $2}')
#[ $Mnum -gt 2 ]&&[$Snum -gt 4]&&echo "yes" || echo "no"
#[ $Mnum -eq 2 ]&&[$Snum -gt 4]&&echo "yes" || echo "no"
#[ $Mnum -gt 2 ]||`[ $Mnum -eq 2 ]&&[$Snum -gt 4]`&&echo "yes" || echo "no"
[root@localhost ~]# test $Mnum -eq 3 -a $Snum -gt 4
[root@localhost ~]# echo $?
0
[root@localhost ~]# test $Mnum -eq 2 -a $Snum -gt 4
[root@localhost ~]# echo $?
1

二、if语句结构

在shell脚本应用中,if语句是最为常用的一种流程控制方式,用来根据特定的条件测试结果,分别执行不同的操作(如果…那么…)。根据不同的复杂程度,if语句的选择结构可以分为三种基本类型,适用于不同的应用场合。

1、单分支的if语句

只有在条件成立时才会执行相应的代码,否则不执行任何操作。

语法格式:

if 条件测试语句
then  
     命令序列
else
     命令序列
fi

【例】使用shell脚本挂载光盘,并进行永久挂载

#!/bin/bash
MOUNT_DIR="/mnt/cdrom"
if [ ! -d $MOUNT_DIR ]
then
 mkdir -p $MOUNT_DIR
 mount /dev/sr0 $MOUNT_DIR
else
 echo -e "\033[31m /dev/sr0 mounted on /mnt/cdrom!\033[0m"
fi

# chmod +x moutcd.sh 
# ./moutcd.sh

【例】有些特权命令要求root用户执行,如果当前用户不是root,那么就提示权限不足。

#!/bin/bash
if [ "$USER" != "root" ]
then
  echo -e "\033[31m 错误:非root用户,权限不足!\033[0m"
  exit 1
fi
fdisk -l /dev/sda
2、双分支的if语句

针对条件成立和条件不成立两种情况分别执行不同的操作。

语法格式:

   if 条件测试语句
   then  
     命令序列1
   else
     命令序列2
   fi

【例】使用shell脚本测试主机是否开启

#!/bin/bash
ping -c 3 -i 0.2 -w 3 $1 &>/dev/null
if [ $? -eq 0 ]
then
 echo "Host $1 is up!"
else
 echo "Host $1 is down!"
fi

# ./pinghost.sh 192.168.75.1
Host 192.168.75.1 is up!

【例】 通过shell命令检查vsftpd服务是否运行,如果已经运行则列出其监听地址、PID号,否则输出“警告:vsftpd服务不可用”,其中pgrep -x表示精确匹配。

#!/bin/bash
/usr/bin/systemctl status vsftpd &>/dev/null
if [ $? -eq 0 ]
then
  echo "监听地址:$(netstat -anpt | grep vsftpd | awk '{print $4}')"
  echo "进程PID号:$(pgrep -x vsftpd)"
else
  echo "警告:vsftpd服务不可用!"
fi
#chmod +x chkftpd.sh
# ./chkftpd.sh 
监听地址::::21
进程PID号:102825
3、多分支的if语句

与单分支、双分支的if语句相比,多分支if语句的实际应用并不多见。由于if语句可以根据测试结果的成立、不成立分别执行操作,所以能够嵌套使用,进行多次判断。

语法格式:

 if 条件测试操作1
   then  
     命令序列1   
elif 条件测试操作2

then  
    命令序列2
    elif 条件测试操作2
then  
     命令序列2
eles
     命令序列3
fi	

【例】输入分数判断优良差

#vim gradediv.sh
#!/bin/bash
read -p "请输入您的分数(0-100):" GRADE
if [ $GRADE -ge 85 ]&&[ $GRADE -le 100 ]
then
  echo "$GRADE分!优秀"
elif [ $GRADE -ge 70 ]&&[ $GRADE -le 84 ]
then 
echo "$GRADE分,合格"
else 
echo "$GRADE分?不合格"
  fi
# chmod +x gradediv.sh 
# ./gradediv.sh 

五、case语句与循环语句

一、使用case分支语句

1、case语句的结构
case 变量值 in  #变量值即“$变量名”
    模式 1)
    命令序列1
    ;;

    模式 2)
    命令序列2
    ;;
    .......

    *)
    默认命令序列
    esac

关键字case后面跟的是“变量值”,即“$变量名”,这点需要与for循环语句的结构加以区分。整个分支结构包括在case … esac之间。中间的模式1、模式2 … 对应为变量的不同取值(程序期望的取值),其中作为通配符,可匹配任意值。

case语句的执行流程:首先“变量值”与模式1进行比较,若取值相同则执行模式1后面的命令序列,直到遇见“;;”后跳转至esac,表示结束分支;若与模式1不匹配,则继续模式2进行比较,若取值相同则执行模式2后面的命令序列,直到遇见“;;”后跳转至esac,表示结束分支…以此类推;若找不到任何匹配的值,则执行默认模式“*)”后的命令序列,直到遇见“;;”后结束分支;

注意:
case行尾必须为单词“in”,每一模式必须以右括号“)”结束。
双分号“;;”表示命令序列的结束。
模式字符串中,可以用方括号表示一个连续的范围,如[0-9];还可以用竖杠符号“|”表示或,如A|B。
最后“*)”表示默认模式,其中*相当于通配符。
2、case语句应用示例
# vim hitkey.sh
    #!/bin/bash
    read -p "请输入一个字符,并按Enter键确认:" KEY
    case "$KEY" in
    [a-z]|[A-Z])
    echo "您输入的是字母: $KEY"
    ;;
    [0-9])
    echo "你输入的是数字: $KEY"
    ;;
    *)
    echo "您输入的是空格、功能键或其他控制字符!"
    esac
# chmod +x hitkey.sh 
#./hitkey.sh

二、使用for循环语句

1、for循环语句的语法结构
for 变量名 in  取值列表
do
命令序列
Done

​ for语句结构中,for语句的操作对象为用户指定名称的变量,并通过in关键字为该变量预先设置了一个取值列表,多个取值之间以空格进行分隔。位于do … done之间的命令序列称为循环体,其中的执行语句需要引用变量以完成相应的任务。

for语句的执行流程:首先将列表中的第1个取值赋值给变量,并执行do .... done循环体中的命令序列;然后将列表中的第2个取值赋值给变量,并执行循环体中的命令序列....以此类推,直到列表中的所有取值用完,最后将跳至done语句,表示结束循环。
2、for语句应用示例
(1)根据姓名列表批量添加和删除用户
# vim /root/users.txt  //用做测试的列表文件
    chenye
    dengchao
    zhangjie

# vim uaddfor.sh		//批量添加用户的脚本
    #!/bin/bash
    ULIST=$(cat /root/users.txt)
    for UNAME in $ULIST  //变量UNAME的取值范围为 $ULIST
    do
        useradd $UNAME
        echo "${UNAME}已创建成功"
        echo "123456" | passwd --stdin $UNAME &>/dev/null  //设置每个用户的密码为123456 且不显示提示信息
    done
    
# chmod +x uaddfor.sh
# ./uaddfor.sh		//测试并确认执行结果
# tail -3 /etc/passwd
chenye:x:1011:101 1:/home/chenye:/bin/bash
dengchao:x:1012:1012:/home/dengchao:/bin/bash
zhangjie:x:1013:03:/home/zhangjie:/bin/bash

# vim udelfor.sh		//批量删除用户的脚本
    #!/bin/bash
    ULIST=$(cat /root/users.txt)
    for UNAME in $ULIST
    do
    userdel -r $UNAME &>/dev/null
    done
    
# chmod +x udelfor.sh
# ./udelfor.sh			//测试并确认执行结果
# id chenye
id: chenye:无此用户
(2)根据IP地址列表检查主机状态
# vim /root/ipadds.txt	//用做测试的列表文件
    192.168.4.11
    192.168.4.110
    192.168.4.120
# vim chkhosts.sh		//循环检查各主机的脚本
    #!/bin/bash
    HLIST=$(cat /root/ipadds.txt)
    for IP in $HLIST
    do
    ping -c 3 -i 0.2 -W 3 $IP &> /dev/null
    if [ $? -eq 0 ]
    then
    echo "Host $IP is up."
    else
    echo "Host $IP is down."
    fi
    done

# chmod +x chkhosts.sh
# ./chkhosts.sh		//测试并确认执行结果
Host 192.168.4.11 is up.
Host 192.168.4.110 is down.
Host 192.168.4.120 is up.

三、使用while循环语句

1、while循环语句的语法结构
while 条件测试操作
do
命令序列
done
2、while语句应用示例
(1)批量添加规律编号的用户

批量添加用户

# vim uaddwhile.sh
    #!/bin/bash
    PREFIX="stu"
    i=1
    while [ $i -le 20 ]
    do
        useradd ${PREFIX}$i
        echo "123456" | passwd --stdin ${PREFIX}$i &> /dev/null
        let i++
    done
# chmod +x uaddwhile.sh
# ./uaddwhile.sh
(2)批量删除规律编号的用户
# vim udelwhile.sh
    #!/bin/bash
    PREFIX="stu"
    i=1
    while [ $i -le 20 ]
    do
        userdel -r ${PREFIX}$i
        let i++
    done
# chmod +x udelwhile.sh
# ./udelwhile.sh
(3)猜价格游戏
# vim pricegame.sh
    #!/bin/bash
    PRICE=$(expr $RANDOM % 1000)
    TIMES=0
    echo "商品实际价格范围为0~999,猜猜看是多少?"
    while true
    do
        read -p "请输入你猜测的价格数目:" INT
        let TIMES++
        if [ $INT -eq $PRICE ];then
        	echo "恭喜你答对了!实际价格是$PRICE"
            echo "你总共猜测了$TIMES次"
            exit 0
        elif [ $INT -gt $PRICE ];then
            echo "太高了!"
        else
            echo "太低了!"
        fi
    done

# chmod +x pricegame.sh
# ./pricegame.sh

四、Shell函数应用

1、定义函数的语法结构
[function]函数名(){
命令序列
[return x]
  • [function]是可选的,表示该函数的功能,是可以省略的;
  • ()跟在函数名后,里面是没有内容的;
  • {}我们所需要执行的命令序列放在这里面;
  • [return x] 当命令序列执行完后返回给系统一个值,是可以省略的。
  • 在脚本中调用函数的方式是直接输入函数名即可,有时还需要输入一些参数。
2、定义并调用函数的应用示例

定义一个求和函数并在脚本中引用

#!/bin/bash
sum(){
    read -p "Please Input First number:" NUM1
    read -p "Please Input Second number:" NUM2
    echo "The numbers is:$NUM1 and $NUM2"
    SUM=$(($NUM1+$NUM2))
    echo "The Sum is :$SUM"
}

sum  //引用函数

#chmod +x sum.sh
#./sum.sh

在Linux系统中有很多服务启动脚本定义了丰富的shell函数,并嵌套了各种语句,如if语句等。我们在编写服务启动脚本可参考系统原有的启动脚本编写。

五、shell脚本调试

在排错时要注意把复杂的脚本简单化,要思路清晰,并且分段实现。当执行脚本时出现脚本错误后,不要只看那些提示的错误行,而是要观察整个相关的代码段。为避免编写的脚本出错,除了在编写脚本时注意书写规范,排除语法错误,更重要的是利用调试脚本工具来调试脚本。

(1)使用echo命令进行调试

echo命令是最有用的调试脚本工具之一,一般在可能出现问题的脚本中加入echo命令,采用的是分段排查的方式。

(2)使用bash命令参数进行调试
格式:
sh [-nvx] 脚本名
参数含义:
-n 不会执行该脚本,仅查询脚本语法是否有问题,如果没有语法问题就不显示任何内容,如果有问题就会报错。
-v 在执行脚本时,先将脚本的内容输出到屏幕上然后执行脚本,如果有错误,也会给出提示。
-x 将执行的脚本内容输出到屏幕上,当脚本文件较长时,可以使用set命令指定调试一段脚本
(3)使用set命令指定调试脚本段落
vim tiao.sh
    #!/bin/bash
    set -x ###开启调试模式###
    read -p "请输入您的分数(0-100):" GRADE
    if [ $GRADE -ge 85 ]&&[ $GRADE -le 100 ]
        then
        echo "$GRADE分!优秀"
    set +x ###关闭调试模式###
        elif [ $GRADE -ge 70 ]&&[ $GRADE -le 84 ]
        then 
        echo "$GRADE分,合格"
        else 
        echo "$GRADE分?不合格"
    fi

或者 
set -x #开启调试模式 
./tiao.sh 输出调试模式下的脚本
set +x #关闭调试模式
./tiao.sh 输出正常的脚本 

六、Shell编程之Sed与Awk

一、正则表达式概述

文本处理工具 基础正则表达式 扩展正则表达式
vi编辑器 支持
grep 支持
egrep 支持 支持
sed 支持
awk 支持
1.基础正则表达式元字符
\	转义字符,用于取消特殊符号的含义,如:\!、\n等
^	匹配字符串的开始位置,如: ^world 匹配以world 开头的行
$	匹配字符串的结束位置,如: world$ 匹配以world结尾的行
.			匹配除\n (换行)之外的任意一个字符
*			匹配前面的子表达式0次或者多次
[list]		匹配list列表中的一个字符,如: [0-9] 匹配任一位数字
[^list]		匹配不在list 列表中的一个字符,如: [^0-9] 匹配任意-位非数字字符
\{n\}		匹配前面的子表达式n次,如: [0-9]\{2\} 匹配两位数字
\{n,\}		匹配前面的子表达式不少于n次,如: [0-9]\{2,\} 表示两位及两位以上数字
\{n,m\}		匹配前面的子 表达式n到m次,如: [a-z]\{2,3\} 匹配两到三位的小写字母
-v 表示取反
-e 表示参数查找多个模式
2.扩展正则表达式
+	匹配前面的子表达式1次以上,如: go+d, 将匹配至少一个o
?	匹配前面的子表达式0次或者1次,如go?d,将匹配gd或god
0	将0号中的字符串作为一个整体,如: (xzy)+, 将匹配xzy整体1次以上
|	以或的方式匹配字符串,如:good|great,将匹配good或者great
3.grep工具

【例】以/etc/passwd 文件为例,介绍基础正则表达式。

注意,/etc/passwd 文件由于系统差异,可能会与案例中输出的结果有所不同。

[root@localhost ~]# grep root /etc/passwd		//筛选文件中包含root的行

[root@localhost ~]# grep ^root /etc/passwd		//筛选出以root开头的行
root:x:0:0:root:/root:/bin/bash

[root@localhost ~]# grep bash$ /etc/passwd		//筛选出以bash结尾的行
root:x:0:0:root:/root:/bin/bash

[root@localhost ~]# grep -v root /etc/passwd		//筛选文件中不包含root的行

[root@localhost ~]# grep 'r..d’ /etc/passwd			//筛选出r和d之间有两个字符的行
adm:x:3:4:adm:/var/adm:/sbin/nologin

[root@localhost ~]# grep '[^s]bin' /etc/passwd		//筛选bin前面不是S的行
[root@localhost ~]# grep "^$" /etc/passwd			//筛选出空白行,没有空白行所以没输出
[root@localhost ~]# grep 't[es]' /etc/passwd		//筛选包含字符串te或ts的行
[root@localhost ~]# grep '0\{1,\}' /etc/passwd		//查找数字0出现1次及以上的行
[root@localhost ~]# grep -e "ntp" -e "root" /etc/passwd  	//-e 参数查找多个模式

 

当使用连续的字符时,例如小写英文、大写英文、数字,就可以使用[a-z], [A-Z], [0-9] 的方式书写。

[root@localhost ~]# grep [0-3] /etc/passwd				//筛选包含数字0~3的行
[root@localhost ~]# grep '[^a-z]ae' /etc/passwd		//筛选ae前面不是小写字母的行
[root@ localhlost ~]# grep '^[a-z]ae' /etc/passwd   //筛选ae的开始位置前面是小写字母的行
[root@ localhlost ~]# grep '[a-z]ae' /etc/passwd   //筛选ae的前面是小写字母的行

值得注意的是“*”号,在通配符中表示任意字符,而在正则表达式中表示匹配里面的子表达式0次或者多次,例如:

[root@localhost ~]# grep 0* /etc/passwd
这里0*会匹配所有内容(若是有空白行的文件,甚至包括空白行)[root@localhost ~]# grep 00* /etc/passwd
这里00*匹配至少包含一个0的行(第一个0必须出现,第二个0可以出现0次或多次)
4.egrep工具

【例】/etc/passwd 文件为例,介绍扩展正则表达式。

[root@localhost ~J]#egrep 0+ /etc/passwd	 	//匹配至少包含一个0的行
[root@ localhost ~]# egrep '(root|ntp)' /etc/passwd 	//匹配包含root或ntp的行
[root@localhost ~]# egrep ro?t /etc/passwd			//匹配rt或者rot的行
[root@localhost ~]# egrep -v '^$|^#' /etc/passwd	//过滤文件中的空白行与#开头的行

以上就是正则表达式的基本用法,只要正确运用,能够在字符串提取和文本修改中起到很大作用。

二、Sed工具概述

1.sed基本语法- [地址 1][地址2]操作[参数]
命令语法:
sed -e '编辑指令' 文件1 文件2...
sed -n -e '编辑指令' 文件1 文件2...
sed -i -e '编辑指令' 文件1 文件2....
常用选项:
-e	指定要执行的命令,只有一个编辑命令时可省略。
-n	只输出处理后的行,读入时不显示。
-i	直接编辑文件,而不输出结果。
编辑指令格式: [地址 1][地址2]操作[参数]

**地址:**正则表达式,数字或$,如果没有地址代表所有的行

参数:-般用g代表只要符合条件,全部都进行处理

操作:

P	输出指定的行  6p代表6行
d	删除指定的行
S	字串替换,格式:“行范围s/旧字符串|新字符串/g”
r	读取指定文件,支持扩展的正则表达式
w	保存为文件
i	插入,在当前行前面插入一行或多行
a	插入,在当前行后面插入 
q	退出 
p;n 奇数行
n;p 偶数行
2.Sed用法示例
(1)输出指定的行
# sed -n 'p' /etc/passwd			//将所有内容输出
# sed -n '6p' /etc/passwd			//将第6行内容输出
# sed -n '6, 8p' /etc/passwd		//将第6 ~ 8行内容输出
# sed -n 'p;n' /etc/passwd			//将所有奇数行输出
# sed -n 'n;p' /etc/passwd			//将所有偶数行输出
# sed -n '1,10{p;n}' /etc/passwd 	//将 1 ~ 10行中的奇数行输出
# sed -n '1,10{n;p}' /etc/passwd 	//将 1 ~ 10行中的偶数行输出
# sed -n '10,${n;p}' /etc/passwd 	//将第10行到末尾之间的奇数行输出
# sed -n '$p' /etc/passwd			//将最后一行输出
# sed -n '1,+4p' /etc/passwd		//将第1行开始,连续4行进行输出(1~5行)
# sed -n '/root/p' /etc/passwd    //将匹配包含root 的行进行输出
# sed -n '10,/nom/p' /etc/passwd 	 //将从第10行至第一个包含nom的行进行输出
# sed -nr '/ro{1,}t/p' /etc/passwd 	 //匹配不少于1次前导字符o,加-r参数支持扩展正则表达式
# sed -n '/root\|ntp/p' /etc/passwd //输出包含root或者ntp 的行

注意,如果遇到特殊符号的情况,拓展正则还需要转义字符”\”

# sed -n '/nom/=' /etc/passwd			//将包含nom所在的行行号输出,"=” 号
# sed -e '5q' /etc/passwd				//输出前5行信息后退出,q退出
# sed -e '=' -e 's/^/5q/g' /etc/passwd  //输出前5行信息并输出行号后退出,q退出
(2)插入符合条件的行
# sed '/root/i admin:x:490:490::/:/sbin/nologin' /etc/passwd
//在含有root行的前面一行添加admin:x:490:490:/:/sbin/nologin

# sed '/root/a admin:x:490:490::/:/sbin/nologin' /etc/passwd
//在含有root行的下一行添加 admin ,a表示在当前行的后面行添加 

# sed '3aADMIN' /etc/passwd	
//在第3行之后插入ADMIN

其中使用插入时,如果添加多行数据,除最后一行外,每行末尾都需要用“\n”
符号表示数据未完结,换行。
# sed '3aADMIN\nuuu\n99999' /etc/passwd
(3)删除符合要求的行
# sed '1d' /etc/passwd 		  //删除第1行
# sed '$d' /etc/passwd			//删除最后1行
# sed '/^$/d' /etc/passwd		//删除所有空行
# sed '2,4d' /etc/passwd		   //删除第2~4 行
# sed '/root/d' /etc/passwd		//删除含有root的行
# sed '/root/!d' /etc/passwd	   //删除不包含root的行,这里的“!”号表示取反操作
# sed '/^root/d' /etc/passwd	   //删除以root开头的行
# sed '/nologin$/d' /etc/passwd	//删除以nologin结尾的行
(4)替换符合条件的文本
# sed 's/root//g' /etc/passwd	//将文件中所有的root都删除
# sed '/root/c admin:x:490:490::/:/sbin/nologin' /etc/passwd
//将含有root的行替换为admin:x:490:49/:/:/sbin/nologin

# sed -n 's/root/admin/2p' /etc/passwd //把每行的第2个root替换成admin
# sed '/root/s/root/ROOT/g' /etc/passwd	//将包含root的所有行中的root都替换为ROOT
# sed '1,3s/bin/BIN/g' /etc/passwd 		//将第1~ 3行中的所有bin都督换为BIN
# sed 's/$/ABC/' /etc/passwd			//在每行行尾插入字符串ABC
# sed '5s/$/ABC/' 1.txt           //在第5行行尾插入字符串ABC
# sed '3s/A/000/g' 1.txt         //将第3行的A替换成000
# sed 's/^/#/' /etc/passwd				//在每行行首插入#号
# sed '1,5s/^/#/' 1.txt       //在1到5行插入#号
# sed '/root/s/^/#/' /etc/passwd 		//将包含root的行的行首插入#号
#root:x:0:0:root:/root:/bin/bash
# sed '1cABC' /etc/passwd				//将第一行替换为ABC
# sed 'y/root/ROOT/' /etc/passwd		//将root对应替换为ROOT,y表示对应替换
# sed '1,10y/root/ROOT/' /etc/passwd	   //将第 1-10行中的 root对应替换为 ROOT
(5)迁移符合条件的文本
[root@localhost ~]# sed '/root/w file1' /etc/passwd 		//将包含root的行另存为文件file1
[root@localhost ~]# sed '/root/{H;d};$G' /etc/passwd		//将包含root的行迁移至未尾
[root@localhost ~]# sed '1, 5{H;d};$G' /etc/passwd			//将第1~ 5行内容迁移至未尾
[root@localhost ~]# sed '/root/{H;d);$G' /etc/passwsd		//将包含root的行迁移至末尾

其中h表示保存当前模式到一个缓冲区,G表示取出保存的模式
(6)执行多次命令
[root@localhost ~]# sed -ne ‘s/root/admin/' -ne 's/bash/sh/p’ /etc/passwd //将root和bash行作替换

注意:-e可以将多个命令连接起来,也可将多个编辑命令保存到文件中,通过-f指定文件,已完成多个处理操作。
(7)直接修改文件内容
# sed -i 's/^/#/' /etc/passwd		//在每行开头插入#号,直接修改原文件
# sed -i 's/^#//g' /etc/passwd		//将每行开头的#号删除,直接修改原文件
3.Sed脚本应用案例

调整vsftpd服务的配置文件,实现禁止匿名用户登录,允许本地用户登录并且具有写入权限。

#!/bin/bash
#设置变量,指定配置文件路径
CONFIG=" "
#源配置文件备份
[ -e "$CONFIG.old" ] || cp $CONFIG $CONFIG.old
#修改配置文件,实现禁止匿名用户登录
sed -i ‘/^anonymous_enable/s/YES/NO/g' $CONFIG
#允许本地用户登录,且具有写入权限
sed -i -e '/^local_enable/s/NO/YES/g' -e '/^write_enable/s/NO/YES/g' $CONFIG
#监听端口
sed -i '/^listen/s/NO/YES/g' $CONFIG

任务一:

一键安装ftp,不允许匿名用户访问。配置系统用户student访问指定目录/mnt/pub,并且对/mnt/pub/FTP这个目录具有上传、新建、删除的权限,系统用户stu1只具有下载的权限,配置完成后,重启服务,并在客户端进行测试。

编写思路:

1.FTP配置文件的配置项

12 anonymous_enable=YES  改为 NO   拒绝匿名用户访问

16 local_enable=YES           允许系统用户访问

19 write_enable=YES           允许写入

20 local_root=/mnt/public/         指定FTP的主目录为/mnt/public

21 chroot_local_user=YES         只允许系统用户访问指定的主目录

22 allow_writeable_chroot=YES       允许写入

2.判断/mnt/public目录是否存在,如果不存在则:mkdir /mnt/public/FTP

3.新建两个用户:student  stu1 

4.给student用户权限:chown student  /mnt/public/FTP

5.判断是否安装?如果没有安装则:yum install vsftpd ftp -y

6.修改配置文件:

sed  -i ‘s/ anonymous_enable=YES/anonymous_enable=NO/g’  /etc/vsftpd/vsftpd.conf

sed  -i ‘19alocal_root=/mnt/public/\nchroot_local_user=YES\nallow_writeable_chroot=YES’  /etc/vsftpd/vsftpd.conf

7.启动服务

三、awk工具介绍

Awk是一个功能强大的编辑工具,用于在Linux/UNIX下对文本和数据进行处理。数据可以来自一个或多个文件,也可以为其他命令的输出,常作为脚本来使用。在执行操作时,Awk逐行读取文本,默认以空格为分隔符进行分隔,将分隔所得的各个字段保存到内建变量中,对比该行是否与给定的模式相匹配,并按模式或者条件执行编辑命令,也可从脚本中调用编辑指令过滤输出相应内容。

1.Awk基本语法
Awk的两种语法格式:
awk[选项] '模式或条件 {编辑指令}' 文件1文件2

awk -f 脚本文件 文件1文件2

在Awk语句中,模式部分决定何时对数据进行操作,若省略则后续动作时刻保持执行状态,模式可以为条件语句、复合语句或正则表达式等。每条编辑指令可以包含多条语句,多条语句之间要使用分号或者空格分隔的多个区域。常用选项F定义字段分隔符,默认以空格或者制表符作为分隔符。

Awk提供了很多内置变量,经常用于处理文本,了解这些内置变量的使用是很有必要的。

FS	指定每行文本的字段分隔符,缺省为空格或制表位
NF	当前处理的行的字段个数
NR	当前处理的行的行号(序数)
$o	当前处理的行的整行内容
Sn	当前处理行的第n个字段(第n列) 

在Awk中,缺省的情况下总是将文本文件中的一行视为一个记录,而将一行中的某一部分作为记录中的一个字段。为了操作这些不同的字段,Awk借用Shell的方法,用1,2,3… 这样的方式来顺序地表示行(记录)中的不同字段。例如:

# awk -F: '{print $0,NF}' /etc/passwd
输出以冒号为分隔的/etc/passwd文件中记录的字段,共7个字段,$1、$2、$3...$7.
$0指当前处理的行的整行内容,换句话说也就是输出所有内容

【例】截取指定的列

用Awk截取命令df -Th输出的结果,不带任何条件(也就是执行所有内容) ,进行格式化输出,打印第1列和第6列数据。

# df -Th|awk '{print $1,$6}'

特殊的,$0指当前处理的行的整行内容,换句话说也就是输出所有内容,那么

# awk '{print $0}' /etc/passwd
相当于
# cat /etc/passwd
2.Awk用法示例
(1)打印文本内容
# awk '/^root/{print}' /etc/passwd” 		//输出以root开头的行
# awk '/nologin$/{print}' /etc/passwd		//输出以nologin结尾的行
# awk -F ":" '/bash$/{print | "wc -l"}' /etc/passwd
//统计可登录系统用户的个数。使用管道符调用命令wc -|统计使用bash 的用户个数即为可以
//登录系统用户的个数

在使用Awk的过程中,可以使用关系运算符作为“条件”,用于比较数字与字符串,运算符有大于(>)、小于(<)、大于等于(>=) 、小于等于(<=)、等于(==)、不等于(!=)这些;也可以使用逻辑操作符&&,表示“与”,||表示“或”,!表示“非”;还可以进行简单的数学运算加(+)、减(-)、乘(*)、除(/)、取余(%)、乘方(^)。只有当条件为真,才执行指定的动作。

# awk 'NR==1,NR= 3 {print)' /etc/passwd 	 //输出第1行至第3行内容
# awk 'NR== 1||NR-3 {print)' /etc/passwd	//输出第1、3行内容
# awk '(NR>=1)&&(NR<=3){print}' /etc/passwd  //输出第 1行到第3行内容
# awk ‘(NR%2)==1 {print}' /etc/passwd 		//输出所有奇数行的内容
# awk ‘(NR%2)==0 {print}' /etc/passwd 		//输出所有偶数行的内容
# awk -F “:” ‘!($3 < 900 )’ /etc/passwd	//输出第3个字段不小于900的行“!”号表示取反

在使用Awk过程中还可以使用条件表达式,条件表达式的运算涉及两个符号,冒号和问号,其实质就是if…else语句的捷径,有着和if…else相同的结果。

# awk -F: '{if($3>200) {print $0}}' /etc/passwd //输出第3个字段大于200的行
# awk -F: '{max=($3>$4)? $3:$4; print max}' /etc/passwd
//如果第3个字段的值大于第4个字段的值,则把问号前表达式$3的值赋给max,否则就将冒号后那个表达式的值赋给max

# awk -F: ‘{max =($3>200)? $3:$1; print max}’ /etc/passwd
//如果第3个字段的值大于200,则把第3个字段的值赋给max,否则就将第1个字段的值赋给max
(2) 按字段输出文本
#awk -F: '{print NR, $0}' /etc/passwd
//输出处理数据的行号,每处理完一条记录,NR值加1
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
…
# echo "you-me-he" | awk -F '[-]' '{print $NF}'
# awk -F ":" '$3<5{print $1,$3}' /etc/passwd	//输出第3列小于5的第1列与第3列数据
# awk -F ":" '($1~"root")&&(NF=7){print $1,$2}' /etc/passwd
//输出包含7个字段,并且第1个字段中包含root的行的第1与第2个字段内容

# awk -F “:” ‘NR==3,NR==7 {print $1,$7}’ /etc/passwd
//输出第3行到第7行中以冒号为分隔符的第1列与第7列数据

需要的话,输出数据时还可以插入一些文本标签:
# awk -F “:” ‘NR==3,NR==7 {print “USERNAME:” $1,”SHELL:” $7}’ /etc/passwd
# awk -F: '/^root/{print "hi:"$1}' /etc/passwd
# awk -F: '$7~"/bash"{print $1}' /etc/passwd
# awk -F’:’ ‘{print $1“:”$2”:”$3”:”$4}’ /etc/passwd
//保留原来的格式,输出以冒号为分割。/etc/passwd文件的前4个字段
# awk -F “:” ‘{print $1,$3}’ /etc/passwd
//输出以冒号为分隔符的第1列和第3列数据

或者

# awk ‘BEGIN {FS=”:”} {print $1,$3}’ /etc/passwd
//在FS之前加一个BEGIN(注意是大写),当读取第一条数据之前,先把分隔符加上后再进去操作。相似的还有END,在所有数据处理完毕后执行。

# awk ‘BEGIN {X=0};/\/bin\/bash$/{x++};END{printx}‘ /etc/passwd
//统计以/bin/bash为结尾的行数

AWK执行顺序就是这样的:首先执行BEGIN{ }中的操作;然后从指定文件中逐行读取数据,自动更新NF、NR、$0、$1等内建变量的值,去s执行‘模式或条件{编辑指令}‘;最后执行END{ }中的后续操作。
(3)处理命令输出的结果

AWK也可以利用管道符“ | ”处理命令结果。

# date | awk '{print "Month:"$2"\nYear:"$1}'
	Month:03月
	Year:CST
3.AWK应用案例

​ 本脚本用来实现对磁盘空间的监控,当磁盘的使用空间超过90%则发E-mail报警。

#!bin/bash
#monitor available disk space
#截取以“/”为结尾的行,打印出第5个字段也就是跟分区使用百分比,截取掉“%”
SPACE=df | sed -ne'/\/$/ p' | awk '{print $5}' | sed 's/%//'
     //
#截取出的数据与90进行相比,大于90给管理员发邮件报警
if [ $SPACE -ge 90 ]
then
echo “Disk spaceis not enough” | mail -s “Disk Alarm”admin@example.com
fi

四、Shell编程实战

1.例:系统概况
(1) 需求描述

为root用户编写登陆欢迎脚本,成功登陆后报告当前主机中的进程数,已登录用户数、登录的用户名、根文件系统的磁盘使用率

(2) 实现步骤
1) 新建脚本文件welcome.sh,用来输出各种监控信息。
[root@localhost ~]# vim /root/welcome.sh
    #!/bin/bash
    #此脚本用于显示进程数,登录的用户与用户名,根分区的磁盘使用率
    echo “已开启进程数:$(($(ps aux | wc -l)-l))”	#注意要减1
    echo “已登录用户数:$(who | wc -l)”
    echo “已登录的用户账号:$(who | awk ‘{print $1})”
    echo “根分区磁盘使用率:$(df -h | grep “/$” | awk ‘{print $5})[root@localhost ~]# chmod +x /root/welcome.sh

2) 修改/root/.bash_profile文件,调用welcome.sh脚本程序
[root@localhost ~]# vim /root/.bash_profile
……	//省略部分内容
/root/welcome.sh

3) 使用root用户重新登录,验证欢迎脚本的输出信息
2.例:MAC记录与端口扫描脚本
(1) 需求描述

● 编写名为system.sh的脚本,记录局域网中各主机的MAC地址,保存到/etc/ethers文件中;若此文件已存在,应先转移进行备份;每行一条记录,第1列为IP地址,第2列为对应的MAC地址。

● 检查有哪些主机开启了匿名FTP服务,扫描对象为/etc/ethers文件中的所有IP地址,扫描的端口为21.

(2) 实现步骤
[root@localhost ~]#vim system.sh
    #!/bin/bash
    #定义网段地址、mac列表文件
    NADD=192.168.4.”
    FILE=/etc/ethers”
    #发送ARP请求,并记录反馈信息
    [ -f $FILE ] && /bin/cp -f $FILE $FILE.old		#备份原有文件
    HADD=1		#定义起始扫描地址
    while [ $HADD -lt 254 ]
    do
        arping -c 2 -w 1 ${NADD}${HADD} &> /dev/null
        if [ $? -eq 0 ];then
        	arp -n grep ${NADD}${HADD} | awk ‘{print $1,$3}>> $FILE
        fi
        let HADD++
    done
    TARGET=$(awk ‘{print $1}/etc/ethers)
    echo “以下主机已开放匿名FTP服务:”
    for IP in $TARGET
    do
        wget [ftp://$IP/](ftp://$IP/) &> /dev/null
        if [ $? -eq 0 ];then
        	echo $IP
        	rm -rf index.html  #事先在ftp服务器上准备下载文件,测试后删除				
        fi
    done
# chmod +x system.sh
#./system.sh		#执行检测程序
#cat /etc/ethers  #确认记录结果
3.例:开发系统监控脚本
(1) 需求描述

编写名为sysmon.sh的shell监控脚本

监控内容包括CPU使用率、内存使用率、根分区磁盘占用率

百分比只需精确到个位,如7%、12%、23%等

出现以下任一情况时告警:磁盘占用率超过90%、CPU使用率超过80%、内存使用率超过90%,告警邮件通过mail命令发送到指定邮箱

结合crond服务,每半小时执行一次监控脚本

(2) 实现步骤
1) 编写Shell监控脚本
[root@localhost ~]# vim /root/sysmom.sh
#!/bin/bash
#提取性能监控指标(磁盘占用、CPU使用、内存使用)
DUG=$(df -h | grep “/$” | awk ‘{print $5}| awk -F%{print $1})
CUG=$(expr 100 -$(mpstat | tail -l | awk ‘{print $12}| awk -F.{print $1}))
MUG=$(expr $(free | grep “cache:| awk ‘{print $3})\* 100/ $(free | grep “Mem:| awk ‘{print $2}))
#设置告警日志文件、告警邮箱
ALOG=/tmp/alert.txt”
AMAIL=”root@localhost”
#判断是否记录告警
if [ $DUG -gt 90 ]
then
	echo “磁盘占用率:$DUG%>> $ALOG
fi
if [ $CUG -gt 80 ]
then
	echo “CPU使用率:$CUG%>> $ALOG
fi
if [ $MUG -gt 90 ]
then
	echo “内存使用率:$MUG%>> $ALOG
fi
#判断是否发送告警邮件,最后删除告警日志文件
if [ -f $ALOG ] 
then
	cat $ALOG | mail -s “Host Alert” $AMAIL
	rm -rf $ALOG
fi
[root@localhost ~]# chmod +x /root/sysmom.sh

2) 测试sysmon.sh脚本的执行情况
首先确认有可用的邮件服务器(如postfix,sendmail等),然后调低监控阈值,执行sysmon.sh脚本进行测试。
[root@localhost ~]# /root/sysmon.sh
然后查收root@localhost的邮箱,确认告警邮件内容。
[root@localhost ~]# mail

3) 设置crontab计划任务
首先确认系统服务crond已经运行
[root@localhost ~]# /etc/init.d/crond status
crond (pid 5839)正在运行…
然后添加crontab计划任务配置,每半小时调用一次sysmon.sh脚本程序
[root@localhost ~]# crontab -e
*/30 * * * * /root/sysmon.sh

你可能感兴趣的:(Linux)