Shell脚本 & Sed流编辑器 & awk语法

Shell脚本 & Sed流编辑器 & awk语法

参考

文章目录

shell脚本

一、什么是shell脚本?

  • 一句话概括

简单来说就是将需要执行的命令保存到文本中,按照顺序执行。它是解释型的,意味着不需要编译。

  • 准确叙述

若干命令 + 脚本的基本格式 + 脚本特定语法 + 思想= shell脚本

1、什么时候用到脚本?(★★★)

重复化、复杂化的工作,通过把工作的命令写成脚本,以后仅仅需要执行脚本就能完成这些工作。

2、shell脚本能干啥?(★★★)

①自动化软件部署 LAMP/LNMP/Tomcat…

②自动化管理 系统初始化脚本、批量更改主机密码、推送公钥…

③自动化分析处理 统计网站访问量

④自动化备份 数据库备份、日志转储…

⑤自动化监控脚本

3、如何学习shell脚本?(★★★)
  1. 尽可能记忆更多的命令(记忆命令使用功能和场景)
  2. 掌握脚本的标准的格式(指定魔法字节、使用标准的执行方式运行脚本)
  3. 必须==熟悉掌握==脚本的基本语法(重点)
4、学习shell脚本的秘诀

多看(看懂)——>模仿(多练)——>多思考(多写)

5、shell脚本的基本写法

1)脚本第一行,魔法字符==#!==指定解释器【必写】

#!/bin/bash 表示以下内容使用bash解释器解析

注意: 如果直接将解释器路径写死在脚本里,可能在某些系统就会存在找不到解释器的兼容性问题,所以可以使用:#!/bin/env 解释器 #!/bin/env bash

2)脚本第二部分,注释(#号)说明,对脚本的基本信息进行描述【可选】

#!/bin/env bash

# 以下内容是对脚本的基本信息的描述
# Name: 名字
# Desc:描述describe
# Path:存放路径
# Usage:用法
# Update:更新时间

#下面就是脚本的具体内容
commands
...

3)脚本第三部分,脚本要实现的具体代码内容

6、shell脚本的执行方法(★★★)
  • 标准脚本执行方法(建议)
1) 编写人生第一个shell脚本
[root@MissHou shell01]# cat first_shell.sh
#!/bin/env bash

# 以下内容是对脚本的基本信息的描述
# Name: first_shell.sh
# Desc: num1
# Path: /shell01/first_shell.sh
# Usage:/shell01/first_shell.sh
# Update:2019-05-05

echo "hello world"
echo "hello world"
echo "hello world"

2) 脚本增加可执行权限
[root@MissHou shell01]# chmod +x first_shell.sh

3) 标准方式执行脚本
[root@MissHou shell01]# pwd
/shell01
[root@MissHou shell01]# /shell01/first_shell.sh
或者
[root@MissHou shell01]# ./first_shell.sh

注意:标准执行方式脚本必须要有可执行权限。
  • 非标准的执行方法(不建议)
  1. 直接在命令行指定解释器执行
[root@MissHou shell01]# bash first_shell.sh
[root@MissHou shell01]# sh first_shell.sh
[root@MissHou shell01]# bash -x first_shell.sh
+ echo 'hello world'
hello world
+ echo 'hello world'
hello world
+ echo 'hello world'
hello world
----------------
-x:一般用于排错,查看脚本的执行过程
-n:用来查看脚本的语法是否有问题
------------
  1. 使用source命令读取脚本文件,执行文件里的代码
[root@MissHou shell01]# source first_shell.sh
hello world
hello world
hello world

小试牛刀: 写一个木有灵魂的脚本,要求如下:

  1. 删除/tmp/目录下的所有文件

  2. 然后在/tmp目录里创建3个目录,分别是dir1~dir3

  3. 拷贝/etc/hosts文件到刚创建的dir1目录里

  4. 最后打印"报告首长,任务已于2019-05-05 10:10:10时间完成"内容

    echo "报告首长,任务已于$(date +'%F %T')"

二、变量的定义

1、变量是什么?

一句话概括:变量是用来临时保存数据的,该数据是可以变化的数据。

2、什么时候需要定义变量?
  • 如果某个内容需要多次使用,并且在代码中重复出现,那么可以用变量代表该内容。这样在修改内容的时候,仅仅需要修改变量的值。
  • 在代码运作的过程中,可能会把某些命令的执行结果保存起来,后续代码需要使用这些结果,就可以直接使用这个变量。
3、变量如何定义?(★★★)

变量名=====变量值

变量名:用来临时保存数据的

变量值:就是临时的可变化的数据

[root@MissHou ~]# A=hello            定义变量A
[root@MissHou ~]# echo $A            调用变量A,要给钱的,不是人民币是美元"$"
hello
[root@MissHou ~]# echo ${A}        还可以这样调用,不管你的姿势多优雅,总之要给钱
hello
[root@MissHou ~]# A=world            因为是变量所以可以变,移情别恋是常事
[root@MissHou ~]# echo $A            不管你是谁,只要调用就要给钱
world
[root@MissHou ~]# unset A            不跟你玩了,取消变量
[root@MissHou ~]# echo $A            从此,我单身了,你可以给我介绍任何人
4、变量的定义规则

虽然可以给变量(变量名)赋予任何值;但是,对于变量名也是要求的!

1)变量名区分大小写(★★★)
[root@MissHou ~]# A=hello
[root@MissHou ~]# a=world
[root@MissHou ~]# echo $A
hello
[root@MissHou ~]# echo $a
world
2)变量名不能有特殊符号(★★★)
[root@MissHou ~]# *A=hello
-bash: *A=hello: command not found
[root@MissHou ~]# ?A=hello
-bash: ?A=hello: command not found
[root@MissHou ~]# @A=hello
-bash: @A=hello: command not found

特别说明:对于有空格的字符串给变量赋值时,要用引号引起来
[root@MissHou ~]# A=hello world
-bash: world: command not found
[root@MissHou ~]# A="hello world"
[root@MissHou ~]# A='hello world'
3)变量名不能以数字开头(★★★)
[root@MissHou ~]# 1A=hello
-bash: 1A=hello: command not found
[root@MissHou ~]# A1=hello
注意:不能以数字开头并不代表变量名中不能包含数字呦。
4)等号两边不能有任何空格(★★★)
[root@MissHou ~]# A =123
-bash: A: command not found
[root@MissHou ~]# A= 123
-bash: 123: command not found
[root@MissHou ~]# A = 123
-bash: A: command not found
[root@MissHou ~]# A=123
[root@MissHou ~]# echo $A
123
5)变量名尽量做到见名知意
NTP_IP=10.1.1.1
DIR=/u01/app1
TMP_FILE=/var/log/1.log
...

说明:一般变量名使用大写(小写也可以),不要同一个脚本中变量全是a,b,c等不容易阅读
5、变量的定义方式有哪些?(★★★)
1)直接赋值

直接赋值给一个变量

[root@MissHou ~]# A=1234567
[root@MissHou ~]# echo $A
1234567
[root@MissHou ~]# echo ${A:2:4}        表示从A变量中第3个字符开始截取,截取4个字符
3456

说明:
$变量名 和 ${变量名}的异同
相同点:都可以调用变量
不同点:${变量名}可以只截取变量的一部分,而$变量名不可以
2)命令执行结果赋值给变量
[root@MissHou ~]# B=`date +%F`
[root@MissHou ~]# echo $B
2019-04-16
[root@MissHou ~]# C=$(uname -r)
[root@MissHou ~]# echo $C
2.6.32-696.el6.x86_64
3)交互式定义变量(read)

目的: 让用户自己给变量赋值,比较灵活。

语法:read [选项] 变量名

常见选项:

选项释义
-p定义提示用户的信息
-n定义字符数(限制变量值的长度)
-s不显示(不显示用户输入的内容)
-t定义超时时间,默认单位为秒(限制用户输入变量值的超时时间)

举例说明:

用法1:用户自己定义变量值
[root@MissHou ~]# read name
harry
[root@MissHou ~]# echo $name
harry
[root@MissHou ~]# read -p "Input your name:" name
Input your name:tom
[root@MissHou ~]# echo $name
tom

用法2:读取来自文件的变量值

[root@MissHou ~]# cat 1.txt 
10.1.1.1 255.255.255.0

[root@MissHou ~]# read ip mask < 1.txt 
[root@MissHou ~]# echo $ip
10.1.1.1
[root@MissHou ~]# echo $mask
255.255.255.0
4)定义有类型的变量(declare)

目的: 给变量做一些限制,固定变量的类型,比如:整型、只读

用法:declare 选项 变量名=变量值

常用选项:

选项释义举例
-i将变量看成整数declare -i A=123
-r定义只读变量declare -r B=hello
-a定义普通数组;查看普通数组
-A定义关联数组;查看关联数组
-x将变量通过环境导出declare -x AAA=123456 等于 export AAA=123456

举例说明:

[root@MissHou ~]# declare -i A=123
[root@MissHou ~]# echo $A
123
[root@MissHou ~]# A=hello
[root@MissHou ~]# echo $A
0

[root@MissHou ~]# declare -r B=hello
[root@MissHou ~]# echo $B
hello
[root@MissHou ~]# B=world
-bash: B: readonly variable
[root@MissHou ~]# unset B
-bash: unset: B: cannot unset: readonly variable
6、变量的分类
1)本地变量
  • 本地变量:当前用户自定义的变量。当前进程中有效,其他进程及当前进程的子进程无效。
2)环境变量(★★★)

1)按生效的范围分类。

系统环境变量:公共的,对全部的用户都生效。

用户环境变量:用户私有的、自定义的个性化设置,只对该用户生效。

2)按生存周期分类。

永久环境变量:在环境变量脚本文件中配置,用户每次登录时会自动执行这些脚本,相当于永久生效。

临时环境变量:使用时在Shell中临时定义,退出Shell后失效。

环境变量 (也是和程序绑定的, 程序结束时, 动态设置的环境变量也就结束):当前进程有效,并且能够被子进程调用, 反之不行(子进程环境变量的设置对父进程无效)。

  1. 环境变量的值由用户决定, 和Windows的用户环境变量一样. 即使用户qpyue 使用su 切换root用户,echo USER依旧是以前的用户名称(qpyue), 环境变量的值依旧不变.(除了HOME)

  2. 即使两个qpyue用户同时登录, 他们的环境变量都会重新从配置文件刷新, 单独存储, 所以环境变量的值互不影响(在两个bash程序存在过程中, 任意一方环境变量变化不影响另一方)*

  • env查看当前用户的环境变量
  • set查询当前用户的所有变量(临时变量与环境变量)
  • export 变量名=变量值 或者 变量名=变量值;export 变量名
[root@MissHou ~]# export A=hello        临时将一个本地变量(临时变量)变成环境变量
[root@MissHou ~]# env|grep ^A
A=hello

永久生效:设置为全局变量
vim /etc/profile 或者 ~/.bashrc
export A=hello
或者
A=hello
export A

说明:系统中有一个变量PATH,环境变量
export PATH=/usr/local/mysql/bin:$PATH
3)全局变量(★★★)
  • 全局变量全局所有的用户和程序都能调用(文件配置),且继承,新建的用户也默认能调用.
  • 解读相关配置文件
文件名说明备注
$HOME/.bashrc当前用户的bash信息,用户登录时读取定义别名、umask、函数等
$HOME/.bash_profile当前用户的环境变量,用户登录时读取
$HOME/.bash_logout当前用户退出当前shell时最后读取定义用户退出时执行的程序等
/etc/bashrc全局的bash信息,所有用户都生效
/etc/profile全局环境变量信息系统和所有用户都生效
$HOME/.bash_history用户的历史命令history -w 保存历史记录 history -c 清空历史记录

说明: 以上文件修改后,都需要重新source让其生效或者退出重新登录。

  • 用户登录系统后, 读取相关文件的顺序

    1. /etc/profile
    2. $HOME/.bash_profile
    3. $HOME/.bashrc
    4. /etc/bashrc
    5. $HOME/.bash_logout
4)系统变量
  • 系统变量(内置bash中变量) : shell本身已经固定好了它的名字和作用.

./02.sh a b c -> (3个参数)

内置变量含义
$?上一条命令执行后返回的状态;状态值为0表示执行正常,非0表示执行异常或错误
$0当前执行的程序或脚本名 (./02.sh)
$#脚本后面接的参数的个数 3个
$*脚本后面所有参数,参数当成一个整体输出,每一个变量参数之间以空格隔开 (参数数组a b c)
$@脚本后面所有参数参数是独立的,也是全部输出 (参数数组a b c)
1~9脚本后面的位置参数,$1表示第1个位置参数,依次类推
{10}~{n}扩展位置参数,第10个位置变量必须用{}大括号括起来(2位数字以上扩起来)
$$当前所在进程的进程号,如echo $$
$!后台运行的最后一个进程号 测试: sleep 400 &(后台运行)/sleep 400(ctrl+z 暂停运行), 再运行jobs, 查看当前进程的后台子进程.
!$调用最后一条命令历史中的参数
  • 进一步了解位置参数$1~${n}
#!/bin/bash
#了解shell内置变量中的位置参数含义
echo "\$0 = $0"
echo "\$# = $#"
echo "\$* = $*"
echo "\$@ = $@"
echo "\$1 = $1" 
echo "\$2 = $2" 
echo "\$3 = $3" 
echo "\$11 = ${11}" 
echo "\$12 = ${12}" 
  • 进一步了解*和@的区别

$*:表示将变量看成一个整体$@:表示变量是独立的

#!/bin/bash
for i in "$@"
do
echo $i
done

echo "======我是分割线======="

for i in "$*"
do
echo $i
done

[root@MissHou ~]# bash 3.sh a b c
a
b
c
======我是分割线=======
a b c

三、变量的四则运算

算术运算:默认情况下,shell就只能支持简单的整数运算

运算内容:加(+)、减(-)、乘(*)、除(/)、求余数(%)

1、四则运算符号(★★★)
表达式举例
$(( ))echo $((1+1))
$[ ]echo $[10-5]
exprexpr 10 / 5
letn=1;let n+=1 等价于 let n=n+1
2、了解i和i++
  • 对变量的值的影响
[root@MissHou ~]# i=1
[root@MissHou ~]# let i++
[root@MissHou ~]# echo $i
2
[root@MissHou ~]# j=1
[root@MissHou ~]# let ++j
[root@MissHou ~]# echo $j
2
  • 对表达式的值的影响
[root@MissHou ~]# unset i j
[root@MissHou ~]# i=1;j=1
[root@MissHou ~]# let x=i++         先赋值,再运算
[root@MissHou ~]# let y=++j         先运算,再赋值
[root@MissHou ~]# echo $i
2
[root@MissHou ~]# echo $j
2
[root@MissHou ~]# echo $x
1
[root@MissHou ~]# echo $y
2

四、普通数组和关联数组

1、数组分类
  • 普通数组:只能使用整数作为数组索引(元素的下标)
  • 关联数组:可以使用字符串作为数组索引(元素的下标)
2、普通数组定义(★★★)
  • 一次赋予一个值
数组名[索引下标]=值
array[0]=v1
array[1]=v2
array[2]=v3
array[3]=v4
  • 一次赋予多个值
数组名=(值1 值2 值3 ...)
array=(var1 var2 var3 var4)

array1=(`cat /etc/passwd`)            将文件中每一行赋值给array1数组
array2=(`ls /root`)
array3=(harry amy jack "Miss Hou")
array4=(1 2 3 4 "hello world" [10]=linux)
3、数组的读取(★★★)
${数组名[元素下标]}

echo ${array[0]}            获取数组里第一个元素
echo ${array[*]}            获取数组里的所有元素
echo ${#array[*]}            获取数组里所有元素个数
echo ${!array[@]}        获取数组元素的索引下标
echo ${array[@]:1:2}    访问指定的元素;1代表从下标为1的元素开始获取;2代表获取后面几个元素

查看普通数组信息:
[root@MissHou ~]# declare -a
4、关联数组定义(★★★)
1)首先声明关联数组
declare -A asso_array1
declare -A asso_array2
declare -A asso_array3
2)关联数组赋值
  • 一次赋一个值
数组名[索引or下标]=变量值
asso_array1[linux]=one
asso_array1[java]=two
asso_array1[php]=three
  • 一次赋多个值(用(赋多值,且中间没逗号)
asso_array2=([name1]=harry [name2]=jack [name3]=amy [name4]="Miss Hou")
  • 查看当前进程中所有关联数组
# declare -A
declare -A asso_array1='([php]="three" [java]="two" [linux]="one" )'
declare -A asso_array2='([name3]="amy" [name2]="jack" [name1]="harry" [name4]="Miss Hou" )'
  • 获取关联数组值(记得加{}
# echo ${asso_array1[linux]}
one
# echo ${asso_array1[php]}
three
# echo ${asso_array1[*]}
three two one
# echo ${!asso_array1[*]}
php java linux
# echo ${#asso_array1[*]}
3
# echo ${#asso_array2[*]}
4
# echo ${!asso_array2[*]}
name3 name2 name1 name4
  • 其他定义方式
[root@MissHou shell05]# declare -A books
[root@MissHou shell05]# let books[linux]++
[root@MissHou shell05]# declare -A|grep books
declare -A books='([linux]="1" )'
[root@MissHou shell05]# let books[linux]++
[root@MissHou shell05]# declare -A|grep books
declare -A books='([linux]="2" )'

五、变量其他常见操作

1、获取目录和文件字符串
  • 取出一个目录下的目录和文件:dirnamebasename
# A=/root/Desktop/shell/mem.txt 
# echo $A
/root/Desktop/shell/mem.txt
# dirname $A   取出目录
/root/Desktop/shell
# basename $A  取出文件
mem.txt
2、变量"内容"的删除和替换
一个“%”代表从右往左删除
两个“%%”代表从右往左去掉最多
一个“#”代表从左往右去掉删除
两个“##”代表从左往右去掉最多

举例说明:
# url=www.taobao.com
# echo ${#url}             获取变量的长度
14
# echo ${url#*.}
taobao.com
# echo ${url##*.}
com
# echo ${url%.*}
www.taobao
# echo ${url%%.*}
www

替换:///

echo ${url/ao/AO}  用AO代替ao(从左往右第一个)
www.tAObao.com

echo ${url//ao/AO}   贪婪替换(替代所有)
www.tAObAO.com

六、条件判断语法结构

1、条件判断3种语法格式(★★★)
  • 格式1: test 条件表达式
  • 格式2: [ 条件表达式 ]
  • 格式3:[[ 条件表达式 ]] 支持正则 =~

Note

| 表达式 | 左右两边空格 | 字符串是否需加"" | 是否支持&&和|| | 是否支持C风格 |
| -------- | ------ | --------- | ---------- | ----------------------------------------------------- |
| [ ] | 需要 | 需要 | 不支持 | 不支持 |
| [[ ]] | 需要 | 不需要 | 支持 | 支持,比如a=1; b=2<br/> [[ a > b ]] && echo a \|| echo b |

2、条件判断的相关参数
1)条件判断常用场景

判断文件类型,判断文件新旧,判断字符串是否相等,判断权限等等…

2)文件类型判断
  1. 判断参数含义说明
    -e判断文件是否存在(link文件指向的也必须存在)exists
    -f判断文件是否存在并且是一个普通文件file
    -d判断文件是否存在并且是一个目录directory
    -L判断文件是否存在并且是一个软连接文件soft link
    -b判断文件是否存在并且是一个块设备文件block
    -S判断文件是否存在并且是一个套接字文件socket
    -c判断文件是否存在并且是一个字符设备文件char
    -p判断文件是否存在并且是一个命名管道文件pipe
    -s判断文件是否存在并且是一个非空文件(有内容)is not empty

举例说明:

test -e file                    只要文件存在条件为真
[ -d /shell01/dir1 ]             判断目录是否存在,存在条件为真
[ ! -d /shell01/dir1 ]        判断目录是否存在,不存在条件为真
[[ -f /shell01/1.sh ]]        判断文件是否存在,并且是一个普通的文件
3)文件权限判断
判断参数含义
-r当前用户对其是否可读
-w当前用户对其是否可写
-x当前用户对其是否可执行
-u是否有suid,高级权限冒险位
-g是否sgid,高级权限强制位
-k是否有t位,高级权限粘滞位 (创建者/root才能删除)
4)文件新旧比较

说明:这里的新旧指的是文件的修改时间。

判断参数含义
file1-nt file2比较file1是否比file2新
file1 -ot file2比较file1是否比file2旧
file1 -ef file2比较是否为同一个文件,或者用于判断硬连接,是否指向同一个inode
5)整数比较(★★★)
判断参数含义
-eq==相等
-ne<>!=不等
-gt大于
-lt小于
-ge大于等于
-le小于等于
6)字符串比较(★★★)
判断参数含义
-z判断是否为空字符串,字符串长度为0则成立
-n判断是否为非空字符串,字符串长度不为0则成立
string1 = string2判断字符串是否相等
string1 != string2判断字符串是否不相等
7)逻辑运算符(★★★)
判断符号含义举例
-a&&逻辑与[ 1 -eq 1 -a 1 -ne 0 ] 或者 [ 1 -eq 1 ] && [ 1 -ne 0 ]
-o\\
[ 33 <> 55 -a 33 -gt $[300/10] ]; echo $?
[ 33 -le 44 ] && [ 33 -gt $[999/100] ] ; echo $?
3、条件判断参数的举例说明
1)数值比较
[root@server ~]# [ $(id -u) -eq 0 ] && echo "the user is admin"
[root@server ~]$ [ $(id -u) -ne 0 ] && echo "the user is not admin"
[root@server ~]$ [ $(id -u) -eq 0 ] && echo "the user is admin" || echo "the user is not admin"

[root@server ~]# uid=`id -u`
[root@server ~]# test $uid -eq 0 && echo this is admin
this is admin
[root@server ~]# [ $(id -u) -ne 0 ]  || echo this is admin
this is admin
[root@server ~]# [ $(id -u) -eq 0 ]  && echo this is admin || echo this is not admin
this is admin
[root@server ~]# su - stu1
[stu1@server ~]$ [ $(id -u) -eq 0 ]  && echo this is admin || echo this is not admin
this is not admin

 [ $(id -u) -eq 0 ] && echo 'root用户' || echo '普通用户'
2)类C风格的数值比较
注意:在(( ))中,=表示赋值;==表示判断
[root@server ~]# ((1==2));echo $?
[root@server ~]# ((1<2));echo $?
[root@server ~]# ((2>=1));echo $?
[root@server ~]# ((2!=1));echo $?
[root@server ~]# ((`id -u`==0));echo $?

[root@server ~]# ((a=123));echo $a
[root@server ~]# unset a
[root@server ~]# ((a==123));echo $?
3)字符串比较
注意:双引号引起来,看作一个整体;===[ 字符串 ] 比较中都表示判断
[root@server ~]# a='hello world';b=world
[root@server ~]# [ $a = $b ];echo $?
[root@server ~]# [ "$a" = "$b" ];echo $?
[root@server ~]# [ "$a" != "$b" ];echo $?
[root@server ~]# [ "$a" !== "$b" ];echo $?        错误
[root@server ~]# [ "$a" == "$b" ];echo $?
[root@server ~]# test "$a" != "$b";echo $?
4)[][[]]表达式用法比较(★★★)
test  表达式
[ 表达式 ]
[[ 表达式 ]]

思考:[ ][[ ]] 有什么区别?
单个 [  ] 使用字符串对字符串必须加双引号
两个 [[  ]] 不用对字符串变量加双引号
两个 [[  ]] 里面可以使用 &&,||, 而单个不行
两个 [[  ]] 支持c风格
如:
    a=1; b=2
    [[ $a > $b ]] && echo $a || echo $b

[root@server ~]# a=
[root@server ~]# test -z $a;echo $?
[root@server ~]# a=hello
[root@server ~]# test -z $a;echo $?
[root@server ~]# test -n $a;echo $?
[root@server ~]# test -n "$a";echo $?

# [ '' = $a ];echo $?
-bash: [: : unary operator expected
2
# [[ '' = $a ]];echo $?
0

[root@server ~]# [ 1 -eq 0 -a 1 -ne 0 ];echo $?
[root@server ~]# [ 1 -eq 0 && 1 -ne 0 ];echo $?
[root@server ~]# [[ 1 -eq 0 && 1 -ne 0 ]];echo $?

七、流程控制语句

1、if…else语法结构
1)if 结构
if [ condition ];then
        command
        command
fi

if test 条件;then
    命令
fi

if [[ 条件 ]];then
    命令
fi

[ 条件 ] && command
2)if…else结构
if [ condition ];then
        command1
    else
        command2
fi

[ 条件 ] && command1 || command2
3)if…elif…else结构(★★★)
if [ condition1 ];then
        command1      结束
    elif [ condition2 ];then
        command2       结束
    else
        command3
fi
4)嵌套结构
if [ condition1 ];then
        command1       
        if [ condition2 ];then
            command2
        fi
 else
        if [ condition3 ];then
            command3
        elif [ condition4 ];then
            command4
        else
            command5
        fi
fi
2、case…esac 语句(★★★)

Note

  1. case语句为多重匹配语句
  2. 如果匹配成功,执行相匹配的命令
说明:`pattern`表示需要匹配的模式

case var in             定义变量;var代表是变量名
pattern 1)              模式1;| 分割多个模式,相当于or
    command1            需要执行的语句
    ;;                  两个分号代表命令结束
pattern 2)
    command2
    ;;
pattern 3)
    command3
    ;;
*)              default,不满足以上模式,默认执行*)下面的语句
    command4
    ;;
esac                            esac表示case语句结束
1)案例1:脚本传不同值做不同事

具体需求: 当给程序传入startstoprestart三个不同参数时分别执行相应命令

实现代码

#!/bin/env bash
case $1 in
        start|S)
        service apache start &>/dev/null && echo "apache 启动成功"
        ;;
        stop|T)
        service apache stop &>/dev/null && echo "apache 停止成功"
        ;;
        restart|R)
        service apache restart &>/dev/null && echo "apache 重启完毕"
        ;;
        *)
        echo "请输入要做的事情..."
        ;;
esac
2)案例2:根据用户需求选择做事

具体需求:

脚本提示让用户输入需要管理的服务名,然后提示用户需要对服务做什么操作,如启动,关闭等操作

#!/bin/env bash
read -p "请输入你要管理的服务名称(vsftpd):" service
case $service in
        vsftpd|ftp)
        read -p "请选择你需要做的事情(restart|stop):" action
        case $action in
                stop|S)
                service vsftpd stop &>/dev/null && echo "该$serivce服务已经停止成功"
                ;;
                start)
                service vsftpd start &>/dev/null && echo "该$serivce服务已经成功启动"
                ;;
        esac
        ;;
        httpd|apache)
        echo "apache hello world"
        ;;
        *)
        echo "请输入你要管理的服务名称(vsftpd)"
        ;;
esac
3)案例3:菜单提示让用户选择需要做的事

具体需求:

模拟一个多任务维护界面;当执行程序时先显示总菜单,然后进行选择后做相应维护监控操作

**********请选择*********
h    显示命令帮助
f    显示磁盘分区
d    显示磁盘挂载
m    查看内存使用
u    查看系统负载
q    退出程序
*************************

思路:

  1. 菜单打印出来
  2. 交互式让用户输入操作编号,然后做出相应处理

落地实现:

  1. 菜单打印(分解动作)
#!/bin/env bash
cat <<-EOF
    h    显示命令帮助
    f    显示磁盘分区
    d    显示磁盘挂载
    m    查看内存使用
    u    查看系统负载
    q    退出程序
    EOF
  1. 代码实现
#!/bin/bash
#打印菜单
cat <<-EOF
    h    显示命令帮助
    f    显示磁盘分区
    d    显示磁盘挂载
    m    查看内存使用
    u    查看系统负载
    q    退出程序
    EOF

#让用户输入需要的操作
while true
do
read -p "请输入需要操作的选项[f|d]:" var1
case $var1 in
    h)
    cat <<-EOF
        h       显示命令帮助
        f       显示磁盘分区
        d       显示磁盘挂载
        m       查看内存使用
        u       查看系统负载
        q       退出程序
    EOF
    ;;
    f)
    fdisk -l
    ;;
    d)
    df -h
    ;;
    m)
    free -m
    ;;
    u)
    uptime
    ;;
    q)
    exit
    ;;
esac
done



#!/bin/bash
#打印菜单
menu(){
cat <<-END
    h    显示命令帮助
    f    显示磁盘分区
    d    显示磁盘挂载
    m    查看内存使用
    u    查看系统负载
    q    退出程序
    END
}
menu
while true
do
read -p "请输入你的操作[h for help]:" var1
case $var1 in
    h)
    menu
    ;;
    f)
    read -p "请输入你要查看的设备名字[/dev/sdb]:" var2
    case $var2 in
        /dev/sda)
        fdisk -l /dev/sda
        ;;
        /dev/sdb)
        fdisk -l /dev/sdb
        ;;
    esac
    ;;
    d)
    lsblk
    ;;
    m)
    free -m
    ;;
    u)
    uptime
    ;;
    q)
    exit
    ;;
esac
done
2、应用案例
1)判断两台主机是否ping通

需求: 判断当前主机是否和远程主机是否ping通

思路

  1. 使用哪个命令实现 ping -c次数
  2. 根据命令的执行结果状态来判断是否通$?
  3. 根据逻辑和语法结构来编写脚本(条件判断或者流程控制)

代码实现

#!/bin/env bash
# 该脚本用于判断当前主机是否和远程指定主机互通

# 交互式定义变量,让用户自己决定ping哪个主机
read -p "请输入你要ping的主机的IP:" ip

# 使用ping程序判断主机是否互通
ping -c1 $ip &>/dev/null

if [ $? -eq 0 ];then
    echo "当前主机和远程主机$ip是互通的"
 else
     echo "当前主机和远程主机$ip不通的"
fi

逻辑运算符
test $? -eq 0 &&  echo "当前主机和远程主机$ip是互通的" || echo "当前主机和远程主机$ip不通的"
2)判断一个进程是否存在

需求: 判断web服务器中httpd进程是否存在

思路

  1. 查看进程的相关命令 ps pgrep

    pgrep命令:以名称为依据从运行进程队列中查找进程,并显示查找到的进程id
    选项
    -o:仅显示找到的最小(起始)进程号;
    -n:仅显示找到的最大(结束)进程号;
    -l:显示进程名称;
    -P:指定父进程号;pgrep -p 4764  查看父进程下的子进程id
    -g:指定进程组;
    -t:指定开启进程的终端;
    -u:指定进程的有效用户ID。
    
  2. 根据命令的返回状态值来判断进程是否存在

  3. 根据逻辑用脚本语言实现

代码实现

#!/bin/env bash
# 判断一个程序(httpd)的进程是否存在
pgrep httpd &>/dev/null
if [ $? -ne 0 ];then
    echo "当前httpd进程不存在"
else
    echo "当前httpd进程存在"
fi

或者
test $? -eq 0 && echo "当前httpd进程存在" || echo "当前httpd进程不存在"
3)判断一个服务是否正常

需求: 判断门户网站是否能够正常访问

思路

  1. 可以判断进程是否存在,用/etc/init.d/httpd status判断状态等方法

  2. 最好的方法是直接去访问一下,通过访问成功和失败的返回值来判断

代码实现

#!/bin/env bash
# 判断门户网站是否能够正常提供服务

#定义变量
web_server=www.itcast.cn
#访问网站
wget -P /shell/ $web_server &>/dev/null
[ $? -eq 0 ] && echo "当前网站服务是ok" && rm -f /shell/index.* || echo "当前网站服务不ok,请立刻处理"
4)判断用户是否存在(id或者grep -w $username /etc/passwd

需求1: 输入一个用户,用脚本判断该用户是否存在

 #!/bin/env bash
read -p "请输入一个用户名:" user_name
id $user_name &>/dev/null
if [ $? -eq 0 ];then
    echo "该用户存在!"
else
    echo "用户不存在!"
fi


#!/bin/bash
# 判断 用户(id) 是否存在
read -p "输入壹个用户:" id
id $id &> /dev/null
if [ $? -eq 0 ];then
        echo "该用户存在"
else
        echo "该用户不存在"
fi

#!/bin/env bash
read -p "请输入你要查询的用户名:" username
grep -w $username /etc/passwd &>/dev/null
if [ $? -eq 0 ]
then
    echo "该用户已存在"
else
    echo "该用户不存在"
fi

#!/bin/bash
read -p "请输入你要检查的用户名:" name
id $name &>/dev/null
if [ $? -eq 0 ]
then
echo 用户"$name"已经存在
else
echo 用户"$name"不存在
fi

#!/bin/env bash
#判断用户是否存在
read -p "请写出用户名" id
id $id
if [ $? -eq 0 ];then
        echo "用户存在"
else
        echo "用户不存在"
fi

#!/bin/env bash
read -p '请输入用户名:' username
id $username &>/dev/null
[ $? -eq 0 ] && echo '用户存在' || echo '不存在'
5)判断软件包是否安装

需求: 用脚本判断一个软件包是否安装,如果没安装则安装它(假设本地已配有yum

#!/bin/env bash
read -p '请输入要查询的软件: ' soft
rpm -qa|grep $soft

if [[ $? == 0 ]]; then
    echo "软件 $soft 已经安装"
else 
    echo "======准备安装软件 $soft"
    sudo yum install -y $soft
    echo "======软件 $soft 安装完成"
fi
6)判断当前主机的内核版本

需求3: 判断当前内核主版本是否为2,且次版本是否大于等于6;如果都满足则输出当前内核版本。

思路

  1. 先查看内核的版本号uname -r
  2. 先将内核的版本号保存到一个变量里,然后再根据需求截取出该变量的一部分:主版本和次版本
  3. 根据需求进步判断
#!/bin/bash
kernel=`uname -r`
var1=`echo $kernel|cut -d. -f1`
var2=`echo $kernel|cut -d. -f2`
test $var1 -eq 2 -a $var2 -ge 6 && echo $kernel || echo "当前内核版本不符合要求"
或者
[ $var1 -eq 2 -a $var2 -ge 6 ] && echo $kernel || echo "当前内核版本不符合要求"
或者
[[ $var1 -eq 2 && $var2 -ge 6 ]] && echo $kernel || echo "当前内核版本不符合要求"

或者
#!/bin/bash
kernel=`uname -r`
test ${kernel:0:1} -eq 2 -a ${kernel:2:1} -ge 6 && echo $kernel || echo '不符合要求'

其他命令参考:
uname -r|grep ^2.[6-9] || echo '不符合要求'

八、循环控制语句

1、for循环语法结构
1)列表循环(★★★)

列表for循环:用于将一组命令执行已知的次数

基本语法格式

for variable in {list}
     do
          command 
          commanddone
或者
for variable in a b c
     do
         command
         command
     done

举例说明

for var in {1..10}; do echo $var; done
for var in 1 2 3 4 5;do echo $var;done
for var in `seq 10`;do echo $var;done
for var in $(seq 10);do echo $var;done

for var in {0..10..2};do echo $var;done
0
2
4
6
8
10

for var in {2..10..2};do echo $var;done
for var in {10..1};do echo $var;done
for var in {10..1..-2};do echo $var;done
10
8
6
4
2


for var in `seq 10 -2 1`;do echo $var;done
10
8
6
4
2
2)不带列表循环(用户指定参数和个数)

不带列表的for循环执行时由用户指定参数和参数的个数

基本语法格式

for variable
    do
        command 
        commanddone

举例说明

#!/bin/bash
for var
do
echo $var
done

echo "脚本后面有$#个参数"
3)类C风格的for循环(★★★)

基本语法结构

for(( expr1;expr2;expr3 ))
    do
        command
        commanddone
for (( i=1;i<=5;i++))
    do
        echo $i
    done


expr1:定义变量并赋初值
expr2:决定是否进行循环(条件)
expr3:决定循环变量如何改变,决定循环什么时候退出

举例说明

for ((i=1;i<=5;i++));do echo $i;done
for ((i=1;i<=10;i+=2));do echo $i;done
for ((i=2;i<=10;i+=2));do echo $i;done
4)循环体中的关键字

循环体为:do ... done

  • continue:继续;表示循环体内下面的代码不执行,重新开始下一次循环
  • break:打断;马上停止执行本次循环,执行循环体后面的代码
  • exit:表示直接跳出程序
2、while循环语句

特点:

  • 条件为真就进入循环;条件为假就退出循环

  • 循环体为:do ... done

while 表达式
    do
        command...
    done

while  [ 1 -eq 1 ] 或者 (( 1 > 2 ))
  do
     command
     command
     ...
 done

循环打印1-5数字

FOR循环打印:
for ((i=1;i<=5;i++))
do
    echo $i
done

while循环打印:
i=1
while [ $i -le 5 ]
do
    echo $i
    let i++
done
3、until循环语句(与while相反)

特点:条件为假就进入循环;条件为真就退出循环

until expression   eg:[ 1 -eq 1 ]  (( 1 >= 1 ))
    do
        command
        command
        ...
    done

打印1-5数字

i=1
while [ $i -le 5 ]
do
    echo $i
    let i++
done

i=1
until [ $i -gt 5 ]
do
    echo $i
    let i++
done
4、应用案例
1)脚本计算1-100奇数和

思路

  1. 定义一个变量来保存奇数的和 sum=0
  2. 找出1-100的奇数,保存到另一个变量里 i=遍历出来的奇数
  3. 从1-100中找出奇数后,再相加,然后将和赋值给变量 循环变量 for
  4. 遍历完毕后,将sum的值打印出来

代码实现

#!/bin/env bash
# 计算1-100的奇数和
# 定义变量来保存奇数和
sum=0

#for循环遍历1-100的奇数,并且相加,把结果重新赋值给sum

for i in {1..100..2}
do
    let sum=$sum+$i
done
#打印所有奇数的和
echo "1-100的奇数和是:$sum"


方法1:
#!/bin/bash
sum=0
for i in {1..100..2}
do
    sum=$[$i+$sum]
done
echo "1-100的奇数和为:$sum"

方法2:
#!/bin/bash
sum=0
for ((i=1;i<=100;i+=2))
do
    let sum=$i+$sum
done
echo "1-100的奇数和为:$sum"

方法3:
#!/bin/bash
sum=0
for ((i=1;i<=100;i++))
do
    if [ $[$i%2] -ne 0 ];then
    let sum=$sum+$i
    fi
或者
test $[$i%2] -ne 0 && let sum=$sum+$i

done
echo "1-100的奇数和为:$sum"

方法4:
sum=0
for ((i=1;i<=100;i++))
do
    if [ $[$i%2] -eq 0 ];then
    continue
    else
    let sum=$sum+$i
    fi
done
echo "1-100的奇数和为:$sum"

#!/bin/bash
sum=0
for ((i=1;i<=100;i++))
do
    test $[$i%2] -eq 0 && continue || let sum=sum+$i
done
echo "1-100的奇数和是:$sum"
2)判断所输整数是否为质数

质数(素数): 只能被1和它本身整除的数叫质数。 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

思路

  1. 让用户输入一个数,保存到一个变量里
  2. 如果能被其他数整除就不是质数——>$num%$i是否等于0 $i=2到$num-1
  3. 如果输入的数是1或者2取模根据上面判断又不符合,所以先排除1和2
  4. 测试序列从2开始,输入的数是4——>得出结果$num不能和$i相等,并且$num不能小于$i

代码实现

#!/bin/bash
read -p "请输入一个正整数字:" number

[ $number -eq 1 ] && echo "$number不是质数" && exit
[ $number -eq 2 ] && echo "$number是质数" && exit

for i in `seq 2 $[$number-1]`
    do
     [ $[$number%$i] -eq 0 ] && echo "$number不是质数" && exit
    done
echo "$number是质数" && exit
3)批量创建用户(统一组名和密码)

需求: 批量加5个新用户,以u1到u5命名,并统一加一个新组,组名为class,统一改密码为123

思路

  1. 添加用户的命令
  2. 判断class组是否存在
  3. 根据题意,判断该脚本循环5次来添加用户
  4. 给用户设置密码,应该放到循环体里面

代码实现

方法一:
#!/bin/bash
#判断class组是否存在
grep -w class /etc/group &>/dev/null
[ $? -ne 0 ] && groupadd class
#批量创建5个用户
for i in {1..5}
do
    useradd -G class u$i
    echo 123|passwd --stdin u$i
done

方法二:
#!/bin/bash
#判断class组是否存在
cut -d: -f1 /etc/group|grep -w class &>/dev/null
[ $? -ne 0 ] && groupadd class

#循环增加用户,循环次数5次,for循环,给用户设定密码
for ((i=1;i<=5;i++))
do
    useradd u$i -G class
    echo 123|passwd --stdin u$i
done


方法三:
#!/bin/bash
grep -w class /etc/group &>/dev/null
test $? -ne 0 && groupadd class
或者
groupadd class &>/dev/null

for ((i=1;i<=5;i++))
do
useradd -G class u$i && echo 123|passwd --stdin u$i
done
4)批量创建用户(目录统一在/rhome下)

需求1: 批量新建5个用户stu1~stu5,要求这几个用户的家目录都在/rhome

#!/bin/bash
#判断/rhome是否存在
[ -f /rhome ] && mv /rhome /rhome.bak
test ! -f /rhome -a ! -d /rhome && mkdir /rhome
或者
[ -f /rhome ] && mv /rhome /rhome.bak || [ ! -d /rhome ] && mkdir /rhome 
#创建用户,循环5次
for ((i=1;i<=5;i++))
do
    useradd -d /rhome/stu$i stu$i
    echo 123|passwd --stdin stu$i
done
5)局域网内脚本检查主机网络通讯

需求2:

写一个脚本,局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里,以10.1.1.1~10.1.1.10为例

#!/bin/bash
#定义变量
ip=10.1.1
#循环去ping主机的IP
for ((i=1;i<=10;i++))
do
    ping -c1 $ip.$i &>/dev/null
    if [ $? -eq 0 ];then
        echo "$ip.$i is ok" >> /tmp/ip_up.txt
    else
        echo "$ip.$i is down" >> /tmp/ip_down.txt
    fi
    或者
    [ $? -eq 0 ] && echo "$ip.$i is ok" >> /tmp/ip_up.txt || echo "$ip.$i is down" >> /tmp/ip_down.txt
done

[root@server shell03]# time ./ping.sh         

real    0m24.129s
user    0m0.006s
sys     0m0.005s

延伸扩展:shell脚本并发

并行执行:
{程序}&表示将程序放到后台并行执行,如果需要等待程序执行完毕再进行下面内容,需要加wait

#!/bin/bash
#定义变量
ip=10.1.1
#循环去ping主机的IP
for ((i=1;i<=10;i++))
do
{

        ping -c1 $ip.$i &>/dev/null
        if [ $? -eq 0 ];then
                echo "$ip.$i is ok" >> /tmp/ip_up.txt
        else
                echo "$ip.$i is down" >> /tmp/ip_down.txt
        fi
}&
done
wait
echo "ip is ok...."

[root@server ~]# time ./ping.sh 
ip is ok...

real    0m3.091s
user    0m0.001s
sys     0m0.008s
6)判断闰年

需求:输入一个年份,判断是否是润年(能被4整除但不能被100整除,或能被400整除的年份即为闰年)

#!/bin/bash
read -p "Please input year:(2017)" year
if [ $[$year%4] -eq 0 -a $[$year%100] -ne 0 ];then
    echo "$year is leap year"
elif [ $[$year%400] -eq 0 ];then
    echo "$year is leap year"
else
    echo "$year is not leap year"
fi
7)脚本同步系统时间

具体需求

  1. 写一个脚本,30秒同步一次系统时间,时间同步服务器10.1.1.1
  2. 如果同步失败,则进行邮件报警,每次失败都报警
  3. 同步成功,也进行邮件通知,但是成功100次才通知一次

思路

  1. 每个30s同步一次时间,该脚本是一个死循环
  2. 同步失败发送邮件
  3. 同步成功100次发送邮件

代码实现

#!/bin/bash
#定义变量
count=0
ntp_server=10.1.1.1
while true
do
    rdate -s $ntp-server&>/dev/null
    if [ $? -ne 0 ];then
        echo "system date failed" |mail -s 'check system date'  root@localhost    
    else
        let count++
        if [ $[$count%100] -eq 0 ];then
        echo "system date successfull" |mail -s 'check system date'  root@localhost && count=0
        fi
    fi
sleep 3
done
8)使用until语句批量创建10个用户

需求

  1. 使用until语句批量创建10个用户,要求stu1—stu5用户的UID分别为1001—1005;
  2. stu6~stu10用户的家目录分别在/rhome/stu6—/rhome/stu10

代码实现:

#!/bin/bash
i=1
until [ $i -gt 10 ]
do
    if [ $i -le 5 ];then
        useradd -u $[1000+$i] stu$i && echo 123|passwd --stdin stu$i
    else
        [ ! -d /rhome ] && mkdir /rhome
        useradd -d /rhome/stu$i stu$i && echo 123|passwd --stdin stu$i        
    fi
let i++
done

九、函数

  • shell中允许将一组命令集合语句形成一段可用代码,这些代码块称为shell函数
  • 给这段代码起个名字称为函数名,后续可以直接调用该段代码的功能
1、如何定义函数?

方法1:

函数名()
{
  函数体(一堆命令的集合,来实现某个功能)   
}

方法2:

function 函数名()
{
   函数体(一堆命令的集合,来实现某个功能)
   echo hello
   echo world
}

函数中return说明:

  1. return可以结束一个函数。类似于循环控制语句break(结束当前循环,执行循环体后面的代码)。
  2. return默认返回函数中最后一个命令状态值,也可以给定参数值,范围是0-256之间。
  3. 如果没有return命令,函数将返回最后一个指令的退出状态值
2、函数如何调用?(★★★)
1)当前命令行调用(source
[root@MissHou shell04]# cat fun1.sh 
#!/bin/bash
hello(){
echo "hello lilei $1"
hostname
}
menu(){
cat <<-EOF
1. mysql
2. web
3. app
4. exit
EOF
}

[root@MissHou shell04]# source fun1.sh 或者 . fun1.sh
[root@MissHou shell04]# hello 888
hello lilei 888
DESKTOP-SPF2TPF   
[root@MissHou shell04]# menu
1. mysql
2. web
3. app
4. exit
2)定义到用户的环境变量中(~/.bashrc
[root@MissHou shell05]# vim ~/.bashrc 
文件中增加如下内容:
hello(){
echo "hello lilei $1"
hostname
}
menu(){
cat <<-EOF
1. mysql
2. web
3. app
4. exit
EOF
}

注意:
当用户打开bash的时候会读取该文件
3)脚本中调用
#!/bin/bash
#打印菜单
source ./fun1.sh

hello(){
echo "hello lilei $1"
hostname
}

hello beautiful//调用函数

menu(){
cat <<-END
    h    显示命令帮助
    f    显示磁盘分区
    d    显示磁盘挂载
    m    查看内存使用
    u    查看系统负载
    q    退出程序
    END
}
menu        //调用函数
3、应用案例

具体需求:

  1. 写一个脚本收集用户输入的基本信息(姓名,性别,年龄),如不输入一直提示输入
  2. 最后根据用户的信息输出相对应的内容

思路:

  1. 交互式定义多个变量来保存用户信息 姓名、性别、年龄
  2. 如果不输一直提示输入
    • 循环直到输入字符串不为空 while 判断输入字符串是否为空
    • 每个信息都必须不能为空,该功能可以定义为一个函数,方便下面脚本调用
  3. 根据用户输入信息做出匹配判断

代码实现:

#!/bin/bash
#该函数实现用户如果不输入内容则一直循环直到用户输入为止,并且将用户输入的内容打印出来
input_fun()
{
  input_var=""
  output_var=$1
  while [ -z $input_var ]
    do
    read -p "$output_var" input_var
    done
    echo $input_var
}

input_fun 请输入你的姓名:

或者
#!/bin/bash
fun()
{
    read -p "$1" var
    if [ -z $var ];then
        fun $1
    else
        echo $var
    fi
}


#调用函数并且获取用户的姓名、性别、年龄分别赋值给name、sex、age变量
name=$(input_fun 请输入你的姓名:)
sex=$(input_fun 请输入你的性别:)
age=$(input_fun 请输入你的年龄:)

#根据用户输入的性别进行匹配判断
case $sex in
            man)
            if [ $age -gt 18 -a $age -le 35 ];then
                echo "中年大叔你油腻了吗?加油"
            elif [ $age -gt 35 ];then
                echo "保温杯里泡枸杞"
            else
                echo "年轻有为。。。"
            fi
            ;;
            woman)
            xxx
            ;;
            *)
            xxx
            ;;
esac
4、综合案例(★★★)
1)任务背景

现有的跳板机虽然实现了统一入口来访问生产服务器,yunwei用户权限太大可以操作跳板机上的所有目录文件,存在数据被误删的安全隐患,所以希望你做一些安全策略来保证跳板机的正常使用

2)具体要求
  1. 只允许yunwei用户通过跳板机远程连接后台的应用服务器做一些维护操作
  2. 公司运维人员远程通过yunwei用户连接跳板机时,跳出以下菜单供选择:
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit
  1. 当用户选择相应主机后,直接免密码登录成功
  2. 如果用户不输入一直提示用户输入,直到用户选择退出
3)综合分析
  1. 将脚本放到yunwei用户家目录里的.bashrc文件里(/shell05/jumper-server.sh)
  2. 将菜单定义为一个函数[打印菜单],方便后面调用
  3. case语句来实现用户的选择【交互式定义变量】
  4. 当用户选择了某一台服务器后,进一步询问用户需要做的事情 case...esac 交互式定义变量
  5. 使用循环来实现用户不选择一直让其选择
  6. 限制用户退出后直接关闭终端 exit
4)实现
#!/bin/bash
# jumper-server
# 定义菜单打印功能的函数
menu()
{
cat <<-EOF
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit
    EOF
}
# 屏蔽以下信号
trap '' 1 2 3 19
# 调用函数来打印菜单
menu
#循环等待用户选择
while true
do
# 菜单选择,case...esac语句
read -p "请选择你要访问的主机:" host
case $host in
    1|DB1)
    ssh root@10.1.1.1
    ;;
    2|DB2)
    ssh root@10.1.1.2
    ;;
    3|web1)
    ssh root@10.1.1.250
    ;;
    h|help)
    clear;menu
    ;;
    q|quit)
    exit
    ;;
esac
done


将脚本放到yunwei用户家目录里的.bashrc里执行:
bash ~/jumper-server.sh
exit

十、正则表达式

1、正则表达式是什么?

正则表达式(Regular Expression、regex或regexp,缩写为RE),也译为正规表示法、常规表示法,是一种字符模式,用于在查找过程中匹配指定的字符。

许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。

正则表达式这个概念最初是由Unix中的工具软件(例如sedgrep)普及开的

支持正则表达式的程序如:locate |find|vim| grep|sed |awk

2、正则能干什么?(★★★)
  1. 匹配邮箱、匹配身份证号码、手机号、银行卡号
  2. 匹配某些特定字符串,做特定处理等等
3、正则当中名词解释
  • 元字符:指那些在正则表达式中具有特殊意义的专用字符,如:点(.) 星(*) 问号(?)等

  • 前导字符:位于元字符前面的字符.ab*c*aoo*o.*

4、第一类正则表达式
1)正则中普通常用的元字符(★★★)
元字符功能备注
.匹配除了换行符以外的任意单个字符
*前导字符出现0次或连续多次
.*任意长度字符ab.*
^行首(以…开头)^root
$行尾(以…结尾)bash$
^$空行
[]匹配括号里任意单个字符或一组单个字符[abc]
[^]匹配不包含括号里任一单个字符或一组单个字符[^abc]
^[]匹配以括号里任意单个字符或一组单个字符开头 ^[abc]
^[^]匹配不以括号里任意单个字符或一组单个字符开头^[^abc]
  • 示例文本
# cat 1.txt
ggle
gogle
google
gooogle
goooooogle
gooooooogle
taobao.com
taotaobaobao.com

jingdong.com
dingdingdongdong.com
10.1.1.1
Adfjd8789JHfdsdf/
a87fdjfkdLKJK
7kdjfd989KJK;
bSKJjkksdjf878.
cidufKJHJ6576,

hello world
helloworld yourself
  • 举例说明
$ grep ^goo reg_test.txt 
google
gooogle
goooooogle
gooooooogle

$ grep ^[goo] reg_test.txt 
ggle
gogle
google
gooogle
goooooogle
gooooooogle

$ grep com$ reg_test.txt
taobao.com
taotaobaobao.com
jingdong.com
dingdingdongdong.com
2)正则中其他常用元字符(★★★)
元字符功能备注
\<取单词的头\<hel匹配hello worldhello yourself
\>取单词的尾world\>匹配hello worldhello yourself
\< \>精确匹配
\{n\}匹配前导字符连续出现n次go\{3\}匹配goooglegooooooglegooooooogle
\{n,\}匹配前导字符至少出现n次
\{n,m\}匹配前导字符出现n次与m次之间
\( \)保存被匹配的字符
\d匹配数字(grep -P[0-9]
\w匹配字母数字下划线(grep -P[a-zA-Z0-9_]
\s匹配空格、制表符、换页符(grep -P[\t\r\n]

举例说明:

首先文本内容如下:

# cat 1.txt
ggle
gogle
google
gooogle
goooooogle
gooooooogle
taobao.com
taotaobaobao.com

jingdong.com
dingdingdongdong.com
10.1.1.2.1.1
Adfjd8789JHfdsdf/
a87fdjfkdLKJK
7kdjfd989KJK;
bSKJjkksdjf878.
cidufKJHJ6576,

hello world
helloworld yourself
  • 需求:匹配单词的头和尾

    $ grep '\<hel' reg_test.txt
    hello world
    helloworld yourself
    
    $ grep 'world\>' reg_test.txt
    hello world
    helloworld yoursel
    
  • 需求:将10.1.1.2.1.1替换成10.1.1.345.1.678,参考Linux vim 文本替换 %s/原文/替换文本/g

    1)vim编辑器支持正则表达式
    # vim 1.txt
    :%s#\(10.1.1\).1#\1.254#g   或者
    :%s/\(10.1.1\).2.\(1\).1/\1.345.\2.678/g
    
    2)sed支持正则表达式【后面学】
    sed -n 's#\(10.1.1\).1#\1.254#p' 1.txt 或者
    sed -n 's/\(10.1.1\).2.\(1\)/\1.345\2.678/p' reg_test.txt
    10.1.1.254
    

    Note

    • 分隔符可以使用#,也可以使用/

    • 找出含有10.1.1的行,同时保留10.1.1并标记为标签1,之后可以使用\1来引用它。

    • 最多可以定义9个标签,从左边开始编号,最左边的是第一个。

  • 需求:匹配前导字符出现n次的行

    $ grep 'go\{3\}' reg_test.txt  #前导字符o连续出现3次
    gooogle
    goooooogle
    gooooooogle
    
    $ grep -E 'go\{3\}' reg_test.txt
    #无效果
    
  • 需求:匹配有数字的行,匹配有制表符,空格的行

    $ grep -P '\d' reg_test.txt
    10.1.1.1
    Adfjd8789JHfdsdf/
    a87fdjfkdLKJK
    7kdjfd989KJK;
    bSKJjkksdjf878.
    cidufKJHJ6576,
    
    $ grep -P '\d' reg_test.txt
    hello world
    helloworld yourself
    
3)扩展类正则常用元字符(★★★)
  • grep要使用正则,必须加 -E ,或者 使用egrep(试了发现没必要加入-E参数)
  • sed要使用正则,必须加 -r
扩展元字符功能备注
+匹配一个或多个前导字符bo+ 匹配boo、 bo
?匹配零个或一个前导字符bo? 匹配b、 bo
\
()组字符(看成整体)(my\
{n}前导字符重复n次
{n,}前导字符重复至少n次
{n,m}前导字符重复n到m次

举例说明:

grep "root|ftp|adm" /etc/passwd
egrep "root|ftp|adm" /etc/passwd
grep -E "root|ftp|adm" /etc/passwd

grep -E 'o+gle' test.txt 
grep -E 'o?gle' test.txt 

# egrep 'go{2,}' 1.txt
# egrep '(my|your)self' 1.txt


使用正则过滤出文件中的IP地址:
# grep '[0-9]\{2\}\.[0-9]\{1\}\.[0-9]\{1\}\.[0-9]\{1\}' 1.txt 
10.1.1.1
# grep '[0-9]{2}\.[0-9]{1}\.[0-9]{1}\.[0-9]{1}' 1.txt 
# grep -E '[0-9]{2}\.[0-9]{1}\.[0-9]{1}\.[0-9]{1}' 1.txt 
10.1.1.1
# grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' 1.txt 
10.1.1.1
# grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt 
10.1.1.1
5、第二类正则表达式
表达式功能示例
[:alnum:]字母与数字字符[[:alnum:]]+
[:alpha:]字母字符(包括大小写字母)[[:alpha:]]{4}
[:blank:]空格与制表符[[:blank:]]*
[:digit:]数字[[:digit:]]?
[:lower:]小写字母[[:lower:]]{4,}
[:upper:]大写字母[[:upper:]]+
[:punct:]标点符号[[:punct:]]
[:space:]包括换行符,回车等在内的所有空白[[:space:]]+
[root@server shell05]$ grep -E '^[[:digit:]]+' reg_test.txt  #以数字开头的行
10.1.1.2.1.1
7kdjfd989KJK;

[root@server shell05]# grep -E '^[^[:digit:]]+' reg_test.txt   #不是以数字开头的行
ggle
gogle
google
gooogle
goooooogle
gooooooogle
taobao.com
taotaobaobao.com
jingdong.com
dingdingdongdong.com
Adfjd8789JHfdsdf/
a87fdjfkdLKJK
bSKJjkksdjf878.
cidufKJHJ6576,
hello world
helloworld yourself

[root@server shell05]$ grep -E '[[:lower:]]{4,}' reg_test.txt   #开头至少出现4个小写字母的行少
ggle
gogle
google
gooogle
goooooogle
gooooooogle
taobao.com
taotaobaobao.com
jingdong.com
dingdingdongdong.com
Adfjd8789JHfdsdf/
a87fdjfkdLKJK
7kdjfd989KJK;
bSKJjkksdjf878.
cidufKJHJ6576,
hello world
helloworld yourself
6. 正则表达式总结(★★★)

把握一个原则,让你轻松搞定可恶的正则符号:

  1. 我要找什么?
    • 找数字:[0-9]
    • 找字母: [a-zA-Z]
    • 找标点符号: [[:punct:]]
  2. 我要如何找?看心情找
    • 以什么为首 ^key
    • 以什么结尾 key$
    • 包含什么或不包含什么 [abc],[abc],[abc],[abc]
  3. 我要找多少呀?
    • 找前导字符出现0次或连续多次ab*
    • 找任意单个 (一次)字符ab.
    • 任意字符ab.*
    • 找前导字符连续出现几次:{n}{n,m}{n,}
    • 找前导字符出现1次或多次go+
    • 找前到字符出现0次或1次:go?
7、正则元字符一栏表

元字符:在正则中,具有特殊意义的专用字符,如: 星号(*)、加号(+)等

前导字符:元字符前面的字符叫前导字符

元字符功能示例
*前导字符出现0次或者连续多次ab* abbbb
.除了换行符以外,任意单个字符ab. ab8 abu
.*任意长度的字符ab.* adfdfdf
[]括号里的任意单个字符或一组单个字符[abc][0-9][a-z]
[^]不匹配括号里的任意单个字符或一组单个字符[^abc]
^[]匹配以括号里的任意单个字符开头^[abc]
\^[^]不匹配以括号里的任意单个字符开头
^行的开头^root
$行的结尾bash$
^$空行
\{n\}前导字符连续出现n次[0-9]{3}
\{n,\}前导字符至少出现n次[a-z]{4,}
\{n,m\}前导字符连续出现n-m次go{2,4}
\<\>精确匹配单词<hello>
\(\)保留匹配到的字符(hello)
+前导字符出现1次或者多次[0-9]+
?前导字符出现0次或者1次go?
`\`
()组字符(hello\
\dperl内置正则grep -P \d+
\w匹配字母数字下划线
8、正则表达式案例
1)文件准备
# vim test.txt 
Aieur45869Root0000
9h847RkjfkIIIhello
rootHllow88000dfjj
8ikuioerhfhupliooking
hello world
192.168.0.254
welcome to uplooking.
abcderfkdjfkdtest
rlllA899kdfkdfj
iiiA848890ldkfjdkfj
abc
12345678908374
123456@qq.com
123456@163.com
abcdefg@itcast.com23ed
2)具体要求(★★★)
1、查找不以大写字母开头的行(三种写法)。
grep '^[^A-Z]' 2.txt
grep -v '^[A-Z]' 2.txt
grep '^[^[:upper:]]' 2.txt

2、查找有数字的行(两种写法)
grep '[0-9]' 2.txt
grep -P '\d' 2.txt

3、查找一个数字和一个字母连起来的
grep -E '[0-9][a-zA-Z]|[a-zA-Z][0-9]' 2.txt

4、查找不以r开头的行
grep -v '^r' 2.txt
grep '^[^r]' 2.txt

5、查找以数字开头的
grep '^[0-9]' 2.txt

6、查找以大写字母开头的
grep '^[A-Z]' 2.txt

7、查找以小写字母开头的
grep '^[a-z]' 2.txt

8、查找以点结束的
grep '\.$' 2.txt

9、去掉空行
grep -v '^$' 2.txt

10、查找完全匹配abc的行
grep '\<abc\>' 2.txt

11、查找A后有三个数字的行
grep -E 'A[0-9]{3}' 2.txt
grep  'A[0-9]\{3\}' 2.txt

12、统计root在/etc/passwd里出现了几次
grep -o 'root' 1.txt |wc -l

13、用正则表达式找出自己的IP地址、广播地址、子网掩码
ifconfig eth0|grep Bcast|grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}'
ifconfig eth0|grep Bcast| grep -E -o '([0-9]{1,3}.){3}[0-9]{1,3}'
ifconfig eth0|grep Bcast| grep -P -o '\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}'
ifconfig eth0|grep Bcast| grep -P -o '(\d{1,3}.){3}\d{1,3}'
ifconfig eth0|grep Bcast| grep -P -o '(\d+.){3}\d+'

# egrep --color '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' /etc/sysconfig/network-scripts/ifcfg-eth0
IPADDR=10.1.1.1
NETMASK=255.255.255.0
GATEWAY=10.1.1.254

# egrep --color '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}' /etc/sysconfig/network-scripts/ifcfg-eth0 
IPADDR=10.1.1.1
NETMASK=255.255.255.0
GATEWAY=10.1.1.254


14、找出文件中的ip地址并且打印替换成172.16.2.254
grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt |sed -n 's/192.168.0.\(254\)/172.16.2.\1/p'

15、找出文件中的ip地址
grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt

16、找出全部是数字的行
grep -E '^[0-9]+$' test
17、找出邮箱地址
grep -E '^[0-9]+@[a-z0-9]+\.[a-z]+$'


grep --help:
匹配模式选择:
Regexp selection and interpretation:
  -E, --extended-regexp     扩展正则
  -G, --basic-regexp        基本正则
  -P, --perl-regexp         调用perl的正则
  -e, --regexp=PATTERN      use PATTERN for matching
  -f, --file=FILE           obtain PATTERN from FILE
  -i, --ignore-case         忽略大小写
  -w, --word-regexp         匹配整个单词
3)脚本搭建web服务

要求如下

  1. 用户输入web服务器的IP、域名以及数据根目录
  2. 如果用户不输入则一直提示输入,直到输入为止
  3. 当访问www.test.cc时可以访问到数据根目录里的首页文件“this is test page”

参考脚本:

参考:
#!/bin/bash
conf=/etc/httpd/conf/httpd.conf
input_fun()
{
  input_var=""
  output_var=$1
  while [ -z $input_var ]
    do
    read -p "$output_var" input_var
    done
    echo $input_var
}
ipaddr=$(input_fun "Input Host ip[192.168.0.1]:")
web_host_name=$(input_fun "Input VirtualHostName [www.test.cc]:")
root_dir=$(input_fun "Input host Documentroot dir:[/var/www/html]:")

[ ! -d $root_dir ] && mkdir -p $root_dir
chown apache.apache $root_dir && chmod 755 $root_dir
echo this is $web_host_name > $root_dir/index.html
echo "$ipaddr $web_host_name" >> /etc/hosts

[ -f $conf ] && cat >> $conf <<end
NameVirtualHost $ipaddr:80
<VirtualHost $ipaddr:80>
    ServerAdmin webmaster@$web_host_name
    DocumentRoot $root_dir
    ServerName $web_host_name
    ErrorLog logs/$web_host_name-error_log
    CustomLog logs/$web_host_name-access_loh common
</VirtualHost>
end

Sed流编辑器

sed是Stream Editor(流编辑器)的缩写,简称流编辑器;用来处理文件的

一、sed如何处理文件

sed是一行一行读取文件内容并按照要求进行处理,把处理后的结果输出到屏幕。

  1. 首先sed读取文件中的一行内容,把其保存在一个临时缓存区中(也称为模式空间)
  2. 然后根据需求处理临时缓冲区中的行,完成后把该行发送到屏幕上。

总结:

  1. 由于sed每一行都存在临时缓冲区中,对这个副本进行编辑,所以不会直接修改原文件
  2. Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作,对文件进行过滤和转换操作

二、sed使用方法介绍

sed常见的语法格式有两种,一种叫命令行模式,另一种叫脚本模式

1、命令行格式
1)语法格式(★★★)

语法格式:sed [options] '处理动作' 文件名

常用选项

选项说明备注
-e进行多项(多次)编辑
-n取消默认输出不自动打印模式空间
-r使用扩展正则表达式
-i原地编辑(修改源文件)
-f指定sed脚本的文件名

常见处理动作:以下所有的动作都要在单引号

动作说明备注
'p'打印需要使用-n取消默认输出
'i'在指定行之前插入内容(insert)类似vim里的大写O
'a'在指定行之后插入内容(append)类似vim里的小写o
'c'替换指定行所有内容
'd'删除指定行
2)举例说明

文件准备

# vim a.txt 
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
298374837483
172.16.0.254
10.1.1.1
a)‘p’ - 打印文件内容(★★★)
[root@server ~]# sed ''  a.txt                        对文件什么都不做
[root@server ~]# sed -n 'p'  a.txt                    打印每一行,并取消默认输出
[root@server ~]# sed -n '1p'  a.txt                    打印第1行
[root@server ~]# sed -n '2p'  a.txt                    打印第2行
[root@server ~]# sed -n '1,5p'  a.txt                打印1到5行
[root@server ~]# sed -n '$p' a.txt                     打印最后1行
b)‘i’ | ‘a’ - 增加文件内容

'i'表示在地址定位的上面插入,'a'在地址定位的下面插入(但不会编辑源文件)

[root@server ~]# sed '$a99999' a.txt                 文件最后一行下面增加内容
[root@server ~]# sed 'a99999' a.txt                 文件每行下面增加内容
[root@server ~]# sed '5a99999' a.txt                 文件第5行下面增加内容
[root@server ~]# sed '$i99999' a.txt                 文件最后一行上一行增加内容
[root@server ~]# sed 'i99999' a.txt                 文件每行上一行增加内容
[root@server ~]# sed '6i99999' a.txt                 文件第6行上一行增加内容
[root@server ~]# sed '/^uucp/ihello'                以uucp开头行的上一行插入内容
c)‘c’ - 修改文件内容

'c'替换指定的整行内容,并不会修改源文件

[root@server ~]# sed '5chello world' a.txt         替换文件第5行内容
[root@server ~]# sed 'chello world' a.txt         替换文件所有内容
[root@server ~]# sed '1,5chello world' a.txt     替换文件1到5号内容为hello world
[root@server ~]# sed '/^user01/c888888' a.txt    替换以user01开头的行
d)‘d’ - 删除文件内容

'd'删除指定的整行内容

[root@server ~]# sed '1d' a.txt                         删除文件第1行
[root@server ~]# sed '1,5d' a.txt                     删除文件1到5行
[root@server ~]# sed '$d' a.txt                        删除文件最后一行
d)‘s’ - 对文件进行搜索替换操作(★★★)

语法:

  • sed 选项 ‘s/搜索的内容/替换的内容/动作’ 需要处理的文件,其中,s表示search搜索;斜杠/表示分隔符,可以自己定义;动作一般是打印p和全局替换g

  • 如果要在源文件上进行修改,则使用-i,不要使用-n和’p’

[root@server ~]# sed -n 's/root/ROOT/p' 1.txt 
[root@server ~]# sed -n 's/root/ROOT/gp' 1.txt 
[root@server ~]# sed -n 's/^#//gp' 1.txt 
[root@server ~]# sed -n 's@/sbin/nologin@itcast@gp' a.txt
[root@server ~]# sed -n 's/\/sbin\/nologin/itcast/gp' a.txt
[root@server ~]# sed -n '10s#/sbin/nologin#itcast#p' a.txt 
uucp:x:10:14:uucp:/var/spool/uucp:itcast
[root@server ~]# sed -n 's@/sbin/nologin@itcastheima@p' 2.txt 
注意:搜索替换中的分隔符可以自己指定

[root@server ~]# sed -n '1,5s/^/#/p' a.txt         注释掉文件的1-5行内容
#root:x:0:0:root:/root:/bin/bash
#bin:x:1:1:bin:/bin:/sbin/nologin
#daemon:x:2:2:daemon:/sbin:/sbin/nologin
#adm:x:3:4:adm:/var/adm:/sbin/nologin
#lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
3)其他命令
命令解释备注
r另外文件中读取内容
w内容另存为
&保存查找串以便在替换串中引用和()相同
=打印行号
!所选行以外的所有行应用命令,放到行数之后‘1,5!’
q退出
a)‘r | w’ - 读取其他文件 和 文件另存为(★★★)

需求:

  • 在2.txt第3行下插入/etc/hosts文件内容,可用sed '3r /etc/hosts' 2.txt

  • 在2.txt每一行下插入/etc/hosts文件内容,可用sed 'r /etc/hosts' 2.txt

  • 将reg_test2.txt中包含hello关键字的行写入reg_test3.txt中,可用sed '/hello/w reg_test3.txt' reg_test2.txt

r    从文件中读取输入行
w    将所选的行写入文件
[root@server ~]# sed '3r /etc/hosts' 2.txt  
[root@server ~]# sed '$r /etc/hosts' 2.txt

[root@server ~]# sed '/hello/w reg_test3.txt' reg_test2.txt
cat reg_test3.txt
hello world
helloworld yourself

[root@server ~]# sed '/[0-9]{4}/w a.txt' 2.txt
[root@server ~]# sed  -r '/([0-9]{1,3}\.){3}[0-9]{1,3}/w b.txt' 2.txt
b)‘!’ - 应用非所选行
!    对所选行以外的所有行应用命令,放到行数之后
[root@server ~]# sed -n '1!p' 1.txt 
[root@server ~]# sed -n '4p' 1.txt 
[root@server ~]# sed -n '4!p' 1.txt 
[root@server ~]# cat -n 1.txt 
[root@server ~]# sed -n '1,17p' 1.txt 
[root@server ~]# sed -n '1,17!p' 1.txt 
c)‘&’ - 保存查找串以便在替换串中引用(★★★)
&   保存查找串以便在替换串中引用   \(\)

[root@server ~]# sed -n '/root/p' a.txt 
root:x:0:0:root:/root:/bin/bash
[root@server ~]# sed -n 's/root/#&/p' a.txt 
#root:x:0:0:root:/root:/bin/bash

# sed -n 's/^root/#&/p' passwd   注释掉以root开头的行
# sed -n -r 's/^root|^stu/#&/p' /etc/passwd    注释掉以root开头或者以stu开头的行
# sed -n '1,5s/^[a-z].*/#&/p' passwd  注释掉1~5行中以任意小写字母开头的行
# sed -n '1,5s/^/#/p' /etc/passwd  注释1~5行
或者
sed -n '1,5s/^/#/p' passwd 以空开头的加上#
sed -n '1,5s/^#//p' passwd#开头的替换成空

[root@server ~]# sed -n '/^root/p' 1.txt 
[root@server ~]# sed -n 's/^root/#&/p' 1.txt 
[root@server ~]# sed -n 's/\(^root\)/#\1/p' 1.txt 
[root@server ~]# sed -nr '/^root|^stu/p' 1.txt 
[root@server ~]# sed -nr 's/^root|^stu/#&/p' 1.txt 
d)‘=’ - 打印行号
=     打印行号
$ sed -n '/nologin$/=;/nologin$/p' sed_test.txt
2
bin:x:1:1:bin:/bin:/sbin/nologin
3
daemon:x:2:2:daemon:/sbin:/sbin/nologin
4
adm:x:3:4:adm:/var/adm:/sbin/nologin
5
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin


$ sed -ne '/nologin$/=' -ne '/nologin$/p' sed_test.txt
2
bin:x:1:1:bin:/bin:/sbin/nologin
3
daemon:x:2:2:daemon:/sbin:/sbin/nologin
4
adm:x:3:4:adm:/var/adm:/sbin/nologin
5
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
e)‘q’ - 退出

需求:读取到指定行时,不再继续往下读取

$ sed '/daemon/q' sed_test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin


sed '5q' 1.txt
sed '/mail/q' 1.txt
sed -r '/^yunwei|^mail/q' 1.txt
[root@server ~]# sed -n '/bash$/p;10q' 1.txt
ROOT:x:0:0:root:/root:/bin/bash
4)其他选项
  • -e 多项编辑

  • -r 扩展正则

  • -i 修改原文件

注意:

  • -ni不要一起使用

  • 'p'命令 不要在使用-i时使用

a)-i - 修改源文件 (★★★)
cat sed_test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
298374837483
172.16.0.254
10.1.1.1

sed -i '1,5s/daemon/&123/' sed_test.txt

cat sed_test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon123:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
298374837483
172.16.0.254
10.1.1.1
b)-e - 多项编辑
-e 多项编辑
-r    扩展正则

[root@server ~]# sed -ne '/root/p' 1.txt -ne '/root/='
root:x:0:0:root:/root:/bin/bash
1
[root@server ~]# sed -ne '/root/=' -ne '/root/p' 1.txt 
1
root:x:0:0:root:/root:/bin/bash

在1.txt文件中的第5行的前面插入“hello world”;在1.txt文件的第8行下面插入“哈哈哈哈”

[root@server ~]# sed -e '5ihello world' -e '8a哈哈哈哈哈' 1.txt  -e '5=;8='

sed -n '1,5p' 1.txt
sed -ne '1p' -ne '5p' 1.txt
sed -ne '1p;5p' 1.txt

过滤vsftpd.conf文件中以#开头和空行:
[root@server ~]# grep -Ev '^#|^$' /etc/vsftpd/vsftpd.conf
[root@server ~]# sed -e '/^#/d' -e '/^$/d' /etc/vsftpd/vsftpd.conf
[root@server ~]# sed '/^#/d;/^$/d' /etc/vsftpd/vsftpd.conf
[root@server ~]# sed -r '/^#|^$/d' /etc/vsftpd/vsftpd.conf

过滤smb.conf文件中生效的行:
# sed -e '/^#/d' -e '/^;/d' -e '/^$/d' -e '/^\t$/d' -e '/^\t#/d' smb.conf
# sed -r '/^(#|$|;|\t#|\t$)/d' smb.conf 

# sed -e '/^#/d' -e '/^;/d' -e '/^$/d' -e '/^\t$/d' -e '/^\t#/' smb.conf
[root@server ~]# grep '^[^a-z]' 1.txt
[root@server ~]# sed -n '/^[^a-z]/p' 1.txt

过滤出文件中的IP地址:
[root@server ~]# grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt 
192.168.0.254
[root@server ~]# sed -nr '/([0-9]{1,3}\.){3}[0-9]{1,3}/p' 1.txt 
192.168.0.254

[root@server ~]# grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 2.txt 
10.1.1.1
10.1.1.255
255.255.255.0

[root@server ~]# sed -nr '/([0-9]{1,3}\.){3}[0-9]{1,3}/p' 2.txt
10.1.1.1
10.1.1.255
255.255.255.0
过滤出ifcfg-eth0文件中的IP、子网掩码、广播地址
[root@server shell06]# grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' ifcfg-eth0 
10.1.1.1
255.255.255.0
10.1.1.254
[root@server shell06]# sed -nr '/([0-9]{1,3}\.){3}[0-9]{1,3}/p' ifcfg-eth0|cut -d'=' -f2
10.1.1.1
255.255.255.0
10.1.1.254
[root@server shell06]# sed -nr '/([0-9]{1,3}\.){3}[0-9]{1,3}/p' ifcfg-eth0|sed -n 's/[A-Z=]//gp'
10.1.1.1
255.255.255.0
10.1.1.254

[root@server shell06]# ifconfig eth0|sed -n '2p'|sed -n 's/[:a-Z]//gp'|sed -n 's/ /\n/gp'|sed '/^$/d'
10.1.1.1
10.1.1.255
255.255.255.0
[root@server shell06]# ifconfig | sed -nr '/([0-9]{1,3}\.)[0-9]{1,3}/p' | head -1|sed -r 's/([a-z:]|[A-Z/t])//g'|sed 's/ /\n/g'|sed  '/^$/d'

[root@server shell06]# ifconfig eth0|sed -n '2p'|sed -n 's/.*addr:\(.*\) Bcast:\(.*\) Mask:\(.*\)/\1\n\2\n\3/p'
10.1.1.1 
10.1.1.255 
255.255.255.0
5)sed结合正则使用(★★★)

sed 选项 ‘sed命令或者正则表达式或者地址定位’ 文件名

  1. 定址用于决定对哪些行进行编辑。地址的形式可以是数字、正则表达式、或二者的结合。
  2. 如果没有指定地址,sed将处理输入文件的所有行。
正则说明备注
'/key/'查询包含关键字的行sed -n ‘/root/p’ 1.txt
'/key1/,/key2/'匹配包含两个关键字之间的行sed -n ‘/adm/,/mysql/p’ 1.txt
'/key/,x'匹配关键字的行开始到文件第x行之间的行(包含关键字所在行)sed -n ‘/^ftp/,7p’
'x,/key/'从文件的第x行开始到与关键字的匹配行之间的行
'x,y!'不包含x到y行
'/key/!'不包括关键字的行sed -n ‘/bash$/!p’ 1.txt
2、脚本格式
1)用法(sed -f sedScript.sh file ★★★)
# sed -f scripts.sh  file        //使用脚本处理文件
建议使用   ./sed.sh   file

脚本的第一行写上
#!/bin/sed -f
1,5d
s/root/hello/g
3i777
5i888
a999
p

#执行sed脚本:
sed -f sed_test.sh reg_test.txt   #sed_test.sh为sed命令清单,reg_test.txt为要处理的文件
2)注意事项
  • 1)脚本文件是一个sed命令行清单,包含多条sed commands

  • 2)在每行的末尾不能有任何空格、制表符(tab)或其它文本

  • 3)如果在一行中有多个命令,应该用分号分隔

  • 4)不需要且不可用引号保护命令

  • 5)#号开头的行为注释

3)举例说明
# cat passwd
stu3:x:509:512::/home/user3:/bin/bash
stu4:x:510:513::/home/user4:/bin/bash
stu5:x:511:514::/home/user5:/bin/bash

# cat sed.sh 
#!/bin/sed -f
2a\
******************
2,$s/stu/user/
$a\
we inster new line
s/^[a-z].*/#&/

[root@server ~]# cat 1.sed 
#!/bin/sed -f
3a**********************
$chelloworld
1,3s/^/#&/

[root@server ~]# sed -f 1.sed -i 11.txt 
[root@server ~]# cat 11.txt 
#root:x:0:0:root:/root:/bin/bash
#bin:x:1:1:bin:/bin:/sbin/nologin
#daemon:x:2:2:daemon:/sbin:/sbin/nologin
**********************
adm:x:3:4:adm:/var/adm:/sbin/nologin
helloworld
3. sed的补充扩展总结
1、正则表达式必须以”/“前后规范间隔
例如:sed '/root/d' file
例如:sed '/^root/d' file

2、如果匹配的是扩展正则表达式,需要使用-r选来扩展sed
grep -E
sed -r
+ ? () {n,m} | \d

注意:         
在正则表达式中如果出现特殊字符(^$.*/[]),需要以前导 "\" 号做转义
eg:sed '/\$foo/p' file

3、逗号分隔符
例如:sed '5,7d' file                  删除5到7行
例如:sed '/root/,/ftp/d' file    
删除第一个匹配字符串"root"到第一个匹配字符串"ftp"的所有行本行不找 循环执行

4、组合方式
例如:sed '1,/foo/d' file            删除第一行到第一个匹配字符串"foo"的所有行
例如:sed '/foo/,+4d' file            删除从匹配字符串”foo“开始到其后四行为止的行
例如:sed '/foo/,~3d' file            删除从匹配字符串”foo“开始删除到3的倍数行(文件中)
例如:sed '1~5d' file                从第一行开始删每五行删除一行
例如:sed -nr '/foo|bar/p' file    显示配置字符串"foo"或"bar"的行
例如:sed -n '/foo/,/bar/p' file    显示匹配从foo到bar的行
例如:sed '1~2d'  file                删除奇数行
例如:sed '0-2d'   file                删除偶数行 sed '1~2!d'  file

5、特殊情况
例如:sed '$d' file                    删除最后一行
例如:sed '1d' file                    删除第一行

6、其他:
sed 's/.//' a.txt                        删除每一行中的第一个字符
sed 's/.//2' a.txt                    删除每一行中的第二个字符
sed 's/.//N' a.txt                    从文件中第N行开始,删除每行中第N个字符(N>2sed 's/.$//' a.txt                    删除每一行中的最后一个字符


[root@server ~]# cat 2.txt 
1 a
2 b
3 c
4 d
5 e
6 f
7 u
8 k
9 o
[root@server ~]# sed '/c/,~2d' 2.txt 
1 a
2 b
5 e
6 f
7 u
8 k
9 o
4、练习与案例分析
1)练习
  1. 将任意数字替换成空或者制表符
  2. 去掉文件1-5行中的数字、冒号、斜杠
  3. 匹配root关键字替换成hello itcast,并保存到test.txt文件中
  4. 删除vsftpd.conf、smb.conf、main.cf配置文件里所有注释的行及空行(不要直接修改原文件)
  5. 使用sed命令截取自己的ip地址
  6. 使用sed命令一次性截取ip地址、广播地址、子网掩码
  7. 注释掉文件的2-3行和匹配到以root开头或者以ftp开头的行
1、将文件中任意数字替换成空或者制表符
2、去掉文件1-5行中的数字、冒号、斜杠
3、匹配root关键字的行替换成hello itcast,并保存到test.txt文件中
4、删除vsftpd.conf、smb.conf、main.cf配置文件里所有注释的行及空行(不要直接修改原文件)
5、使用sed命令截取自己的ip地址
# ifconfig eth0|sed -n '2p'|sed -n 's/.*addr://pg'|sed -n 's/Bcast.*//gp'
10.1.1.1  
# ifconfig eth0|sed -n '2p'|sed 's/.*addr://g'|sed 's/ Bcast:.*//g'
6、使用sed命令一次性截取ip地址、广播地址、子网掩码
# ifconfig eth0|sed -n '2p'|sed -n 's#.*addr:\(.*\) Bcast:\(.*\) Mask:\(.*\)#\1\n\2\n\3#p'
10.1.1.1 
10.1.1.255 
255.255.255.0

7、注释掉文件的2-3行和匹配到以root开头或者以ftp开头的行
# sed -nr '2,3s/^/#&/p;s/^ROOT|^ftp/#&/p' 1.txt
#ROOT:x:0:0:root:/root:/bin/bash
#bin:x:1:1:bin:/bin:/sbin/nologin
#3daemon:x:2:2:daemon:/sbin:/sbin/nologin

# sed -ne '1,2s/^/#&/gp' a.txt -nre 's/^lp|^mail/#&/gp'
# sed -nr '1,2s/^/#&/gp;s/^lp|^mail/#&/gp' a.txt
2)案例分析

1、写一个初始化系统的脚本

  • 1)自动修改主机名(如:ip是192.168.0.88,则主机名改为server88.itcast.cc

    • a. 更改文件非交互式 sed

          `/etc/sysconfig/network`
      
  • b.将本主机的IP截取出来赋值给一个变量ip;再然后将ip变量里以.分割的最后一位赋值给另一个变量ip1

  • 2)自动配置可用的yum源

  • 3)自动关闭防火墙和selinux

2、写一个搭建ftp服务的脚本,要求如下:

  • 1)不支持本地用户登录 local_enable=NO

  • 2) 匿名用户可以上传 新建 删除 anon_upload_enable=YES anon_mkdir_write_enable=YES

  • 3) 匿名用户限速500KBps anon_max_rate=500000

仅供参考:
#!/bin/bash
ipaddr=`ifconfig eth0|sed -n '2p'|sed -e 's/.*inet addr:\(.*\) Bcast.*/\1/g'`
iptail=`echo $ipaddr|cut -d'.' -f4`
ipremote=192.168.1.10
#修改主机名
hostname server$iptail.itcast.com
sed -i "/HOSTNAME/cHOSTNAME=server$iptail.itcast.com" /etc/sysconfig/network
echo "$ipaddr server$iptail.itcast.cc" >>/etc/hosts
#关闭防火墙和selinux
service iptables stop
setenforce 0 >/dev/null 2>&1
sed -i '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
#配置yum源(一般是内网源)
#test network
ping -c 1 $ipremote > /dev/null 2>&1
if [ $? -ne 0 ];then
    echo "你的网络不通,请先检查你的网络"
    exit 1
else
    echo "网络ok."
fi
cat > /etc/yum.repos.d/server.repo << end
[server]
name=server
baseurl=ftp://$ipremote
enabled=1
gpgcheck=0
end

#安装软件
read -p "请输入需要安装的软件,多个用空格隔开:" soft
yum -y install $soft &>/dev/null

#备份配置文件
conf=/etc/vsftpd/vsftpd.conf
\cp $conf $conf.default
#根据需求修改配置文件
sed -ir '/^#|^$/d' $conf
sed -i '/local_enable/c\local_enable=NO' $conf
sed -i '$a anon_upload_enable=YES' $conf
sed -i '$a anon_mkdir_write_enable=YES' $conf
sed -i '$a anon_other_write_enable=YES' $conf
sed -i '$a anon_max_rate=512000' $conf
#启动服务
service vsftpd restart &>/dev/null && echo"vsftpd服务启动成功"

#测试验证
chmod 777 /var/ftp/pub
cp /etc/hosts /var/ftp/pub
#测试下载
cd /tmp
lftp $ipaddr <<end
cd pub
get hosts
exit
end

if [ -f /tmp/hosts ];then
    echo "匿名用户下载成功"
    rm -f /tmp/hosts
else
    echo "匿名用户下载失败"
fi
#测试上传、创建目录、删除目录等
cd /tmp
lftp $ipaddr << end
cd pub
mkdir test1
mkdir test2
put /etc/group
rmdir test2
exit
end

if [ -d /var/ftp/pub/test1 ];then
    echo "创建目录成功"
    if [ ! -d /var/ftp/pub/test2 ];then
        echo "文件删除成功"
        fi
else
    if [ -f /var/ftp/pub/group ];then
    echo "文件上传成功"
        else
        echo "上传、创建目录删除目录部ok"
        fi 
fi   
[ -f /var/ftp/pub/group ] && echo "上传文件成功"

awk语言

一、awk概述与使用场景

  • awk是一种编程语言,主要用于在linux/unix下对文本和数据进行处理,是linux/unix下的一个工具。数据可以来自标准输入、一个或多个文件,或其它命令的输出。

  • awk的处理文本和数据的方式:逐行扫描文件,默认从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行你想要的操作。

  • awk分别代表其作者姓氏的第一个字母。因为它的作者是三个人,分别是Alfred Aho、Brian Kernighan、Peter Weinberger。

  • gawk是awk的GNU版本,它提供了Bell实验室和GNU的一些扩展。

  • 下面介绍的awk是以GNU的gawk为例的,在linux系统中已把awk链接到gawk,所以下面全部以awk进行介绍。

awk能干啥

  1. awk用来处理文件和数据的,是类unix下的一个工具,也是一种编程语言

  2. 可以用来统计数据,比如网站的访问量,访问的IP量等等

  3. 支持条件判断,支持for和while循环

二、awk使用方式

1、命令行模式使用(★★★)

语法结构

awk 选项 '命令部分' 文件名

特别说明:
引用shell变量需用双引号引起

常用选项

  • -F 定义字段分割符号,默认的分隔符是空格
  • -v 定义变量并赋值

‘命名部分’ 说明

  • 正则表达式,地址定位
'/root/{awk语句}'                sed中: '/root/p'
'NR==1,NR==5{awk语句}'            sed中: '1,5p'
'/^root/,/^ftp/{awk语句}'      sed中:'/^root/,/^ftp/p'
  • {awk语句1; awk语句2;...}
'{print $0;print $1}'        sed中:'p'
'NR==5{print $0}'                sed中:'5p'
注:awk命令语句间用分号间隔
  • BEGIN...END....
'BEGIN{awk语句};{处理中};END{awk语句}'
'BEGIN{awk语句};{处理中}'
'{处理中};END{awk语句}'
2. 脚本模式使用(awk -f awkScript.sh file ★★★)

脚本编写

#!/bin/awk -f         定义魔法字符
以下是awk引号里的命令清单,不要用引号保护命令,多个命令用分号间隔
BEGIN{FS=":"}
NR==1,NR==3{print $1"\t"$NF}
...

脚本执行

方法1:
awk 选项 -f awk的脚本文件  要处理的文本文件
awk -f awk.sh filename

sed -f sed.sh -i filename

方法2:
./awk的脚本文件(或者绝对路径)    要处理的文本文件
./awk.sh filename

./sed.sh filename

三、 awk内部相关变量(★★★)

变量变量说明备注
$0当前处理行的所有记录
1,2,3...n文件中每行以间隔符号分割的不同字段awk -F: ‘{print 1,3}’
NF当前记录的字段数(列数)awk -F: ‘{print NF}’
$NF最后一列$(NF-1)表示倒数第二列
FNR/NR行号
FS定义间隔符‘BEGIN{FS=“:”};{print 1,3}’
OFS定义输出字段分隔符,默认空格‘BEGIN{OFS=“\t”};print 1,3}’
RS输入记录分割符,默认换行‘BEGIN{RS=“\t”};{print $0}’
ORS输出记录分割符,默认换行‘BEGIN{ORS=“\n\n”};{print 1,3}’
FILENAME当前输入的文件名

文件准备

root:x:0:0:root:/roots:/bin/bash
bin:x:1:1:bin:/bins:/sbin/nologin
daemon123:x:2:2:daemon:/sbins:/sbin/nologin
adm:x:3:4:adm:/var/adms:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpds:/sbin/nologin
298374837483
172.16.0.254
10.1.1.1
1、常用内置变量举例
awk -F: '{print $1,$(NF-1)}' sed_test.txt  #使用":"分隔符对行进行切分
root /roots
bin /bins
daemon123 /sbins
adm /var/adms
lp /var/spool/lpds
298374837483 298374837483
172.16.0.254 172.16.0.254
10.1.1.1 10.1.1.1

awk -F: '{print $1,$(NF-1),$NF,NF}' sed_test.txt
root /roots /bin/bash 7
bin /bins /sbin/nologin 7
daemon123 /sbins /sbin/nologin 7
adm /var/adms /sbin/nologin 7
lp /var/spool/lpds /sbin/nologin 7
298374837483 298374837483 298374837483 1
172.16.0.254 172.16.0.254 172.16.0.254 1
10.1.1.1 10.1.1.1 10.1.1.1 1


awk '/root/{print $0}' 1.txt
# awk '/root/' 1.txt
# awk -F: '/root/{print $1,$NF}' 1.txt 
root /bin/bash
# awk -F: '/root/{print $0}' 1.txt      
root:x:0:0:root:/root:/bin/bash
# awk 'NR==1,NR==5' 1.txt 
# awk 'NR==1,NR==5{print $0}' 1.txt
# awk 'NR==1,NR==5;/^root/{print $0}' 1.txt 
root:x:0:0:root:/root:/bin/bash
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
2、内置变量分隔符举例
FS和OFS:
awk 'BEGIN{FS=":"};/^root/,/^lp/{print $1,$NF}' 1.txt
awk -F: 'BEGIN{OFS="\t\t"};/^root/,/^lp/{print $1,$NF}' 1.txt        
root            /bin/bash
bin             /sbin/nologin
daemon          /sbin/nologin
adm             /sbin/nologin
lp              /sbin/nologin

awk -F: 'BEGIN{OFS="@@@"};/^root/,/^lp/{print $1,$NF}' 1.txt     
root@@@/bin/bash
bin@@@/sbin/nologin
daemon@@@/sbin/nologin
adm@@@/sbin/nologin
lp@@@/sbin/nologin
[root@server shell07]# 

RS和ORS:
修改源文件前2行增加制表符和内容:
vim 1.txt
root:x:0:0:root:/root:/bin/bash hello   world
bin:x:1:1:bin:/bin:/sbin/nologin        test1   test2

# awk 'BEGIN{RS="\t"};{print $0}' 1.txt
# awk 'BEGIN{ORS="\t"};{print $0}' 1.txt

四、 awk工作原理(★★★)

awk -F: '{print $1,$3}' /etc/passwd
  1. awk使用一行作为输入,并将这一行赋给内部变量$0,每一行也可称为一个记录,以换行符(RS)结束

  2. 每行被间隔符:(默认为空格或制表符)分解成字段(或域),每个字段存储在已编号的变量中,从$1开始

    问:awk如何知道用空格来分隔字段的呢?

    答:因为有一个内部变量FS来确定字段分隔符。初始时,FS赋为空格

  3. awk使用print函数打印字段,打印出来的字段会以空格分隔,因为1,3之间有一个逗号。逗号比较特殊,它映射为另一个内部变量,称为输出字段分隔符OFS,OFS默认为空格

  4. awk处理完一行后,将从文件中获取另一行,并将其存储在$0中,覆盖原来的内容,然后将新的字符串分隔成字段并进行处理。该过程将持续到所有行处理完毕

五、awk使用进阶

1、格式化输出printprintf

Note

  • %s 字符类型 strings %-20s

  • %d 数值类型

  • -表示左对齐,默认是右对齐

  • printf默认不会在行尾自动换行,加\n

print函数        类似echo "hello world"
# date |awk '{print "Month: "$2 "\nYear: "$NF}'
# awk -F: '{print "username is: " $1 "\t uid is: "$3}' /etc/passwd


printf函数        类似echo -n
# awk -F: '{printf "%-15s %-10s %-15s\n", $1,$2,$3}'  /etc/passwd
# awk -F: '{printf "|%15s| %10s| %15s|\n", $1,$2,$3}' /etc/passwd
# awk -F: '{printf "|%-15s| %-10s| %-15s|\n", $1,$2,$3}' /etc/passwd

awk 'BEGIN{FS=":"};{printf "%-15s %-15s %-15s\n",$1,$6,$NF}' a.txt
2、awk变量定义(★★★)

Noteawk中调用定义的变量不需要加$

awk -v NUM=3 -F: '{ print $NUM }' /etc/passwd
awk -v NUM=3 -F: '{ print NUM }' /etc/passwd
awk -v num=1 'BEGIN{print num}' 
1
awk -v num=1 'BEGIN{print $num}' 
3、awk中BEGIN…END使用(★★★)
  • BEGIN:表示在程序开始前执行

  • END :表示所有文件处理完后执行

  • 用法:'BEGIN{开始处理之前};{处理中};END{处理结束后}',可以只存在'BEGIN{开始处理之前};{处理中};'或者'{处理中};END{处理结束后}'

1)举例说明1

打印最后一列和倒数第二列(登录shell和家目录)

awk -F: 'BEGIN{ print "Login_shell\t\tLogin_home\n*******************"};{print $NF"\t\t"$(NF-1)};END{print "************************"}' 1.txt

awk 'BEGIN{ FS=":";print "Login_shell\tLogin_home\n*******************"};{print $NF"\t"$(NF-1)};END{print "************************"}' 1.txt

Login_shell        Login_home
************************
/bin/bash        /root
/sbin/nologin        /bin
/sbin/nologin        /sbin
/sbin/nologin        /var/adm
/sbin/nologin        /var/spool/lpd
/bin/bash        /home/redhat
/bin/bash        /home/user01
/sbin/nologin        /var/named
/bin/bash        /home/u01
/bin/bash        /home/YUNWEI
************************************
2)举例说明2

打印/etc/passwd里的用户名、家目录及登录shell

u_name      h_dir       shell
***************************

***************************

awk -F: 'BEGIN{OFS="\t\t";print"u_name\t\th_dir\t\tshell\n***************************"};{printf "%-20s %-20s %-20s\n",$1,$(NF-1),$NF};END{print "****************************"}'


# awk -F: 'BEGIN{print "u_name\t\th_dir\t\tshell" RS "*****************"}  {printf "%-15s %-20s %-20s\n",$1,$(NF-1),$NF}END{print "***************************"}'  /etc/passwd

格式化输出:
echo        print
echo -n    printf

{printf "%-15s %-20s %-20s\n",$1,$(NF-1),$NF}
4、awk和正则的综合运用(★★★)
运算符说明
==等于
!=不等于
>大于
<小于
>=大于等于
<=小于等于
~匹配
!~不匹配
!逻辑非
&&逻辑与
\\
1)举例说明
从第一行开始匹配到以lp开头行
awk -F: 'NR==1,/^lp/{print $0 }' passwd  
从第一行到第5行          
awk -F: 'NR==1,NR==5{print $0 }' passwd
从以lp开头的行匹配到第10行       
awk -F: '/^lp/,NR==10{print $0 }' passwd 
从以root开头的行匹配到以lp开头的行       
awk -F: '/^root/,/^lp/{print $0}' passwd
打印以root开头或者以lp开头的行            
awk -F: '/^root/ || /^lp/{print $0}' passwd
awk -F: '/^root/;/^lp/{print $0}' passwd
显示5-10行   
awk -F':' 'NR>=5 && NR<=10 {print $0}' /etc/passwd     
awk -F: 'NR<10 && NR>5 {print $0}' passwd 

打印30-39行以bash结尾的内容:
[root@MissHou shell06]$ awk 'NR>=30 && NR<=39 && $0 ~ /bash$/{print $0}' passwd 
stu1:x:500:500::/home/stu1:/bin/bash
yunwei:x:501:501::/home/yunwei:/bin/bash
user01:x:502:502::/home/user01:/bin/bash
user02:x:503:503::/home/user02:/bin/bash
user03:x:504:504::/home/user03:/bin/bash

[root@MissHou shell06]$ awk 'NR>=3 && NR<=8 && /bash$/' 1.txt  
stu7:x:1007:1007::/rhome/stu7:/bin/bash
stu8:x:1008:1008::/rhome/stu8:/bin/bash
stu9:x:1009:1009::/rhome/stu9:/bin/bash

打印文件中1-5并且以root开头的行
[root@MissHou shell06]$ awk 'NR>=1 && NR<=5 && $0 ~ /^root/{print $0}' 1.txt
root:x:0:0:root:/root:/bin/bash
[root@MissHou shell06]$ awk 'NR>=1 && NR<=5 && $0 !~ /^root/{print $0}' 1.txt
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin


理解;号和||的含义:
[root@MissHou shell06]$ awk 'NR>=3 && NR<=8 || /bash$/' 1.txt
[root@MissHou shell06]$ awk 'NR>=3 && NR<=8;/bash$/' 1.txt


打印IP地址
$ ifconfig eth0|awk 'NR>1 {print $2}'|awk -F':' 'NR<2 {print $2}'    
$ ifconfig eth0|grep Bcast|awk -F':' '{print $2}'|awk '{print $1}'
$ ifconfig eth0|grep Bcast|awk '{print $2}'|awk -F: '{print $2}'


$ ifconfig eth0|awk NR==2|awk -F '[ :]+' '{print $4RS$6RS$8}'
$ ifconfig eth0|awk -F"[ :]+" '/inet addr:/{print $4}'
2)练习
  1. 显示可以登录操作系统的用户所有信息 从第7列匹配以bash结尾,输出整行(当前行所有的列)
[root@MissHou ~] awk '/bash$/{print $0}'    /etc/passwd
[root@MissHou ~] awk '/bash$/{print $0}' /etc/passwd
[root@MissHou ~] awk '/bash$/' /etc/passwd
[root@MissHou ~] awk -F: '$7 ~ /bash/' /etc/passwd
[root@MissHou ~] awk -F: '$NF ~ /bash/' /etc/passwd
[root@MissHou ~] awk -F: '$0 ~ /bash/' /etc/passwd
[root@MissHou ~] awk -F: '$0 ~ /\/bin\/bash/' /etc/passwd
  1. 显示可以登录系统的用户名
$ awk -F: '$0 ~ /\/bin\/bash/{print $1}' /etc/passwd
  1. 打印出系统中普通用户的UID和用户名
500    stu1
501    yunwei
502    user01
503    user02
504    user03

$ awk -F: 'BEGIN{print "UID\tUSERNAME"} {if($3>=500 && $3 !=65534 ) {print $3"\t"$1} }' /etc/passwdUID    USERNAME

$ awk -F: '{if($3 >= 500 && $3 != 65534) print $1,$3}' a.txt 
redhat 508
user01 509
u01 510
YUNWEI 511
5、awk的脚本编程
1)流程控制语句
a)if 结构
if语句:

if [ xxx ];then
xxx
fi

格式:
awk 选项 '正则,地址定位{awk语句}'  文件名

{ if(表达式){语句1;语句2;...}}

awk -F: '{if($3>=500 && $3<=60000) {print $1,$3} }' passwd

$ awk -F: '{if($3==0) {print $1"是管理员"} }' passwd 
root是管理员

$ awk 'BEGIN{if('$(id -u)'==0) {print "admin"} }'
admin
b)if…else 结构
if...else语句:
if [ xxx ];then
    xxxxx

else
    xxx
fi

格式:
{if(表达式){语句;语句;...}else{语句;语句;...}}

awk -F: '{ if($3>=500 && $3 != 65534) {print $1"是普通用户"} else {print $1,"不是普通用户"}}' passwd 

awk 'BEGIN{if( '$(id -u)'>=500 && '$(id -u)' !=65534 ) {print "是普通用户"} else {print "不是普通用户"}}'
c)if…elif…else 结构(★★★)
if [xxxx];then
    xxxx
elif [xxx];then
    xxx
....
else
...
fi


if...else if...else语句:

格式:
{ if(表达式1){语句;语句;...}else if(表达式2){语句;语句;...}else if(表达式3){语句;语句;...}else{语句;语句;...}}

awk -F: '{ if($3==0) {print $1,":是管理员"} else if($3>=1 && $3<=499 || $3==65534 ) {print $1,":是系统用户"} else {print $1,":是普通用户"}}'


awk -F: '{ if($3==0) {i++} else if($3>=1 && $3<=499 || $3==65534 ) {j++} else {k++}};END{print "管理员个数为:"i "\n系统用户个数为:"j"\n普通用户的个数为:"k }'


# awk -F: '{if($3==0) {print $1,"is admin"} else if($3>=1 && $3<=499 || $3==65534) {print $1,"is sys users"} else {print $1,"is general user"} }' a.txt 

root is admin
bin is sys users
daemon is sys users
adm is sys users
lp is sys users
redhat is general user
user01 is general user
named is sys users
u01 is general user
YUNWEI is general user

awk -F: '{  if($3==0) {print $1":管理员"} else if($3>=1 && $3<500 || $3==65534 ) {print $1":是系统用户"} else {print $1":是普通用户"}}'   /etc/passwd


awk -F: '{if($3==0) {i++} else if($3>=1 && $3<500 || $3==65534){j++} else {k++}};END{print "管理员个数为:" i RS "系统用户个数为:"j RS "普通用户的个数为:"k }' /etc/passwd
管理员个数为:1
系统用户个数为:28
普通用户的个数为:27


# awk -F: '{ if($3==0) {print $1":是管理员"} else if($3>=500 && $3!=65534) {print $1":是普通用户"} else {print $1":是系统用户"}}' passwd 

awk -F: '{if($3==0){i++} else if($3>=500){k++} else{j++}} END{print i; print k; print j}' /etc/passwd

awk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print "管理员个数: "i; print "普通用个数: "k; print "系统用户: "j}' /etc/passwd 

如果是普通用户打印默认shell,如果是系统用户打印用户名
# awk -F: '{if($3>=1 && $3<500 || $3 == 65534) {print $1} else if($3>=500 && $3<=60000 ) {print $NF} }' /etc/passwd
2)循环语句
a)for循环
打印1~5
for ((i=1;i<=5;i++));do echo $i;done

awk 'BEGIN { for(i=1;i<=5;i++) {print i} }'
打印1~10中的奇数
for ((i=1;i<=10;i+=2));do echo $i;done|awk '{sum+=$0};END{print sum}'
awk 'BEGIN{ for(i=1;i<=10;i+=2) {print i} }'
awk 'BEGIN{ for(i=1;i<=10;i+=2) print i }'

计算1-5的和
awk 'BEGIN{sum=0;for(i=1;i<=5;i++) sum+=i;print sum}'
awk 'BEGIN{for(i=1;i<=5;i++) (sum+=i);{print sum}}'
awk 'BEGIN{for(i=1;i<=5;i++) (sum+=i);print sum}'
b)while循环
打印1-5
i=1;while (($i<=5));do echo $i;let i++;done

awk 'BEGIN { i=1;while(i<=5) {print i;i++} }'
打印1~10中的奇数
awk 'BEGIN{i=1;while(i<=10) {print i;i+=2} }'
计算1-5的和
awk 'BEGIN{i=1;sum=0;while(i<=5) {sum+=i;i++}; print sum }'
awk 'BEGIN {i=1;while(i<=5) {(sum+=i) i++};print sum }'
c)嵌套循环(★★★)
嵌套循环:
#!/bin/bash
for ((y=1;y<=5;y++))
do
    for ((x=1;x<=$y;x++))
    do
        echo -n $x    
    done
echo
done

awk 'BEGIN{ for(y=1;y<=5;y++) {for(x=1;x<=y;x++) {printf x} ;print } }'


awk 'BEGIN { for(y=1;y<=5;y++) { for(x=1;x<=y;x++) {printf x};print} }'
1
12
123
1234
12345

awk 'BEGIN{ y=1;while(y<=5) { for(x=1;x<=y;x++) {printf x};y++;print}}'
1
12
123
1234
12345

尝试用三种方法打印99口诀表:
awk 'BEGIN{for(y=1;y<=9;y++) { for(x=1;x<=y;x++) {printf x"*"y"="x*y"\t"};print} }'

awk 'BEGIN{for(y=1;y<=9;y++) { for(x=1;x<=y;x++) printf x"*"y"="x*y"\t";print} }'
awk 'BEGIN{i=1;while(i<=9){for(j=1;j<=i;j++) {printf j"*"i"="j*i"\t"};print;i++ }}'

awk 'BEGIN{for(i=1;i<=9;i++){j=1;while(j<=i) {printf j"*"i"="i*j"\t";j++};print}}'

循环的控制:
break        条件满足的时候中断循环
continue    条件满足的时候跳过循环
# awk 'BEGIN{for(i=1;i<=5;i++) {if(i==3) break;print i} }'
1
2
# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3) continue;print i}}'
1
2
4
5
6、awk算数运算
+ - * / %() ^(幂2^3)
可以在模式中执行计算,awk都将按浮点数方式执行算术运算
awk 'BEGIN{print 1+1}'
awk 'BEGIN{print 1**1}'
awk 'BEGIN{print 2**3}'
awk 'BEGIN{print 2/3}'

六、awk统计案例

1、统计系统中各种类型的shell
awk -F: '{ shells[$NF]++ };END{for (i in shells) {print i,shells[i]} }' /etc/passwd

books[linux]++
books[linux]=1
shells[/bin/bash]++
shells[/sbin/nologin]++

/bin/bash 5
/sbin/nologin 6

shells[/bin/bash]++            a
shells[/sbin/nologin]++        b
shells[/sbin/shutdown]++    c

books[linux]++
books[php]++
2、统计网站访问状态
ss -antp|grep 80|awk '{states[$1]++};END{for(i in states){print i,states[i]}}'
TIME_WAIT 578
ESTABLISHED 1
LISTEN 1

ss -an |grep :80 |awk '{states[$2]++};END{for(i in states){print i,states[i]}}'
LISTEN 1
ESTAB 5
TIME-WAIT 25

ss -an |grep :80 |awk '{states[$2]++};END{for(i in states){print i,states[i]}}' |sort -k2 -rn
TIME-WAIT 18
ESTAB 8
LISTEN 1
3、统计访问网站的每个IP的数量
netstat -ant |grep :80 |awk -F: '{ip_count[$8]++};END{for(i in ip_count){print i,ip_count[i]} }' |sort


ss -an |grep :80 |awk -F":" '!/LISTEN/{ip_count[$(NF-1)]++};END{for(i in ip_count){print i,ip_count[i]}}' |sort -k2 -rn |head
4、统计网站日志中PV量
统计Apache/Nginx日志中某一天的PV量  <统计日志>
grep '27/Jul/2017' mysqladmin.cc-access_log |wc -l
14519

统计Apache/Nginx日志中某一天不同IP的访问量 <统计日志>
grep '27/Jul/2017' mysqladmin.cc-access_log |awk '{ips[$1]++};END{for(i in ips){print i,ips[i]} }' |sort -k2 -rn |head

grep '07/Aug/2017' access.log |awk '{ips[$1]++};END{for(i in ips){print i,ips[i]} }' |awk '$2>100' |sort -k2 -rn

名词解释:

网站浏览量(PV) 名词:PV=PageView (网站浏览量) 说明:指页面的浏览次数,用以衡量网站用户访问的网页数量。多次打开同一页面则浏览量累计。用户每打开一个页面便记录1次PV

名词:VV = Visit View(访问次数) 说明:从访客来到您网站到最终关闭网站的所有页面离开,计为1次访问。若访客连续30分钟没有新开和刷新页面,或者访客关闭了浏览器,则被计算为本次访问结束。

独立访客(UV) 名词:UV= Unique Visitor(独立访客数) 说明:1天内相同的访客多次访问您的网站只计算1个UV。

独立IP(IP) 名词:IP=独立IP数 说明:指1天内使用不同IP地址的用户访问网站的数量。同一IP无论访问了几个页面,独立IP数均为1

八、企业实战案例

1、任务/背景

web服务器集群中总共有9台机器,上面部署的是Apache服务。由于业务不断增长,每天每台机器上都会产生大量的访问日志,现需要将每台web服务器上的apache访问日志保留最近3天的,3天以前的日志转储到一台专门的日志服务器上,已做后续分析。如何实现每台服务器上只保留3天以内的日志?

2、具体要求
  1. 每台web服务器的日志对应日志服务器相应的目录里。如:web1——>web1.log(在日志服务器上)
  2. 每台web服务器上保留最近3天的访问日志,3天以前的日志每天凌晨5:03分转储到日志服务器
  3. 如果脚本转储失败,运维人员需要通过跳板机的菜单选择手动清理日志
3、涉及知识点
  1. shell的基本语法结构
  2. 文件同步rsync
  3. 文件查找命令find
  4. 计划任务crontab
  5. apache日志切割
  6. 其他
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值