在一些复杂的Linux维护工作,大量重复性的输入与交互操作不但费时费力,而且容易出错,而编写一个恰好片的shell脚本程序,可以批量处理、自动化地完成一系列维护任务,大大减轻管理员的负担。
Shell脚本(shell scrip)就是将要执行的命令按顺序保存到一个文件文件,并给该文件可执行权限,方便一次性执行的一个程序文件。主要是方便管理员进行设置或管理,可结合各种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 9月 9 18:40 vmlinuz-0-rescue-396e2077721a48da8ea2738c4bd8e831
-rwxr-xr-x. 1 root root 6.4M 11月 9 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
由于shell脚本”批量处理”的特殊性,其大部份操作过程位于后台,不需要用记进行干预。因此学会提取、过滤执行信息变得十分重要。
管道操作为不同命令之间的协同工作提供了一种机制,位于管道符号”|”左边的命令输出的结果,将作为右侧命令的输入(处理对象),同一行命令中可以使用多个管道。
在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%
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 查看内核版本信息
将执行命令过程中出现的错误保存到指定文件,使用”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
解压文件—进入到解压文件后执行. /configure进行环境配置— make 编译— makeinstall 安装软件
Shell变量用来存放系统和用户需要使用的特定参数(值),而且这些参数可以根据用户的设定或系统环境的变化而相应变化。通过使用变量,shell程序能够提供更加灵活的功能,适应性更强。
自定义变量、环境变量、位置变量、预定义变量
格式:变量名=变量值
注意:等号两边没有空格。变量名称需以字母或下划线开头,名称中不要包含特殊字符:
(+ - * / . ? % & #)
【例】定义变量Product(值为Weixin),Version(值为6.0)
# Product=Weixin
# Version=6.0
引用变量值:在变量名称前添加”$”
查看变量值:使用echo命令,可以在一条echo命令同时查看多个变量值。
# echo $Product
Weixin
# echo $Product$Version
Weixin6.0 因为输出变量时是连在一起的所以结果也挨在一起
当变量名称容易和紧跟其后其他字符相混淆时,需要添加”{}”将其括起来,否则将无法确定正确的变量名称。对未定义的变量,将显示空值。
# echo $Prouduct4.5
# echo ${Product}4.5
双引号主要起界定字符串的作用,特别是当要赋值的内容包含空格时,必须以双引号括起来;其他情况下双引号通常可以省略。
【例】变量值包含空格的正确赋值方法
# Weixin=weixin 4.5
bash: 4.5: 未找到命令...
# Weixin="weixin 4.5"
在双引号范围内,使用$符号可以引用其他变量的值(变量引用)。
# Version=8.0
# QQ="QQ$Version"
# echo $QQ
QQ8.0
当要赋值的内容包含$ “ \等具有特殊含义的字符时,应使用单引号括起来。在单引号的范围内,将无法引用其他变量的值,任何字符均作为普通字符看待。但赋值内容中包含单引号时,需要使用\进行转义,以免冲突。
举例:使用单引号对变量赋值
# 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 10月 31 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
参数 | 含义 |
---|---|
-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
默认情况下,新定义的变量只在当前的shell环境中有效,因此称为局部变量。当进入子程序或新的子shell环境时,局部变量将无法再使用。
# Weixin=weixin
# Version=6.0
# echo "$weixin $Version"
weixin 6.0
#bash
echo "$weixin $Version"
为了使用定义的变量在所有的子shell环境中能够继续使用,减少重复设置工作,可以通过内部命令export将指定的变量导出为”全局变量”。可以同时指定多个变量名称作为参数(不需要使用$),变量名之间以空格分隔。
# export weixin Version
# bash
# echo "$weixin $Version"
weixin 6.0
使用export导出全局变量的同时,也可以为变量进行赋值。
#export KGC=”www.kgc.cn”
# echo $KGC
www.kgc.cn
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不能删除只读变量
环境变量指的是出于运行需要而由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
# declare -i a=100
# a=$a2
# a=100
# a=$(($a/2))
# a=100
# a=`expr $a / 100`
# [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
为了在使用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
预定义变量是由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
整数值比较指的是根据给定的两个整数值,判断第一个数与第二个数的关系,如是否是大于、等于、小于第二个数。整数值比较的常用操作选项如下:
-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
字符串比较通常用来检查用户输入、系统环境变量等是否满足条件,在提供交互式操作的shell脚本中,也可用来判断用户输入的位置参数是否符合要求。字符串比较常用的操作选项如下:
= 第一个字符串与第二个字符串相同
!= 第一个字符串与第二个字符串不相同
-z 检查字符串为空(zero),对于未定义或赋予空值的变量将视为空串
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 "
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
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
使用shell脚本程序具备一定的“智能”,面临的第一个问题就是如何区分不同的情况以确定执行何种操作。例如,当磁盘使用率超过95%时发送警告信息;当备份目录存在时能够自动创建;当源码编译程序时若配置失败则不再继续安装等。
shell环境根据命令执行后的返回状态($?)来判断是否执行成功:
当返回0时表示成功
当返回1时表示失败或异常
test命令的使用: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”
# [[ -d /media/cdrom ]]&&echo “YES” || echo “NO”
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”
整数值比较指的是根据给定的两个整数值,判断第一个数与第二个数的关系,如是否是大于、等于、小于第二个数。整数值比较的常用操作选项如下:
-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
字符串比较通常用来检查用户输入、系统环境变量等是否满足条件,在提供交互式操作的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 "不覆盖"
不覆盖
逻辑测试指的是判断两个或多个条件之间的依赖关系。当系统任务取决于多个不同的条件时,判断是根据这些条件同时成立还是只要有其中一个成立等情况,需要有一个测试的过程,常用的逻辑测试操作如下,使用时放在不同的测试语句或命令之间。
&& 逻辑与,表示而且,只有当前后两个条件都成立时,整个测试的命令返回值才为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
在shell脚本应用中,if语句是最为常用的一种流程控制方式,用来根据特定的条件测试结果,分别执行不同的操作(如果…那么…)。根据不同的复杂程度,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
针对条件成立和条件不成立两种情况分别执行不同的操作。
语法格式:
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
与单分支、双分支的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 变量值 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。
最后“*)”表示默认模式,其中*相当于通配符。
# 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 变量名 in 取值列表
do
命令序列
Done
for语句结构中,for语句的操作对象为用户指定名称的变量,并通过in关键字为该变量预先设置了一个取值列表,多个取值之间以空格进行分隔。位于do … done之间的命令序列称为循环体,其中的执行语句需要引用变量以完成相应的任务。
for语句的执行流程:首先将列表中的第1个取值赋值给变量,并执行do .... done循环体中的命令序列;然后将列表中的第2个取值赋值给变量,并执行循环体中的命令序列....以此类推,直到列表中的所有取值用完,最后将跳至done语句,表示结束循环。
# 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:无此用户
# 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 条件测试操作
do
命令序列
done
批量添加用户
# 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
# 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
# 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
[function]函数名(){
命令序列
[return x]
}
定义一个求和函数并在脚本中引用
#!/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语句等。我们在编写服务启动脚本可参考系统原有的启动脚本编写。
在排错时要注意把复杂的脚本简单化,要思路清晰,并且分段实现。当执行脚本时出现脚本错误后,不要只看那些提示的错误行,而是要观察整个相关的代码段。为避免编写的脚本出错,除了在编写脚本时注意书写规范,排除语法错误,更重要的是利用调试脚本工具来调试脚本。
echo命令是最有用的调试脚本工具之一,一般在可能出现问题的脚本中加入echo命令,采用的是分段排查的方式。
sh [-nvx] 脚本名
-n 不会执行该脚本,仅查询脚本语法是否有问题,如果没有语法问题就不显示任何内容,如果有问题就会报错。
-v 在执行脚本时,先将脚本的内容输出到屏幕上然后执行脚本,如果有错误,也会给出提示。
-x 将执行的脚本内容输出到屏幕上,当脚本文件较长时,可以使用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 输出正常的脚本
文本处理工具 | 基础正则表达式 | 扩展正则表达式 |
---|---|---|
vi编辑器 | 支持 | |
grep | 支持 | |
egrep | 支持 | 支持 |
sed | 支持 | |
awk | 支持 | 支 |
\ 转义字符,用于取消特殊符号的含义,如:\!、\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 表示参数查找多个模式
+ 匹配前面的子表达式1次以上,如: go+d, 将匹配至少一个o
? 匹配前面的子表达式0次或者1次,如go?d,将匹配gd或god
0 将0号中的字符串作为一个整体,如: (xzy)+, 将匹配xzy整体1次以上
| 以或的方式匹配字符串,如:good|great,将匹配good或者great
【例】以/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次或多次)。
【例】/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 -e '编辑指令' 文件1 文件2...
sed -n -e '编辑指令' 文件1 文件2...
sed -i -e '编辑指令' 文件1 文件2....
-e 指定要执行的命令,只有一个编辑命令时可省略。
-n 只输出处理后的行,读入时不显示。
-i 直接编辑文件,而不输出结果。
**地址:**正则表达式,数字或$,如果没有地址代表所有的行
参数:-般用g代表只要符合条件,全部都进行处理
操作:
P 输出指定的行 6p代表6行
d 删除指定的行
S 字串替换,格式:“行范围s/旧字符串|新字符串/g”
r 读取指定文件,支持扩展的正则表达式
w 保存为文件
i 插入,在当前行前面插入一行或多行
a 插入,在当前行后面插入
q 退出
p;n 奇数行
n;p 偶数行
# 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退出
# 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
# 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结尾的行
# 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
[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表示取出保存的模式
[root@localhost ~]# sed -ne ‘s/root/admin/' -ne 's/bash/sh/p’ /etc/passwd //将root和bash行作替换
注意:-e可以将多个命令连接起来,也可将多个编辑命令保存到文件中,通过-f指定文件,已完成多个处理操作。
# sed -i 's/^/#/' /etc/passwd //在每行开头插入#号,直接修改原文件
# sed -i 's/^#//g' /etc/passwd //将每行开头的#号删除,直接修改原文件
调整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是一个功能强大的编辑工具,用于在Linux/UNIX下对文本和数据进行处理。数据可以来自一个或多个文件,也可以为其他命令的输出,常作为脚本来使用。在执行操作时,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
# 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
#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{ }中的后续操作。
AWK也可以利用管道符“ | ”处理命令结果。
# date | awk '{print "Month:"$2"\nYear:"$1}'
Month:03月
Year:CST
本脚本用来实现对磁盘空间的监控,当磁盘的使用空间超过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
为root用户编写登陆欢迎脚本,成功登陆后报告当前主机中的进程数,已登录用户数、登录的用户名、根文件系统的磁盘使用率
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用户重新登录,验证欢迎脚本的输出信息
● 编写名为system.sh的脚本,记录局域网中各主机的MAC地址,保存到/etc/ethers文件中;若此文件已存在,应先转移进行备份;每行一条记录,第1列为IP地址,第2列为对应的MAC地址。
● 检查有哪些主机开启了匿名FTP服务,扫描对象为/etc/ethers文件中的所有IP地址,扫描的端口为21.
[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 #确认记录结果
编写名为sysmon.sh的shell监控脚本
监控内容包括CPU使用率、内存使用率、根分区磁盘占用率
百分比只需精确到个位,如7%、12%、23%等
出现以下任一情况时告警:磁盘占用率超过90%、CPU使用率超过80%、内存使用率超过90%,告警邮件通过mail命令发送到指定邮箱
结合crond服务,每半小时执行一次监控脚本
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