【linux】之shell脚本

文章目录

第一章节 SHELL编程之变量定义

SHELL编程

  • 该课程主要包括以下内容:

    ① Shell的基本语法结构

    如:变量定义、条件判断、循环语句(for、until、while)、分支语句、函数和数组等;

    ② 基本正则表达式的运用;

    ③ 文件处理三剑客:grep、sed、awk工具的使用;

    ④ 使用shell脚本完成一些较复杂的任务,如:服务搭建、批量处理等。

    说明:以上内容仅仅是基本要求,还有很多更深更难的语法需要扩充学习。

  • 今日目标

    • 熟悉grep、cut、sort等小工具和shell中的通配符的使用
    • 熟练掌握shell变量的定义和获取(重点)
    • 能够进行shell简单的四则运算
    • 熟悉条件判断语句,如判断整数,判断字符串等

学习前奏

1. 文件处理工具

1.1 grep工具

过滤

grep用于根据关键字进行行过滤
grep options 'keys' filename
OPTIONS:
    -i: 不区分大小写
    -v: 查找不包含指定内容的行,反向选择
    -w: 按单词搜索
    -o: 打印匹配关键字
    -c: 统计匹配到的次数
    -n: 显示行号
    -r: 逐层遍历目录查找
    -A: 显示匹配行及后面多少行	
    -B: 显示匹配行及前面多少行
    -C: 显示匹配行前后多少行
    -l:只列出匹配的文件名
    -L:列出不匹配的文件名
    -e: 使用正则匹配
    -E:使用扩展正则匹配
    ^key:以关键字开头
    key$:以关键字结尾
    ^$:匹配空行
    --color=auto :可以将找到的关键词部分加上颜色的显示

临时设置:
# alias grep='grep --color=auto'			//只针对当前终端和当前用户生效

永久设置:
1)全局(针对所有用户生效)
vim /etc/bashrc
alias grep='grep --color=auto'
source /etc/bashrc

2)局部(针对具体的某个用户)
vim ~/.bashrc
alias grep='grep --color=auto'
source ~/.bashrc

示例:
# grep -i root passwd				忽略大小写匹配包含root的行
# grep -w ftp passwd 				精确匹配ftp单词
# grep -w hello passwd 				精确匹配hello单词;自己添加包含hello的行到文件中
# grep -wo ftp passwd 				打印匹配到的关键字ftp
# grep -n root passwd 				打印匹配到root关键字的行好
# grep -ni root passwd 				忽略大小写匹配统计包含关键字root的行
# grep -nic root passwd				忽略大小写匹配统计包含关键字root的行数
# grep -i ^root passwd 				忽略大小写匹配以root开头的行
# grep bash$ passwd 							匹配以bash结尾的行
# grep -n ^$ passwd 							匹配空行并打印行号
# grep ^# /etc/vsftpd/vsftpd.conf		匹配以#号开头的行
# grep -v ^# /etc/vsftpd/vsftpd.conf	匹配不以#号开头的行
# grep -A 5 mail passwd 				 	匹配包含mail关键字及其后5行
# grep -B 5 mail passwd 				 	匹配包含mail关键字及其前5行
# grep -C 5 mail passwd 					匹配包含mail关键字及其前后5行

1.2 cut工具

截取

cut用于列截取
-c:	以字符为单位进行分割。
-d:	自定义分隔符,默认为制表符。\t
-f:	与-d一起使用,指定显示哪个区域。

# cut -d: -f1 1.txt 			以:冒号分割,截取第1列内容
# cut -d: -f1,6,7 1.txt 	以:冒号分割,截取第1,6,7列内容
# cut -c4 1.txt 				截取文件中每行第4个字符
# cut -c1-4 1.txt 			截取文件中每行的1-4个字符
# cut -c4-10 1.txt 			
# cut -c5- 1.txt 				从第5个字符开始截取后面所有字符

课堂练习:
用小工具列出你当系统的运行级别。5/3
1.3 sort工具

排序

sort:将文件的每一行作为一个单位,从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。

-u :去除重复行
-r :降序排列,默认是升序
-o : 将排序结果输出到文件中  类似 重定向符号>
-n :以数字排序,默认是按字符排序
-t :分隔符
-k :第N列
-b :忽略前导空格。
-R :随机排序,每次运行的结果均不同。
 
 示例:
# sort -n -t: -k3 1.txt 			按照用户的uid进行升序排列
# sort -nr -t: -k3 1.txt 			按照用户的uid进行降序排列
# sort -n 2.txt 						按照数字排序
# sort -nu 2.txt 						按照数字排序并且去重
# sort -nr 2.txt 
# sort -nru 2.txt 
# sort -nru 2.txt 
# sort -n 2.txt -o 3.txt 		按照数字排序并将结果重定向到文件
# sort -R 2.txt 
# sort -u 2.txt 
1.4 uniq工具

去除连续重复

uniq:去除连续重复行
-i: 忽略大小写
-c: 统计重复行次数
-d:只显示重复行

# uniq 2.txt 
# uniq -d 2.txt 
# uniq -dc 2.txt 
1.5 tee工具
tee工具从标准输入读取并写入标准输出和文件,即:双向覆盖重定向<屏幕输出|文本输入>
-a 双向追加重定向

# echo hello world
# echo hello world|tee file1
# cat file1 
# echo 999|tee -a file1
# cat file1 
1.6 paste工具
paste工具用于合并文件行

-d:自定义间隔符,默认是tab
-s:串行处理,非并行

[root@server shell01]# cat a.txt 
hello
[root@server shell01]# cat b.txt 
hello world
888
999
[root@server shell01]# paste a.txt b.txt 
hello   hello world
        888
        999
[root@server shell01]# paste b.txt a.txt   
hello world     hello
888
999

[root@server shell01]# paste -d'@' b.txt a.txt 
hello world@hello
888@
999@

[root@server shell01]# paste -s b.txt a.txt 
hello world     888     999
hello

1.7 tr工具

字符转换:替换,删除

tr用来从标准输入中通过替换或删除操作进行字符转换;主要用于删除文件中控制字符或进行字符转换。
使用tr时要转换两个字符串:字符串1用于查询,字符串2用于处理各种转换。

语法:
commands|tr  'string1'  'string2'
tr  'string1'  'string2' < filename

tr options 'string1' < filename

-d 删除字符串1中所有输入字符。
-s 删除所有重复出现字符序列,只保留第一个;即将重复出现字符串压缩为一个字符串。


	a-z 任意小写
	A-Z 任意大写
	0-9 任意数字
  [:alnum:]       all letters and digits		所有字母和数字
  [:alpha:]       all letters						所有字母
  [:blank:]       all horizontal whitespace	所有水平空白
  [:cntrl:]       all control characters		所有控制字符
\b Ctrl-H  		退格符
\f Ctrl-L  		走行换页
\n Ctrl-J  		新行
\r Ctrl-M  		回车
\t Ctrl-I  		tab键
  [:digit:]    all digits	所有数字
  [:graph:]    all printable characters, not including space
  所有可打印的字符,不包含空格
  [:lower:]       all lower case letters		所有小写字母
  [:print:]       all printable characters, including space
  所有可打印的字符,包含空格
  [:punct:]       all punctuation characters			所有的标点符号
  [:space:]       all horizontal or vertical whitespace	所有水平或垂直的空格
  [:upper:]       all upper case letters				所有大写字母
  [:xdigit:]      all hexadecimal digits				所有十六进制数字
  [=CHAR=]        all characters which are equivalent to CHAR	所有字符
  
 

[root@server shell01]# cat 3.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
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
boss02:x:516:511::/home/boss02:/bin/bash
vip:x:517:517::/home/vip:/bin/bash
stu1:x:518:518::/home/stu1:/bin/bash
mailnull:x:47:47::/var/spool/mqueue:/sbin/nologin
smmsp:x:51:51::/var/spool/mqueue:/sbin/nologin
aaaaaaaaaaaaaaaaaaaa
bbbbbb111111122222222222233333333cccccccc
hello world 888
666
777
999


# tr -d '[:/]' < 3.txt 				删除文件中的:和/
# cat 3.txt |tr -d '[:/]'			删除文件中的:和/
# tr '[0-9]' '@' < 3.txt 			将文件中的数字替换为@符号
# tr '[a-z]' '[A-Z]' < 3.txt 		将文件中的小写字母替换成大写字母
# tr -s '[a-z]' < 3.txt 			匹配小写字母并将重复的压缩为一个
# tr -s '[a-z0-9]' < 3.txt 		匹配小写字母和数字并将重复的压缩为一个
# tr -d '[:digit:]' < 3.txt 		删除文件中的数字
# tr -d '[:blank:]' < 3.txt 		删除水平空白
# tr -d '[:space:]' < 3.txt 		删除所有水平和垂直空白

1.8 diff工具

diff工具用于逐行比较文件的不同

注意:diff描述两个文件不同的方式是告诉我们怎样改变第一个文件之后与第二个文件匹配

语法和选项

语法:

diff [选项] 文件1 文件2

常用选项:

选项含义备注
-b不检查空格
-B不检查空白行
-i不检查大小写
-w忽略所有的空格
–normal正常格式显示(默认)
-c上下文格式显示
-u合并格式显示

举例说明:

  • 比较两个普通文件异同,文件准备:
[root@MissHou ~]# cat file1
aaaa
111
hello world
222
333
bbb
[root@MissHou ~]#
[root@MissHou ~]# cat file2
aaa
hello
111
222
bbb
333
world

1)正常显示

diff目的:file1如何改变才能和file2匹配
[root@MissHou ~]# diff file1 file2
1c1,2					第一个文件的第1行需要改变(c=change)才能和第二个文件的第1到2行匹配			
< aaaa				小于号"<"表示左边文件(file1)文件内容
---					---表示分隔符
> aaa					大于号">"表示右边文件(file2)文件内容
> hello
3d3					第一个文件的第3行删除(d=delete)后才能和第二个文件的第3行匹配
< hello world
5d4					第一个文件的第5行删除后才能和第二个文件的第4行匹配
< 333
6a6,7					第一个文件的第6行增加(a=add)内容后才能和第二个文件的第6到7行匹配
> 333					需要增加的内容在第二个文件里是333和world
> world

2)上下文格式显示

[root@MissHou ~]# diff -c file1 file2
前两行主要列出需要比较的文件名和文件的时间戳;文件名前面的符号***表示file1,---表示file2
*** file1       2019-04-16 16:26:05.748650262 +0800
--- file2       2019-04-16 16:26:30.470646030 +0800
***************	我是分隔符
*** 1,6 *******开头表示file1文件,1,6表示1到6行
! aaaa				!表示该行需要修改才与第二个文件匹配
  111
- hello world		-表示需要删除该行才与第二个文件匹配
  222
- 333					-表示需要删除该行才与第二个文件匹配
  bbb
--- 1,7 -------开头表示file2文件,1,7表示1到7行
! aaa					表示第一个文件需要修改才与第二个文件匹配
! hello				表示第一个文件需要修改才与第二个文件匹配
  111
  222
  bbb
+ 333					表示第一个文件需要加上该行才与第二个文件匹配
+ world				表示第一个文件需要加上该行才与第二个文件匹配

3)合并格式显示

[root@MissHou ~]# diff -u file1 file2
前两行主要列出需要比较的文件名和文件的时间戳;文件名前面的符号---表示file1,+++表示file2
--- file1       2019-04-16 16:26:05.748650262 +0800
+++ file2       2019-04-16 16:26:30.470646030 +0800
@@ -1,6 +1,7 @@
-aaaa
+aaa
+hello
 111
-hello world
 222
-333
 bbb
+333
+world
  • 比较两个目录不同
默认情况下也会比较两个目录里相同文件的内容
[root@MissHou  tmp]# diff dir1 dir2
diff dir1/file1 dir2/file1
0a1
> hello
Only in dir1: file3
Only in dir2: test1
如果只需要比较两个目录里文件的不同,不需要进一步比较文件内容,需要加-q选项
[root@MissHou  tmp]# diff -q dir1 dir2
Files dir1/file1 and dir2/file1 differ
Only in dir1: file3
Only in dir2: test1

其他小技巧:

有时候我们需要以一个文件为标准,去修改其他文件,并且修改的地方较多时,我们可以通过打补丁的方式完成。

1)先找出文件不同,然后输出到一个文件
[root@MissHou ~]# diff -uN file1 file2 > file.patch
-u:上下文模式
-N:将不存在的文件当作空文件
2)将不同内容打补丁到文件
[root@MissHou ~]# patch file1 file.patch
patching file file1
3)测试验证
[root@MissHou ~]# diff file1 file2
[root@MissHou ~]#

小试牛刀
  1. 使用小工具分别截取当前主机IP;截取NETMASK;截取广播地址;截取MAC地址
[root@server shell01]# ifconfig eth0|grep 'Bcast'|tr -d '[a-zA-Z ]'|cut -d: -f2,3,4
10.1.1.1:10.1.1.255:255.255.255.0
[root@server shell01]# ifconfig eth0|grep 'Bcast'|tr -d '[a-zA-Z ]'|cut -d: -f2,3,4|tr ':' '\n'
10.1.1.1
10.1.1.255
255.255.255.0
[root@server shell01]# ifconfig eth0|grep 'HWaddr'|cut -d: -f2-|cut -d' ' -f4
00:0C:29:25:AE:54

# ifconfig eth1|grep Bcast|cut -d: -f2|cut -d' ' -f1
# ifconfig eth1|grep Bcast|cut -d: -f2|tr -d '[ a-zA-Z]'
# ifconfig eth1|grep Bcast|tr -d '[:a-zA-Z]'|tr ' ' '@'|tr -s '@'|tr '@' '\n'|grep -v ^$
# ifconfig eth0|grep 'Bcast'|tr -d [:alpha:]|tr '[ :]' '\n'|grep -v ^$
# ifconfig eth1|grep HWaddr|cut -d ' ' -f11
# ifconfig eth0|grep HWaddr|tr -s ' '|cut -d' ' -f5
# ifconfig eth1|grep HWaddr|tr -s ' '|cut -d' ' -f5
  1. 将系统中所有普通用户的用户名、密码和默认shell保存到一个文件中,要求用户名密码和默认shell之间用tab键分割
[root@server shell01]# grep 'bash$' passwd |grep -v '^root'|cut -d: -f1,2,7|tr ':' '\t'
stu1    x       /bin/bash
code    x       /bin/bash
kefu    x       /bin/bash
kefu1   x       /bin/bash
kefu2   x       /bin/bash
user01  x       /bin/bash
stu2    x       /bin/bash
[root@server shell01]# grep bash$ passwd |grep -viE 'root|mysql'|cut -d: -f1,2,7|tr ':' '\t' |tee a.txt

注释:
-E 匹配扩展正则表达式,|代表或者,是一个扩展正则

2. 编程语言分类

  • 编译型语言:

程序在执行之前需要一个专门的编译过程,把程序编译成为机器语言文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如C、C++

  • 解释型语言:

​ 程序不需要编译,程序在运行时由**解释器**翻译成机器语言,每执行一次都要翻译一次。因此效率比较低。比如Python/JavaScript/ Perl /ruby/Shell等都是解释型语言。

在这里插入图片描述

  • 总结:

编译型语言比解释型语言速度较快,但是不如解释型语言跨平台性好。如果做底层开发或者大型应用程序或者操作系开发一般都用编译型语言;如果是一些服务器脚本及一些辅助的接口,对速度要求不高、对各个平台的兼容性有要求的话则一般都用解释型语言

3. shell介绍

在这里插入图片描述

总结:

  • shell就是人机交互的一个桥梁
  • shell的种类
[root@MissHou ~]# cat /etc/shells 
/bin/sh			#是bash shell的一个快捷方式
/bin/bash		#bash shell是大多数Linux默认的shell,包含的功能几乎可以涵盖shell所有的功能
/sbin/nologin	#表示非交互,不能登录操作系统
/bin/dash		#小巧,高效,功能相比少一些
/bin/tcsh		#是csh的增强版,完全兼容csh
/bin/csh		#具有C语言风格的一种shell,具有许多特性,但也有一些缺陷
  • 用户在终端(终端就是bash的接口)输入命令

    ​ |
    ​ bash //bash就是shell的一种类型(bash shell)
    ​ |
    ​ kernel
    ​ |
    ​ 物理硬件等

4. shell脚本

  • 什么是shell脚本?

    • 一句话概括

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

    • 准确叙述

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

  • 什么时候用到脚本?

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

    ①自动化分析处理

    ②自动化备份

    ③自动化批量部署安装

    ④等等…

  • 如何学习shell脚本?

  1. 尽可能记忆更多的命令
  2. 掌握脚本的标准的格式(指定魔法字节、使用标准的执行方式运行脚本)
  3. 必须熟悉掌握脚本的基本语法(重点)
  • 学习脚本的秘诀:

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

  • 脚本的基本写法:
#!/bin/bash
//脚本第一行, #!魔法字符,指定脚本代码执行的程序。即它告诉系统这个脚本需要什么解释器来执行,也就是使用哪一种Shell

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

//下面就是脚本的具体内容
commands
...
  • 脚本执行方法:

    • 标准脚本执行方法(建议):(魔法字节指定的程序会生效)
    [root@MissHou shell01]# cat 1.sh 
    #!/bin/bash
    #xxxx
    #xxx
    #xxx
    hostname
    date
    [root@MissHou shell01]# chmod +x 1.sh 
    [root@MissHou shell01]# ll
    total 4
    -rwxr-xr-x 1 root root 42 Jul 22 14:40 1.sh
    [root@MissHou shell01]# /shell/shell01/1.sh 
    MissHou.itcast.cc
    Sun Jul 22 14:41:00 CST 2018
    [root@MissHou shell01]# ./1.sh 
    MissHou.itcast.cc
    Sun Jul 22 14:41:30 CST 2018
    
    
    • 非标准的执行方法(不建议):(魔法字节指定的程序不会运作)
    [root@MissHou shell01]# bash 1.sh 
    MissHou.itcast.cc
    Sun Jul 22 14:42:51 CST 2018
    [root@MissHou shell01]# sh 1.sh
    MissHou.itcast.cc
    Sun Jul 22 14:43:01 CST 2018
    [root@MissHou shell01]# 
    [root@MissHou shell01]# bash -x 1.sh
    + hostname
    MissHou.itcast.cc
    + date
    Sun Jul 22 14:43:20 CST 2018
    
    -x:一般用于排错,查看脚本的执行过程
    -n:用来查看脚本的语法是否有问题
    
    注意:如果脚本没有加可执行权限,不能使用标准的执行方法执行,bash 1.sh
    
    其他:
    [root@server shell01]# source 2.sh
    server
    Thu Nov 22 15:45:50 CST 2018
    [root@server shell01]# . 2.sh
    server
    Thu Nov 22 15:46:07 CST 2018
    
    source 和 . 表示读取文件,执行文件里的命令
    

5. bash基本特性

5.1 命令和文件自动补全

Tab只能补全命令和文件 (RHEL6/Centos6)

5.2 常见的快捷键
^c   			终止前台运行的程序
^z	  			将前台运行的程序挂起到后台
^d   			退出 等价exit
^l   			清屏 
^a |home  	光标移到命令行的最前端
^e |end  	光标移到命令行的后端
^u   			删除光标前所有字符
^k   			删除光标后所有字符
^r	 			搜索历史命令
5.3 常用的通配符(重点)
*:	匹配0或多个任意字符
?:	匹配任意单个字符
[list]:	匹配[list]中的任意单个字符
[!list]: 匹配除list中的任意单个字符
{string1,string2,...}:匹配string1,string2或更多字符串

举例:
touch file{1..3}
touch file{1..13}.jpg
# ls file*
# ls *.jpg
# ll file?
# ll file?.jpg
# ll file??.jpg
# ll file1?.jpg
# ll file?.jpg
# ll file[1023].jpg
# ll file[0-13].jpg
# ll file1[0-9].jpg
# ll file[0-9].jpg
# ll file?[1-13].jpg
# ll file[1,2,3,10,11,12].jpg
# ll file1{11,10,1,2,3}.jpg
# ll file{1..10}.jpg
# ll file{1...10}.jpg
5.4 bash中的引号(重点)
  • 双引号"" :会把引号的内容当成整体来看待,允许通过$符号引用其他变量值
  • 单引号’’ :会把引号的内容当成整体来看待,禁止引用其他变量值,shell中特殊符号都被视为普通字符
  • 反撇号`` :反撇号和$()一样,引号或括号里的命令会优先执行,如果存在嵌套,反撇号不能用
[root@server dir1]# echo "$(hostname)"
server
[root@server dir1]# echo '$(hostname)'
$(hostname)
[root@server dir1]# echo "hello world"
hello world
[root@server dir1]# echo 'hello world'
hello world

[root@server dir1]# echo $(date +%F)
2018-11-22
[root@server dir1]# echo `echo $(date +%F)`
2018-11-22
[root@server dir1]# echo `date +%F`
2018-11-22
[root@server dir1]# echo `echo `date +%F``
date +%F
[root@server dir1]# echo $(echo `date +%F`)
2018-11-22

变量的定义

1. 变量的分类

  • 本地变量:当前用户自定义的变量。当前进程中有效,其他进程及当前进程的子进程无效。
  • 环境变量:当前进程有效,并且能够被子进程调用。
    • 查看当前用户的环境变量 env
    • 查询当前用户的所有变量(临时变量与环境变量) set
    • export //将当前变量变成环境变量
[root@MissHou tmp]# export A=hello		//临时将一个本地变量(临时变量)变成环境变量
[root@MissHou tmp]# env|grep ^A
A=hello

永久生效:
vim /etc/profile 或者 ~/.bashrc
export A=hello
或者
A=hello
export A

说明:系统中有一个变量PATH,环境变量
export PATH=/usr/local/mysql/bin:$PATH

  • 全局变量:全局所有的用户和程序都能调用,且继承,新建的用户也默认能调用.
$HOME/.bashrc     		当前用户的bash信息(aliase、umask等)
$HOME/.bash_profile  	当前用户的环境变量()
oracle——>oracle用户——>$oracle/.bash_profile——>export home_install=/u01/app/xxx

$HOME/.bash_logout  		每个用户退出当前shell时最后读取的文件

/etc/bashrc             使用bash shell用户全局变量
grep --color=auto
umask

/etc/profile   		   系统和每个用户的环境变量信息

mycat_home=/usr/local/mycat/bin
export mycat_home
执行mycat命令
# mycat
$ mycat

用户登录系统读取相关文件的顺序:
/etc/profile——>$HOME/.bash_profile——>$HOME/.bashrc——>/etc/bashrc——>$HOME/.bash_logout

source /etc/bashrc

  • 系统变量(内置bash中变量) : shell本身已经固定好了它的名字和作用.
$?:上一条命令执行后返回的状态,当返回状态值为0时表示执行正常,非0值表示执行异常或出错
 若退出状态值为0,表示命令运行成功
 若退出状态值为127,表示command not found
 若退出状态值为126,表示找到了该命令但无法执行(权限不够)
 若退出状态值为1&2,表示没有那个文件或目录
 
$$:当前所在进程的进程号     echo $$   eg:kill -9 `echo $$`  = exit   退出当前会话
$!:后台运行的最后一个进程号  (当前终端)  # gedit &
!$	调用最后一条命令历史中的参数
!!	调用最后一条命令历史


$#:脚本后面接的参数的个数
$*:脚本后面所有参数,参数当成一个整体输出,每一个变量参数之间以空格隔开
$@: 脚本后面所有参数,参数是独立的,也是全部输出

$0:当前执行的进程/程序名  echo $0	    
$1~$9 位置参数变量
${10}~${n} 扩展位置参数变量  第10个位置变量必须用{}大括号括起来
./1.sh a b c

[root@MissHou shell01]# cat 2.sh 
#!/bin/bash
#xxxx
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 shell01]# bash 3.sh a b c
a
b
c
======我是分割线=======
a b c

2. 什么时候需要定义变量?

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

3. 变量的定义规则

1. 默认情况下,shell里定义的变量是不分类型的,可以给变量赋与任何类型的值;等号两边不能有空格,对于有空格的字符串做为赋值时,要用引号引起来
变量名=变量值

2. 变量的获取方式:	$变量名     ${变量名}	
[root@MissHou shell01]# a=12345678
[root@MissHou shell01]# echo $a
12345678
[root@MissHou shell01]# echo ${a}
12345678
[root@MissHou shell01]# echo ${a:2:3}		a表示变量名;2表示从第3个字符开始;3表示后面3个字符
345

如果获取变量的全部两个都可以;如果获取变量的某一部分,用${}

3. 取消变量:     unset  变量名

4. 变量名区分大小写,同名称但大小写不同的变量名是不同的变量
5. 变量名可以是字母或数字或下划线,但是不能以数字开头或者特殊字符
[root@MissHou shell01]# 1a=hello
-bash: 1a=hello: command not found
[root@MissHou shell01]# ?a=hello
-bash: ?a=hello: command not found
[root@MissHou shell01]# _a=hello
[root@MissHou shell01]# echo $_a
hello

6. 命令的执行结果可以保存到变量
[root@server shell01]# kernel=`uname -r`
[root@server shell01]# echo $kernel
2.6.32-431.el6.x86_64
[root@server shell01]# name=$(uname -n)
[root@server shell01]# echo $name
server.itcast.cc

7. 有类型变量 declare
-i 将变量看成整数 
-r 使变量只读  readonly
-x 标记变量通过环境导出  export
-a	指定为索引数组(普通数组);查看普通数组
-A 指定为关联数组;查看关联数组

[root@server shell01]# a=10
[root@server shell01]# b=20
[root@server shell01]# echo $a+$b
10+20

[root@server shell01]# declare -i a=2
[root@server shell01]# declare -i b=4
[root@server shell01]# declare -i c=$a+$b
[root@server shell01]# echo $c
6

[root@server shell01]# AAAA=hello
[root@server shell01]# export AAAA
[root@server shell01]# env|grep AAAA
AAAA=hello
[root@server shell01]# declare -x BBBB=hello
[root@server shell01]# env|grep BBBB
BBBB=hello


8. 数组
普通数组:只能使用整数作为数组索引(元素的下标)
关联数组:可以使用字符串作为数组索引(元素的下标)

普通数组定义:用括号来表示数组,数组元素(变量)用“空格”符号分割开。定义数组的一般形式为:
一次赋一个值:
变量名=变量值
array[0]=v1
array[1]=v2
array[3]=v3
一次赋多个值:
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)

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


[root@server shell01]# array[0]=var1
[root@server shell01]# array[1]=var2
[root@server shell01]# array[2]=var3
[root@server shell01]# array1=(uu1 uu2 uu3 uu4)
[root@server shell01]# ls
1.sh  2.sh  3.sh  4.sh  passwd
[root@server shell01]# array2=(`ls ./`)
[root@server shell01]# array3=(jack harry "Miss Hou" [5]=tom)

查看普通数组信息:
[root@server shell01]# declare -a
declare -a array='([0]="var1" [1]="var2" [2]="var3")'
declare -a array1='([0]="uu1" [1]="uu2" [2]="uu3" [3]="uu4")'
declare -a array2='([0]="1.sh" [1]="2.sh" [2]="3.sh" [3]="4.sh" [4]="passwd")'
declare -a array3='([0]="jack" [1]="harry" [2]="Miss Hou" [5]="tom")'
[root@server shell01]# 
[root@server shell01]# 
[root@server shell01]# echo ${array[*]}
var1 var2 var3
[root@server shell01]# echo ${array[@]}
var1 var2 var3
[root@server shell01]# echo ${array[2]}
var3
[root@server shell01]# echo ${array2[@]}
1.sh 2.sh 3.sh 4.sh passwd
[root@server shell01]# echo ${array2[3]}
4.sh
[root@server shell01]# 
[root@server shell01]# echo ${array2[*]:2:2}
3.sh 4.sh
[root@server shell01]# echo ${#array2[*]}
5
[root@server shell01]# echo ${!array2[*]}
0 1 2 3 4
[root@server shell01]# echo ${!array3[*]}
0 1 2 5


关联数组定义:
首先声明关联数组
declare -A asso_array1
declare -A asso_array2
declare -A asso_array3

数组赋值:
一次赋一个值:
数组名[索引|下标]=变量值
[root@server ~]# asso_array1[linux]=one
[root@server ~]# asso_array1[java]=two
[root@server ~]# asso_array1[php]=three
一次赋多个值:
[root@server ~]# asso_array2=([name1]=harry [name2]=jack [name3]=amy [name4]="Miss Hou")
查看关联数组:
[root@server ~]# 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" )'

[root@server ~]# echo ${asso_array1[linux]}
one
[root@server ~]# echo ${asso_array1[php]}
three
[root@server ~]# echo ${asso_array1[*]}
three two one
[root@server ~]# echo ${!asso_array1[*]}
php java linux
[root@server ~]# echo ${#asso_array1[*]}
3
[root@server ~]# echo ${#asso_array2[*]}
4
[root@server ~]# echo ${!asso_array2[*]}
name3 name2 name1 name4


9. 交互式定义变量的值 read    主要用于让用户去定义变量值
-p 提示信息
-n 字符数 (限制变量值的字符数)
-s 不显示   
-t 超时(默认单位秒)(限制用户输入变量值的超时时间)

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

[root@MissHou shell01]# read -p "Input your IP and Netmask:" ip mask < 1.txt 
[root@MissHou shell01]# echo $ip
10.1.1.1
[root@MissHou shell01]# echo $mask
255.255.255.0


10. 其他变量(扩展)
1)取出一个目录下的目录和文件:dirname和 basename 
2)变量"内容"的删除和替换
一个“%”代表从右往左去掉一个/key/
两个“%%”代表从右往左最大去掉/key/
一个“#”代表从左往右去掉一个/key/
两个“##”代表从左往右最大去掉/key/

# A=/root/Desktop/shell/mem.txt 
# echo $A
/root/Desktop/shell/mem.txt
# dirname $A   取出目录
/root/Desktop/shell
# basename $A  取出文件
mem.txt

# url=www.taobao.com
# echo ${#url}		     获取变量的长度
# echo ${url#*.}
# echo ${url##*.}
# echo ${url%.*}
# echo ${url%%.*}

++++++++++++++++++++++++++++++++++++++++++++++++++
以下内容自己完成:
替换:///
 1015  echo ${url/ao/AO}
 1017  echo ${url//ao/AO}   贪婪替换
 
替代: - 和 :-  +和:+
 1019  echo ${abc-123}
 1020  abc=hello
 1021  echo ${abc-444}
 1022  echo $abc
 1024  abc=
 1025  echo ${abc-222}

${变量名-新的变量值} 或者 ${变量名=新的变量值}
变量没有被赋值:会使用“新的变量值“ 替代
变量有被赋值(包括空值): 不会被替代

 1062  echo ${ABC:-123}
 1063  ABC=HELLO
 1064  echo ${ABC:-123}
 1065  ABC=
 1066  echo ${ABC:-123}

${变量名:-新的变量值} 或者 ${变量名:=新的变量值}
变量没有被赋值或者赋空值:会使用“新的变量值“ 替代
变量有被赋值: 不会被替代

 1116  echo ${abc=123}
 1118  echo ${abc:=123}

[root@server ~]# unset abc
[root@server ~]# echo ${abc:+123}

[root@server ~]# abc=hello
[root@server ~]# echo ${abc:+123}
123
[root@server ~]# abc=
[root@server ~]# echo ${abc:+123}

${变量名+新的变量值}
变量没有被赋值或者赋空值:不会使用“新的变量值“ 替代
变量有被赋值: 会被替代
[root@server ~]# unset abc
[root@server ~]# echo ${abc+123}

[root@server ~]# abc=hello
[root@server ~]# echo ${abc+123}
123
[root@server ~]# abc=
[root@server ~]# echo ${abc+123}
123
${变量名:+新的变量值}
变量没有被赋值:不会使用“新的变量值“ 替代
变量有被赋值(包括空值): 会被替代

[root@server ~]# unset abc
[root@server ~]# echo ${abc?123}
-bash: abc: 123

[root@server ~]# abc=hello
[root@server ~]# echo ${abc?123}
hello
[root@server ~]# abc=
[root@server ~]# echo ${abc?123}

${变量名?新的变量值}
变量没有被赋值:提示错误信息
变量被赋值(包括空值):不会使用“新的变量值“ 替代

[root@server ~]# unset abc
[root@server ~]# echo ${abc:?123}
-bash: abc: 123
[root@server ~]# abc=hello
[root@server ~]# echo ${abc:?123}
hello
[root@server ~]# abc=
[root@server ~]# echo ${abc:?123}
-bash: abc: 123

${变量名:?新的变量值}
变量没有被赋值或者赋空值时:提示错误信息
变量被赋值:不会使用“新的变量值“ 替代

说明:?主要是当变量没有赋值提示错误信息的,没有赋值功能

简单的四则运算

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

+ - * /  %(取模,求余数) 
Bash shell 的算术运算有四种方式:
1. 使用 $(( ))
2. 使用$[ ]
3. 使用 expr 外部程式
4. 使用let 命令

注意:
n=1
let n+=1  等价于let n=n+1



思考:能不能用shell做小数运算?
[root@server shell01]# echo 1+1.5|bc
2.5


i++ 和 ++i (了解)
对变量的值的影响:
[root@node1 ~]# i=1
[root@node1 ~]# let i++
[root@node1 ~]# echo $i
2
[root@node1 ~]# j=1
[root@node1 ~]# let ++j
[root@node1 ~]# echo $j
2

对表达式的值的影响:
[root@node1 ~]# unset i j
[root@node1 ~]# i=1;j=1
[root@node1 ~]# let x=i++         先赋值,再运算
[root@node1 ~]# let y=++j         先运算,再赋值
[root@node1 ~]# echo $i
2
[root@node1 ~]# echo $j
2
[root@node1 ~]# echo $x
1
[root@node1 ~]# echo $y
2


总结:

$(())  $[]
expr 注意空格,*要进行转义 \

let n+=1  等价   let n=n+1
let n=n**5		n有初值,然后求次幂

i++	++i	
对变量本身没有影响(自己+1);
表达式中有影响;i++ 先赋值再运算  ++i先运算再赋值
let x=i++   let x=++i

条件判断

1. 语法格式

  • 格式1: test 条件表达式
  • 格式2: [ 条件表达式 ]
  • 格式3: [[ 条件表达式 ]] 支持正则 =~

说明:

man test去查看,很多的参数都用来进行条件判断

2. 条件判断相关参数

  • 与文件存在与否的判断
-e	是否存在   不管是文件还是目录,只要存在,条件就成立
-f	是否为普通文件
-d	是否为目录
-S	socket
-p	pipe
-c	character
-b	block
-L	软link

三种语法格式:
test -e file					只要文件存在条件为真
[ -d /shell01/dir1 ]		 	判断目录是否存在,存在条件为真
[ ! -d /shell01/dir1 ]		判断目录是否存在,不存在条件为真
[[ -f /shell01/1.sh ]]		判断文件是否存在,并且是一个普通的文件

-s 判断文件是否有内容(大小),非空文件条件满足
说明:-s表示非空,! -s 表示空文件
说明:1.sh文件里有内容的。
[root@server shell01]# test -s 1.sh
[root@server shell01]# echo $?
0
[root@server shell01]# touch aaa
[root@server shell01]# cat aaa
[root@server shell01]# test -s aaa
[root@server shell01]# echo $?
1
[root@server shell01]# test ! -s aaa
[root@server shell01]# echo $?
0
[root@server shell01]# test ! -s 1.sh
[root@server shell01]# echo $?
1

  • 文件权限相关的判断
-r	当前用户对其是否可读
-w	当前用户对其是否可写
-x	当前用户对其是否可执行
-u	是否有suid
-g	是否sgid
-k	是否有t位

  • 两个文件的比较判断
file1 -nt  file2	比较file1是否比file2新	
file1 -ot  file2 	比较file1是否比file2旧
file1 -ef  file2	比较是否为同一个文件,或者用于判断硬连接,是否指向同一个inode

test file1 -nt file2	
[ file1 -ot file2 ]
  • 整数之间的判断
-eq	相等
-ne	不等
-gt	大于
-lt	小于
-ge   大于等于
-le	小于等于
  • 字符串之间的判断
-z  是否为空字符串   		字符串长度为0,就成立
-n  是否为非空字符串    	只要字符串非空,就是成立
string1 = string2 		是否相等
string1 != string2 		不等

[root@server shell01]# AAA=hello
[root@server shell01]# BBB=world
[root@server shell01]# test -z $AAA
[root@server shell01]# echo $?
1
[root@server shell01]# test -n $AAA
[root@server shell01]# echo $?
0

[root@server shell01]# [ $AAA = $BBB ]
[root@server shell01]# echo $?
1
[root@server shell01]# [ $AAA != $BBB ]
[root@server shell01]# echo $?
0

  • 多重条件判断
逻辑判断符号:
 -a     和 &&  	(and 逻辑与) 		两个条件同时满足,整个大条件为真
 -o     和 ||	(or 逻辑或)		  	两个条件满足任意一个,整个大条件为真

 
[ 1 -eq 1 -a 1 -ne 0 ]				整个表达式为真
[ 1 -eq 1 ] && [ 1 -ne 0 ]			


[ 1 -eq 1 -o 1 -ne 1 ]				整个表达式为真
[ 1 -eq 1 ] || [ 1 -ne 1 ]

[root@server shell01]# [ 1 -eq 0 ] && echo true || echo false
false
[root@server shell01]# [ 1 -eq 1 ] && echo true || echo false
true

&&:前面的表达式为真
||:前面的表达式为假



总结:
1、; && ||都可以用来分割命令或者表达式
2、; 完全不考虑前面的语句是否正确执行,都会执行;号后面的内容
3、&& 需要考虑&&前面的语句的正确性,前面语句正确执行才会执行&&后的内容;反之亦然
make && make install
4、|| 需要考虑||前面的语句的非正确性,前面语句执行错误才会执行||后的内容;反之亦然
5、如果&&和||一起出现,从左往右依次看,按照以上原则


3. 示例

示例:
数值比较:
[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
[stu1@server ~]$ 


类C风格的数值比较:
注意:在(( ))中,=表示赋值;==表示判断
 1159  ((1==2));echo $?
 1160  ((1<2));echo $?
 1161  ((2>=1));echo $?
 1162  ((2!=1));echo $?
 1163  ((`id -u`==0));echo $?
 
 1209  ((a=123));echo $a
 1210  unset a
 1211  ((a==123));echo $?
 


字符串比较:
注意:双引号引起来,看作一个整体;= 和 == 在 [ 字符串 ] 比较中都表示判断
 1196  a='hello world';b=world
 1197  [ $a = $b ];echo $?
 1198  [ "$a" = "$b" ];echo $?
 1199  [ "$a" != "$b" ];echo $?
 1200  [ "$a" !== "$b" ];echo $?        错误
 1201  [ "$a" == "$b" ];echo $?
 1202  test "$a" != "$b";echo $?



思考:[ ] 和 [[ ]] 有什么区别?

 1213  a=
 1214  test -z $a;echo $?
 1215  a=hello
 1216  test -z $a;echo $?
 1217  test -n $a;echo $?
 1217  test -n "$a";echo $?

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


 1278  [ 1 -eq 0 -a 1 -ne 0 ];echo $?
 1279  [ 1 -eq 0 && 1 -ne 0 ];echo $?
 1280  [[ 1 -eq 0 && 1 -ne 0 ]];echo $?


第二章节 SHELL编程之流程控制和循环语句

一、流程控制语句

1. 基本语法结构

F: false 假

T: true 真

if [ condition ];then
		command
		command
fi

[ 条件 ] && command

在这里插入图片描述

if [ condition ];then
		command1
	else
		command2
fi

[ 条件 ] && command1 || command2

在这里插入图片描述

if [ condition1 ];then
		command1  结束
	elif [ condition2 ];then
		command2   结束
	else
		command3
fi
注释:
如果条件1满足,执行命令1后结束;如果条件1不满足,再看条件2,如果条件2满足执行命令2后结束;如果条件1和条件2都不满足执行命令3结束.

在这里插入图片描述

if [ condition1 ];then
		command1		
		if [ condition2 ];then
			command2
		fi
	else
		if [ condition3 ];then
			command3
		elif [ condition4 ];then
			command4
		else
			command5
		fi
fi
注释:
如果条件1满足,执行命令1;如果条件2也满足执行命令2,如果不满足就只执行命令1结束;
如果条件1不满足,不看条件2;直接看条件3,如果条件3满足执行命令3;如果不满足则看条件4,如果条件4满足执行命令4;否则执行命令5

在这里插入图片描述

2. 应用案例

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

思路:
1. 使用哪个命令实现 ping -c
2. 根据命令的执行结果状态来判断是否通		$?
3. 根据逻辑和语法结构来编写脚本(条件判断或者流程控制)
步骤:
vim ping.sh
#!/bin/bash
# Name:ping.sh
# Path:/shell02/
# Usage:/shell02/ping.sh
# ...

#获取远程主机的IP地址(定义变量让用户自己输入)
read -p "请输入你要ping的远程主机IP:" IP
#使用ping命令来判断是否和远程主机互通
ping -c1 $IP &>/dev/null
if [ $? -eq 0 ];then
	echo "当前主机和远程主机$IP是互通的。"	
else
	echo "当前主机和远程主机$IP是不通的。"
fi
或者

#!/bin/bash
# Name:ping.sh
# Path:/shell02/
# Usage:/shell02/ping.sh
# ...

#使用ping命令来判断是否和远程主机互通
if [ $# -ne 1 ];then
        echo "Usage:$0 remote_ip" && exit

fi
或者
[ $# -ne 1 ] && echo "Usage:$0 remote_ip" && exit

ping -c1 $1 &>/dev/null
[ $? -eq 0 ] && echo "当前主机和远程主机$1是互通的。" ||echo "当前主机和远程主机$1是不通的。"

说明:exit直接退出程序

需求2:判断一个进程是否存在

思路:
1.查看进程的相关命令 ps -ef  pgrep  ps auxf pidof
2.根据命令的返回状态值来判断进程是否存在  $?
3.根据逻辑用脚本语言实现

#!/bin/bash
# Name:process.sh
# Path:/shell02/
# Usage:/shell02/process.sh
# Describe:判断一个进程是否存在

# 定义变量
read -p "请输入需要判断的进程名(httpd):" process
# 通过命令来查看进程是否存在
pgrep $process &>/dev/null
# 通过命令执行的状态来判断是否存在
if [ $? -eq 0 ];then
	echo "进程$process存在"
else
	echo "进程$process不存在"
fi
或者
[ $? -eq 0 ] && echo "进程$process存在" || echo "进程$process不存在"


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

需求3:判断一个服务是否正常(以httpd为例):

思路:

  1. 可以判断进程是否存在,用/etc/init.d/httpd status判断状态等方法
  2. 最好的方法是直接去访问一下,通过访问成功和失败的返回值来判断
#!/bin/bash
wget http://10.1.1.2 &>/dev/null
[ $? -eq 0 ] && echo "该web服务是正常的" && rm -f /shell/shell01/index.* || echo "该web服务异常请检查"


3. 课堂练习

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

read -p "请输入需要判断的用户名:" user
id $user &>/dev/null
test $? -eq 0 && echo "该$user存在" || echo "该$user不存在"

2、判断vsftpd软件包是否安装,如果没有则自动安装(yum源已配好)

#!/bin/bash
rpm -q vsftpd &> /dev/null
if [ $? -eq 0 ];then
        echo "vsftpd已经安装"
else
        echo "该软件包没有安装,正在安装...."
        yum install -y vsftpd &> /dev/null

        if [ $? -eq 0 ];then
                echo "vsftpd安装成功"
        else
                echo "vsftpd安装失败"
        fi
fi

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 '不符合要求'

4、判断ftp服务是否已启动,如果已启动输出以下信息:

vsftpd服务器已启动…
vsftpd监听的端口是:
vsftpd的进程PID是:

参考1:
#!/bin/bash
service vsftpd status &>/dev/null
if [ $? -eq 0 ];then
	port=`netstat -tnltp|grep vsftpd|cut -d: -f2|cut -d' ' -f1`
	pid=`pgrep -l vsftpd|cut -d ' ' -f1`
	echo -e "vsftpd服务器已启动...\nvsftpd监听的端口是:$port\nvsftpd的进程PID是:$pid"
else
	service vsftpd start &>/dev/null
	port=`netstat -tnltp|grep vsftpd|cut -d: -f2|cut -d' ' -f1`
   pid=`pgrep -l vsftpd|cut -d ' ' -f1`
   echo -e "vsftpd服务器已启动...\nvsftpd监听的端口是:$port\nvsftpd的进程PID是:$pid"
fi


参考2:
[root@server shell02]# cat liufeng.sh 
#!/bin/bash
service $1 status
if [ $? -eq 0 ];then
        echo " '$1'服务器已启动..."
        a=$( netstat -tnulp | grep $1 )
        array=($a)
        echo "$1的监听端口是:$(echo ${array[3]} | cut -d: -f2) "
        echo "$1的进程ID为:$(echo ${array[6]}| cut -d/ -f1)"
else
        echo "$1进程未启动!"
fi

参考3:
vim /lt/2.sh
#! /bin/bash
duankou=`netstat -ntlp|grep vsftpd|cut -d: -f2|cut -d" " -f1`
pid=`pgrep -o vsftpd`

vim 1.sh
pgrep -l vsftpd >/dev/null
if [ $? -eq 0 ];then
        echo "vsftpd服务器已启动..."
        echo "vsftpd监听的端口是:$duankou"
        echo "vsftpd的进程PID是:$pid"
else
        echo "vsftpd服务器没启动"
        service vsftpd start
        source /lt/2.sh
fi

二、循环语句

1. for循环
1.1 语法结构
  • 列表循环

列表for循环:用于将一组命令执行已知的次数,下面给出了for循环语句的基本格式:

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

语法结构举例说明:

 1045  for var in {1..10};do echo $var;done
 1046  for var in 1 2 3 4 5;do echo $var;done
 1047  for var in `seq 10`;do echo $var;done
 1048  for var in $(seq 10);do echo $var;done
 1049  for var in {0..10..2};do echo $var;done
 1050  for var in {2..10..2};do echo $var;done
 1051  for var in {10..1};do echo $var;done
 1052  for var in {10..1..-2};do echo $var;done
 1055  for var in `seq 10 -2 1`;do echo $var;done

  • 1.2 不带列表循环

不带列表的for循环执行时由用户指定参数和参数的个数,下面给出了不带列表的for循环的基本格式:

for variable
    do
        command 
        command
        …
   done

语法结构举例说明:

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

echo "脚本后面有$#个参数"

  • 1.3 类C风格的for循环
for(( expr1;expr2;expr3 ))
	do
		command
		command
		…
	done
for (( i=1;i<=5;i++))
	do
		echo $i
	done


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

语法结构举例说明:

 1068  for ((i=1;i<=5;i++));do echo $i;done
 1069  for ((i=1;i<=10;i+=2));do echo $i;done
 1070  for ((i=2;i<=10;i+=2));do echo $i;done

1.2 举例说明

**例1:**计算1到100的奇数之和,方法不止一种

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

#!/bin/bash
#定义一个变量来保存奇数的和
sum=0
#打印1-100的奇数并且相加重新赋值给sum
for i in {1..100..2}
do
	sum=$[ $i + $sum ]
done
#打印1-100的奇数和
echo "1-100的奇数和为:$sum"

#!/bin/bash
#定义一个变量来保存奇数的和
sum=0
#打印1-100的奇数并且相加重新赋值给sum
for (( i=1;i<=100;i+=2))
do
	let sum=sum+$i
	或者
	let sum=sum+i
	或者
	let sum=$sum+$i
done
#打印1-100的奇数和
echo "1-100的奇数和为:$sum"


#!/bin/bash
sum=0
for ((i=1;i<=100;i++))
do
	if [ $[$i%2] -ne 0 ];then
	let sum=sum+$i
	fi
done
echo "1-100的奇数和是:$sum"


#!/bin/bash
sum=0
for ((i=1;i<=100;i++))
do
	[ $[$i%2] -eq 0 ] && true  || let sum=sum+$i
done
echo "1-100的奇数和是:$sum"


延伸:
true	真
:		真
false	 假



方法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"

循环控制:

循环体: do…done之间的内容

  • continue:继续;表示循环体内下面的代码不执行,重新开始下一次循环
  • break:打断;马上停止执行本次循环,执行循环体后面的代码
  • exit:表示直接跳出程序
[root@server ~]# cat for5.sh 
#!/bin/bash
for i in {1..5}
do
	test $i -eq 2 && break || touch /tmp/file$i
done
echo hello hahahah

**例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

思路:
0. 让用户输入一个数,保存到一个变量里   read num
1、如果能被其他数整除就不是质数——>$num%$i 是否等于0	$i=2~$num-1
2、如果输入的数是1或者2取模根据上面判断又不符合,所以先排除1和2
3、测试序列从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

bash -x for6.sh

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

思路:
1. 添加用户的命令 useradd -G
2. 判断class组是否存在	grep -w class /etc/groupecho $?
3. 根据题意,判断该脚本循环5次来添加用户	for循环
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

1.3 课堂练习
  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

  1. 写一个脚本,局域网内,把能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

并行执行:
{程序}&表示将程序放到后台并行执行,如果需要等待程序执行完毕再进行下面内容,需要加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

发送邮件参考博客:https://blog.csdn.net/whandgdh/article/details/127425496

3、输入一个年份,判断是否是润年(能被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
1.4 总结
  • FOR循环语法结构
  • FOR循环可以结合条件判断和流程控制语句
    • do …done 循环体
    • 循环体里可以是命令集合,再加上条件判断以及流程控制
  • 控制循环语句
    • continue 继续,跳过本次循环,继续下一次循环
    • break 打断,跳出循环,执行循环体外的代码
    • exit 退出,直接退出程序
2. while循环

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

2.1 语法结构
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
2.2 举例说明

需求:用while循环计算1-50的偶数和

#!/bin/bash
#定义变量
sum=0
i=2
#循环打印1-50的偶数和并且计算后重新赋值给sum
while [ $i -le 50 ]
do
	let sum=sum+i
	let i+=2
done
#打印sum的值
echo "1-50的偶数和为:$sum"
2.3 应用案例

需求:

写一个30秒同步一次时间,向同步服务器10.1.1.250的脚本,如果同步失败,则进行邮件报警,每次失败都报警;同步成功,也进行邮件通知,但是成功100次才通知一次。

分析:

  • 每个30s同步一次时间,该脚本是一个死循环
    • while true;do 同步时间,然后休息30s(sleep 30)done
  • 同步失败发送邮件
    • 在do…done循环体之间加if…else…(判断同步失败还是成功)
  • 同步成功100次发送邮件
    • 统计成功次数——>count=0——>成功1次加+1——>let count++
#!/bin/bash
#定义变量
count=0
ntp_server=10.1.1.250
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

以上脚本还有更多的写法,课后自己完成
3. until循环
3.1 语法结构

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

until expression   [ 1 -eq 1 ]  (( 1 >= 1 ))
	do
		command
		command
		...
	done
	
i=1
while [ $i -le 5 ]
do
	echo $i
	let i++
done

i=1
until [ $i -gt 5 ]
do
	echo $i
	let i++
done
3.2 举例说明

使用until语句批量创建10个用户,要求stu1—stu5用户的UID分别为1001—1005;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编程之嵌套循环+随机数及综合案例

一、随机数

bash默认有一个$RANDOM的变量		默认是0~32767。使用set |grep RANDOM	查看上一次产生的随机数
echo $RANDOM

产生0~1之间的随机数
echo $[$RANDOM%2]

产生0~2之间的随机数
echo $[$RANDOM%3]

产生0~3之间的随机数
echo $[$RANDOM%4]
。。。。
产生0~9内的随机数
echo $[$RANDOM%10]

产生0~100内的随机数
echo $[$RANDOM%101]


产生50-100之内的随机数
echo $[$RANDOM%51+50]

产生三位数的随机数
echo $[$RANDOM%900+100]
实战案例1
  1. 写一个脚本,产生一个phonenum.txt文件,随机产生以139开头的手机号1000个,每个一行。
分析:
1. 产生1000个电话号码,脚本需要循环1000次
2. 139+8位,后8位随机产生,可以让每一位数字都随机产生,$[RANDOM%10] 0-9
3. 将随机产生的数字分别保存到变量里,然后加上139保存到文件里

#!/bin/bash
# random phonenum
# 循环1000次产生电话号码并保存到文件
for i in {1..1000}
do
	n1=$[RANDOM%10]
	n2=$[RANDOM%10]
	n3=$[RANDOM%10]
	n4=$[RANDOM%10]
	n5=$[RANDOM%10]
	n6=$[RANDOM%10]
	n7=$[RANDOM%10]
	n8=$[RANDOM%10]
	echo "139$n1$n2$n3$n4$n5$n6$n7$n8" >> phonenum.txt
done

#!/bin/bash
# random phonenum
# 循环1000次产生电话号码
for ((i=1;i<=1000;i++))
do
	n1=$[$RANDOM%10]
	n2=$[$RANDOM%10]
	n3=$[$RANDOM%10]
	n4=$[$RANDOM%10]
	n5=$[$RANDOM%10]
	n6=$[$RANDOM%10]
	n7=$[$RANDOM%10]
	n8=$[$RANDOM%10]
	echo "139$n1$n2$n3$n4$n5$n6$n7$n8" >> phonenum.txt
done

#!/bin/bash
i=1
while [ $i -le 1000 ]
do
	n1=$[$RANDOM%10]
	n2=$[$RANDOM%10]
	n3=$[$RANDOM%10]
	n4=$[$RANDOM%10]
	n5=$[$RANDOM%10]
	n6=$[$RANDOM%10]
	n7=$[$RANDOM%10]
	n8=$[$RANDOM%10]
	echo "139$n1$n2$n3$n4$n5$n6$n7$n8" >> phonenum.txt
	let i++
done

continue:继续,跳过本次循环,执行下一次循环
break:打断,执行循环体外的代码do..done外
exit:退出程序


#!/bin/bash
for i in {1..1000}
do
	n1=$[$RANDOM%10]
	n2=$[$RANDOM%10]
	n3=$[$RANDOM%10]
	n4=$[$RANDOM%10]
	n5=$[$RANDOM%10]
	n6=$[$RANDOM%10]
	n7=$[$RANDOM%10]
	n8=$[$RANDOM%10]
	echo "139$n1$n2$n3$n4$n5$n6$n7$n8" >> phonenum.txt
done

#!/bin/bash
#create phone num file
for ((i=1;i<=1000;i++))
do
	n1=$[$RANDOM%10]
	n2=$[$RANDOM%10]
	n3=$[$RANDOM%10]
	n4=$[$RANDOM%10]
	n5=$[$RANDOM%10]
	n6=$[$RANDOM%10]
	n7=$[$RANDOM%10]
	n8=$[$RANDOM%10]
	echo "139$n1$n2$n3$n4$n5$n6$n7$n8" |tee -a phonenum.txt
done

#!/bin/bash
count=0
while true
do
	n1=$[$RANDOM%10]
	n2=$[$RANDOM%10]
	n3=$[$RANDOM%10]
	n4=$[$RANDOM%10]
	n5=$[$RANDOM%10]
	n6=$[$RANDOM%10]
	n7=$[$RANDOM%10]
	n8=$[$RANDOM%10]
	echo "139$n1$n2$n3$n4$n5$n6$n7$n8" |tee -a phonenum.txt && let count++
	if [ $count -eq 1000 ];then
		break
	fi
done
  1. 在上面的1000个手机号里抽奖5个幸运观众,显示出这5个幸运观众。但只显示头3个数和尾号的4个数,中间的都用*代替

思路:

  • 确定幸运观众所在的行 随机生成 RANDOM $[RANDOM%1000+1]
  • 将电话号码提取出来 head 和 tail
  • 显示前3个和后4个数到屏幕 最后将电话号码输出到屏幕 echo ${电话号码部分}
#!/bin/bash
#定义变量
phone=/shell04/phonenum.txt
for ((i=1;i<=5;i++))
do
	#定位幸运观众所在行号
	line=`wc -l $phone |cut -d' ' -f1`
	luck_line=$[RANDOM%$line+1]
	#取出幸运观众所在行的电话号码
	luck_num=`head -$luck_line $phone|tail -1`
	#显示到屏幕
	echo "139****${luck_num:7:4}"
	echo $luck_num >> luck.txt
	#删除已经被抽取的幸运观众号码
	sed -i "/$luck_num/d" $phone
done



#!/bin/bash
file=/shell04/phonenum.txt
for i in {1..5}
do
	file_num=`wc -l $file |cut -d' ' -f1`
	line=`echo $[$RANDOM%$file_num+1]`
	luck=`head -n $line  $file|tail -1`
	echo "139****${luck:7:4}" && echo $luck >> /shell04/luck_num.txt
done


#!/bin/bash
for ((i=1;i<=5;i++))
do
file=phonenum.txt
line=`cat phonenum.txt |wc -l`	1000
luckline=$[$RANDOM%$line+1]
phone=`cat $file|head -$luckline|tail -1`
echo "幸运观众为:139****${phone:7:4}"
done


或者
#!/bin/bash
# choujiang
phone=phonenum.txt
for ((i=1;i<=5;i++))
do
	num=`wc -l phonenum.txt |cut -d' ' -f1`
	line=`echo $[$RANDOM%$num+1]`
	luck=`head -$line $phone |tail -1`
	sed -i "/$luck/d" $phone
	echo "幸运观众是:139****${luck:7:4}"
done

  1. 批量创建5个用户,每个用户的密码为一个随机数

思路:

  • 循环5次创建用户
  • 产生一个密码文件来保存用户的随机密码
  • 从密码文件中取出随机密码赋值给用户
#!/bin/bash
#crate user and set passwd
#产生一个保存用户名和密码的文件
echo user0{1..3}:itcast$[$RANDOM%9000+1000]#@~|tr ' ' '\n'>> user_pass.file
#循环创建5个用户
for ((i=1;i<=5;i++))
do
	user=`head -$i user_pass.file|tail -1|cut -d: -f1`
	pass=`head -$i user_pass.file|tail -1|cut -d: -f2`
	useradd $user
	echo $pass|passwd --stdin $user
done

或者
for i in `cat user_pass.file`
do
	user=`echo $i|cut -d: -f1`
	pass=`echo $i|cut -d: -f2`
	useradd $user
	echo $pass|passwd --stdin $user
done

#!/bin/bash
#crate user and set passwd
#产生一个保存用户名和密码的文件
echo user0{1..3}:itcast$[$RANDOM%9000+1000]#@~|tr ' ' '\n'|tr ':' ' ' >> user_pass.file
#循环创建5个用户
while read user pass
do
useradd $user
echo $pass|passwd --stdin $user
done < user_pass.file


pwgen工具产生随机密码:
[root@server shell04]# pwgen -cn1 12
Meep5ob1aesa
[root@server shell04]# echo user0{1..3}:$(pwgen -cn1 12)
user01:Bahqu9haipho user02:Feiphoh7moo4 user03:eilahj5eth2R
[root@server shell04]# echo user0{1..3}:$(pwgen -cn1 12)|tr ' ' '\n'
user01:eiwaShuZo5hi
user02:eiDeih7aim9k
user03:aeBahwien8co

二、嵌套循环

一个循环体内又包含另一个完整的循环结构,称为循环的嵌套。在外部循环的每次执行过程中都会触发内部循环,直至内部完成一次循环,才接着执行下一次的外部循环。for循环、while循环和until循环可以相互嵌套。

demo1:打印如下图案

1
12
123
1234
12345


X轴:
for ((i=1;i<=5;i++));do echo -n $i;done
Y轴:
负责打印换行

#!/bin/bash
for ((y=1;y<=5;y++))
do
	for ((x=1;x<=$y;x++))
	do
		echo -n $x
	done
echo
done

#!/bin/bash
for ((y=1;y<=5;y++))
do
	x=1
	while [ $x -le $y ]
		do
		echo -n $x
		let x++
		done
echo
done

demo2:打印如下图案

5
54
543
5432
54321

Y轴:打印换行
X轴:打印数字 5-1

#!/bin/bash
y=5
while (( $y >= 1 ))
do
	for ((x=5;x>=$y;x--))
	do
		echo -n $x
	done
echo
let y--
done


#!/bin/bash
for (( y=5;y>=1;y--))
do
	for (( x=5;x>=$y;x--))
	do
	echo -n $x
	done
echo
done

#!/bin/bash
y=5
while [ $y -ge 1 ]
do
	for ((x=5;x>=$y;x--))
	do
	echo -n $x
	done
echo
let y--
done


#!/bin/bash
y=1
until (( $y >5 ))
do
	x=1
	while (( $x <= $y ))
	do
	echo -n $[6-$x]
	let x++
	done	
echo
let y++
done


课后打印:
54321
5432
543
54
5

课堂练习:打印九九乘法表(三种方法)

1
12
123
1234
12345

for ((y=1;y<=5;y++))
do
	for ((x=1;x<=$y;x++))
		do
		echo -n $x
		done
echo
done

1*1=1

1*2=2   2*2=4

1*3=3   2*3=6   3*3=9

1*4=4   2*4=8   3*4=12  4*4=16

1*5=5   2*5=10  3*5=15  4*5=20  5*5=25

1*6=6   2*6=12  3*6=18  4*6=24  5*6=30  6*6=36

1*7=7   2*7=14  3*7=21  4*7=28  5*7=35  6*7=42  7*7=49

1*8=8   2*8=16  3*8=24  4*8=32  5*8=40  6*8=48  7*8=56  8*8=64

1*9=9   2*9=18  3*9=27  4*9=36  5*9=45  6*9=54  7*9=63  8*9=72  9*9=81


Y轴:循环9次,打印9行空行
X轴:循环次数和Y轴相关;打印的是X和Y轴乘积 $[] $(())

#!/bin/bash
for ((y=1;y<=9;y++))
do
	for ((x=1;x<=$y;x++))
	do
		echo -ne "$x*$y=$[$x*$y]\t"
	done
echo
echo
done


#!/bin/bash
y=1
while [ $y -le 9 ]
do
        x=1
        while [ $x -le $y ]
        do
                echo -ne "$x*$y=$[$x*$y]\t"
                let x++
        done
echo
echo
let y++
done

或者
#!/bin/bash
for i in `seq 9`
do
    for j in `seq $i`
    do
        echo -ne  "$j*$i=$[$i*$j]\t"
    done
echo
echo
done
或者
#!/bin/bash
y=1
until [ $y -gt 9 ]
do
        x=1
        until [ $x -gt $y ]
        do
                echo -ne "$x*$y=$[ $x*$y ]\t"
                let x++
        done
echo
echo
let y++
done

三、阶段性总结

1. 变量定义
普通变量定义:
变量名=值		shell变量默认可以赋予任何类型
$变量名		${变量名}		${变量名:从第几个字符开始:截取几个字符}
unset 变量名

交互式:
read 变量名
-p
-t
-s
-n

数组定义:
array=(var1 var2 var3 ...)
array[0]=var1
array[1]=var2
array[2]=var3
普通数组:数组的索引是整数

定义关联数组
关联数组:索引是字符串

获取数组里的元素:
${array[*]}
${array[2]}
${array[@]:1:2}
${!array[@]}		获取数组的索引号(下标)
${#array[@]}		获取数组索引号的个数

定义有类型的变量:
declare
-i
-x
-a
-A	




2. 循环语句
for:
列表循环、非列表循环、类C风格			循环次数已知
while:
条件为真,进入循环,条件为假,退出循环	循环次数跟条件有关
until:
条件为假,进入循环,条件为真,退出循环	循环次数跟条件有关

3. 影响shell程序的内置命令
exit			退出整个程序
break		   结束当前循环,或跳出本层循环
continue 	忽略本次循环剩余的代码,直接进行下一次循环
shift			使位置参数向左移动,默认移动1位,可以使用shift 2

以下脚本都能够实现用户自定义输入数字,然后脚本计算和:
[root@MissHou shell04]# cat shift.sh 
#!/bin/bash
sum=0
while [ $# -ne 0 ]
do
let sum=$sum+$1
shift
done
echo sum=$sum


[root@MissHou shell04]# cat for3.sh 
#!/bin/bash
sum=0
for i
do
let sum=$sum+$i
done
echo sum=$sum


:
true
false

4. 补充扩展expect

expect 自动应答 tcl语言

需求1:A远程登录到server上什么都不做

#!/usr/bin/expect
# 开启一个程序
spawn ssh root@10.1.1.1
# 捕获相关内容
expect {
        "(yes/no)?" { send "yes\r";exp_continue }
        "password:" { send "123456\r" }
}
interact   //交互

脚本执行方式:
# ./expect1.sh
# /shell04/expect1.sh
# expect -f expect1.sh

1)定义变量
#!/usr/bin/expect
set ip 10.1.1.2
set pass 123456
set timeout 5
spawn ssh root@$ip
expect {
	"yes/no" { send "yes\r";exp_continue }
	"password:" { send "$pass\r" }
}
interact


2)使用位置参数
#!/usr/bin/expect
set ip [ lindex $argv 0 ]
set pass [ lindex $argv 1 ]
set timeout 5
spawn ssh root@$ip
expect {
	"yes/no" { send "yes\r";exp_continue }
	"password:" { send "$pass\r" }
}
interact

需求2:A远程登录到server上操作

#!/usr/bin/expect
set ip 10.1.1.1
set pass 123456
set timeout 5
spawn ssh root@$ip
expect {
	"yes/no" { send "yes\r";exp_continue }
	"password:" { send "$pass\r" }
}

expect "#"
send "rm -rf /tmp/*\r"
send "touch /tmp/file{1..3}\r"
send "date\r"
send "exit\r"
expect eof

需求3:shell脚本和expect结合使用,在多台服务器上创建1个用户

[root@server shell04]# cat ip.txt 
10.1.1.1 123456
10.1.1.2 123456


1. 循环
2. 登录远程主机——>ssh——>从ip.txt文件里获取IP和密码分别赋值给两个变量
3. 使用expect程序来解决交互问题

#!/bin/bash
# 循环在指定的服务器上创建用户和文件
while read ip pass
do
	/usr/bin/expect <<-END &>/dev/null
	spawn ssh root@$ip
	expect {
	"yes/no" { send "yes\r";exp_continue }
	"password:" { send "$pass\r" }
	}
	expect "#" { send "useradd yy1;rm -rf /tmp/*;exit\r" }
	expect eof
	END
done < ip.txt



#!/bin/bash
cat ip.txt|while read ip pass
do
        {

        /usr/bin/expect <<-HOU
        spawn ssh root@$ip
        expect {
                "yes/no" { send "yes\r";exp_continue }
                "password:" { send "$pass\r" }
        }
        expect "#"
        send "hostname\r"
        send "exit\r"
        expect eof
        HOU

        }&
done
wait
echo "user is ok...."


或者
#!/bin/bash
while read ip pass
do
        {

        /usr/bin/expect <<-HOU
        spawn ssh root@$ip
        expect {
                "yes/no" { send "yes\r";exp_continue }
                "password:" { send "$pass\r" }
        }
        expect "#"
        send "hostname\r"
        send "exit\r"
        expect eof
        HOU

        }&
done<ip.txt
wait
echo "user is ok...."

四、综合案例

实战案例2

写一个脚本,将跳板机上yunwei用户的公钥推送到局域网内可以ping通的所有机器上

说明:主机和密码文件已经提供

10.1.1.1:123456

10.1.1.2:123456

案例分析
  • 关闭防火墙和selinux
  • 判断ssh服务是否开启(默认ok)
  • 循环判断给定密码文件里的哪些IP是可以ping通 ip pass
  • 判断IP是否可以ping通——>$?—>流程控制语句
  • 密码文件里获取主机的IP和密码保存变量 ip pass
  • 判断公钥是否存在—>不存在创建它
  • ssh-copy-id 将跳板机上的yunwei用户的公钥推送到远程主机—>expect解决交互
  • 将ping通的主机IP单独保存到一个文件
  • 测试验证
代码拆分
1.判断yunwei用户的公钥是否存在
[ ! -f /hoem/yunwei/.ssh/id_rsa ] && ssh-keygen -P '' -f ./id_rsa

2.获取IP并且判断是否可以ping通
1)主机密码文件ip.txt
	10.1.1.1:123456
   10.1.1.2:123456
2) 循环判断主机是否ping通
	tr ':' ' ' < ip.txt|while read ip pass
	do
		ping -c1 $ip &>/dev/null
      if [ $? -eq 0 ];then
      	推送公钥
      fi
	done
   


3.非交互式推送公钥
/usr/bin/expect <<-END &>/dev/null
        spawn ssh-copy-id root@$ip
        expect {
                "yes/no" { send "yes\r";exp_continue }
                "password:" { send "$pass\r" }
        }
        expect eof
	END


最终实现
环境准备:
jumper-server	有yunwei用户

yunwei用户sudo授权:
visudo
## Allow root to run any commands anywhere
root    ALL=(ALL)       ALL
yunwei  ALL=(root)      NOPASSWD:ALL,!/sbin/shutdown,!/sbin/init,!/bin/rm -rf /

解释说明:
1)第一个字段yunwei指定的是用户:可以是用户名,也可以是别名。每个用户设置一行,多个用户设置多行,也可以将多个用户设置成一个别名后再进行设置。
2)第二个字段ALL指定的是用户所在的主机:可以是ip,也可以是主机名,表示该sudo设置只在该主机上生效,ALL表示在所有主机上都生效!限制的一般都是本机,也就是限制使用这个文件的主机;一般都指定为"ALL"表示所有的主机,不管文件拷到那里都可以用。比如:10.1.1.1=...则表示只在当前主机生效。
3)第三个字段(root)括号里指定的也是用户:指定以什么用户身份执行sudo,即使用sudo后可以享有所有root账号下的权限。如果要排除个别用户,可以在括号内设置,比如ALL=(ALL,!oracle,!pos)。
4)第四个字段ALL指定的是执行的命令:即使用sudo后可以执行所有的命令。除了关机和删除根内容以外;也可以设置别名。NOPASSWD: ALL表示使用sudo的不需要输入密码。
5)也可以授权给一个用户组
	%admin ALL=(ALL) ALL	表示admin组里的所有成员可以在任何主机上以任何用户身份执行任何命令

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
脚本实现:
#!/bin/bash
#判断公钥是否存在
[ ! -f /home/yunwei/.ssh/id_rsa ] && ssh-keygen -P '' -f ~/.ssh/id_rsa

#循环判断主机是否ping通,如果ping通推送公钥
tr ':' ' ' < /shell04/ip.txt|while read ip pass
do
{
        ping -c1 $ip &>/dev/null
        if [ $? -eq 0 ];then
        echo $ip >> ~/ip_up.txt
        /usr/bin/expect <<-END &>/dev/null
         spawn ssh-copy-id root@$ip
         expect {
                "yes/no" { send "yes\r";exp_continue }
                "password:" { send "$pass\r" }
                }
        expect eof
        END
        fi
}&
done
wait
echo "公钥已经推送完毕,正在测试...."
#测试验证
remote_ip=`tail -1 ~/ip_up.txt`
ssh root@$remote_ip hostname &>/dev/null
test $? -eq 0 && echo "公钥成功推送完毕"

实战案例3

写一个脚本,统计web服务的不同连接状态个数

#!/bin/bash
#count_http_80_state
#统计每个状态的个数
declare -A array1
states=`ss -ant|grep 80|cut -d' ' -f1`
for i in $states
do
        let array1[$i]++
done
#通过遍历数组里的索引和元素打印出来
for j in ${!array1[@]}
do
        echo $j:${array1[$j]}
done      

第四章 SHELL编程之CASE语句+函数+正则

课程目标

  • 掌握case语句的基本语法结构
  • 掌握函数的定义及调用
  • 掌握常用的正则表达式元字符含义

一、case语句

case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相
匹配的命令。

case var in             定义变量;var代表是变量名
pattern 1)              模式1;| 分割多个模式,相当于or
    command1            需要执行的语句
    ;;                  两个分号代表命令结束
pattern 2)
    command2
    ;;
pattern 3)
    command3
    ;;
		  *)              default,不满足以上模式,默认执行*)下面的语句
    command4
    ;;
esac							esac表示case语句结束
案例1
  • 当给程序传入start、stop、reload三个不同参数时分别执行相应命令。
#!/bin/bash
case $1 in
	start|S)
	echo "service is running...."
	;;
	stop|T)
	echo "service is stoped..."
	;;
	reload|R)
	echo "service is restart..."
	;;
	*)
	echo "请输入你要的动作"
	;;
esac


  • 脚本提示让用户输入需要管理的服务名,然后提示用户需要对服务做什么操作,如启动,关闭,重启等
#!/bin/bash
read -p "请输入需要管理的服务名称(vsftpd):" service
case $service in
		vsftpd)
		read -p "请输入要操作的动作:" action
		case $action in
			start|S)
			service vsftpd start
			;;
			stop|P)
			service vsftpd stop
			;;
			reload|restart|R)
			service vsftpd reload
			;;	
		esac
		;;
		httpd)
		echo "apache is running..."
		;;
		*)
		echo "请输入需要管理的服务名称(vsftpd):"
		;;
esac		

案例2

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

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

分析:
1. 打印菜单
2. 等待用户输入需要的操作编号  read case


echo "h 显示命令帮助"
...

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




#!/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

课堂练习1
  1. 输入一个等级(A-E),查看每个等级的成绩;如:输入A,则显示“90分~100分”,依次类推
  2. 模拟2人第一次相亲的场景,使用read让用户输入它的名字,性别,年龄(年龄放在性别判断后);在case里面再嵌套case菜单,使之选项更丰富。

要求:
1)

  • 对性别进行判断,如果不输入男或者女,则显示”你是泰国来的吗?“
  • 如果是男的,对其年龄进行判断。

2)

  • 如果男的年龄大于等于18岁则显示“某某先生,你结婚了吗?”;
  • 如果对方回答结了或者yes,则显示“结了你来这凑什么热闹”;
  • 如果对方回答没有或者no,再次询问“那你有房有车吗?”;
  • 如果既不说结了也不说没结则显示:“你到底结没结婚啊?”
  • 如果回答有房有车,则显示”咱去民政局领证吧“;
  • 如果回答没有,则显示“不好意思,我去下洗手间。”;
  • 如果既不说有又不说没有,则显示“别浪费时间,请正面回答”。
  • 如果男的年龄小于18岁,则显示“某某某你个小毛孩也来这凑热闹啦”

3)如果是女的,并且年龄大于等于18岁,则显示”某某女士你好“;否则显示”某某小姐你好“

参考:
#!/bin/bash
read -p "输入你的姓名:" name
read -p "输入你的性别:" gender

case "$gender" in
	男|man|male|boy )
		read -p "输入你的年龄:" age
		if [ $age -ge 18 ];then
			read -p "$name先生,你结婚了吗?" anwser
				case "$anwser" in
					结了||yes )
						echo "结了你还来干嘛?"
						;;
					没结|没有||no )
						read -p "有房有车吗?" anwser2
						case "$anwser2" in
							有)
								echo "咱就直接去民政局领证吧"
								;;
							没有 )
								echo "不好意思,我去下洗手间"
								;;
							* )
								echo "别浪费时间,请正面回答"
						esac
						;;
					* )
						echo "你到底结没结?"
				esac

		else
			echo "$name小子"
		fi
		;;|woman|female|girl|lady )
		read -p "输入你的年龄:" age
		if [ $age -ge 18 ];then
			echo "$name女士"
		else
			echo "$name小姐"
		fi
		;;
	* )
		echo "你是泰国来的吗?"
esac

该程序有个bug:如果输入年龄为负数或者0也是可以的,如何修复bug?增加一个条件:如果输入的年龄小于等于10则显示:”不跟你玩了。。。“

在最后加入如下语句即可:
if [ $age -ge 18 ];then
                        echo "$name女士"
                elif
                   [ $age -le 10 ];then
                        echo "不跟你玩了"
                     exit 1
                else
                        echo "$name小姐"
                fi
                ;;
        * )
                echo "你是泰国来的吗?"

esac

二、函数

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

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

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


function_name() {
		command
		command
}


function function_name() {
		command
		command
}


函数中return说明:
1.return可以结束一个函数,类似于前面讲的循环控制语句break(结束当前循环,执行循环体后面的代码)
2.return默认返回函数中最后一个命令的退出状态,也可以给定参数值,该参数值的范围是0-256之间。
3.如果没有return命令,函数将返回最后一个Shell的退出值。
2.2 函数的调用
  • 当前命令行调用
[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 
[root@MissHou shell04]# . fun1.sh 

[root@MissHou shell04]# hello 888
hello lilei 888
MissHou.itcast.cc
[root@MissHou shell04]# menu
1. mysql
2. web
3. app
4. exit


  • 定义到用户的环境变量中
/etc/profile	/etc/bashrc		~/.bash_profile	~/.bashrc

[root@MissHou shell04]# cat ~/.bashrc 
# .bashrc

# User specific aliases and functions

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
# Source global definitions
if [ -f /etc/bashrc ]; then
	. /etc/bashrc
fi

hello(){
echo "hello lilei $1"
hostname
}
menu(){
cat <<-EOF
1. mysql
2. web
3. app
4. exit
EOF
}

注意:
当用户打开bash的时候会读取该文件
  • 脚本中调用
#!/bin/bash
#打印菜单
source ./fun1.sh
menu(){
cat <<-END
	h	显示命令帮助
	f	显示磁盘分区
	d	显示磁盘挂载
	m	查看内存使用
	u	查看系统负载
	q	退出程序
	END
}
menu		//调用函数

2.3 应用案例

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

思路:

  • 循环直到输入字符串不为空 (该功能可以定义为一个函数,方便下面脚本调用)
  • 根据用户输入信息做出匹配判断 case 语句
#!/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" name
if [ -z $name ];then
fun $1
else
echo $name
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

	
	
描述以下代码含义:	
:()
{
   :|:&
}
:

:(){:|:&}:

三、综合案例

任务/背景:

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

具体要求:

  1. 只允许yunwei用户通过跳板机远程连接后台的应用服务器做一些维护操作
  2. 公司运维人员远程通过yunwei用户连接跳板机时,跳出以下菜单供选择:
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit
  1. 当用户选择相应主机后,直接免密码登录成功
  2. 如果用户不输入一直提示用户输入,直到用户选择退出

分析:

  1. 打印菜单——>定义函数 echo cat
  2. 让用户选择需要操作的机器 case…esac
  3. 配置免密登录
  4. 每个菜单提供功能——>case…esac用户选择做的事情
  5. 循环让用户输入选择
  6. 每个功能写成函数——>不是必须
  7. 脚本放的位置——>yunwei用户的家目录
#!/bin/bash
# jumper-server
# 定义菜单打印功能的函数
menu()
{
cat <<-EOF
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit
	EOF
}

while true
do
# 调用函数来打印菜单
clear
menu
# 菜单选择,case...esac语句
read -p "请选择你要访问的主机:" host
case $host in
	1)
	ssh root@10.1.1.2
	;;
	2)
	ssh root@10.1.1.3
	;;
	3)
	ssh root@10.1.1.2
	;;
	h)
	menu
	;;
	q)
	exit
	;;
esac
done


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

	
#!/bin/bash
#公钥推送成功
trap '' 1 2 3 19
#打印菜单用户选择
menu(){
cat <<-EOF
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit
EOF
}

#调用函数来打印菜单
menu
while true
do
read -p "请输入你要选择的主机[h for help]:" host

#通过case语句来匹配用户所输入的主机
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


#!/bin/bash
#jumper-server
#菜单打印
trap '' 1 2 3 
while true
do
cat <<-END
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
5. exit
END
#让用户选择相应的操作
read -p "请输入你要操作的主机:" host
case $host in
	1)
	ssh root@10.1.1.2
	;;
	2)
	ssh root@10.1.1.3
	;;
	3)
	ssh root@10.1.1.4
	;;
	5)
	exit
	;;
	*)
	clear
	echo "输入错误,请重新输入..."
	;;
esac
done

自己完善功能:
1. 用户选择主机后,需要事先推送公钥;如何判断公钥是否已推
2. 比如选择web1时,再次提示需要做的操作,比如:
clean log
重启服务
kill某个进程


补充:

1) SIGHUP 		重新加载配置    
2) SIGINT		键盘中断^C
3) SIGQUIT      键盘退出
9) SIGKILL		 强制终止
15) SIGTERM	    终止(正常结束),缺省信号
18) SIGCONT	   继续
19) SIGSTOP	   停止
20) SIGTSTP     暂停^Z

四、正则表达式

1. 什么是正则表达式

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

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

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

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

2. 第一类正则
  • 名词解释:

元字符:指那些在正则表达式中具有特殊意义的专用字符,如:点(.) 星(*) 问号(?)等

前导字符:即位于元字符前面的字符 abc* aooo.

  • 正则中常用的元字符

示例文本:

[root@server ~]# 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
(1).		任意单个字符,除了换行符 
(2)*		前导字符出现0次或连续多次  ab*能匹配“a”,“ab”以及“abb”,但是不匹配“cb”
(3).*		任意长度的字符  ab.*  ab123 abbb abab
(4)^		行的开头
(5)$		行的结尾
(6)^$		空行


(7)[]		匹配指定字符组内的任一单个字符   [abc]
(8)[^]	匹配不在指定字符组内的任一字符 	[^abc]

(9)^[]	匹配以指定字符组内的任一字符开头   ^[abc]
(10)^[^]	匹配不以指定字符组内的任一字符开头  ^[^abc] 



(11)\<		取单词的头
(12)\>		取单词的尾
(13)\<\>		精确匹配符号 	grep -w 'xxx'


(14)\{n\}	匹配前导字符连续出现n次    go\{2\}   google  gooogle
(15)\{n,\}	匹配前导字符至少出现n次 
(16)\{n,m\}	匹配前导字符出现n次与m次之间 


 (17) \(strings\)	保存被匹配的字符


将192.168.0.254 换成 192.168.1.254
vim 1.txt
:%s#\(192\.168\)\.0\.\(254\)#\1\.100\.\2	//底行模式下匹配
将10.1.1.1替换成10.1.1.254
:%s#\(10.1.1\).1#\1.254#g 
:%s/\(10.1.1\).1/\1.254/g 

# sed -n 's#\(192\.168\)\.0\.254#\1\.1\.254#p'      
找出含有192.168的行,同时保留192.168并标记为标签1,之后可以使用\1来引用它。最多可以定义9个标签,从左边开始编号,最左边的是第一个。

[root@server shell05]# sed -n 's#10.1.1.1#10.1.1.254#p' 1.txt
10.1.1.254
[root@server shell05]# sed -n 's#\(10.1.1\).1#\1.254#p' 1.txt
10.1.1.254

将helloworld yourself 换成hellolilei myself

vim 1.txt
:%s#\(hello\)world your\(self\)#\1lilei my\2#g

# sed -n 's/\(hello\)world your\(self\)/\1lilei my\2/p' 1.txt 
hellolilei myself

[root@server shell05]# sed -n 's/helloworld yourself/hellolilei myself/p' 1.txt 
hellolilei myself
[root@server shell05]# sed -n 's/\(hello\)world your\(self\)/\1lilei my\2/p' 1.txt 
hellolilei myself



[0-9] [a-z] [A-Z] [a-zA-Z] [a-Z]
=================================================================================
Perl内置正则:
\d      匹配数字  [0-9]
\w      匹配字母数字下划线[a-zA-Z0-9_]
\s      匹配空格、制表符、换页符[\t\r\n]

#grep -P '\d' test.txt
#grep -P '\w' test.txt
#grep -P '\s' test.txt


  • 扩展类的正则表达式 grep -E 或则 egrep
扩展正则表达式元字符
+			匹配一个或多个前导字符		bo+	boo bo
?			匹配零个或一个前导字符		bo?	b bo

a|b		匹配a或b
()			组字符	   hello myself yourself     (my|your)self

{n}		前导字符重复n次			\{n\}
{n,}		前导字符重复至少n次	  \{n,\}
{n,m}		前导字符重复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


[root@jumper shell06]# grep '[0-9]\{2\}\.[0-9]\{1\}\.[0-9]\{1\}\.[0-9]\{1\}' 1.txt 
10.1.1.1
[root@jumper shell06]# grep '[0-9]{2}\.[0-9]{1}\.[0-9]{1}\.[0-9]{1}' 1.txt 
[root@jumper shell06]# grep -E '[0-9]{2}\.[0-9]{1}\.[0-9]{1}\.[0-9]{1}' 1.txt 
10.1.1.1
[root@jumper shell06]# grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' 1.txt 
10.1.1.1
[root@jumper shell06]# grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt 
10.1.1.1

3. 第二类正则
表达式功能示例
[:alnum:]字母与数字字符[[:alnum:]]+
[:alpha:]字母字符(包括大小写字母)[[:alpha:]]{4}
[:blank:]空格与制表符[[:blank:]]*
[:digit:]数字[[:digit:]]?
[:lower:]小写字母[[:lower:]]{4,}
[:upper:]大写字母[[:upper:]]+
[:punct:]标点符号[[:punct:]]
[:space:]包括换行符,回车等在内的所有空白[[:space:]]+
[root@server shell05]# grep -E '^[[:digit:]]+' 1.txt
[root@server shell05]# grep -E '^[^[:digit:]]+' 1.txt
[root@server shell05]# grep -E '[[:lower:]]{4,}' 1.txt
4. 课堂作业
在自己虚拟机里创建如下内容的文件:
# cat 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

要求如下:

1、查找不以大写字母开头的行(三种写法)。
2、查找有数字的行(两种写法)
3、查找一个数字和一个字母连起来的
4、查找不以r开头的行
5、查找以数字开头的
6、查找以大写字母开头的
7、查找以小写字母开头的 
8、查找以点结束的
9、去掉空行
10、查找完全匹配abc的行
11、查找A后有三个数字的行
12、统计root在/etc/passwd里出现了几次
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         匹配整个单词
  

五、正则总结

元字符:在正则中,具有特殊意义的专用字符. *

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

元字符字符说明示例
*前导字符出现0次或者连续多次ab* abbbb
.除了换行符以外,任意单个字符ab. ab8 abu
.*任意长度的字符ab.* adfdfdf
[]括号里的任意单个字符或一组单个字符[abc][0-9][a-z]
[^]不匹配括号里的任意单个字符或一组单个字符[^abc]
1匹配以括号里的任意单个字符开头2
^[^]不匹配以括号里的任意单个字符开头
^行的开头^root
$行的结尾bash$
^$空行
\{n\}和{n}前导字符连续出现n次[0-9]\{3\}
\{n,\}和{n,}前导字符至少出现n次[a-z]{4,}
\{n,m\}和{n,m}前导字符连续出现n-m次go{2,4}
\<\>精确匹配单词\<hello\>
\(\)保留匹配到的字符\(hello\)
+前导字符出现1次或者多次[0-9]+
?前导字符出现0次或者1次go?
|^root|^ftp
()组字符(hello|world)123
\dperl内置正则grep -P \d+
\w匹配字母数字下划线grep -P ‘\w’

六、课后作业

写一个自动搭建apache服务的脚本,要求如下:
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

第五章 SHELL编程之文本处理工具SED

课程目标

  • 掌握sed的基本语法结构
  • 熟悉sed常用的命令,如打印p,删除d,插入i等

一、sed介绍

1. sed的工作流程

在这里插入图片描述

  • 首先sed把当前正在处理的行保存在一个临时缓存区中(也称为模式空间),然后处理临时缓冲区中的行,完成后把该行发送到屏幕上。
  • sed把每一行都存在临时缓冲区中,对这个副本进行编辑,所以不会修改原文件。
  • Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。
2. sed使用方法

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

2.1 命令行格式
  • 格式
sed [option]  'sed的命令|地址定位' filename

说明:引用shell script中的变量应使用双引号,而非通常使用的单引号

option:
-e	进行多项编辑,即对输入行应用多条sed命令时使用
-n	取消默认的输出
-f	指定sed脚本的文件名
-r  使用扩展正则表达式
-i inplace,原地编辑(修改源文件)
  • 常用命令和选项
参考文件:
[root@server shell06]# cat 2.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

p	打印行
d	删除行
[root@server ~]# sed -n 'p'  a.txt
[root@server ~]# sed -n '1p'  a.txt
[root@server ~]# sed -n '2p'  a.txt
[root@server ~]# sed -n '1,5p'  a.txt
[root@server ~]# sed -n '5,10p' a.txt 
[root@server ~]# sed -n '$p' a.txt 
[root@server ~]# sed '5p' a.txt 
[root@server ~]# sed -n '5p' a.txt 
[root@server ~]# sed '1d' a.txt 
[root@server ~]# sed -n '1d' a.txt 
[root@server ~]# sed '1d' a.txt 
[root@server ~]# sed '1,5d' a.txt 
[root@server ~]# sed '$d' a.txt 

i\	在当前行之前插入文本。多行时除最后一行外,每行末尾需用"\"续行  vim——>O
a\	在当前行后添加一行或多行。多行时除最后一行外,每行末尾需用“\”续行 vim——> o
c\	用此符号后的新文本替换当前行中的文本。多行时除最后一行外,每行末尾需用"\"续行  整行替换

[root@server ~]# sed '$a99999' a.txt 
[root@server ~]# sed 'a99999' a.txt 
[root@server ~]# cat -n a.txt 
[root@server ~]# sed '5chello world' a.txt 
[root@server ~]# sed 'chello world' a.txt 
[root@server ~]# cat -n a.txt 
[root@server ~]# sed '1,5chello world' a.txt 

[root@server ~]# sed 'i\
aaaaa\
bbbbb\
88888' 1.txt

# sed '$a\
yyyyy\
8888' 1.txt

[root@server ~]# sed '/^user01/c888888' 1.txt
[root@server ~]# sed '18chello world' 1.txt


命令2:
r	从文件中读取输入行
w	将所选的行写入文件

[root@server ~]# sed '3r /etc/hosts' 2.txt 
[root@server ~]# sed '$r /etc/hosts' 2.txt
[root@server ~]# sed '/root/w a.txt' 2.txt 
[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

!	对所选行以外的所有行应用命令,放到行数之后
 [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 

s	用一个字符串替换另一个
g	在行内进行全局替换

[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


&   保存查找串以便在替换串中引用   \(\)

[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 


= 	打印行号
# sed -n '/bash$/=' passwd    打印以bash结尾的行的行号
# sed -ne '/root/=' -ne '/root/p' passwd 
# sed -n '/nologin$/=;/nologin$/p' 1.txt
# sed -ne '/nologin$/=' -ne '/nologin$/p' 1.txt


综合运用:
[root@server ~]# sed -n '1,5s/^/#&/p' 1.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

[root@server ~]# sed -n '1,5s/\(^\)/#\1/p' 1.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



选项:-e -r -i
-e 多项编辑
-r	扩展正则
-i 修改原文件
[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='

过滤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文件中生效的行:
[root@server shell06]# sed -e '/^#/d' -e '/^;/d' -e '/^$/d' -e '/^\t$/d' -e '/^\t#/d' smb.conf
[root@server shell06]# sed -r '/^(#|$|;|\t#|\t$)/d' smb.conf 
[root@server shell06]# 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

-i 选项  直接修改原文件
# sed -i 's/root/ROOT/;s/stu/STU/' 11.txt
[root@server ~]# sed -i '17{s/YUNWEI/yunwei/;s#/bin/bash#/sbin/nologin#}' 1.txt
[root@server ~]# sed -i '1,5s/^/#&/' a.txt
注意:
-ni  不要一起使用
p命令 不要再使用-i时使用

  • 总结

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

定址用于决定对哪些行进行编辑。地址的形式可以是数字、正则表达式、或二者的结合。如果没有指定地址,sed将处理输入文件的所有行。

x   				指定x行号						 sed -n '5p' 1.txt
x,y 				指定x到y行号					 sed -n '1,5p' 1.txt
/key/ 			查询包含关键字的行		sed -n '/root/p' 1.txt
/key1/,/key2/    匹配包含两个关键字之间的行	sed -n '/^adm/,/^mysql/p' 1.txt
/key/,x  		从匹配关键字的行开始到文件第x行之间的行(包含关键字所在行)sed -n '/^lp/,7p' 
x,/key/  		从第x行开始到与关键字的匹配行之间的行
x,y! 				不包含x到y行

[root@server ~]# sed -n '/bash$/!p' 1.txt

注意:sed使用的正则表达式是括在斜杠线"/"之间的模式。

//以下命令是找出以lp开头或者以mail开头的行

  • 其他命令讲解
y命令
该命令与UNIX/Linux中的tr命令类似,字符按照一对一的方式从左到右进行转换。
正则表达式元字符对y命令不起作用。与s命令的分隔符一样,斜线可以被替换成其它的字符。
s/xxx/xxx/
y/xxx/xxx/
# sed '39,41y/stu/STU/' /etc/passwd
# sed '39,41y/stu:x/STU@%/' /etc/passwd

q	退出
# 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

2.2 脚本格式
  • 用法
# sed -f scripts.sed  file		//使用脚本处理文件
建议使用   ./sed.sh   file

脚本的第一行写上
#!/bin/sed -f
1,5d
s/root/hello/g
3i777
5i888
a
p
  • 注意事项
1) 脚本文件是一个sed的命令行清单。'commands'
2) 在每行的末尾不能有任何空格、制表符(tab)或其它文本。
3) 如果在一行中有多个命令,应该用分号分隔。
4) 不需要且不可用引号保护命令
5) #号开头的行为注释


  • 示例
# 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>2)
sed '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. 将任意数字替换成空或者制表符
  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

二、课后作业

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 "上传文件成功"

第六章 SHELL编程之文本处理工具AWK

课程目标

  • 熟悉awk的命令行模式基本语法结构
  • 熟悉awk的相关内部变量
  • 熟悉awk常用的打印函数print
  • 能够在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进行介绍。

1. awk使用方式
  • 命令模式语法

    awk 选项 'commands' 文件名
    
    • 常用选项

      • -F 定义字段分割符号,默认的分隔符是空格
      • -v 定义变量并赋值
    • '命名部分(commands)

      • 正则表达式,地址定位
      '/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语句}'
      
      • 引用shell变量需用双引号引起
  • 脚本模式

    • 脚本执行
    方法1:
    awk 选项 -f awk的脚本文件  要处理的文本文件
    awk -f awk.sh filename
    
    sed -f sed.sh -i filename
    
    方法2:
    ./awk的脚本文件(或者绝对路径)	要处理的文本文件
    ./awk.sh filename
    
    ./sed.sh filename
    
    • 脚本编写
    #!/bin/awk -f 		定义魔法字符
    以下是awk引号里的命令清单,不要用引号保护命令,多个命令用分号间隔
    BEGIN{FS=":"}
    NR==1,NR==3{print $1"\t"$NF}
    ...
    
2. 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当前输入的文件名
  • 示例1
[root@server shell07]# awk -F: '{print $1,$(NF-1)}' 1.txt
[root@server shell07]# awk -F: '{print $1,$(NF-1),$NF,NF}' 1.txt
[root@server shell07]# awk '/root/{print $0}' 1.txt
[root@server shell07]# awk '/root/' 1.txt
[root@server shell07]# awk -F: '/root/{print $1,$NF}' 1.txt 
root /bin/bash
[root@server shell07]# awk -F: '/root/{print $0}' 1.txt      
root:x:0:0:root:/root:/bin/bash
[root@server shell07]# awk 'NR==1,NR==5' 1.txt 
[root@server shell07]# awk 'NR==1,NR==5{print $0}' 1.txt
[root@server shell07]# 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:
[root@server shell07]# awk 'BEGIN{FS=":"};/^root/,/^lp/{print $1,$NF}' 1.txt
[root@server shell07]# 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
[root@server shell07]# 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

[root@server shell07]# awk 'BEGIN{RS="\t"};{print $0}' 1.txt
[root@server shell07]# awk 'BEGIN{ORS="\t"};{print $0}' 1.txt
  • 格式化输出printprintf
print函数		类似echo
# 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

%s 字符类型  strings			%-20s
%d 数值类型	
占15字符
- 表示左对齐,默认是右对齐
printf默认不会在行尾自动换行,加\n
3. 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中,覆盖原来的内容,然后将新的字符串分隔成字段并进行处理。该过程将持续到所有行处理完毕

4. awk变量定义
# 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}' 
注意:
awk中调用定义的变量不需要加$
5. awk中BEGIN…END使用

​ ①BEGIN:表示在程序开始前执行

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

​ ③用法:'BEGIN{开始处理之前};{处理中};END{处理结束后}'

  • 示例:
  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
************************************
  1. 打印/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}
6. awk和正则的综合运用
运算符说明
==等于
!=不等于
>大于
<小于
>=大于等于
<=小于等于
~匹配
!~不匹配
!逻辑非
&&逻辑与
||逻辑或
  • 示例
从第一行开始匹配到以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 shell05]# 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@server shell07]# 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

理解;号和||的含义:
[root@server shell07]# awk 'NR>=3 && NR<=8 || /bash$/' 1.txt
[root@server shell07]# 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}'
7. 课堂练习
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

2、显示可以登录系统的用户名 
# awk -F: '$0 ~ /\/bin\/bash/{print $1}' /etc/passwd
3、打印出系统中普通用户的UID和用户名如下显示:
# awk -F: 'BEGIN{print "UID\tUSERNAME"} {if($3>=500 && $3 !=65534 ) {print $3"\t"$1} }' /etc/passwdUID	USERNAME
500	stu1
501	yunwei
502	user01
503	user02
504	user03

# awk -F: '{if($3 >= 500 && $3 != 65534) print $1,$3}' a.txt 
redhat 508
user01 509
u01 510
YUNWEI 511

4、以数字开头
awk '/^[0-9]/{print $0}'   1.txt
5、以任意大小写字母开头
awk '/^[a-Z]/{print $0}' 1.txt
8. awk的脚本编程
8.1 流程控制语句
if语句:

if [ xxx ];then
xxx
fi

格式:
awk 选项 '正则,地址定位{awk语句}'  文件名
{ if(表达式){语句1;语句2;...}

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

[root@MissHou shell05]# awk -F: '{if($3==0) {print $1"是管理员"} }' passwd 
root是管理员

# awk 'BEGIN{if($(id -u)==0) {print "admin"} }'
admin


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 "不是普通用户"}}'



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

8.2 循环语句
while:
# awk 'BEGIN { i=1; while(i<=10) {print i;i++} }'

文件里的每一行循环打印10次:
# awk -F: '{ i=1; while(i<=10) {print $0;i++} }' /etc/passwd

for:
# awk 'BEGIN { for(i=1;i<=5;i++) {print i} }'

文件里的每一行循环打印10次:
# awk -F: '{ for(i=1;i<=10;i++) {print $0}}' /etc/passwd


打印1~5
# awk 'BEGIN { for(i=1;i<=5;i++) {print i} }'

# awk 'BEGIN { i=1;while(i<=5) {print i;i++} }'

打印1~10中的奇数
# awk 'BEGIN{i=1;while(i<=10) {print i;i+=2} }'

awk 选项 '{awk语句1 语句2 }'

计算1-5的和:
# awk 'BEGIN { 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}'

# awk 'BEGIN { i=1;while(i<=5) {(sum+=i) i++};print sum }'


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

嵌套循环:
#!/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} }'
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

# awk 'BEGIN{i=1;while(i<=5){if(i==3) break;print i;i++}}'
1
2

# awk 'BEGIN{i=0;while(i<5){i++;if(i==3) continue;print i}}'
1
2
4
5


8.3 算数运算
+ - * / %() ^(幂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. 统计/etc/passwd 中各种类型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. 网站访问状态统计 <当前时实状态 netstat>
# 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,ss>
# 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. 统计Apache/Nginx日志中某一天的PV量  <统计日志>
# grep '27/Jul/2017' mysqladmin.cc-access_log |wc -l
14519

5. 统计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

名词引入:

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

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

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

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

三、课后作业

作业1:
1、写一个自动检测磁盘使用率的脚本,当磁盘使用空间达到90%以上时,需要发送邮件给相关人员
2、写一个脚本监控系统内存和交换分区使用情况

作业2:
输入一个IP地址,使用脚本判断其合法性:
必须符合ip地址规范,第1、4位不能以0开头,不能大于255不能小于0

四、企业实战案例

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. ↩︎

  2. abc ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值