小工具用法

grep 用法

语法:行过滤

1
grep [选项] '关键字' 文件名 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
OPTIONS:
-i: 不区分大小写
-v: 查找不包含指定内容的行,反向选择(取反)
-w: 按单词搜索
-o: 打印匹配关键字
-c: 统计匹配到的行数
-n: 显示行号(注:区分大小写。不区分大小写的话 -ni
-r: 逐层遍历目录查找
-A: 显示匹配行及后面多少行
-B: 显示匹配行及前面多少行
-C: 显示匹配行前后多少行
-l:只列出匹配的文件名
-L:列出不匹配的文件名
-e: 使用正则匹配
-E:使用扩展正则匹配
^key:以关键字开头
key$:以关键字结尾
^$:匹配空行
--color=auto :可以将找到的关键词部分加上颜色的显示

1.1 对 grep 起个别名 每次输入 grep 达到 grep —color=auto 效果

1
2
3
4
5
6
7
8
9
10
11
12
13
1.临时生效
alias grep='grep --color=auto'

2.永久生效,全局,修改配置文件/etc/bashrc 文末添加
此文件是用户在登陆时被读取
vim /etc/bashrc


# vim:ts=4:sw=4
alias grep='grep --color=auto'

重新加载文件
source /etc/bashrc

1.2 需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
前置:cp /etc/passwd .
1.过滤文件内容包含 root 的
grep 'root' passwd

2.过滤文件内容包含 root 的行显示行号(区分大小写)
grep -n 'root' passwd

3.过滤文件内容包含 root 的行显示行号(不区分大小写)
grep -ni 'root' passwd

4.过滤文件内容以 root 开头的行
grep '^root' passwd

5.过滤文件内容以 bash 结尾的行
grep 'bash$' passwd

6.过滤不以 root 开头的行
grep -iv '^root' passwd

7.过滤包含 root 的后5行,显示行号
grep -nA 5 'root' passwd

8.过滤包含 mail 的前5行,显示行号
grep -nB 'mail' passwd

9.过滤包含 mail 的前后3行,显示行号
grep -nC 3 'mail' passwd

10.统计 passwd 文件中 root 出现的次数
grep -c 'root' passwd

cut 用法

语法:列截取的工具

1
cut 选项  文件名

2.1 常见选项:

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

2.2 需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.分割以 :为分隔符第一列的内容
cut -d ':' -f 1 passwd

2.以 :为分割符 第1列和第5列的内容,显示前10
cut -d ':' -f 1,5 passwd | head

3.截取第2到第5个字符
cut -c 2-5 passwd

4.截取第1个字符
cut -c 1 passwd

5.截取第10个字符以后的内容
cut -c 10- passwd

6.显示当前系统的运行级别
runlevel | cut -d ' ' -f 2
runlevel | cut -c 3

tee 用法

语法:tee工具是从标准输入读取并写入到标准输出和文件,即:双向覆盖重定向(屏幕输出|文本输入)

1
[语法] | tee [内容]

3.1 常见选项

1
2
3
选项:
-a:双向追加重定向
注:不加 -a 默认是覆盖的

3.2 需求

1
2
3
4
5
6
7
8
1.查看主机的网络配置文件并保存到 network_config.txt
grep -v '^#' /etc/sysconfig/network-scripts/ifcfg-ens33 | grep -v '^$' | tee network_config.txt

2.查看主机的广播地址并保存到 broadcast.txt
ifconfig | grep 'broadcast' | cut -d ' ' -f 15,16 | tee broadcast.txt

3.查看主机的子网掩码并追加到 broadcast.txt
ifconfig | grep 'broadcast' | cut -d ' ' -f 12,13 | tee -a broadcast.txt

sort 用法

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

4.1 常见选项

1
2
3
4
5
6
7
8
-u :去除重复行
-r :降序排列,默认是升序
-o : 将排序结果输出到文件中,类似重定向符号>
-n :以数字排序,默认是按字符排序
-t :分隔符
-k :第N列
-b :忽略前导空格。
-R :随机排序,每次运行的结果均不同

4.2 需求

1
2
3
4
5
1. 按照用户的uid进行升序排列
sort -n -t ':' -k 3 passwd

2.按照用户的uid进行降序排列
sort -nr -t ':' -k 3 passwd

uniq 用法

语法:uniq用于去除 连续的 重复行

5.1 常见选项

1
2
3
-i: 忽略大小写
-c: 统计重复行次数
-d:只显示重复行

diff 用法

语法:逐行比较两个文件的不同,即怎样改变第一个文件才能和第二个文件相同。

两个文件的内容相同的时候就不出结果

1
diff [选项] 文件1 文件2 

6.1 常见选项

1
2
3
4
5
6
7
-b:不检查空格
-B:不检查空白行
-i:不检查大小写
-w:忽略所有的空格
--normal:正常格式显示(默认)
-c:上下文格式显示
-u:合并格式显示

6.2 举例说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vim file1
添加内容:
aaaa
111
hello world
222
333
bbb

vim file2
添加内容:
aaa
hello
111
222
bbb
333
world

6.2.1 diff 正常格式显示即 —normal (默认)

1
diff file1 file2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
结果显示:
(base) [root@master test]# diff file1 file2
1c1,2 第一个文件(file1)的第1行需改变(c=change)和第二个文件(file2)的 12行匹配
< aaaa “<” 即左边的文件(file1)内容 aaaa
--- 分割线
> aaa “>” 即右边文件(file2)的内容
> hello
3d3 第一个文件的第3行需删除(d=delete)和第二个文件(file2)的第 3 行匹配
< hello world 文件左边的内容 即删除 hello world
5d4 第一个文件的第5行需删除(d=delete)和第二个文件(file2)的第 4 行匹配
< 333 文件左边的内容 即删除 333
6a6,7 第一个文件的第6行需添加(a=add)和第二个文件(file2)的第 67 行匹配
> 333 添加的内容是第二个文件67行的 333 和 world
> world

6.2.2 diff 上下文格式显示 -c

1
diff -c file1 file2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
结果显示:
(base) [root@master test]# diff -c file1 file2
下面两行显示了文件名,时间戳; *** 表示前一个文件即file1 ---表示后一个文件即 file2
下面两个部分都是在 file1的角度,指如何改变file1 才能和file2 一样
*** file1 2022-07-03 09:51:21.046292165 +0800
--- file2 2022-07-03 09:51:39.631278483 +0800
*************** 分隔符
*** 1,6 **** 以***开头的表示file1 数字1,6 表示16
! aaaa ! 表示此行需要修改才可以与file2 匹配
111
- hello world - 表示此行需要删除才可以与file2 匹配
222
- 333 - 表示此行需要删除才可以与file2 匹配
bbb
--- 1,7 -------开头的表示file2 数字1,7 表示17
! aaa 表示第一个文件需要修改才与第二个文件匹配
! hello 表示第一个文件需要修改才与第二个文件匹配
111
222
bbb
+ 333 表示第一个文件需要加上该行才与第二个文件匹配
+ world 表示第一个文件需要加上该行才与第二个文件匹配

6.2.3 diff 合并模式 -u

1
diff -u file1 file2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
结果显示:
(base) [root@master test]# diff -u file1 file2
--- 指的是第一个文件即 file1 +++ 指的是第二个文件即file2 数字是时间戳
--- file1 2022-07-03 09:51:21.046292165 +0800
+++ file2 2022-07-03 09:51:39.631278483 +0800
@@ -1,6 +1,7 @@
-aaaa 第一个文件需要 删除 aaaa
+aaa 第一个文件 添加 aaa
+hello 第一个文件 添加 hello
111 111不变
-hello world 第一个文件 删除 hello world
222 222不变
-333 第一个文件 删除 333
bbb bbb不变
+333 第一个文件 添加 333
+world 第一个文件 添加 world

6.3 diff 工具比较两个目录的不同(注:此功能较常用)

数据准备:

1
2
3
4
5
6
7
8
9
10
11
12
(base) [root@master test]# mkdir dir1 dir2
(base) [root@master test]# touch dir1/file{1..5}
(base) [root@master test]# touch dir2/file{1..3}
(base) [root@master test]# touch dir2/test{1..2}
(base) [root@master test]# ls -R dir*
dir1:
file1 file2 file3 file4 file5

dir2:
file1 file2 file3 test1 test2
(base) [root@master test]#
文件里面随便添加一些数据

6.3.1 比较两个文件目录的不同,也会比较两个文件里面文件内容的不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(base) [root@master test]# diff dir1 dir2
diff dir1/file1 dir2/file1
1c1
< aa
---
> fja
diff dir1/file2 dir2/file2
1d0
< aa
diff dir1/file3 dir2/file3
1d0
< afdas
Only in dir1: file4
Only in dir1: file5
Only in dir2: test1
Only in dir2: test2

6.3.1 只比较文件目录的不同不比较文件内容不同 -q

1
2
3
4
5
6
7
8
(base) [root@master test]# diff -q dir1 dir2
Files dir1/file1 and dir2/file1 differ
Files dir1/file2 and dir2/file2 differ
Files dir1/file3 and dir2/file3 differ
Only in dir1: file4
Only in dir1: file5
Only in dir2: test1
Only in dir2: test2

6.4 打补丁

diff 找出文件的不同的目的是变相同,以一个文件为标准,去修改其他文件,并且修改的地方较多时,我们可以通过打补丁的方式完成。

1
2
3
4
5
6
7
8
9
10
1.先找出两个文件的不同输出到另一个文件中
diff -uN file1 file2 | tee file.patch
-u:合并模式
-N:将不存在的文件当作空文件

2.将不同的内容打补丁到 file1
patch file1 file.patch

3.测试验证(注:内容为空则修改完成)
diff file1 file2

paste用法

用法:paste工具用于合并文件行,并不改变原文件

7.1 常见选项

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

7.2 举例说明

1
2
3
4
5
echo 'file1的第一行内容' > file1
echo 'file1的第二行内容' >> file1
echo 'file2的第一行内容' > file2
echo 'file2的第二行内容' >> file2
echo 'file2的第三行内容' >> file2

7.2.1 默认分隔符

1
paste file1 file2
1
2
3
4
5
6
结果显示:
(base) [root@master test]# paste file1 file2
file1的第一行内容 file2的第一行内容
file1的第二行内容 file2的第二行内容
file2的第三行内容
(base) [root@master test]#

7.2.1 以 : 作为分隔符

1
paste -d ':' file1 file2
1
2
3
4
5
6
结果显示:
(base) [root@master test]# paste -d ':' file1 file2
file1的第一行内容:file2的第一行内容
file1的第二行内容:file2的第二行内容
:file2的第三行内容
(base) [root@master test]#

7.2.2 串行合并

1
paste -d ':' -s file1 file2
1
2
3
4
5
结果显示:
(base) [root@master test]# paste -d ':' -s file1 file2
file1的第一行内容:file1的第二行内容
file2的第一行内容:file2的第二行内容:file2的第三行内容
(base) [root@master test]#

tr 用法

用法:tr用于字符转换,替换和删除;主要用于 删除文件中控制字符 或 进行字符转换

1
2
3
4
5
6
7
8
1、命令的执行结果交给tr处理,其中string1用于查询,string2用于转换处理
commands|tr 'string1' 'string2'

2、tr处理的内容来自文件,记住要使用"<"标准输入
tr 'string1' 'string2' < filename

3、匹配string1进行相应操作,如删除操作
tr [options] 'string1' < filename

8.1 常见选项

1
2
-d:删除字符串1中所有输入字符。
-s:删除所有重复出现字符序列,只保留第一个;即将重复出现字符串压缩为一个字符串
1
2
3
4
5
6
7
8
9
10
a-z 或 [:lower:] : 匹配所有小写字母    
A-Z 或 [:upper:] : 匹配所有大写字母
0-9 或 [:digit:] : 匹配所有数字
[a-zA-Z0-9] : 所有大小写和数字
[:alnum:] : 匹配所有字母和数字
[:alpha:] : 匹配所有字母
[:blank:] : 所有水平空白
[:punct:] : 匹配所有标点符号
[:space:] : 所有水平或垂直的空格
[:cntrl:] : 匹配所有的控制字符如:\f 走行换页 \n 换行 \r 回车 \t tab键

8.2需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cp /etc/passwd .
1.找到文件中所有的小写字母替换为大写字母
cat passwd | tr 'a-z' 'A-Z'
tr '[:lower:]' '[:upper:]' < passwd

2.找到文件中所有的数字替换成为 @
cat passwd | tr '0-9' '@'
tr '[:digit:]' '@' < passwd

3.找到文件中的所有的 : 和 / 替换为 #
cat passwd | tr ':/' '#'
tr ':/' '#' < passwd

4.删除文件中的所有小写字母
cat passwd | tr -d 'a-z'
tr -d '[:lower:]' < passwd

6.查看系统的内置模块,并且按照模块的大小进行升序排列
lsmod | tr -s ' ' | sort -n -t ' ' -k 2

7.查看本机网卡 ens33 的 IP,NETMASK,BROADCAST 并存储到net_conf
ifconfig ens33 | grep 'netmask' | tr -s ' ' | cut -d ' ' -f 2-7 | tr ' ' '\n' | tee net_conf

bash 特性

命令和文件自动补全

Tab只能补全 命令 和 文件

1.1 常见的快捷键

1
2
3
4
5
6
7
8
9
^c   			终止前台运行的程序
^z 将前台运行的程序挂起到后台
^d 退出 等价exit
^l 或 clear 清屏
^a |home 光标移到命令行的最前端
^e |end 光标移到命令行的后端
^u 删除光标前所有字符
^k 删除光标后所有字符
^r 搜索历史命令

1.2 常用的通配符(重点)

1
2
3
4
5
6
7
8
9
10
* :	匹配0或多个任意字符
? : 匹配任意单个字符
[list] : 匹配[list]中的任意单个字符,或者一组单个字符 [a-z]
[!list] : 匹配除list中的任意单个字符
{string1,string2,...} :匹配string1,string2或更多字符串


# rm -f file*
# cp *.conf /dir1
# touch file{1..5}

1.3 bash中的引号(重点)

  • 双引号”” :会把引号的内容当成整体来看待,允许通过$符号引用其他变量值
  • 单引号’’ :会把引号的内容当成整体来看待,禁止引用其他变量值,shell中特殊符号都被视为普通字符
  • 反撇号`` :反撇号和$()一样,引号或括号里的命令会优先执行,如果存在嵌套,反撇号不能用
1
2
3
4
5
1.把 passwd 文件做备份,要求看出备份的日期
cp /etc/passwd ./passwd_`date +%F_%H:%M:%S`.bak
cp /etc/passwd ./passwd_$(date +%F_%H:%M:%S).bak
(base) [root@master test]# ls
passwd_2022-07-04_17:31:00.bak passwd_2022-07-04_17:31:28.bak

shell 初识

第一个shll 脚本

1
2
3
4
5
6
7
8
9
10
11
#!/bin/env bash
######################################################
# Name : first_shell.sh #
# Desc : OUPUT HELLO WORLD #
# Path : /root/test/first_shell.sh #
# Auther : Jermyn #
# Date : 2022-07-04 #
# Version : 1.0 #
######################################################

echo "HELLO WORLD!!!!"

1.1 执行方式

1.1.1 标准执行(必须有可执行权限)

1
2
3
4
5
给执行的脚本添加一个可执行的权限!后面加脚本的相对路径或者绝对路径
chmod +x first_shell.sh
./first_shell.sh

/root/test/first_shell.sh

1.1.2 在命令行指定解释器执行(可以没有可执行权限)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bash first_shell.sh

sh first_shell.sh

------------------------------
-x:一般用于排错,查看脚本的执行过程
-n:用来查看脚本的语法是否有问题
------------------------------

(base) [root@master test]# bash -x first_shell.sh
+ echo 'HELLO WORLD!!!!'
HELLO WORLD!!!!
(base) [root@master test]# bash -n first_shell.sh
(base) [root@master test]#

1.1.3 source 重新读取文件的内容(可以没有可执行权限)

1
source first_shell.sh

写一个 shell 脚本

需求:拷贝 /etc/passwd 的内容到 /test 目录下,拷贝文件的命名需要显示时间,运行此脚本的时候显示运行脚本的时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vim cp_passwd.sh

#!/bin/env bash
######################################################
# Name : cp_passwd.sh #
# Desc : COPY PASSWD #
# Path : /root/testcp_passwd.sh #
# Auther : Jermyn #
# Date : 2022-07-04 #
# Version : 1.0 #
######################################################

cp /etc/passwd /root/test/passwd_$(date +%F_%H:%M:%S).bak
echo -e "\033[32m --------------------------------------------------- \033[0m"
echo -e "\033[32m 您运行此脚本的时间是$(date +%F_%H:%M:%S)!!! \033[0m"
echo -e "\033[32m --------------------------------------------------- \033[0m"
ls

变量的定义

变量:变量是用来临时保存数据的,该数据是可以变化的数据。

定义变量

1
2
3
4
变量名=变量值

变量名:用来临时保存数据的
变量值:就是临时的可变化的数据
1
2
3
4
5
6
7
8
#定义变量,调用变量
A=25
echo "${A}"


# 取消变量
unset A
echo "${A}"

变量名定义规则

2.1 变量名严格区分大小写

1
2
3
4
5
6
7
A=hello
a=world

(base) [root@master test]# echo "${A}"
hello
(base) [root@master test]# echo "${a}"
world

2.2 变量名不能有特殊符号

命名要求:命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。 中间不能有空格,可以使用下划线(_)。不能使用标点符号不能使用bash里的关键字(可用help命令查看保留关键字)。

注:对于有空格的字符串给变量赋值时,要用引号引起来

1
A="hello world"
合法变量非法变量
A=10\t=10
a=10*a=10
A_A=105=10
A_=10A =10
a_=10A-=10
_a=10A A=10
_A=105A=10
ABC=10A = 10
aA=hello world
A_=10
……………….

变量名最好做到见名知意

1
2
3
4
5
好的变量名:
IPADDR=192.168.0.0
DIR=/export/server
TMP_FILE=/var/log/1.log
.......

变量名的定义方式

3.1 基本方式

直接赋值

1
2
3
4
5
6
7
8
9
(base) [root@master test]# A=1234567
(base) [root@master test]# echo ${A:2:4}
3456 表示从A变量中第3个字符开始截取,截取4个字符
(base) [root@master test]#

说明:
$变量名 和 ${变量名}的异同
相同点:都可以调用变量
不同点:${变量名}可以只截取变量的一部分,而$变量名不可以

3.2 命令执行结果赋值给变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1.时间
(base) [root@master test]# TIME="$(date +%F_%T)"
(base) [root@master test]# echo "${TIME}"
2022-07-04_21:21:51
(base) [root@master test]#

2.主机名
(base) [root@master test]# HOSTNAME="$(hostname)"
(base) [root@master test]# echo "${HOSTNAME}"
master
(base) [root@master test]#

3.内核版本
(base) [root@master test]# c="$(uname -r)"
(base) [root@master test]# echo "${c}"
3.10.0-1160.el7.x86_64
(base) [root@master test]#

4.系统版本
(base) [root@master test]# a="$(cat /etc/redhat-release)"
(base) [root@master test]# echo "${a}"
CentOS Linux release 7.9.2009 (Core)
(base) [root@master test]#

3.3 交互式变量

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

语法:

1
read [选项] 变量名

3.3.1 常见选项

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

3.3.2 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1.默认
(base) [root@master test]# read name
Jermyn
(base) [root@master test]# echo "${name}"
Jermyn
(base) [root@master test]#

2.提示
(base) [root@master test]# read -p "请输入您的姓名:" name
请输入您的姓名:Jermyn
(base) [root@master test]# echo "${name}"
Jermyn
(base) [root@master test]#

3.不显示
(base) [root@master test]# read -s passwd
# 输入不显示
(base) [root@master test]# echo "${passwd}"
123456
(base) [root@master test]#

4.定义时间
(base) [root@master test]# read -t 5 passwd
# 5秒后自动跳转 或者 输入回车后跳转
123456
(base) [root@master test]# echo "${passwd}"
123456
(base) [root@master test]#

5.定义字符数
(base) [root@master test]# read -n 5 -p "Please input your name:" name
Please input your name:Jermy(字符超过5个的时候自动跳转)(base) [root@master test]#

注:read -s -p "" -p后面跟内容 不可写作 read -p -s ""

变量值还以来自于文件

1
2
3
4
5
6
7
8
(base) [root@master test]# echo "192.168.0.0 255.255.255.0" > net_conf
(base) [root@master test]#
(base) [root@master test]# read IPADDR NETMASK < net_conf
(base) [root@master test]# echo "${IPADDR}"
192.168.0.0
(base) [root@master test]# echo "${NETMASK}"
255.255.255.0
(base) [root@master test]#

3.4 定义有类型的变量

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

用法:

1
declare 选项 变量名=变量值

3.4.1 常用选项

选项释义举例
-i将变量看成整数(只可赋值整数,其他数据类型为0)declare -i A=123
-r定义只读变量(不可重新赋值,不可 unset )declare -r B=hello
-a定义普通数组;查看普通数组
-A定义关联数组;查看关联数组
-x将变量通过环境导出declare -x AAA=123456 等于 export AAA=123456

变量的分类

4.1 本地变量

  • 当前用户自定义的变量。当前进程中有效,其他进程及当前进程的子进程无效。

4.2 环境变量

  • 当前进程有效,并且能够被子进程调用。

    • env 查看当前用户的环境变量
    • set 查询当前用户的所有变量(临时变量与环境变量)
    • export 变量名=变量值 或者 变量名=变量值;export 变量名
  • 定义一个环境变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    1.直接定义
    declare -x NAME=Jermyn

    2.定义一个本地变量,转换为环境变量
    NAME=Jermyn
    export NAME

    export NAME=Jermyn

    3.想要当前登陆的用户生效的话,修改/root/.bashrc 文件内容
    vim /root/.bashrc
    export NAME=Jermyn

    3.永久生效,配置 profile 文件
    vim /etc/profile
    export NAME=Jermyn

    4.配置 HADOOP 的环境变量
    # hadoop 环境变量
    export HADOOP_HOME=/export/server/hadoop-3.3.0
    export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin

4.3 全局变量

全局所有的用户和程序都能调用,且继承,新建的用户也默认能调用。

4.3.1 配置文件

文件名说明备注
$HOME/.bashrc当前用户的bash信息,用户登录时读取定义别名、umask、函数等
$HOME/.bash_profile当前用户的环境变量,用户登录时读取
$HOME/.bash_logout当前 用户退出 当前shell时最后读取定义用户退出时执行的程序等
/etc/bashrc全局的bash信息,所有用户都生效
/etc/profile全局环境变量信息系统和所有用户都生效
$HOME/.bash_history用户的历史命令history -w 保存历史记录 history -c 清空历史记录

注:修改后都需要 source 执行,重现加载环境变量才可以生效

4.3.2 用户登录系统读取相关文件的顺序

  1. /etc/profile
  2. $HOME/.bash_profile
  3. $HOME/.bashrc
  4. /etc/bashrc
  5. $HOME/.bash_logout

4.4 系统变量

shell本身已经固定好了它的名字和作用.

内置变量含义
$?上一条命令执行后返回的状态;状态值为0表示执行正常非0表示执行异常或错误
$0当前执行的程序或脚本名
$#脚本后面接的参数的个数
$*脚本后面所有参数,参数当成一个整体输出,每一个变量参数之间以空格隔开
$@脚本后面所有参数,参数是独立的,也是全部输出
$1~$9脚本后面的位置参数,$1表示第1个位置参数,依次类推
${10}~${n}扩展位置参数,第10个位置变量必须用{}大括号括起来(2位数字以上扩起来)
$$当前所在进程的进程号,如echo $$
$!后台运行的最后一个进程号 (当前终端)
!$调用最后一条命令历史中的参数

区分 $* 和 $@ 的区别

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

1
2
3
4
5
6
例子:
运行 ./demo.sh 1 2 3
$* 是指 1 2 3
$@ 是指 1
2
3

简单的四则运算

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

算数内容:加(+),减(-),乘(*),除(/),取余(%)

四则运算符号

表达式举例
$(())echo “$((1+1))”
$[]echo “$[1+1]”;
exprexpr 1 + 1 (必须有空格);expr 10 * 5(* 需要转义);expr 5 % 10 注:不可求次幂
letn=1;let n+=1;echo “${n}”

默认支持整数运算,特殊支持小数

1
2
3
4
5
6
7
8
9
(base) [root@master shell_scripts]# echo 1+1.5|bc
2.5
(base) [root@master shell_scripts]# bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
1+1.5
2.5

i++ 和 ++i

1
2
i++ :先赋值,在计算
++i :先计算,在赋值

数组定义

3.1 数组分类

  • 普通数组:只能使用整数作为数组索引(元素的下标)

  • 关联数组:可以使用字符串作为数组索引(元素的下标)(类似:K,V)

3.2 普通数组定义

  • 一次赋予一个值
1
2
3
4
5
6
数组名[索引下标]=值

array[0]=v1
array[1]=v2
array[2]=v3
array[3]=v4
  • 一次赋予多个值
1
2
3
4
5
6
7
8
9
10
11
数组名=(值123 ...)

array=(var1 var2 var3 var4)

array1=(`cat /etc/passwd`) 将文件中每一行赋值给array1数组

array2=(`ls /root`)

array3=(harry amy jack "Miss Hou")

array4=(1 2 3 4 "hello world" [10]=linux)

3.3 数组的读取

1
2
3
4
5
6
7
8
9
10
 ${数组名[元素下标]}

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

查看普通数组信息:
(base) [root@master ~]# declare -a

3.4 关联数组定义

  • 首先声明关联数组
1
declare -A asso_array1
1
2
3
declare -A asso_array1
declare -A asso_array2
declare -A asso_array3
  • 数组赋值

    • 一次赋一个值

      1
      2
      3
      4
      5
      数组名[索引or下标]=变量值

      # asso_array1[linux]=one
      # asso_array1[java]=two
      # asso_array1[php]=three
    • 一次赋多个值

      1
      # asso_array2=([name1]=harry [name2]=jack [name3]=amy [name4]="Jermyn")
  • 查看关联数组

1
2
3
# declare -A
declare -A asso_array1='([php]="three" [java]="two" [linux]="one" )'
declare -A asso_array2='([name3]="amy" [name2]="jack" [name1]="harry" [name4]="Jermyn" )'
  • 获取关联数组值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# echo ${asso_array1[linux]}
one
# echo ${asso_array1[php]}
three
# echo ${asso_array1[*]}
three two one
# echo ${!asso_array1[*]}
php java linux
# echo ${#asso_array1[*]}
3
# echo ${#asso_array2[*]}
4
# echo ${!asso_array2[*]}
name3 name2 name1 name4

3.5 其他变量定义

  • 取出一个目录下的目录和文件:dirnamebasename
1
2
3
4
5
6
7
8
(base) [root@master shell_scripts]# A=/root/test/shell_scripts/cp_passwd.sh
(base) [root@master shell_scripts]# echo "${A}"
/root/test/shell_scripts/cp_passwd.sh
(base) [root@master shell_scripts]# dirname ${A}
/root/test/shell_scripts
(base) [root@master shell_scripts]# basename ${A}
cp_passwd.sh
(base) [root@master shell_scripts]#
  • 变量”内容”的删除和替换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
一个“%”代表从右往左删除
两个“%%”代表从右往左去掉最多
一个“#”代表从左往右去掉删除
两个“##”代表从左往右去掉最多

举例说明:
(base) [root@master ~]# url=www.taobao.com
(base) [root@master ~]# echo ${#url} 查看字符数
14
(base) [root@master ~]# echo ${url#*.} 从左到右去掉以 . 分割的www. 保留 taobao.com
taobao.com
(base) [root@master ~]# echo ${url#*o} 从左到右去掉以 o 分割的 www.tao 保留 bao.com
bao.com
(base) [root@master ~]# echo ${url##*.}
com
(base) [root@master ~]# echo ${url##*0}
www.taobao.com
(base) [root@master ~]# echo ${url%.*} 从右到左以 . 分割 去掉 .com 保留 www.taobao
www.taobao
(base) [root@master ~]# echo ${url%o*} 从右到左以 o 分割 去掉 m 保留 www.taobao.co
www.taobao.c
(base) [root@master ~]# echo ${url%%.*}
www
(base) [root@master ~]# echo ${url%%o*}
www.ta
(base) [root@master ~]#
  • 了解
1
2
3
4
5
替换:/ 和 //
(base) [root@master ~]# echo ${url/ao/AO} 用AO代替ao(从左往右第一个)
www.tAObao.com
(base) [root@master ~]# echo ${url//ao/AO} 贪婪替换(替代所有)
www.tAObAO.com

条件判断语句

条件判断语句

1.1 语法格式

1
2
3
1. test 条件表达式
2. [ 条件表达式 ] 注:表达式左右两边都有空格
3. [[ 条件表达式 ]] 注:支持正则,表达式左右两边都有空格

条件判断相关参数

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

2.1 判断文件类型

判断参数含义
-e判断文件是否存在(任何类型文件)
-f判断文件是否存在并且是一个普通文件
-d判断文件是否存在并且是一个目录
-L判断文件是否存在并且是一个软连接文件
-b判断文件是否存在并且是一个块设备文件
-S判断文件是否存在并且是一个套接字文件
-c判断文件是否存在并且是一个字符设备文件
-p判断文件是否存在并且是一个命名管道文件
-s判断文件是否存在并且是一个非空文件(有内容)

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(base) [root@master server]# ls
anaconda3 hbase-1.2.4 kafka-eagle spark-3.2.0-bin-hadoop3.2
apache-zookeeper-3.7.0-bin jdk kafka-eagle-bin-master zookeeper
data jdk1.8.0_241 kafka-eagle-web-2.0.2 zookeeper.out
hadoop-3.3.0 kafka mysql57-community-release-el7-10.noarch.rpm
hbase kafka_2.11-2.0.0 spark
(base) [root@master server]# test -e zookeeper
(base) [root@master server]# echo $?
0
(base) [root@master server]# [ -d /export/server/ ]
(base) [root@master server]# echo $?
0
(base) [root@master server]# [ ! -d /export/server/ ]
(base) [root@master server]# echo $?
1

2.2 判断文件的权限

判断参数含义
-r当前用户对其是否可读
-w当前用户对其是否可写
-x当前用户对其是否可执行
-u是否有suid,高级权限冒险位
-g是否sgid,高级权限强制位
-k是否有t位,高级权限粘滞位(只有文件的创建者,管理员,root可以删除)

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
(base) [root@master shell_scripts]# ll
total 8
-rwxr-xr-x 1 root root 768 Jul 4 18:49 cp_passwd.sh
-rw-r--r-- 1 root root 1679 Jul 5 16:32 font_style.sh
(base) [root@master shell_scripts]# [ -x /root/test/shell_scripts/font_style.sh ];echo $?
1
(base) [root@master shell_scripts]# chmod +x font_style.sh
(base) [root@master shell_scripts]# ll
total 8
-rwxr-xr-x 1 root root 768 Jul 4 18:49 cp_passwd.sh
-rwxr-xr-x 1 root root 1679 Jul 5 16:32 font_style.sh
(base) [root@master shell_scripts]# [ -x /root/test/shell_scripts/font_style.sh ];echo $?
0

2.3 判断文件新旧(修改时间的新旧)**

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

举例

1
2
3
4
(base) [root@master shell_scripts]# [ cp_passwd.sh -nt font_style.sh ];echo $?
1
(base) [root@master shell_scripts]# [ cp_passwd.sh -ot font_style.sh ];echo $?
0

2.4 判断整数

判断参数含义
-eq相等
-ne不等
-gt大于
-lt小于
-ge大于等于
-le小于等于

举例

1
2
3
4
5
6
(base) [root@master shell_scripts]# [ 1 -eq 2 ];echo $?
1
(base) [root@master shell_scripts]# [ 1=2 ];echo $?
0
(base) [root@master shell_scripts]# [ 1 = 2 ];echo $? 等号左右两边必须有空格
1

2.5 判字符串

判断参数含义
-z判断是否 为空 字符串,字符串长度为0则成立
-n判断是否为 非空 字符串,字符串长度不为0则成立
string1 = string2判断字符串是否相等
string1 != string2判断字符串是否相不等

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
(base) [root@master shell_scripts]# [[ -z "hello world" ]];echo $?
1
(base) [root@master shell_scripts]# [[ -z " " ]];echo $? 空格也为字符
1
(base) [root@master shell_scripts]# [[ -z "" ]];echo $?
0
(base) [root@master shell_scripts]#


(base) [root@master shell_scripts]# [ "hello"="world" ];echo $?
0
(base) [root@master shell_scripts]# [ "hello" = "world" ];echo $? 等号左右两边必须有空格
1

2.6 多重条件判断

判断符号含义举例
-a 和 &&逻辑与[ 1 -eq 1 -a 1 -ne 0 ] [ 1 -eq 1 ] && [ 1 -ne 0 ]
-o 和 \\逻辑或[ 1 -eq 1 -o 1 -ne 1 ]

&& 前面的表达式为真,才会执行后面的代码(1&&1 —>TRUE)

|| 前面的表达式为假,才会执行后面的代码(1||0—>TRUE)

; 只用分割命令或表达式

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1.判断用户是否是 root 用户,是的话打印"THE USER IS ADMIN"
(base) [root@master shell_scripts]# [ $(id | cut -c 5) -eq 0 ]&& echo "THE USER IS ADMIN"
THE USER IS ADMIN
(base) [root@master shell_scripts]# [ $(id -u) -eq 0 ]&& echo "THE USER IS ADMIN"
THE USER IS ADMIN

2.是 root 的话打印是管理员,不是的话打印不是
[ $(id -u) -eq 0 ] && echo "THE USER IS ADMIN" || echo "THE USER IS NOT ADMIN"

3.
(base) [root@master ~]# [[ 1 -eq 2 ]] && echo "AAA" || echo "BBB" && echo "CCC"
BBB
CCC
(base) [root@master ~]# [[ 1 -eq 2 ]] || echo "AAA" || echo "BBB" && echo "CCC"
AAA
CCC
(base) [root@master ~]# [[ 1 -eq 2 ]] || echo "AAA" || echo "BBB" || echo "CCC"
AAA
(base) [root@master ~]# [[ 1 -eq 2 ]] || echo "AAA" && echo "BBB" || echo "CCC"
AAA
BBB
(base) [root@master ~]# [[ 1 -eq 2 ]] || echo "AAA" && echo "BBB" && echo "CCC"
AAA
BBB
CCC
  • 字符串的比较

    1
    2
    3
    4
    5
    注意:双引号引起来,看作一个整体;= 和 == 在 [ 字符串 ] 比较中都表示判断
    (base) [root@master ~]# A="HELLO";B="HELLO";[[ "${A}" = "${B}" ]];echo $?
    0
    (base) [root@master ~]# A="HELLO";B="hello";[[ "${A}" == "${B}" ]];echo $?
    1

2.7 [ 条件 ][[ 条件 ]] 的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[]和test
两者是一样的,在命令行里test expr和[ expr ]的效果相同。
test的三个基本作用是判断文件、判断字符串、判断整数。支持使用 ”与或非“ 将表达式连接起来。
test中可用的比较运算符只有==和!=,两者都是用于字符串比较的,不可用于整数比较,整数比较只能使用-eq, -gt这种形式。
无论是字符串比较还是整数比较都千万不要使用大于号小于号。当然,如果你实在想用也是可以的,对于字符串比较可以使用尖括号的转义形式, 如果比较"ab"和"bc":[ ab \< bc ],结果为真,也就是返回状态为0.

[[ ]]
这是内置在shell中的一个命令,它就比刚才说的test强大的多了。支持字符串的模式匹配(使用=~操作符时甚至支持shell的正则表达 式)。逻辑组合可以不使用test的-a,-o而使用&& ||。
字符串比较时可以把右边的作为一个模式(这是右边的字符串不加双引号的情况下。如果右边的字符串加了双引号,则认为是一个文本字符串。),而不仅仅是一个字符串,比如[[ hello == hell? ]],结果为真。
注意:使用[]和[[]]的时候不要吝啬空格,每一项两边都要有空格,[[ 1 == 2 ]]的结果为“假”,但[[ 1==2 ]]的结果为“真”!

let和(())
两者也是一样的(或者说基本上是一样的,双括号比let稍弱一些)。主要进行算术运算(上面的两个都不行),也比较适合进 行整数比较,可以直接使用熟悉的<,>等比较运算符。可以直接使用变量名如var而不需要$var这样的形式。支持分号隔开的多个表达式


1. 首先,尽管很相似,但是从概念上讲,二者是不同层次的东西。
"[[",是关键字,许多shell(如ash bsh)并不支持这种方式。ksh, bash(据说从2.02起引入对[[的支持)等支持。
"["是一条命令, 与test等价,大多数shell都支持。在现代的大多数sh实现中,"["与"test"是内部(builtin)命令,换句话说执行"test"/"["时不会调用/some/path/to/test这样的外部命令(如果有这样的命令的话)。

2.[[]]结构比Bash版本的[]更通用。在[[和]]之间的所有的字符都不会被文件扩展或是标记分割,但是会有参数引用和命令替换。
用[[ ... ]]测试结构比用[ ... ]更能防止脚本里的许多逻辑错误。比如说,&&,||,<和>操作符能在一个[[]]测试里通过,但在[]结构会发生错误。

3.(( ))结构扩展并计算一个算术表达式的值。如果表达式值为0,会返回1或假作为退出状态码。一个非零值的表达式返回一个0或真作为退出状态码。这个结构和先前test命令及[]结构的讨论刚好相反。

4.[ ... ]为shell命令,所以在其中的表达式应是它的命令行参数,所以串比较操作符">" 与"<"必须转义,否则就变成IO改向操作符了(请参看上面2中的例子)。在[[中"<"与">"不需转义;
由于"[["是关键字,不会做命令行扩展,因而相对的语法就稍严格些。例如
在[ ... ]中可以用引号括起操作符,因为在做命令行扩展时会去掉这些引号,而在[[ ... ]]则不允许这样做。

5.[[ ... ]]进行算术扩展,而[ ... ]不做

6.[[ ... && ... && ... ]] 和 [ ... -a ... -a ...] 不一样,[[ ]] 是逻辑短路操作,而 [ ] 不会进行逻辑短路
1
2
3
4
5
6
7
(base) [root@master ~]# [ 1 -eq 0 && 1 -ne 0 ];echo $?
-bash: [: missing `]'
2
(base) [root@master ~]# [ 1 -eq 0 -a 1 -ne 0 ];echo $?
1
(base) [root@master ~]# [[ 1 -eq 0 && 1 -ne 0 ]];echo $?
1

流程控制语句(if….)

基本语法结构

1.1 if 结构

语法:只要满足条件就一直执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if [ condition ];then
command
command
fi

if test 条件;then
命令
fi

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

[ 条件 ] && command

1.2 if…else 结构

语法:判断是否满足条件,满足走一条,不满足走另一条

1
2
3
4
5
6
7
if [ condition ];then
command1
else
command2
fi

[ 条件 ] && command1 || command2

1.3 if…elif…else 结构

语法:选择的有很多,能走的只有一条

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

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
1.判断当前主机是否可以连接远程主机
#!/bin/env bash
read -p "请输入您的 IP 或者 域名:" IP
ping ${IP} -c1 >/dev/null
if [[ $? -eq 0 ]];then
echo -e "\033[32m 您的主机网络可以与${IP}联通. \033[0m"
else
echo -e "\033[31m 您的主机网路不可以与${IP}联通!!! \033[0m"
fi

read -p "INPUT YOUR IP :" IP ; ping -c3 ${IP} > /dev/null ; [[ $? -eq 0 ]] && echo "YOUR NETOWRK IS OK" || echo "YOUR NETTWORK IS ERROR"


2.判断一个门户网站是否正常
(直接去访问一下,通过访问成功和失败的返回值来判断)
wget www.baidu.com
curl www.baidu.com
elinks -dump www.baidu.com

脚本:
#!/bin/env bash
read -p "Please input your IP or Domain Name:" web_server

# wget -P /root/test/index ${web_server} &>/dev/null
wget ${web_server} &>/dev/null
if [ $? -eq 0 ];then
# Check whether the folder exists. If not, create it
test -d /root/test/index || mkdir /root/test/index

# Move the build file to index and mark build time.
mv ./index.html /root/test/index/index_$(date +%F_%T).html
echo -e "\033[32m The current website service is normal \033[0m"
else
echo -e "\033[31m The network service of the current website is abnormal! \033[0m"
fi

条件判断语句(for….)

for循环语法结构

1.1 列表循环

语法:用于将一组命令执行已知的次数

1
2
3
4
5
6
7
8
9
10
11
12
for variable in {list}
do
command
command

done
或者
for variable in a b c
do
command
command
done

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
# {start,end,step}
1.打印0-50
for i in {0..50};do echo "${i}";done
2.打印0-50的偶数
for i in {0..50..2};do echo "${i}";done
3.打印 10-1
for i in {10..1};do echo "${i}";done

# $(seq start step end)
4.打印 1-10
for i in $(seq 10);do echo "${i}";done
5.打印10-1
for i in $(seq 10 -1 1);do echo "${i}";done

1.2 不带列表循环

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

1
2
3
4
5
6
for variable
do
command
command

done

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
vim demo.sh

#!/bin/env bash
for i
do
echo "${i}"
done

(base) [root@master shell_scripts]# chmod +x demo.sh && ./demo.sh a b c
a
b
c
(base) [root@master shell_scripts]# chmod +x demo.sh && ./demo.sh abc
abc

解释:
(base) [root@master shell_scripts]# bash -x demo.sh a b c
+ for i in '"$@"' for i 是 for i in '"$@"' 的省略
+ echo a
a
+ for i in '"$@"'
+ echo b
b
+ for i in '"$@"'
+ echo c
c

1.3 案例

1
2
3
4
5
6
7
8
9
10
11
1. 1-100 的奇数和
vim sum_odd_number.sh

#!/bin/env bash

sum=0
for i in {1..100..2}
do
sum=$[ $sum+$i ]
done
echo "1-100的奇数和是:${sum}"

1.4 循环控制语句

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

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

例子:

1.判断输入整数是否是质数(只能被1和它本身整除=的数叫质数。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/env bash

read -p "Please Input A Int Number:" number

# 1 & 2 is a specil case
[ $number -eq 1 ] && echo "$number neither prime number nor composite number" $$ exit
[ $number -eq 2 ] && echo "$number is prime number " $$ exit

# for loop 2 to the input number subtract 1
for i in $(seq 2 $[$number-1])
do
if [[ $number%$i -eq 0 ]];then
echo " $number is not a prime number!"
exit
fi
done
echo " $number is a prime number!"
  1. 批量加5个新用户,以u1到u5命名,并统一加一个新组,组名为 class ,统一改密码为123

  2. 添加用户的命令 useradd -G development

  3. 判断class组是否存在 grep -w ^development /etc/group 或者groupadd development
  4. 根据题意,判断该脚本循环5次来添加用户 for
  5. 给用户设置密码,应该放到循环体里面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/env bash
########################################################
# Name : creating_users.sh
# Desc : 批量创建用户,归为统一组,统一密码为
# Path : /root/test/shell_scripts/creating_users.sh
# Auther : Jermyn
# Date : 2022-07-07
# Version : 1.0
########################################################
read -p "请输入您创建的用户组:" group
read -p "请输入您创建的用户名:" user_name
read -s -p "请输入您统一的密码:" password

grep -w "^${group}" /etc/group &>/dev/null
[ $? -ne 0 ] && useradd ${group}
for i in {1..5}
do
useradd -G ${group} ${user_name}${i}
echo "${password}"|passwd --stdin ${user_name}${i}
done

1
2
3
4
5
6
7
8
9
10
11
12
13
结果测试:
[root@Jermyn ~]# id employe1
uid=1001(employe1) gid=1001(employe1) groups=1001(employe1),1000(development)
[root@Jermyn ~]# su - employe1
Last login: Thu Jul 7 20:39:30 CST 2022 on pts/0
[employe1@Jermyn ~]$ su - employe2
Password:
Last login: Thu Jul 7 20:39:42 CST 2022 on pts/0
[employe2@Jermyn ~]$ exit
logout
[employe1@Jermyn ~]$ exit
logout
[root@Jermyn ~]#
  1. 写一个脚本,局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里(以10.1.1.1~10.1.1.10 为例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/env bash
###########################################################################
# Name : check_ping_network.sh
# Desc : Check whether the PING succeeds and classification preservation
# Path : /root/test/shell_scripts/check_ping_netowrk.sh
# Auther : Jermyn
# Date : 2022-07-07
# Version : 1.0
###########################################################################

#!/bin/bash
#定义变量
read -p "请输入您需要PING的主机的主机位(如:10.1.1):" ip
#循环去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
1
2
3
4
5
6
7
运行结果:
[root@Jermyn shell_scripts]# time ./check_ping_network.sh
请输入您需要PING的主机的主机位(如:10.1.1):192.168.88

real 0m28.787s
user 0m0.012s
sys 0m0.080s

因为上述的 检测方式 花费时间太长,所以提供一种并发机制

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/env bash
###########################################################################
# Name : check_ping_network.sh
# Desc : Check whether the PING succeeds and classification preservation
# Path : /root/test/shell_scripts/check_ping_netowrk.sh
# Auther : Jermyn
# Date : 2022-07-07
# Version : 2.0
###########################################################################

#!/bin/bash
#定义变量
read -p "请输入您需要PING的主机的主机位(如:10.1.1):" ip
#循环去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 "检测已经完成,可以PING通的主机存放在/tmp/ip_up.txt "
echo "检测已经完成,不可以PING通的主机存放在/tmp/ip_down.txt "

1
2
3
4
5
6
7
8
9
结果显示:
[root@Jermyn shell_scripts]# time ./check_ping_network.sh
请输入您需要PING的主机的主机位(如:10.1.1):192.168.88
检测已经完成,可以PING通的主机存放在/tmp/ip_up.txt
检测已经完成,不可以PING通的主机存放在/tmp/ip_down.txt

real 0m6.781s
user 0m0.016s
sys 0m0.030s

条件判断语句(while…&until)

while循环

语法:不确定循环换次数;条件为真就进入循环;条件为假就退出循环

1
2
3
4
5
6
7
8
9
10
11
while 表达式
do
command...
done

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

实例:

1. 脚本同步系统时间

① 具体需求

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

② 思路

  1. 每隔30s同步一次时间,该脚本是一个死循环 while 循环
  2. 同步失败发送邮件
    • ntpdate 10.1.1.1
    • rdate -s 10.1.1.1
  3. 同步成功100次发送邮件 定义变量保存成功次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/bin/env bash
# 该脚本用于时间同步
NTP=10.1.1.1
count=0
while true
do
ntpdate $NTP &>/dev/null
if [ $? -ne 0 ];then
echo "system date failed" |mail -s "check system date" root@localhost
else
let count++
if [ $count -eq 100 ];then
echo "systemc date success" |mail -s "check system date" root@localhost && count=0
fi
fi
sleep 30
done


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

以上脚本还有更多的写法,课后自己完成

until 循环

语法:条件为假就进入循环;条件为真就退出循环

1
2
3
4
5
6
until expression   [ 1 -eq 1 ]  (( 1 >= 1 ))
do
command
command
...
done

打印 1-5

1
2
3
4
5
6
7
8
9
10
11
12
13
i=1
while [ $i -le 5 ]
do
echo $i
let i++
done

i=1
until [ $i -gt 5 ]
do
echo $i
let i++
done

实例

  • 具体需求
  1. 使用until语句批量创建10个用户,要求stu1—stu5用户的UID分别为1001—1005;
  2. stu6~stu10用户的家目录分别在/rhome/stu6—/rhome/stu10
  • 思路
  1. 创建用户语句 useradd -u|useradd -d
  2. 使用循环语句(until)批量创建用户 until循环语句结构
  3. 判断用户前5个和后5个 条件判断语句
  • 落地实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/bin/env bash
if [ -d /rhome ];then
echo "/rhome目录已存在"
else
mkdir /rhome
echo "/rhome不存在,已完成创建"
fi

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
useradd -d /rhome/stu$i stu$i
echo 123|passwd --stdin stu$i
fi
let i++
done

==================================================

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

随机数

生成随机数

系统变量:RANDOM,默认会产生0~32767的随机整数

前言:要想调用变量,必须加 $

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1.随机产生 0-32767 之间的数字(注:RAMDOM必须大写)
echo $RANDOM

2.随机产生 0-2 之间的数字(注:产生 0-X 之间的数字 宗旨就是随机数除以 X+1 后取余数)
echo "$[ ${RANDOM}%3 ]"

3.随机产生 0-99 之间的数字
echo "$[ ${RANDOM}%100 ]"

4.随机产生 50-99 之间的数字(注:产生 A-B 之间的数字,宗旨就是,产生随机数除以 A+1 后加上 B-A
例如:随机数字为 756 除以 511442 加上 4991
例如:随机数字为 764 除以 511450 加上 4999
例如:随机数字为 52 除以 5111 加上 4950
echo "$[ ${RANDOM}%51+49 ]"


5.随机产生一个一位数
echo "$[ ${RANDOM}%10 ]"

随机产生一个两位数
echo "$[ ${RANDOM}%90+10 ]"

随机产生一个三位数字(注:三位数字 100-999
echo "$[ ${RANDOM}%900+100 ]"

随机产生一个四位数字
echo "$[ ${RANDOM}%9000+1000 ]"

示例

2.1 具体需求:写一个脚本,产生一个phone_num.txt文件,随机产生以139开头的手机号1000个,每个一行。

思路:可以每次产生四个,两次产生或者其他,但是如果每个数字单独产生的话,电话容易出现重复的

  1. 产生1000个电话号码,脚本需要循环1000次 FOR WHILE UNTIL
  2. 139+8位,后8位随机产生,可以让每一位数字都随机产生 echo $[$RANDOM%10]
  3. 将随机产生的数字分别保存到变量里,然后加上139保存到文件里

1、for 循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/env bash
touch ./phone_number.txt
for ((i=0;i<=1000;i++))
do
n4=$[ $RANDOM%10 ]
n5=$[ $RANDOM%10 ]
n6=$[ $RANDOM%10 ]
n7=$[ $RANDOM%10 ]
n8=$[ $RANDOM%10 ]
n9=$[ $RANDOM%10 ]
n3=$[ $RANDOM%10 ]
n11=$[ $RANDOM%10 ]
echo "139${n4}${n5}${n6}${n7}${n8}${n9}${n10}${n11}" >>./phone_number.txt
done

2、while 循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/env bash
touch ./phone_number.txt
count=0
while [ $count -lt 1000 ]
do
n4=$[ $RANDOM%10 ]
n5=$[ $RANDOM%10 ]
n6=$[ $RANDOM%10 ]
n7=$[ $RANDOM%10 ]
n8=$[ $RANDOM%10 ]
n9=$[ $RANDOM%10 ]
n3=$[ $RANDOM%10 ]
n11=$[ $RANDOM%10 ]
echo "139${n4}${n5}${n6}${n7}${n8}${n9}${n10}${n11}" >>./phone_number.txt
let count++
done

3、while 死循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/env bash
touch ./phone_number.txt
count=0
while true
do
n4=$[ $RANDOM%10 ]
n5=$[ $RANDOM%10 ]
n6=$[ $RANDOM%10 ]
n7=$[ $RANDOM%10 ]
n8=$[ $RANDOM%10 ]
n9=$[ $RANDOM%10 ]
n3=$[ $RANDOM%10 ]
n11=$[ $RANDOM%10 ]
echo "139${n4}${n5}${n6}${n7}${n8}${n9}${n10}${n11}" >>./phone_number.txt
let count++
if [ $count -eq 1000 ];then exit;fi
done

4、until 循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/env bash
touch ./phone_number.txt
count=1
until [ $count -gt 1000 ]
do
n4=$[ $RANDOM%10 ]
n5=$[ $RANDOM%10 ]
n6=$[ $RANDOM%10 ]
n7=$[ $RANDOM%10 ]
n8=$[ $RANDOM%10 ]
n9=$[ $RANDOM%10 ]
n3=$[ $RANDOM%10 ]
n11=$[ $RANDOM%10 ]
echo "139${n4}${n5}${n6}${n7}${n8}${n9}${n10}${n11}" >>./phone_number.txt
let count++
done

2.2 具体需求:随机抽出5位幸运观众

  • 具体需求:

    • 在上面的1000个手机号里抽奖 5个 幸运观众,显示出这5个幸运观众。
    • 但只显示头3个数和尾号的4个数,中间的都用*代替
  • 思路:

    • 确定幸运观众所在的行 0-1000 随机找出一个数字 $[$RANDOM%1000+1]

    • 将电话号码提取出来 head -随机产生行号 phonenum.txt |tail -1

    • 显示 前3个和后4个数到屏幕 echo 139****

1
2
3
4
5
6
7
8
9
#!/bin/env bash
>luck_phone_num.txt
for ((i=1;i<=5;i++))
do
all_user=`wc -l phone_number.txt | cut -d ' ' -f1`
random_num=$[ $RANDOM%$all_user+1 ]
random_num_phone=$(head -$random_num phone_number.txt | tail -1 |cut -c '7-')
echo "139****$random_num_phone" | tee -a luck_phone_num.txt
done

2.3 批量创建用户(密码随机产生)

需求:批量创建5个用户,每个用户的密码为一个随机数

思路:1.循环5次创建用户

​ 2.产生一个密码文件来保存用户的随机密码

​ 3.从密码文件中取出随机密码赋值给用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/env bash
########################################################
# Name : creating_users02.sh
# Desc : 批量创建用户,批量创建5个用户,每个用户的密码为一个随机数
# Path : /root/test/shell_scripts/creating_users02.sh
# Auther : Jermyn
# Date : 2022-07-12
# Version : 2.0
########################################################

echo user0{1..5}:jermyn$[ $RANDOM%9000+1000 ]@.. | tr ' ' '\n' | tee -a user_passwd.txt
for ((i=1;i<=5;i++))
do
user=`head -$i uesr_passwd.txt | tail -1 | cut -d ':' -f 1 `
passwd=`head -$i uesr_passwd.txt | tail -1 | cut -d ':' -f 2 `
useradd $user
echo $pass|passwd --stdin $user
done

循环嵌套+shift 位移+补充expect

语法:

  • 一个循环体内又包含另一个完整的循环结构,称为循环的嵌套。

  • 每次外部循环都会触发内部循环,直至内部循环完成,才接着执行下一次的外部循环。

  • for循环、while循环和until循环可以相互嵌套。

1.1 打印九九乘法表

  • for 循环
1
2
3
4
5
6
7
8
9
#!/bin/bash
for ((i=1;i<=9;i++))
do
for ((j=1;j<=$i;j++))
do
echo -ne "$j*$i=$[ $i*$j ]\t"
done
echo
done
  • while 循环
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
i=1
while [ $i -le 9 ]
do
j=1
while [ $j -le $i ]
do
echo -ne "$j*$i=$[ $i*$j ]\t"
let j++
done
echo
let i++
done
  • until 循环
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
i=1
until [ $i -gt 9 ]
do
j=1
until [ $j -gt $i ]
do
echo -ne "$j*$i=$[ $i*$j ]\t"
let j++
done
echo
let i++
done

shift 位移的用法

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

:
true
false

2.1 实现用户自定义输入数字,然后脚本计算和:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
1.常规 for 循环
#!/bin/bash
sum=0
for i
do
let sum=$sum+$i
done
echo sum=$sum

结果显示
[root@Jermyn shell_scripts]# bash -x demo.sh 1 2 3
+ sum=0
+ for i in '"$@"'
+ let sum=0+1
+ for i in '"$@"'
+ let sum=1+2
+ for i in '"$@"'
+ let sum=3+3
+ echo sum=6
sum=6


2.使用 shift
#!/bin/bash
sum=0
while [ $# -ne 0 ]
do
let sum=$sum+$1
shift
done
echo sum=$sum

结果显示:
[root@Jermyn shell_scripts]# bash -x demo.sh 1 2 3
+ sum=0
+ '[' 3 -ne 0 ']'
+ let sum=0+1
+ shift
+ '[' 2 -ne 0 ']'
+ let sum=1+2
+ shift
+ '[' 1 -ne 0 ']'
+ let sum=3+3
+ shift
+ '[' 0 -ne 0 ']'
+ echo sum=6
sum=6

补充扩展expect

语法:expect 自动应答 tcl语言

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/expect
# 开启一个程序
spawn ssh root@192.168.88.139
# 捕获相关内容
expect {
"(yes/no)?" { send "yes\r";exp_continue }
"password:" { send "123456\r" }
}
interact //交互

脚本执行方式:
# ./expect1.sh
# /root/test/shell_scripts/expect1.sh
# expect -f expect1.sh

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


2)使用位置参数 类比 bash 的$* $1 等等
#!/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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/expect

# 定义变量 格式:set 变量名 变量值
set ip 10.1.1.1
set pass 123456

# timeout 时间间隔
set timeout 5

# 执行命令
spawn ssh root@$ip
expect {
# 如果捕获到 "yes/no" 发送 yes回车 反之继续操作
"yes/no" { send "yes\r";exp_continue }
# 如果捕获到 "password:" 发送 $pass 回车
"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.3 shell 脚本和 expect 结合

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
[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 <<-EOF
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
EOF
}&
done
wait
echo "user is ok...."


或者
#!/bin/bash
while read ip pass
do
{
/usr/bin/expect <<-EOF
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
EOF

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

实战示例

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

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

10.1.1.1:123456

10.1.1.2:123456

案例分析

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

落地实现

代码拆分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
1.判断yunwei用户的公钥是否存在
[ ! -f /hoem/yunwei/.ssh/id_rsa ] && ssh-keygen -P '' -f ./id_rsa

2.获取IP并且判断是否可以 ping 通
2.1主机密码文件 ip.txt
10.1.1.1:123456
10.1.1.2:123456
2.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


最终实现

  1. 环境准备
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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组里的所有成员可以在任何主机上以任何用户身份执行任何命令
  1. 脚本实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/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 "公钥成功推送完毕"

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

  1. 找出查看网站连接状态的命令 ss -natp|grep :22
  2. 如何统计==不同的==状态 循环去统计,需要计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
#count_http_22_state
#统计每个状态的个数

declare -A array1
states=`ss -ant|grep 22|cut -d' ' -f1`

for i in $states
do
let array1[$i]++
done

#通过遍历数组里的索引和元素打印出来
for j in ${!array1[@]}
do
echo $j:${array1[$j]}
done

case语句+函数

case语句

语法:case语句为多重匹配语句;如果匹配成功,执行相匹配的命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
说明:pattern表示需要匹配的模式


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

需求:

1.1当给程序传入start、stop、restart三个不同参数时分别执行相应命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/env bash
case $1 in
start|S)
service apache start &>/dev/null && echo "apache 启动成功" || echo "apache 启动失败"
;;
stop|T)
service apache stop &>/dev/null && echo "apache 停止成功" || echo "apache 停止失败"
;;
restart|R)
service apache restart &>/dev/null && echo "apache 重启完毕" || echo "apache 重启失败"
;;
*)
echo -e "\033[31m 此脚本需要传入位置参数 \033[0m"
echo -e "\033[41;30m 请选择 start|S || stop|T || restart|R\033[0m"
;;
esac

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/env bash
read -p "请输入你要管理的服务名称(vsftpd):" service
case $service in
vsftpd|ftp)
read -p "请选择你需要做的事情(restart|stop):" action
case $action in
stop|S)
service vsftpd stop &>/dev/null && echo "该$serivce服务已经停止成功"
;;
start)
service vsftpd start &>/dev/null && echo "该$serivce服务已经成功启动"
;;
esac
;;
httpd|apache)
echo "apache hello world"
;;
*)
echo "请输入你要管理的服务名称(vsftpd):"
;;
esac

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

落地实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/bin/env bash
meue(){
echo -e "\033[32m
################## 菜单栏 #########################
# f 显示磁盘分区 #
# d 显示磁盘挂载 #
# m 查看内存使用 #
# u 查看系统负载 #
# q 退出程序 #
###################################################
\033[0m"
}
meue

while true
do
read -p "请选择您需要查询的内容:" action
case $action in
f)
clear
echo -e "\033[36m########################### 显示分区 ######################\033[0m"
fdisk -l
meue
;;
d)
clear
echo -e "\033[36m########################### 显示磁盘挂载 ######################\033[0m"
df -h
meue
;;
m)
clear
echo -e "\033[36m########################### 查看内存使用 ######################\033[0m"
free -m
meue
;;
u)
clear
echo -e "\033[36m########################### 查看系统负载区 ######################\033[0m"
uptime
meue
;;
q)
echo -e "\033[36m########################### 退出程序 ######################\033[0m"
exit
;;
esac
done

函数

语法 :

  • shell中允许将一组命令集合语句形成一段可用代码

  • 这些代码块称为shell函数给这段代码起个名字称为函数名,后续可以直接调用该段代码的功能

1
2
3
4
5
6
方法一:

函数名()
{
函数体(一堆命令的集合,来实现某个功能)
}
1
2
3
4
5
6
7
8
方法二:

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

函数中return说明:

  1. return可以结束一个函数。类似于循环控制语句break(结束当前循环,执行循环体后面的代码)。
  2. return默认返回函数中最后一个命令状态值,也可以给定参数值,范围是0-256之间。
  3. 如果没有return命令,函数将返回最后一个指令的退出状态值。

2.1 当前命令行调用

1
2
3
4
5
6
7
8
#!/bin/bash
fun_1(){
echo "调用fun_1函数就打印了我!!!"
}

fun_2(){
echo "调用fun_2函数就打印了我!!!"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@Jermyn shell_scripts]# cat demo.sh 
#!/bin/bash
fun_1(){
echo "调用fun_1函数就打印了我!!!"
}

fun_2(){
echo "调用fun_2函数就打印了我!!!"
}
[root@Jermyn shell_scripts]# source demo.sh
[root@Jermyn shell_scripts]# fun_1
调用fun_1函数就打印了我!!!
[root@Jermyn shell_scripts]# fun_2
调用fun_2函数就打印了我!!!
[root@Jermyn shell_scripts]#

2.2 定义到用户的环境变量中

1
vim ~/.bashrc 
1
2
3
4
5
6
7
8
9
10
11
12
添加编写的函数:

fun_1(){
echo "调用fun_1函数就打印了我!!!"
}

fun_2(){
echo "调用fun_2函数就打印了我!!!"
}


注:当用户打开 bash 的时候会读取该文件

2.3 脚本中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/env bash
meue(){
echo -e "\033[32m
################## 菜单栏 #########################
# f 显示磁盘分区 #
# d 显示磁盘挂载 #
# m 查看内存使用 #
# u 查看系统负载 #
# q 退出程序 #
###################################################
\033[0m"
}
# 调用函数
meue

2.4 需求

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

思路:

  1. 交互式定义多个变量来保存用户信息 姓名、性别、年龄
  2. 如果不输一直提示输入

    • 循环==直到输入字符串不为空 while 判断输入字符串是否为空
    • 每个信息都必须不能为空,该功能可以定义为一个函数,方便下面脚本调用
  3. 根据用户输入信息做出匹配判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/env bash
input_fun()
{
input_info=""
output_info=$1
while [ -z $input_info ]
do
read -p "$output_info" input_info
done
echo $input_info
}

while true
do
name=$(input_fun 请输入您的姓名:)
if [[ $name = "exit" ]] ; then
exit
fi
gender=$(input_fun 请输入您的性别:)
age=$(input_fun 请输入您的年龄:)
echo "name $name gender $gender age $age" | tee -a stu_info.txt
done

2.5 需求

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

综合分析

  1. 将脚本放到 yunwei 用户家目录里的.bashrc文件里(/root/yunwei/jump_server.sh)
  2. 将菜单定义为一个函数[打印菜单],方便后面调用
  3. 用case语句来实现用户的选择【交互式定义变量】
  4. 当用户选择了某一台服务器后,进一步询问用户需要做的事情 case…esac 交互式定义变量
  5. 使用循环来实现用户不选择一直让其选择
  6. 限制用户退出后直接关闭终端 exit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/bin/bash
# jumper-server
# 定义菜单打印功能的函数
menu()
{
cat <<-EOF
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit
EOF
}
# 屏蔽以下信号
trap '' 1 2 3 19
# 调用函数来打印菜单
menu
#循环等待用户选择
while true
do
# 菜单选择,case...esac语句
read -p "请选择你要访问的主机:" host
case $host in
1)
ssh root@10.1.1.1
;;
2)
ssh root@10.1.1.2
;;
3)
ssh root@10.1.1.3
;;
h)
clear;menu
;;
q)
exit
;;
esac
done


将脚本放到yunwei 用户家目录里的.bashrc里执行:每次启动体跳板机即运行脚本
bash ~/jumper-server.sh
exit

进一步完善需求

为了进一步增强跳板机的安全性,工作人员通过跳板机访问生产环境,但是不能在跳板机上停留。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/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

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

回顾信号:

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

正则表达式

语法:正则表达式(Regular Expression、regex 或 regexp,缩写为RE),也译为正规表示法、常规表示法,是一种字符模式,用于在查找过程中匹配指定的字符。许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的。支持正则表达式的程序如:locate |find| vim| grep| sed |awk

作用:匹配邮箱、匹配身份证号码、手机号、银行卡号等匹配某些特定字符串,做特定处理等等

正则当中名词解释

  • 元字符

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

  • 前导字符

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

1.1 正则中普通常用的元字符

元字符功能备注
.匹配除了换行符以外的任意单个字符
*前导字符出现0次或连续多次
.*任意长度字符ab.*
^行首(以…开头)^root
$行尾(以…结尾)bash$
^$空行
[]匹配括号里任意单个字符或一组单个字符[abc]
匹配不包含括号里任一单个字符或一组单个字符abc
^[]匹配以括号里任意单个字符或一组单个字符开头^[abc]
\^[\^]匹配不以括号里任意单个字符或一组单个字符开头\^[^abc]

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# cat RegExpTest_02.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
3
4
5
6
7
8
9
10
1.前导字符 g 后面任意字符出现
[root@Jermyn shell_scripts]# grep "g." RegExpTest_02.txt
ggle # 前导字符 g 后面任意字符出现一次 gg ...
gogle # gogl ...
google # go ... gl
gooogle # go ... gl
goooooogle # go ... gl
gooooooogle # go ... gl
jingdong.com # gd ... g.
dingdingdongdong.com # gd ... gd ... gd ... g.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2.前导字符 g 出现零次或多次
[root@Jermyn shell_scripts]# grep "g*" RegExpTest_02.txt
ggle # 前导字符 g 出现 两次
gogle # 前导字符 g 出现 一次
google # ........
gooogle
goooooogle
gooooooogle # .........
taobao.com # 前导字符 g 出现 零次 未出现 g 即出现0次 符合
taotaobaobao.com # 前导字符 g 出现 零次
# 前导字符 g 出现 零次
jingdong.com
dingdingdongdong.com # 前导字符 g 出现 一次
10.1.1.1
Adfjd8789JHfdsdf/
a87fdjfkdLKJK
7kdjfd989KJK;
bSKJjkksdjf878.
cidufKJHJ6576,

hello world
helloworld yourself
1
2
3
4
5
6
7
8
9
10
3.  g 确定 前导字符 o 出现零次或多次
[root@Jermyn shell_scripts]# grep "go*" RegExpTest_02.txt
ggle # g 确定 前导字符 o 出现零次
gogle # g 确定 前导字符 o 出现一次;g 确定 前导字符 o 出现零次
google # g 确定 前导字符 o 出现两次;g 确定 前导字符 o 出现零次
gooogle # g 确定 前导字符 o 出现三次;g 确定 前导字符 o 出现零次
goooooogle # g 确定 前导字符 o 出现六次;g 确定 前导字符 o 出现零次
gooooooogle # g 确定 前导字符 o 出现八次;g 确定 前导字符 o 出现零次
jingdong.com # g 确定 前导字符 o 出现零次
dingdingdongdong.com # g 确定 前导字符 o 出现零次
1
2
3
4
5
6
7
8
4. 以 g 开头的行
[root@Jermyn shell_scripts]# grep "^g" RegExpTest_02.txt
ggle # 以 g 开头的行
gogle # 以 g 开头的行
google
gooogle # 以 g 开头的行
goooooogle
gooooooogle
1
2
3
4
5
6
7
8
5. 以 g 开头的后面的有一个字符
[root@Jermyn shell_scripts]# grep "^g." RegExpTest_02.txt
ggle
gogle
google
gooogle
goooooogle
gooooooogle
1
2
3
4
5
6
7
8
6.以 g 开头的字符 前导字符是任意的后面一个字符 任意字符 出现一次或者多次
[root@Jermyn shell_scripts]# grep "^g.*" RegExpTest_02.txt
ggle
gogle
google
gooogle
goooooogle
gooooooogle
1
2
3
4
5
6
7
8
7.前导字符为 空 后面有任意字符,这个任意字符出现一次或者多次,以 gle 结尾
[root@Jermyn shell_scripts]# grep ".*gle$" RegExpTest_02.txt
ggle
gogle
google
gooogle
goooooogle
gooooooogle
1
2
3
4
8.查看空行
[root@Jermyn shell_scripts]# grep -n "^$" RegExpTest_02.txt
11:
20:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
9.匹配 [ogl] 中的 o 或 g 或 l
[root@Jermyn shell_scripts]# grep "[ogl]" RegExpTest_02.txt
g
gle
ggle
gogle
google
gooogle
goooooogle
gooooooogle
taobao.com
taotaobaobao.com
jingdong.com
dingdingdongdong.com
hello world
helloworld yourself
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
10.匹配除了 o g l 以外的字符 不包含 空
[root@Jermyn shell_scripts]# grep "[^ogl]" RegExpTest_02.txt
gle
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
3
4
5
6
7
8
9
10
11
12
11.匹配 [gh] 任意单个字符开头的
[root@Jermyn shell_scripts]# grep "^[gh]" RegExpTest_02.txt
g
gle
ggle
gogle
google
gooogle
goooooogle
gooooooogle
hello world
helloworld yourself
1
2
3
4
5
6
7
8
9
10
11
12
12.查看 profile 的前20行,行过滤 不以 # 开头的
[root@Jermyn shell_scripts]# head -20 /etc/profile | grep "^[^#]"
pathmunge () {
case ":${PATH}:" in
*:"$1":*)
;;
*)
if [ "$2" = "after" ] ; then
PATH=$PATH:$1
else
PATH=$1:$PATH
fi
1
2
3
4
5
6
7
13.查看 RegExpTest_02.txt 的前 10 行 行过滤不以 go 开头的
[root@Jermyn shell_scripts]# head -10 RegExpTest_02.txt | grep -v "^go"
g
gle
ggle
taobao.com
taotaobaobao.com

1.2 正则中其他常用元字符

元字符功能备注
\<取单词的头
\>取单词的尾
\< \>精确匹配
\{n\}匹配前导字符 连续出现n次
\{n,\}匹配前导字符 至少出现n次
\{n,m\}匹配前导字符出现 n次与m次之间
\( \)保存被匹配的字符
\d匹配数字(grep -P[0-9]
\w匹配字母数字下划线(grep -P[a-zA-Z0-9_]
\s匹配空格、制表符、换页符(grep -P[\t\r\n]

举例说明:

1
2
3
4
5
6
7
8
1.匹配单词的头以 hel 开头的
[root@Jermyn shell_scripts]# grep "\<hel" RegExpTest_02.txt
# helloworld yourself01
hello world
helloworld yourself
# helloworld yourself02
# helloworld yourself03
# helloworld yourself04
1
2
3
2.精确匹配文件中 只是 g 的行
[root@Jermyn shell_scripts]# grep "\<g\>" RegExpTest_02.txt
g
1
2
3
4
5
6
3. g 确定,前导字符 o 出现两次
[root@Jermyn shell_scripts]# grep "go\{2\}" RegExpTest_02.txt
google
gooogle
goooooogle
gooooooogle
1
2
3
4
5
6
7
8
4.前导字符 o 出现两次
[root@Jermyn shell_scripts]# grep "o\{2\}" RegExpTest_02.txt
google
gooogle
goooooogle
gooooooogle

注:此语法可看作 oo 作为整体出现的次数,gogle 则匹配不到,gooole 则只有前两个oo 高亮
1
2
3
4
5
6
5. 前导字符 o 出现至少 2
[root@Jermyn shell_scripts]# grep "o\{2,\}" RegExpTest_02.txt
google
gooogle
goooooogle
gooooooogle
1
2
3
6.匹配 ip 10即[0-9]出现两次; . 需要转义;1 即[0-9] 出现一次
[root@Jermyn shell_scripts]# grep "[0-9]\{2\}\.[0-9]\{1\}.[0-9]\{1\}.[0-9]\{1\}" RegExpTest_02.txt
10.1.1.1
1
2
3
4
5
7.改10.1.1.110.1.1.254
vim 的末行模式 输入 :%s/\(10.1.1\).1/\1.254/g
解释:%s 全局搜索
10.1.1)将此打包起来
1.254 1 是标签名



1
2
3
4
8.修改 helloworld yourself 为 hiworld myself

vim 的末行模式
:%s/hello\(world \)your\(self\)/hi\1my\2/g

1.3 扩展类正则常用元字符

语法:

  • grep你要用我,必须加 grep -E或者 让使用egrep
  • sed你要用我,必须加 sed -r
扩展元字符功能备注
+匹配一个或多个前导字符bo+ 匹配boo、 bo
?匹配零个或一个前导字符bo? 匹配b、 bo
\匹配a或b
()组字符(看成整体)(my\your)self:表示匹配myself或匹配yourself
{n}前导字符重复n次
{n,}前导字符重复至少n次
{n,m}前导字符重复n到m次

举例说明:

1
2
3
4
5
6
7
1. g 固定,前导字符 o 出现一次或者多次,和 go* 区别 后者可以出现零次
[root@Jermyn shell_scripts]# grep -E "go+" RegExpTest_02.txt
gogle
google
gooogle
goooooogle
gooooooogle
1
2
3
4
5
6
7
8
9
10
11
12
2. g 固定,前导字符 o 出现零次或者一次
[root@Jermyn shell_scripts]# grep -E "go?" RegExpTest_02.txt
g
gle
ggle
gogle
google
gooogle
goooooogle
gooooooogle
jingdong.com
dingdingdongdong.com
1
2
3
4
5
6
7
8
9
10
11
12
3. g  是固定的,前导字符是后面任意一个字符 出现零次或者一次(注:就是匹配 g 后面是任意一个字符,如 go 和 gl 是相同的)
[root@Jermyn shell_scripts]# grep -E "g.?" RegExpTest_02.txt
g # 前导字符 g 后空字符出现零次
gle # 前导字符 g 后面字符 l 出现一次
ggle
gogle
google
gooogle
goooooogle
gooooooogle
jingdong.com
dingdingdongdong.com
1
2
3
4
4.匹配以 a 或者 以 b 的行 
[root@Jermyn shell_scripts]# grep -E "^a|^b" RegExpTest_02.txt
a87fdjfkdLKJK
bSKJjkksdjf878.
1
2
3
4
5
6
5.匹配以 a 开头的 或者 含有 b 的行
[root@Jermyn shell_scripts]# grep -E "^a|b" RegExpTest_02.txt
taobao.com
taotaobaobao.com
a87fdjfkdLKJK
bSKJjkksdjf878.
1
2
3
4
5
6
7
6.过滤 ao 或者 bo
[root@Jermyn shell_scripts]# grep -E "(a|b)o" RegExpTest_02.txt
taobao.com
taotaobaobao.com
ao87fdjfkdLKJK
7kdjafd9bo89aoKJK;
boSKaJjkksdajf878.
1
2
3
4
5
6
7
8
9
10
11
7.作用与 不加 \ 的效果相同
[root@Jermyn shell_scripts]# grep "go\{2\}" RegExpTest_02.txt
google
gooogle
goooooogle
gooooooogle
[root@Jermyn shell_scripts]# grep -E "go{2}" RegExpTest_02.txt
google
gooogle
goooooogle
gooooooogle

1.4 第二类正则

表达式功能示例
[:alnum:]字母与数字字符[[:alnum:]]+
[:alpha:]字母字符(包括大小写字母)[[:alpha:]]{4}
[:blank:]空格与制表符[[:blank:]]*
[:digit:]数字[[:digit:]]?
[:lower:]小写字母[[:lower:]]{4,}
[:upper:]大写字母[[:upper:]]+
[:punct:]标点符号[[:punct:]]
[:space:]包括换行符,回车等在内的所有空白[[:space:]]+
1
2
3
4
1.数字连续出现4次 (注:[:lower:] 不可以有空格)
[root@Jermyn shell_scripts]# grep -E "[[:digit:]]{4}" RegExpTest_02.txt
Adfjd8789JHfdsdf/
cidufaKoJHJ6576,

正则字符一览表

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

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

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

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
文件准备:
vim test.txt

Aieur45869Root0000
9h847RkjfkIIIhello
rootHllow88000dfjj
8ikuioerhfhupliooking
hello world
192.168.0.254
welcome to uplooking.
abcderfkdjfkdtest
rlllA899kdfkdfj
iiiA848890ldkfjdkfj
abc
12345678908374
123456@qq.com
123456@163.com
abcdefg@itcast.com23ed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
1、查找不以大写字母开头的行(三种写法)。
grep '^[^A-Z]' RegExpTest_01.txt
grep -v "^[A-Z]" RegExpTest_01.txt
grep -v "\<[A-Z]" RegExpTest_01.txt
grep -E -v "^[[:upper:]]" RegExpTest_01.txt <---> '^[^[:upper:]]'

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

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

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

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

6、查找以大写字母开头的
grep '^[A-Z]' RegExpTest_01.txt
grep -E "^[[:upper:]]" RegExpTest_01.txt

7、查找以小写字母开头的
grep '^[a-z]' RegExpTest_01.txt
grep -E "^[[:lower:]]" RegExpTest_01.txt

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


9、去掉空行
grep '^[^$]' RegExpTest_01.txt

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

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

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


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



14、找出文件中的ip地址
cat /etc/sysconfig/network-scripts/ifcfg-ens33 | grep "IPADDR" | cut -d "=" -f2

15、找出全部是数字的行
grep -E '^[0-9]+$' RegExpTest_01.txt
grep -E -v "[[:alpha:]|[:punct:]|[:space:]]" RegExpTest_01.txt | grep -v "^$"
grep -E -v "[[:alpha:]|[:punct:]|[:space:]]" RegExpTest_01.txt | grep "[0-9]."


16、找出邮箱地址
grep -E '^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$' RegExpTest_01.txt
grep -E "([0-9]|[A-Z]|[a-z]|[._-])+@([0-9]|[A-Z]|[a-z]|[._-])+\.([A-Z]|[a-z])+$" RegExpTest_01.txt

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 匹配整个单词

脚本搭建Web服务

脚本搭建web服务

要求如下

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

参考脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
参考:
#!/bin/bash
conf=/etc/httpd/conf/httpd.conf
input_fun()
{
input_var=""
output_var=$1
while [ -z $input_var ]
do
read -p "$output_var" input_var
done
echo $input_var
}
ipaddr=$(input_fun "Input Host ip[192.168.0.1]:")
web_host_name=$(input_fun "Input VirtualHostName [www.test.cc]:")
root_dir=$(input_fun "Input host Documentroot dir:[/var/www/html]:")

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

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

sed 用法

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

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

  • 首先sed读取文件中的一行内容,把其保存在一个临时缓存区中(也称为模式空间)

  • 然后根据需求处理临时缓冲区中的行,完成后把该行发送到屏幕上

总结:

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

命令行格式

语法:sed [options] 处理动作 文件名 (注:一定是单引号 )

sed [options] ‘处理动作’ 文件名

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

以下所有的动作都要在单引号

动作说明备注
‘p’打印
‘i’在指定行之前插入内容类似vim里的大写O
‘a’在指定行之后插入内容类似vim里的小写o
‘c’替换指定行所有内容
‘d’删除指定行

1.1 例子

1
2
3
4
5
6
7
8
9
10
vim sed_test.txt

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
298374837483
172.16.0.254
10.1.1.1

1.1.1 对文件进行增、删、改、查操作

语法:sed 选项 ‘定位+命令’ 需要处理的文件

1
2
3
4
5
6
7
8
9
10
1. 不加处理动作,打印文件的内容
[root@Jermyn shell_scripts]# sed '' sed_test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
298374837483
172.16.0.254
10.1.1.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2.按照处理流程,会一行一行执行代码,第一次的打印指运行此行时打印出来与代码要求无关,第二次打印指代码此行要求打印(注:简单理解为第一行时sed处理的内容,第二行为处理的结果)
[root@Jermyn shell_scripts]# sed 'p' demo.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
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
298374837483
298374837483
172.16.0.254
172.16.0.254
10.1.1.1
10.1.1.1
1
2
3
4
5
6
7
8
9
10
3. -n 指的是取消默认输出,可以理解为,每次 sed 处理文件的某行时都会打印一次此行的内容,-n 就取消了这次打印,而下面的结果就是,sed 对文件的处理的结果 (注: -n'p' 经常一起使用)
[root@Jermyn shell_scripts]# sed -n 'p' sed_test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
298374837483
172.16.0.254
10.1.1.1
1
2
3
4
5
6
7
8
9
10
11
4.此部分可以理解为,对第二行进行 sed 打印的操作,其他行都是默认打印的内容,第二行第一遍就是默认打印的结果
[root@Jermyn shell_scripts]# sed '2p' sed_test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
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
1
2
3
5.此部分理解为,sed 只对第二行进行了打印的操作,-n 值得默认打印内容都不输出
[root@Jermyn shell_scripts]# sed -n '2p' sed_test.txt
bin:x:1:1:bin:/bin:/sbin/nologin
1
2
6.打印 1-5行 理解上方几点,此无特别之处
[root@Jermyn shell_scripts]# sed -n '1,5p' sed_test.txt
1
2
3
7. $p 指打印最后一行 无 ^p 用法,打印第一行 1p
[root@Jermyn shell_scripts]# sed -n '$p' sed_test.txt
10.1.1.1

1.1.2 增加文件内容

i 定位符上一行插入内容

a 定位符下一行插入内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 在文件的第二行插入 hello world
[root@Jermyn shell_scripts]# sed -n '2i hello world' sed_test.txt
hello world

[root@Jermyn shell_scripts]# sed '2i hello world' sed_test.txt
root:x:0:0:root:/root:/bin/bash
hello world
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2.不加定位符的话,就是每行的上一行都插入 hello world ,可以理解为 sed  对每行进行传入的操作,命令的结果就是内容 hello world 命令执行的对象就是每一行,所以每一行都会打印一次
[root@Jermyn shell_scripts]# sed 'i hello world' sed_test.txt
hello world
root:x:0:0:root:/root:/bin/bash
hello world
bin:x:1:1:bin:/bin:/sbin/nologin
hello world
daemon:x:2:2:daemon:/sbin:/sbin/nologin
hello world
adm:x:3:4:adm:/var/adm:/sbin/nologin
hello world
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
hello world
298374837483
hello world
172.16.0.254
hello world
10.1.1.1
1
2
3.文件的第二行的上一行插入 hello 换行 world 。
[root@Jermyn shell_scripts]# sed '2i hello\nworld' sed_test.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
4.先输入左侧单引号,添加的内容添加完成后在输入右侧的单引号。第一行输入 sed '3i\ 后敲击回车,第二行 \ 后敲击回车
[root@Jermyn shell_scripts]# sed '3i\
> hello\
> world' sed_test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
hello
world
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

注:示例 3 和 4 示例 3 更好操作些,示例4更直观些

1
2
3
4
5. a 类比 i 
[root@Jermyn shell_scripts]# sed '1,3a 111' sed_test.txt[root@Jermyn shell_scripts]# sed '1,3a 111' sed_test.txt

[root@Jermyn shell_scripts]# sed '$a 111' sed_test.txt

1.1.3 修改文件的内容

c 替换指定的整行内容

1
2
3
4
5
6
7
8
9
10
1.将以 adm 开头的行替换为 hello world
[root@Jermyn shell_scripts]# sed '/^adm/c hello world' sed_test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
hello world
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
298374837483
172.16.0.254
10.1.1.1
1
2
2.每一行都替换为 hello world
[root@Jermyn shell_scripts]# sed 'c hello world' sed_test.txt
1
2
3
4
5
6
3.此命令较之前特殊,是将 1-5总共5行 替换为一行 hello world
[root@Jermyn shell_scripts]# sed '1,5c hello world' sed_test.txt
hello world
298374837483
172.16.0.254
10.1.1.1

1.1.4 删除文件的内容

1
2
3
4
5
6
7
8
9
10
11
1.删除文件的某行
[root@Jermyn shell_scripts]# sed '1d' sed_test.txt
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
298374837483
172.16.0.254
10.1.1.1
[root@Jermyn shell_scripts]# cat -n sed_test.txt
1 root:x:0:0:root:/root:/bin/bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2.删除文件的 1-3
[root@Jermyn shell_scripts]# cat -n sed_test.txt
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 298374837483
7 172.16.0.254
8 10.1.1.1
[root@Jermyn shell_scripts]# sed '1,3d' sed_test.txt
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
1
2
3
4
5
6
7
8
9
10
3.删除文件的最后一行
[root@Jermyn shell_scripts]# sed '$d' sed_test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
298374837483
172.16.0.254
[root@Jermyn shell_scripts]#
1
2
3
4.不加定位就是全删
[root@Jermyn shell_scripts]# sed 'd' sed_test.txt
[root@Jermyn shell_scripts]#
1
2
3
5.正则删除文件中含有数字的,结果就是全删除了,每行都带有数字
[root@Jermyn shell_scripts]# sed '/[0-9]/d' sed_test.txt
[root@Jermyn shell_scripts]#
1
2
3
6.只删除 ip 地址的
sed '/[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/d' sed_test.txt
sed -r '([0-9]{1,3}\.){3}[0-9]{1,3}' sed_test.txt

1.2 其他命令

命令解释备注
r从另外文件中读取内容
w内容另存为
&保存查找串以便在替换串中引用和\(\)相同
=打印行号
对所选行以外的所有行应用命令,放到行数之后‘1,5!’
q退出

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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

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

[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

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


综合运用:
[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

1.3 其他选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
-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='

# 打印第 1 和第 5 行
sed -n '1,5p' 1.txt
sed -ne '1p' -ne '5p' 1.txt
sed -ne '1p;5p' 1.txt

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

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

# 打印文件中不以字母开头的行
[root@server ~]# grep '^[^a-z]' 1.txt
[root@server ~]# sed -n '/^[^a-z]/p' 1.txt

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

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

[root@server ~]# sed -nr '/([0-9]{1,3}\.){3}[0-9]{1,3}/p' 2.txt
10.1.1.1
10.1.1.255
255.255.255.0

# 过滤出ifcfg-eth0文件中的IP、子网掩码、广播地址
[root@server shell06]# grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' ifcfg-eth0
10.1.1.1
255.255.255.0
10.1.1.254

[root@server shell06]# sed -nr '/([0-9]{1,3}\.){3}[0-9]{1,3}/p' ifcfg-eth0|cut -d'=' -f2
10.1.1.1
255.255.255.0
10.1.1.254

[root@server shell06]# sed -nr '/([0-9]{1,3}\.){3}[0-9]{1,3}/p' ifcfg-eth0|sed -n 's/[A-Z=]//gp'
10.1.1.1
255.255.255.0
10.1.1.254

[root@server shell06]# ifconfig eth0|sed -n '2p'|sed -n 's/[:a-Z]//gp'|sed -n 's/ /\n/gp'|sed '/^$/d'
10.1.1.1
10.1.1.255
255.255.255.0

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

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

-i 选项 直接修改原文件

1
2
3
# sed -i 's/root/ROOT/;s/stu/STU/' 11.txt
# sed -i '17{s/YUNWEI/yunwei/;s#/bin/bash#/sbin/nologin#}' 1.txt
# sed -i '1,5s/^/#&/' a.txt

注意:
-ni 不要一起使用
p 命令 不要再使用 -i 时使用

1.4 sed 结合正则使用

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

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

示例:

1
2
3
4
5
6
7
1.匹配 bin 到 lpd 之间的行
[root@Jermyn shell_scripts]# sed -n '/bin/,/lpd/p' sed_test.txt
ROOT:x:0:0:ROOT:/ROOT:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
1
2
3
4
2.匹配 adm 开头的行或者 lp 开头的行
[root@Jermyn shell_scripts]# sed -nr '/^adm|lp/p' sed_test.txt
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
1
2
3
4
5
6
3.匹配以 lp 开头的行到 第 8
[root@Jermyn shell_scripts]# sed -n '/^lp/,8p' sed_test.txt
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
298374837483

# 192.168.445.2522
1
2
3
4
5
4.匹配第 8 行到以 10 开头的行
[root@Jermyn shell_scripts]# sed -n '8,/^10/p' sed_test.txt
# 192.168.445.2522
172.16.0.254
10.1.1.1
1
2
3
4
5
5.匹配从第 2 行开始 到以 adm 或者 lp 开头的行
[root@Jermyn shell_scripts]# sed -nr '2,/^adm|^lp/p' sed_test.txt
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin

脚本模式

用法:

1
2
3
4
5
6
7
8
9
10
11
# sed -f scripts.sh  file		//使用脚本处理文件
建议使用 ./sed.sh file

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

注意事项:

1
2
3
4
5
1. 脚本文件是一个sed的命令行清单。'commands'
2. 在每行的末尾不能有任何空格、制表符(tab)或其它文本。
3. 如果在一行中有多个命令,应该用分号分隔。
4. 不需要且不可用引号保护命令
5. #号开头的行为注释

awk用法

awk 介绍

1.1 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.2 awk 作用

  1. awk用来处理文件和数据的,是类unix下的一个工具,也是一种编程语言
  2. 可以用来统计数据,比如网站的访问量,访问的IP量等等
  3. 支持条件判断,支持for和while循环

awk 使用方式

2.1 命令行模式的使用

语法:

1
2
3
4
5
awk 选项 '命令部分' 文件名


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

命名部分说明:

  • 正则表达式,地址定位
1
2
3
'/root/{awk语句}'					sed中: '/root/p'
'NR==1,NR==5{awk语句}' sed中: '1,5p'
'/^root/,/^ftp/{awk语句}' sed中:'/^root/,/^ftp/p'
  • {awk语句1;awk语句2;…}
1
2
3
'{print $0;print $1}'			sed中:'p'
'NR==5{print $0}' sed中:'5p'
注:awk命令语句间用分号间隔
  • BEGIN…END….
1
2
3
'BEGIN{awk语句};{处理中};END{awk语句}'
'BEGIN{awk语句};{处理中}'
'{处理中};END{awk语句}'

2.2 常用选项介绍

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

脚本模式使用

3.1 脚本编写

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

3.2 脚本执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
方法1
awk 选项 -f awk的脚本文件 要处理的文本文件
awk -f awk.sh filename

sed -f sed.sh -i filename

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

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

sed -f sed.sh -i filename

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

./sed.sh filename

3.3 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输出记录分割符,默认换行‘BEGI N{ORS=”\n\n”};{print $1,$3}’
FILENAME当前输入的文件名

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
vim awk_test.txt
...
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
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
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin
ntp:x:38:38::/etc/ntp:/sbin/nologin
Jermyn:x:1000:1000::/home/Jermyn:/bin/bash
1
2
3
4
5
6
7
8
1.awk 执行方式与 sed 类似,即一行一行执行,此命令打印当前处理的行,即打印文件的每行内容
[root@Jermyn shell_scripts]# awk '{print $0}' awk_test.txt 、
[root@Jermyn shell_scripts]# awk '{print $0}' awk_test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
........
ntp:x:38:38::/etc/ntp:/sbin/nologin
Jermyn:x:1000:1000::/home/Jermyn:/bin/bash
1
2
3
4
5
6
7
2.打印 1-5 行的内容,定位即是 NR==1,NR==5 
[root@Jermyn shell_scripts]# awk -F: 'NR==1,NR==5{print $0}' awk_test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
1
2
3
4
3.打印第一行和第五行,此处 || 为逻辑运算符,并非正则, | 为正则
[root@Jermyn shell_scripts]# awk 'NR==1 || NR==5{print $0}' awk_test.txt
root:x:0:0:root:/root:/bin/bash
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
1
2
3
4
5
6
7
8
9
4.打印 3-5 行,第二格命令是 NR 大于等于 3 并且 NR 小于等于 5 的行
[root@Jermyn shell_scripts]# awk 'NR==3,NR==5{print $0}' awk_test.txt
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@Jermyn shell_scripts]# awk 'NR>=3 && NR<=5{print $0}' awk_test.txt
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
1
2
3
4
5
6
7
5.打印以冒号 : 分割的第一列和最后一列以及倒数第二列
[root@Jermyn shell_scripts]# awk -F : '{print $1,$NF,$(NF-1)}' awk_test.txt
root /bin/bash /root
bin /sbin/nologin /bin
.......
ntp /sbin/nologin /etc/ntp
Jermyn /bin/bash /home/Jermyn
1
2
3
4
5
6
7
8
9
10
6.两个命令区别就是 $NF 和 NF ,后者可以理解为 NF 为变量值为 NR=7 即直接 print 7 ,前者可以理解为是打印第 $7
[root@Jermyn shell_scripts]# awk -F : '{print $NF}' awk_test.txt
/bin/bash
/sbin/nologin
.....

[root@Jermyn shell_scripts]# awk -F : '{print NF}' awk_test.txt
7
7
.......
1
2
3
4
5
6
7
8
9
10
11
12
13
14
7.打印出包含 root 的行,/root/ 为地址定位
[root@Jermyn shell_scripts]# awk '/root/{print $0}' awk_test.txt
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

# 不加动作默认是打印
[root@Jermyn shell_scripts]# awk '/root/' awk_test.txt
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

# 可以理解为 匹配关键字 root 出现的行,过滤此行 以冒号 :分割的第一列和最后一列
[root@Jermyn shell_scripts]# awk -F : '/root/{print $1,$NF}' awk_test.txt
root /bin/bash
operator /sbin/nologin
1
2
3
4
5
6
7
8
8.打印 1-5 行和以 root 开头的行
[root@Jermyn shell_scripts]# awk 'NR==1,NR==5;/^root/{print $0}' awk_test.txt
root:x:0:0:root:/root:/bin/bash # 地址定位1-5行的时候打印一次
root:x:0:0:root:/root:/bin/bash # 地址定位以root 开头的时候打印一次
bin:x:1:1:bin:/bin:/sbin/nologin # 地址定位1-5行的时候打印一次
daemon:x:2:2:daemon:/sbin:/sbin/nologin # 地址定位1-5行的时候打印一次
adm:x:3:4:adm:/var/adm:/sbin/nologin # 地址定位1-5行的时候打印一次
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin # 地址定位1-5行的时候打印一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
9.以冒号 :为分隔符取出第一行和最后一行,取出内容以 @ 隔开(注:OFS=" " 为双引号,不可为单引号)
下面两条命令效果相同,如果不定义分隔符的话,可以直接在打印的时候以 定义"分隔符"
[root@Jermyn shell_scripts]# awk -F : 'BEGIN{OFS="@"};{print $1,$NF}' awk_test.txt
[root@Jermyn shell_scripts]# awk -F : '{print $1"@"$NF}' awk_test.txt
root@/bin/bash
bin@/sbin/nologin
........
ntp@/sbin/nologin
Jermyn@/bin/bash

# 其他方式
[root@Jermyn shell_scripts]# awk -F : '{print "用户名:"$1"\tSHELL解释器:"$NF}' awk_test.txt
用户名:root SHELL解释器:/bin/bash
用户名:bin SHELL解释器:/sbin/nologin
用户名:daemon SHELL解释器:/sbin/nologin
用户名:adm SHELL解释器:/sbin/nologin
用户名:lp SHELL解释器:/sbin/nologin
用户名:sync SHELL解释器:/bin/sync
.......
1
2
3
10.下面两条命令效果相同
[root@Jermyn shell_scripts]# awk 'BEGIN{FS=":";OFS="@"};{print $1,$NF}' awk_test.txt
[root@Jermyn shell_scripts]# awk -F : 'BEGIN{OFS="@"};{print $1,$NF}' awk_test.txt

awk工作原理

awk -F: '{print $1,$3}' /etc/passwd

  1. awk使用一行作为输入,并将这一行赋给内部变量$0,每一行也可称为一个记录,以换行符(RS)结束

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

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

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

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

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

awk使用进阶

5.1 格式化输出printprintf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
print函数		类似echo "hello world"
# date |awk '{print "Month: "$2 "\nYear: "$NF}'
# awk -F: '{print "username is: " $1 "\t uid is: "$3}' /etc/passwd


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

awk 'BEGIN{FS=":"};{printf "%-15s %-15s %-15s\n",$1,$6,$NF}' a.txt

%s 字符类型 strings %-20s
%d 数值类型
15字符
- 表示左对齐,默认是右对齐
printf默认不会在行尾自动换行,加\n

5.2 定义变量

1
2
3
4
5
6
7
# 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.3 awk中BEGIN…END使用

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

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

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

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1. 打印最后一列和倒数第二列(登录shell和家目录)
awk -F: 'BEGIN{ print "Login_shell\t\tLogin_home\n***************************************"};{print $NF"\t\t"$(NF-1)};END{print "***************************************"}' awk_test.txt

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

结果显示:
Login_shell Login_home
***************************************
/bin/bash /root
/sbin/nologin /bin
/sbin/nologin /sbin
/sbin/nologin /var/adm
............
/sbin/nologin /dev/null
/sbin/nologin /var/empty/sshd
/sbin/nologin /var/spool/postfix
/sbin/nologin /var/lib/chrony
/sbin/nologin /etc/ntp
/bin/bash /home/Jermyn
***************************************
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2.打印/etc/passwd里的用户名、家目录及登录shell
awk -F ':' 'BEGIN{print "U_NAME\t\tH_DIR\t\t\tSHELL\n" "*********************************************"}
{printf "%-15s %-20s %-10s \n",$1,$(NF-1),$NF}END{print "*********************************************"}' awk_test.txt

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

结果显示:
U_NAME H_DIR SHELL
*********************************************
root /root /bin/bash
bin /bin /sbin/nologin
.........
chrony /var/lib/chrony /sbin/nologin
ntp /etc/ntp /sbin/nologin
Jermyn /home/Jermyn /bin/bash
*********************************************

5.4 awk和正则的综合运用

运算符说明
==等于
!=不等于
>大于
<小于
>=大于等于
<=小于等于
~匹配
!~不匹配
!逻辑非
&&逻辑与
\\逻辑或

实例:

1
2
3
4
5
1.从第一行匹配到以 daemon 开头的行
[root@Jermyn shell_scripts]# awk -F ':' 'NR==1,/^daemon/{print $0}' awk_test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
1
2
3
4
5
2.从第一行到第3
[root@Jermyn shell_scripts]# awk -F ':' 'NR==1,NR==3{print $0}' awk_test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
1
2
3
4
5
3.从以lp开头的行匹配到第7
[root@Jermyn shell_scripts]# awk -F ':' '/^lp/,NR==7{print $0}' awk_test.txt
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
1
2
3
4
5
6
7
8
4.打印以root开头或者以lp开头的行 
[root@Jermyn shell_scripts]# awk -F ':' '/^lp|^root/{print $0}' awk_test.txt
root:x:0:0:root:/root:/bin/bash
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

[root@Jermyn shell_scripts]# awk -F ':' '/^lp/||/^root/{print $0}' awk_test.txt
root:x:0:0:root:/root:/bin/bash
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
1
2
3
4
5
6
7
8
5.显示5-10
[root@Jermyn shell_scripts]# awk -F ':' 'NR>=5 && NR<=10{print $0}' awk_test.txt
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
operator:x:11:0:operator:/root:/sbin/nologin
1
2
3
4
5
6
6.打印5-10行以shutdown结尾的内容:
[root@Jermyn shell_scripts]# awk 'NR>=5 && NR<=10 && $0~ /shutdown$/{print $0}' awk_test.txt
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

[root@Jermyn shell_scripts]# awk 'NR>=5 && NR<=10 && $0~ /shutdown$/' awk_test.txt
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
1
2
3
4
5
6
7
8
7.awk 截取IP
[root@Jermyn shell_scripts]# ifconfig ens33 | awk -F ' ' '/netmask/{print $2}'
192.168.88.139

[root@Jermyn shell_scripts]# ifconfig ens33 | awk -F " " '/netmask/{print $2RS$4RS$6}'
192.168.88.139
255.255.255.0
192.168.88.255

awk的脚本编程

6.1 流程控制语句

6.1.1 if 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if语句:

if [ xxx ];then
xxx
fi

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

例子:
1.UID >=500 && UID <=60000
[root@Jermyn shell_scripts]# awk -F ':' '{ if($3>=500 && $3<=60000) {print $1,$3}}' awk_test.txt
polkitd 999
chrony 998
Jermyn 1000

2.格式打印
[root@Jermyn shell_scripts]# awk -F ':' '{ if($3==0) {print $1" IS ADMIN"}}' awk_test.txt
root IS ADMIN

3.执行命令作为判断条件
[root@Jermyn shell_scripts]# awk 'BEGIN{ if ( '$(id -u)' == 0) {print "THIS USER IS ADMIN"}}'
THIS USER IS ADMIN

6.1.2 if…else 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if...else语句:
if [ xxx ];then
xxxxx

else
xxx
fi

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

1.执行命令作为判断条件 if...else...结构
[root@Jermyn shell_scripts]# awk 'BEGIN{ if ( '$[ id -u ]' != 0) {print "THIS USER IS NOT ADMIN"} else {print "THIS USER IS ADMIN"} }'
THIS USER IS ADMIN
1
2
3
4
5
6
7
8
9
10
11
12
2.如果 if($3>=500 && $3 !=65534 是普通用户 否则不是普通用户
[root@Jermyn shell_scripts]# awk -F ':' '{ if($3>=500 && $3 !=65534 ) {print $1" Is a common user"} else {print $1 " Not ordinary users"}}' awk_test.txt | tail
nobody Not ordinary users
systemd-network Not ordinary users
dbus Not ordinary users
polkitd Is a common user
tss Not ordinary users
sshd Not ordinary users
postfix Not ordinary users
chrony Is a common user
ntp Not ordinary users
Jermyn Is a common user
1
2
3
4
5
6
7
#上面例子可以规范格式,更加直观
awk -F ':' '
{
if($3>=500 && $3 !=65534 )
{print $1" Is a common user"}
else {print $1 " Not ordinary users"}
}' awk_test.txt | tail
1
2
3
3.if( '$(id -u)'>=500 && '$(id -u)' !=65534 ) 
[root@Jermyn shell_scripts]# awk 'BEGIN{if( '$(id -u)'>=500 && '$(id -u)' !=65534 ) {print "是普通用户"} else {print "不是普通用户"}}'
不是普通用户
1
2
3
4
5
6
#上面例子可以规范格式,更加直观
awk 'BEGIN{
if( '$(id -u)'>=500 && '$(id -u)' !=65534 )
{print "是普通用户"}
else {print "不是普通用户"}
}'

6.1.3 if…elif…else结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if [xxxx];then
xxxx
elif [xxx];then
xxx
....
else
...
fi


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

格式:
{ if(表达式1){语句;语句;...}else if(表达式2){语句;语句;...}else if(表达式3){语句;语句;...}else{语句;语句;...}}
1
2
3
4
5
6
7
8
1.判断用户是 管理员 还是 系统用户,还是 普通用户
[root@Jermyn shell_scripts]# awk -F: '{ if($3==0) {print $1,":是管理员"} else if($3>=1 && $3<=499 || $3==65534 ) {print $1,":是系统用户"} else {print $1,":是普通用户"}}' awk_test.txt
root :是管理员
bin :是系统用户
.......
chrony :是普通用户
ntp :是系统用户
Jermyn :是普通用户
1
2
3
4
5
6
7
8
9
awk -F ':' '
{
if($3==0)
{print $1,":是管理员"}
else if($3>=1 && $3<=499 || $3==65534 )
{print $1,":是系统用户"}
else
{print $1,":是普通用户"}
}' awk_test.txt
1
2
3
4
5
2.判断管理员 和 系统用户以及普通用户的个数
[root@Jermyn shell_scripts]# 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_test.txt
管理员个数为:1
系统用户个数为:18
普通用户的个数为:3
1
2
3
4
5
6
7
8
9
10
11
#上面例子可以规范格式,更加直观
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_test.txt

6.2 循环语句

6.2.1 for循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 打印1~5
for ((i=1;i<=5;i++));do echo $i;done

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


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

# 计算1-5的和
awk 'BEGIN{sum=0;for(i=1;i<=5;i++) sum+=i;print sum}'
awk 'BEGIN{for(i=1;i<=5;i++) (sum+=i);{print sum}}'
awk 'BEGIN{for(i=1;i<=5;i++) (sum+=i);print sum}'

6.2.2 while 循环

1
2
3
4
5
6
7
8
9
10
11
12
# 打印1-5
i=1;while (($i<=5));do echo $i;let i++;done

# awk while 循环打印 1-5
awk 'BEGIN { i=1;while(i<=5) {print i;i++} }'

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

# 计算1-5的和
awk 'BEGIN{i=1;sum=0;while(i<=5) {sum+=i;i++}; print sum }'
awk 'BEGIN {i=1;while(i<=5) {(sum+=i) i++};print sum }'

6.2.3 嵌套循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
嵌套循环:
#!/bin/bash
for ((y=1;y<=5;y++))
do
for ((x=1;x<=$y;x++))
do
echo -n $x
done
echo
done



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


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

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

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

循环的控制:
break 条件满足的时候中断循环
continue 条件满足的时候跳过循环
# awk 'BEGIN{for(i=1;i<=5;i++) {if(i==3) break;print i} }'
1
2
# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3) continue;print i}}'
1
2
4
5

6.3 awk算数运算

1
2
3
4
5
6
+ - * / %(模) ^(幂2^3)
可以在模式中执行计算,awk都将按浮点数方式执行算术运算
# awk 'BEGIN{print 1+1}'
# awk 'BEGIN{print 1**1}'
# awk 'BEGIN{print 2**3}'
# awk 'BEGIN{print 2/3}'

统计案例

7.1 统计系统中各种类型的shell

1
2
3
4
5
6
[root@Jermyn shell_scripts]# awk -F: '{ shells[$NF]++ };END{for (i in shells) {print i,shells[i]} }' /etc/passwd
/bin/sync 1
/bin/bash 2
/sbin/nologin 17
/sbin/halt 1
/sbin/shutdown 1
1
2
3
4
5
6
7
8
9
10
解释:此命令目的是打印出 shell 解释器的数量
关键在于创建一个关联数组 shells
$NF 是每行的最后一列即是 shell 解释器
shells[$NF] 中 $NF 可以理解为下标,也可以理解为是 KEY 值
每行在执行的时候就是 shells[/bin/sync] ........
++ 可以理解为是 KEY 对应的 VALUE 值,每次相同的时候就 ++ 即对相同的解释器进行累加
后面的 for 循环可以理解为 for i in shells.KEY
循环遍历数组内的下标
所以 i 就是下标或者理解为 KEY
所以 shells[i] 就是 shells[KEY] 可以理解为 KEY.VALUE

7.2 统计网站访问状态

1
2
3
[root@Jermyn shell_scripts]# ss -antp | grep 22 | awk '{states[$1]++};END{for (i in states) {print i,states[i]}}' | sort -nr -t ' ' -k 2
LISTEN 2
ESTAB 2

7.3 统计访问网站的每个IP的数量

1
2
3
4
# 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

7.4 统计网站日志中PV量

1
2
3
4
5
6
7
8
统计Apache/Nginx日志中某一天的PV量  <统计日志>
# grep '27/Jul/2017' mysqladmin.cc-access_log |wc -l
14519

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

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

名词解释:

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

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

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

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