从这里开始

书写格式

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
# 解释器

<<COMMENT
Author: sujx
Date: 2023-09-23
Version: 1.0
Description: This is my first script.
COMMENT
# 多行注释

echo "Hello World!"
# 单行注释

执行方式

  1. 脚本没有执行权限
    1
    2
    3
    4
    [sujx@LEGION:~]$ bash first.sh
    Hello World!
    [sujx@LEGION:~]$ source first.sh
    Hello World!
  2. 脚本有可执行权限
    1
    2
    3
    4
    5
    [sujx@LEGION:~]$ chmod +x first.sh
    [sujx@LEGION:~]$ ./first.sh
    Hello World!
    [sujx@LEGION:~]$ /home/sujx/first.sh
    Hello World!
  3. 开启子进程执行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    [sujx@LEGION:~]$ cat sleep.sh
    #!/bin/bash
    sleep 1000
    [sujx@LEGION:~]$ chmod +x sleep.sh

    [sujx@LEGION:~]$ ./sleep.sh
    [sujx@LEGION:~]$ pstree
    init(AlmaLinuxO─┬─SessionLeader───bash───sleep.sh───sleep
    ├─SessionLeader───bash───pstree
    ├─init───{init}
    └─{init(AlmaLinuxO}

    [sujx@LEGION:~]$ bash sleep.sh
    [sujx@LEGION:~]$ pstree
    init(AlmaLinuxO─┬─SessionLeader───bash───bash───sleep
    ├─SessionLeader───bash───pstree
    ├─init───{init}
    └─{init(AlmaLinuxO}
  4. 不开启子进程执行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [sujx@LEGION:~]$ source sleep.sh
    [sujx@LEGION:~]$ pstree
    init(AlmaLinuxO─┬─SessionLeader───bash───sleep
    ├─SessionLeader───bash───pstree
    ├─init───{init}
    └─{init(AlmaLinuxO}

    [sujx@LEGION:~]$ . sleep.sh
    [sujx@LEGION:~]$ pstree
    init(AlmaLinuxO─┬─SessionLeader───bash───sleep
    ├─SessionLeader───bash───pstree
    ├─init───{init}
    └─{init(AlmaLinuxO}

输入和输出

使用echo输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[sujx@docker ~]$ cat echo_menu.sh
#!/bin/bash
#Version 2.0
clear

echo -e "\e[42m-----------------------------------\e[0m"
echo -e "\e[2;10H这里是菜单\t\t"
echo -e "# \e[32m 1. 查看网卡信息\e[0m #"
echo -e "# \e[32m 2. 查看内存信息\e[0m #"
echo -e "# \e[32m 3. 查看磁盘信息\e[0m #"
echo -e "# \e[32m 4. 查看CPU信息\e[0m #"
echo -e "# \e[32m 5. 查看账户信息\e[0m #"
echo -e "\e[42m-----------------------------------\e[0m"
echo
[sujx@docker ~]$ bash echo_men.sh
-----------------------------------
这里是菜单
# 1. 查看网卡信息 #
# 2. 查看内存信息 #
# 3. 查看磁盘信息 #
# 4. 查看CPU信息 #
# 5. 查看账户信息 #
-----------------------------------

使用printf输出

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
[sujx@LEGION:~]$ cat echo_menu.sh
#!/bin/bash
# Version 3

clear

printf "\e[42m%s\n\e[0m" "----------------------"
printf "\e[2;10H%s\t\t\n" "这里是菜单"
printf "\e[32m%s\e[0m\n" "1. 查看网卡信息"
printf "\e[35m%s\e[0m\n" "2. 查看内存信息"
printf "\e[36m%s\e[0m\n" "3. 查看磁盘信息"
printf "\e[34m%s\e[0m\n" "4. 查看CPU信息"
printf "\e[33m%s\e[0m\n" "5. 查看账户信息"
printf "\e[42m%s\n\e[0m" "----------------------"
echo

----------------------
这里是菜单
1. 查看网卡信息
2. 查看内存信息
3. 查看磁盘信息
4. 查看CPU信息
5. 查看账户信息
----------------------

输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[sujx@LEGION:~]$ cat user_add.sh
#!/bin/bash
# Read User's name and password from standard input.

clear

read -p "请输入用户名:" username
read -s -p "请输入密码:" pass

printf "\n"
useradd "$username"
echo "$pass" | passwd --stdin "$username"

[sujx@LEGION:~]$ source user_add.sh
请输入用户名:testname
请输入密码:
useradd: Permission denied.
useradd: cannot lock /etc/passwd; try again later.
Only root can do that.

重定向

  1. EOF(end of file)作为分隔符
  2. << Here Document,代表你需要的内容在这里,程序从here document中读取数据,再通过重定向到文件
  3. <<- 系统会忽略所有数据内容及分隔符前的tab键

引号的正确使用

  1. 双引号是引用一个整体
  2. 单引号起到屏蔽变量特殊含义的功能
  3. 反引号` 是命令替换符号,可以使用命令的输出来替代命令
  4. $(),实现命令替换
    1
    2
    3
    4
    [sujx@docker ~]$ echo "当前账户登录数量:`who | wc -l`"
    当前账户登录数量:1
    [sujx@docker ~]$ echo "当前账户登录数量:$(who | wc -l)"
    当前账户登录数量:1

千变万化的变量

变量分为系统预设变量和用户自定义变量。

  1. 定义变量等号两边不能有空格
  2. 不能包含特殊符号、空格
  3. 不能以数字开头
  4. unset取消变量设定
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
[sujx@docker ~]$ cat sys_info.sh
#!/bin/bash
#描述信息:本脚本主要目的是获取主机的数据信息(内存、网卡 IP、CPU 负载)
localip=$(ifconfig eth0 | grep netmask | tr -s " " | cut -d" " -f3)
mem=$(free -h|grep Mem | tr -s " " | cut -d" " -f7)
cpu=$(uptime | tr -s " " | cut -d " " -f13)
echo "本机 IP 地址是:$localip"
echo "本机内存剩余容量为:$mem"
echo "本机 CPU 15min 的平均负载为:$cpu"
echo "当前账户是:$USER,当前账户的 UID 是:$UID"
echo "当前账户的根目录是:$HOME"
echo "当前工作目录是:$PWD"
echo "返回 0~32767 的随机数:$RANDOM"
echo "当前脚本的进程号是:$$"
echo "当前脚本的名称为:$0"

[sujx@docker ~]$ bash sys_info.sh
本机 IP 地址是:192.168.10.5
本机内存剩余容量为:1.3Gi
本机 CPU 15min 的平均负载为:
当前账户是:sujx,当前账户的 UID 是:1000
当前账户的根目录是:/home/sujx
当前工作目录是:/home/sujx
返回 0~32767 的随机数:27147
当前脚本的进程号是:2323
当前脚本的名称为:sys_info.sh

数据过滤和正则表达式

  1. 标准正则规范
  2. 扩展正则规范
  3. POSIX规范正则表达式
  4. GNU规范

算术运算

  1. 常用运算符号
  2. x++与++x, x–与–x的区别
  3. bash只支持对整数的四则运算,不支持小数运算
  4. let和bc计算器
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/bash
#计算 1+2+3,...,+n 的和,可以使用 n*(n+1)/2 公式快速计算结果
read -p "请输入一个正整数:" num
sum=$[num*(num+1)/2]
echo -e "\033[32m$num 以内整数的总和是:$sum\033[0m"

#使用三角形的底边和高计算面积:A=1/2bh
read -p "请输入三角形底边长度:" bottom
read -p "请输入三角形高度:" hight
A=$(echo "scale=1;1/2*$bottom*$hight" | bc)
echo -e "\033[32m 三角形面积是:$A\033[0m"

#梯形面积:(上底边长度+下底边长度)*高/2
read -p "请输入梯形上底边长度:" a
read -p "请输入梯形下底边长度:" b
read -p "请输入梯形高度:" h
A=$(echo "scale=2;($a+$b)*$h/2" | bc)
echo -e "\033[32m 梯形面积是:$A\033[0m"

#使用 A=πr^2 公式计算圆的面积,取 2 位小数点精度,π=3.14
read -p "请输入圆的半径:" r
A=$(echo "scale=2;3.14*$r^2" | bc)
echo -e "\033[32m 圆的面积是:$A\033[0m"
echo "3282820KiB 等于多少 GiB?"
G=$(echo "32828920/1024/1024" | bc)
echo -e "\003[32m 答案${G}G\033[0m"

#注意使用{}防止变量名歧义
#时间格式转化
read -p "请输入秒数:" sec
ms=$[sec*1000]
echo -e "\033[32m$sec 秒=$ms 毫秒\033[0m"
us=$[sec*1000000]
echo -e "\033[32m$sec 秒=$us 微秒\033[0m"
hour=$(echo "scale=2;$sec/60/60"|bc)
echo -e "\033[32m$sec 秒=$hour 小时\033[0m"

很人工、很智能的脚本

智能化脚本的基础

  1. 系统默认都不会有任何输出结果,可以使用echo $?查看退出状态码
  2. shell中可以使用;(顺序执行) &&(与,仅当前序程序成功执行才会执行) ||(非前序程序执行失败或者错误才会执行后序程序,为二选一)
1
2
3
4
5
# 空变量要用双引号,否则容易讲空格作为空变量的值
[sujx@docker ~]$ [ -n "$Jacob" ] && echo Y || echo N
N
[sujx@docker ~]$ [ -n $Jacob ] && echo Y || echo N
Y

整数的判断与比较

1
2
3
4
5
6
# -eq 等于 -ne 不等于 -gt 大于 -ge大于等于 -lt 小于 -le 小于等于
[sujx@docker ~]$ num=$(cat /etc/passwd |wc -l)
[sujx@docker ~]$ [ $num -ge 20 ] && echo Y || echo N
Y
[sujx@docker ~]$ printf $num
29

文件属性的判断与比较

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
[sujx@docker ~]$ touch ver1.txt ver2.txt
[sujx@docker ~]$ mkdir test
# -e 判断文件或目录是否存在
[sujx@docker ~]$ [ -e test ] && echo Y || echo N
Y
[sujx@docker ~]$ [ -e ver1.txt ] && echo Y || echo N
Y
[sujx@docker ~]$ [ ! -e ver1.txt ] && echo Y || echo N
N
# -f 判断是否为目录
[sujx@docker ~]$ [ ! -f ver1.txt ] && echo Y || echo N
N
[sujx@docker ~]$ [ -f ver1.txt ] && echo Y || echo N
Y
[sujx@docker ~]$ [ -f test/ ] && echo Y || echo N
N
# -b 判断是否为块设备
[sujx@docker ~]$ [ -b /dev/sda ] && echo Y || echo N
Y
[sujx@docker ~]$ [ -b /etc/passwd ] && echo Y || echo N
N
# -s 判断文件是否存在并为非空文件
[sujx@docker ~]$ [ -s ver1.txt ] && echo Y || echo N
N
[sujx@docker ~]$ echo "Hello" > ver1.txt
[sujx@docker ~]$ [ -s ver1.txt ] && echo Y || echo N
Y

实战案例

单分支if语句

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
#version:0.5(版本 0.5)

read -p "请输入用户名:" user
read -s -p "请输入密码:" pass

if [ ! -z "$user" ] && [ ! -z "$pass" ];then
useradd "$user"
echo "$pass" | passwd --stdin "$user"
fi

双分支if语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[sujx@docker ~]$ cat ping_test.sh
#!/bin/bash
#功能描述(Description):通过 ping 练习双分支 if 语句
#ping 通脚本返回 up,否则返回 down
if [ -z "$1" ];then
echo -n "用法: 脚本 "
echo -e "\033[32m 域名或 IP\033[0m"
exit
fi

#-c(设置 ping 的次数),-i(设置 ping 的间隔描述),-W(设置超时时间)
ping -c5 -i0.2 -W1 "$1" &>/dev/null
if [ $? -eq 0 ];then
echo "$1 is up"
else
echo "$1 is down"
fi

[sujx@docker ~]$ . ping_test.sh 192.168.10.254
192.168.10.254 is up
[sujx@docker ~]$ . ping_test.sh 192.168.10.253
192.168.10.253 is down

多分支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
25
26
27
28
29
30
31
32
33
34
35
36
#!/bin/bash
#Version 3.0

clear
echo -e "\e[42m-----------------------------------\e[0m"
echo -e "\e[2;8H主机功能巡检\t\t"
echo -e "# \e[32m 1. 查看网卡信息\e[0m #"
echo -e "# \e[32m 2. 查看内存信息\e[0m #"
echo -e "# \e[32m 3. 查看磁盘信息\e[0m #"
echo -e "# \e[32m 4. 查看CPU信息\e[0m #"
echo -e "# \e[32m 5. 查看账户信息\e[0m #"
echo -e "\e[42m-----------------------------------\e[0m"

echo
read -p "请输入选项[1~5]:" key
case $key in
1)
ifconfig | head -2;;
2)
mem=$(free |grep Mem |tr -s " "| cunt -d "" -f7)
echo "本机剩余内存容量为:${mem}K.";;
3)
root_free=$(df |grep /$ | tr -s " " | cut -d " " -f4)
echo "本机根分区剩余容量为${root_free}K.";;
4)
cpu=$(uptime | tr -s " " | cut -d" " -f13)
echo "本机 CPU 15min 的平均负载为:$cpu.";;
5)
login_number=$(who | wc -l)
total_number=$(cat /etc/passwd | wc -l)
echo "当前系统账户为$USER"
echo "当前登录系统的账户数量为:$login_number"
echo "当前系统中总用户数量为:$total_number";;
*)
echo "输入有误,超出 1~5 的范围";;
esac

循环和中断

for循环

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
# 现实字符的颜色、背景、显示方式
[sujx@docker ~]$ cat color-menu.sh
#!/bin/bash
# This is echo color shell
# by author rivers 2021.09-23
# 字体颜色
for i in {31..37}; do
echo -e "\033[$i;40mHello world!\033[0m"
done
# 背景颜色
for i in {41..47}; do
echo -e "\033[47;${i}mHello world!\033[0m"
done
# 显示方式
for i in {1..8}; do
echo -e "\033[$i;31;40mHello world!\033[0m"
done

# 测试主机连通性
[sujx@docker ~]$ cat ping_check2.sh
#!/bin/bash
#功能描述(Description):测试某个网段内所有主机的连通性
#可以使用$()或``对命令进行扩展
net="192.168.10"
for i in $(seq 254)
do

ping -c2 -i0.2 -W1 $net.$i &>/dev/null
if [ $? -eq 0 ];then
echo "$net.$i is up."
else
echo "$net.$i is down."
fi
done

# C语言风格循环
[sujx@docker ~]$ cat c-style.sh
#!/bin/bash
#功能描述(Description):C 语言风格的 for 循环示例
#i 初始值为 1,j 初始值为 5
#每循环一次对 i 进行自加 1 运算、对 j 进行自减 1 运算,当 i 大于 5 时循环结束
for ((i=1,j=5;i<=5;i++,j--))
do
echo "$i $j"
done

while循环

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
# 机选双色球
[sujx@docker ~]$ cat double-color.sh
#!/bin/bash
#功能描述(Descrtiption):机选双色球
#红色球 1~33,蓝色球 1~16,红色球号码不可以重复
#6 组红色球,1 组蓝色球
RED_COL='\033[91m'
BLUE_COL='\033[34m'
NONE_COL='\033[0m'
red_ball=""
#随机选择号码为 1~33 的红色球(6 个),号码为 1~16 的蓝色球(1 个)
#每选出一个号码通过+=的方式存储到变量中
#通过 grep 判断新机选的红色球号码是否已经存在,-w 选项过滤单词
while :
do
clear
echo "---机选双色球---"
tmp=$[RANDOM%33+1]
echo "$red_ball" | grep -q -w $tmp && continue
red_ball+=" $tmp"
echo -en "$RED_COL$red_ball$NONE_COL"
word=$(echo "$red_ball" | wc -w)
if [ $word -eq 6 ]; then
blue_ball=$[RANDOM%16+1]
echo -e "$BLUE_COL $blue_ball$NONE_COL"
break
fi
sleep 0.5
done

break、continue和exit

数组与函数

数组

数组是一组数据的集合,数组中的每个数据被称为一个数组元素。bash仅支持一维索引数组和关联数组,对数组大小没有限制。

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
# 命名数组
[sujx@LEGION:~]$ name[0]="Jacob"
[sujx@LEGION:~]$ name[1]="Rose"
[sujx@LEGION:~]$ name[2]="Vicky"
[sujx@LEGION:~]$ name[3]="Rick"
[sujx@LEGION:~]$ name[4]="TinTin"
[sujx@LEGION:~]$ echo ${name[0]}
Jacob
[sujx@LEGION:~]$ echo ${name[1]}
Rose
[sujx@LEGION:~]$ echo ${name[2]}
Vicky
[sujx@LEGION:~]$ echo ${name[3]}
Rick
[sujx@LEGION:~]$ echo ${name[4]}
TinTin
# 不存在的索引,数值为空
[sujx@LEGION:~]$ echo ${name[5]}

[sujx@LEGION:~]$ name[3]="Lily"
# 数组可以被修改
[sujx@LEGION:~]$ echo ${name[3]}
Lily
# 打印数组所有元素
[sujx@LEGION:~]$ echo ${name[*]}
Jacob Rose Vicky Lily TinTin
# 打印数组中最后一个元素的值
[sujx@LEGION:~]$ echo ${name[-1]}
TinTin
# 统计数组中所有元素的个数
[sujx@LEGION:~]$ echo ${#name[*]}
5
# 打印数组中的值
[sujx@LEGION:~]$ echo ${name[@]}
Jacob Rose Vicky Lily TinTin
# @表示数组中单一的值,*表示所有的值,*将所有元素视为一个整体
[sujx@LEGION:~]$ for i in "${name[*]}"; do echo $i; done
Jacob Rose Vicky Lily TinTin
[sujx@LEGION:~]$ for i in "${name[@]}"; do echo $i; done
Jacob
Rose
Vicky
Lily
TinTin
# 数组赋值方法
[sujx@LEGION:~]$ name1=(Jacob Rose Rick Vicky Tintin)
# 查看数组索引
[sujx@LEGION:~]$ echo ${!name1[*]}
0 1 2 3 4
[sujx@LEGION:~]$ echo ${!name[*]}
0 1 2 3 4
# 将命令执行结果复制给数组变量
[sujx@LEGION:~]$ df /
Filesystem 1K-blocks Used Available Use% Mounted on
rootfs 487415804 265418996 221996808 55% /
[sujx@LEGION:~]$ df / |tail -n +2
rootfs 487415804 265418996 221996808 55% /
[sujx@LEGION:~]$ root=($(df / |tail -n +2))
[sujx@LEGION:~]$ echo ${root[*]}
rootfs 487415804 265419124 221996680 55% /
[sujx@LEGION:~]$ echo ${root[1]}
487415804
[sujx@LEGION:~]$ echo ${root[0]}
rootfs
[sujx@LEGION:~]$ echo ${root[2]}
265419124

父子shell

  1. 脚本中使用管道会开启子shell,可以通过文件重定向的方式读取文件避免zishell
  2. 脚本中调用外部命令、加载其他脚本也都会开启子shell,可以通过source命令加载

启动进程的方式

  1. fork方式:父进程启动一个子进程
  2. exec方式:调用其他命令和脚本,不开启子进程,而是使用新程序替换当前shell
  3. source方式:当前shell环境中,执行命令,执行完成加载的命令后,继续执行脚本后续指令

函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash
#功能描述:函数中变量的作用域示例之全局变量

#默认定义的变量为在当前Shell中全局有效
global_var1="hello"
global_var2="world"

function demo(){
echo -e "\e[46mfunction [demo] stared..\e[0m"
func_var="Topic:"
# 通过local 只在函数内修改全局变量
local global_var2="Broke Girls"
# 直接修改外部全局变量
# global_var2="Broke Girls"
echo "$func_var $global_var2"
echo -e "\e[46mfunction [demo] end.\e[0m"

}

demo
echo "函数内部变量: [$func_var]"
echo "$func_var $global_var1 $global_var2"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 网段存活主机速查
[sujx@docker ~]$ cat ping_check2.sh
#!/bin/bash
#功能描述(Description):测试某个网段内所有主机的连通性
#可以使用$()或``对命令进行扩展

net="192.168.10"
multi_ping() {
ping -c2 -i0.2 -W1 $net.$i &>/dev/null
if [ $? -eq 0 ];then
echo "$1 is up."
else
echo "$1 is down."
fi
}
for i in {1..254}
do

multi_ping $net.$i &
done

实战脚本

webmin的部署脚本

下面这个google的sre编写的一个部署脚本

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#!/bin/sh
# shellcheck disable=SC1090 disable=SC2059 disable=SC2164 disable=SC2181
# setup-repos.sh
# Configures Webmin repository for RHEL and Debian systems (derivatives)

webmin_host="download.webmin.com"
webmin_download="https://$webmin_host"
webmin_key="developers-key.asc"
webmin_key_download="$webmin_download/$webmin_key"
webmin_key_suffix="webmin-developers"
debian_repo_file="/etc/apt/sources.list.d/webmin.list"
rhel_repo_file="/etc/yum.repos.d/webmin.repo"
download_wget="/usr/bin/wget"
download="$download_wget -nv"

# Temporary colors
NORMAL=''
GREEN=''
RED=''
ITALIC=''
BOLD=''
if tty -s; then
NORMAL="$(tput sgr0)"
GREEN=$(tput setaf 2)
RED="$(tput setaf 1)"
BOLD=$(tput bold)
ITALIC=$(tput sitm)
fi

# Check user permission
if [ "$(id -u)" -ne 0 ]; then
echo "${RED}Error:${NORMAL} \`setup-repos.sh\` script must be run as root!" >&2
exit 1
fi

# Go to temp
cd "/tmp" 1>/dev/null 2>&1
if [ "$?" != "0" ]; then
echo "${RED}Error:${NORMAL} Failed to switch to \`/tmp\` directory!"
exit 1
fi

# Check for OS release file
osrelease="/etc/os-release"
if [ ! -f "$osrelease" ]; then
echo "${RED}Error:${NORMAL} Cannot detect OS!"
exit 1
fi

# Detect OS and package manager and install command
. "$osrelease"
if [ -n "${ID_LIKE}" ]; then
osid="$ID_LIKE"
else
osid="$ID"
fi
if [ -z "$osid" ]; then
echo "${RED}Error:${NORMAL} Failed to detect OS!"
exit 1
fi

# Derivatives precise test
osid_debian_like=$(echo "$osid" | grep "debian\|ubuntu")
osid_rhel_like=$(echo "$osid" | grep "rhel\|fedora\|centos")

repoid_debian_like=debian
if [ -n "${ID}" ]; then
repoid_debian_like="${ID}"
fi

# Setup OS dependent
if [ -n "$osid_debian_like" ]; then
package_type=deb
install_cmd="apt-get install --install-recommends"
install="$install_cmd --quiet --assume-yes"
clean="apt-get clean"
update="apt-get update"
elif [ -n "$osid_rhel_like" ]; then
package_type=rpm
if command -pv dnf 1>/dev/null 2>&1; then
install_cmd="dnf install"
install="$install_cmd -y"
clean="dnf clean all"
else
install_cmd="yum install"
install="$install_cmd -y"
clean="yum clean all"
fi
else
echo "${RED}Error:${NORMAL} Unknown OS : $osid"
exit
fi

# Ask first
if [ "$1" != "-f" ] && [ "$1" != "--force" ]; then
printf "Setup repository? (y/N) "
read -r sslyn
if [ "$sslyn" != "y" ] && [ "$sslyn" != "Y" ]; then
exit
fi
fi

# Check for wget or curl or fetch
if [ ! -x "$download_wget" ]; then
if [ -x "/usr/bin/curl" ]; then
download="/usr/bin/curl -f -s -L -O"
elif [ -x "/usr/bin/fetch" ]; then
download="/usr/bin/fetch"
else
# Try installing wget
echo " Installing required ${ITALIC}wget${NORMAL} package from OS repository .."
$install wget 1>/dev/null 2>&1
if [ "$?" != "0" ]; then
echo " .. failed to install 'wget' package!"
exit 1
else
echo " .. done"
fi
fi
fi


# Check if GPG command is installed
if [ -n "$osid_debian_like" ]; then
if [ ! -x /usr/bin/gpg ]; then
$update 1>/dev/null 2>&1
$install gnupg 1>/dev/null 2>&1
fi
fi

# Clean files
rm -f "/tmp/$webmin_key"

# Download key
echo " Downloading Webmin key .."
download_out=$($download $webmin_key_download 2>/dev/null 2>&1)
if [ "$?" != "0" ]; then
download_out=$(echo "$download_out" | tr '\n' ' ')
echo " ..failed : $download_out"
exit
fi
echo " .. done"

# Setup repos
case "$package_type" in
rpm)
# Install our keys
echo " Installing Webmin key .."
rpm --import $webmin_key
cp -f $webmin_key /etc/pki/rpm-gpg/RPM-GPG-KEY-$webmin_key_suffix
echo " .. done"
# Create repo file
echo " Setting up Webmin repository .."
echo "[webmin-noarch]" >$rhel_repo_file
echo "name=Webmin - noarch" >>$rhel_repo_file
echo "baseurl=$webmin_download/download/newkey/yum" >>$rhel_repo_file
echo "enabled=1" >>$rhel_repo_file
echo "gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-$webmin_key_suffix" >>$rhel_repo_file
echo "gpgcheck=1" >>$rhel_repo_file
echo " .. done"
;;
deb)
# Remove our keys
rm -f "/usr/share/keyrings/debian-$webmin_key_suffix.gpg" "/usr/share/keyrings/$repoid_debian_like-$webmin_key_suffix.gpg"
# Install our keys
echo " Installing Webmin key .."
gpg --import $webmin_key 1>/dev/null 2>&1
cat $webmin_key | gpg --dearmor > "/usr/share/keyrings/$repoid_debian_like-$webmin_key_suffix.gpg"
echo " .. done"
# Remove Webmin repo from sources.list
sources_list=$(grep -v "$webmin_host" /etc/apt/sources.list)
echo "$sources_list" > /etc/apt/sources.list
# Create repo file
echo " Setting up Webmin repository .."
echo "deb [signed-by=/usr/share/keyrings/$repoid_debian_like-$webmin_key_suffix.gpg] $webmin_download/download/newkey/repository stable contrib" >$debian_repo_file
echo " .. done"
# Clean meta
echo " Cleaning repository metadata .."
$clean 1>/dev/null 2>&1
echo " .. done"
# Update meta
echo " Downloading repository metadata .."
$update 1>/dev/null 2>&1
echo " .. done"
;;
*)
echo "${RED}Error:${NORMAL} Cannot setup repositories on this system."
exit 1
;;
esac

# Could not setup
if [ ! -x "/usr/bin/webmin" ]; then
echo "Webmin package can now be installed using ${GREEN}${BOLD}${ITALIC}$install_cmd webmin${NORMAL} command."
fi

exit 0

Nginx访问日志分析脚本

下面是⌈Linux Shell 核心编程指南⌋一书中的教学示例

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
82
83
84
85
86
87
88
89
90
91
92
93
94
#!/bin/bash
#功能描述(Description):Nginx 标准日志分析脚本
#统计信息包括:
#1.页面访问量 PV
#2.用户量 UV
#3.人均访问量
#4.每个 IP 的访问次数
#5.HTTP 状态码统计
#6.累计页面字节流量
#7.热点数据

GREEN_COL='\033[32m'
NONE_COL='\033[0m'
line='echo ++++++++++++++++++++++++++++++++++'

read -p "请输入日志文件:" logfile
echo

#统计页面访问量(PV)
PV=$(cat $logfile | wc -l)

#统计用户数量(UV)
UV=$(cut -f1 -d' ' $logfile | sort | uniq | wc -l)

#统计人均访问次量
Average_PV=$(echo "scale=2;$PV/$UV" | bc)

#统计每个 IP 的访问次数
declare -A IP
while read ip other
do
let IP[$ip]+=1
done < $logfile

#统计各种 HTTP 状态码的个数,如 404 报错的次数、500 报错的次数等
declare -A STATUS
while read ip dash user time zone method file protocol code size othe
do
let STATUS[$code]++
done < $logfile

#统计网页累计访问字节大小
while read ip dash user time zone method file protocol code size other
do
let Body_size+=$size
done < $logfile

#统计热点数据
declare -A URI
while read ip dash user time zone method file protocol code size other
do
let URI[$file]++
done < $logfile
echo -e "\033[91m\t 日志分析数据报表\033[0m"

#显示 PV 与 UV 访问量,平均用户访问量
$line
echo -e "累计 PV 量: $GREEN_COL$PV$NONE_COL"
echo -e "累计 UV 量: $GREEN_COL$UV$NONE_COL"
echo -e "平均用户访问量: $GREEN_COL$Average_PV$NONE_COL"

#显示网页累计访问字节数
$line
echo -e "累计访问字节数: $GREEN_COL$Body_size$NONE_COL Byte"

#显示指定的 HTTP 状态码数量
$line
for i in 200 404 500
do
if [ ${STATUS[$i]} ];then
echo -e "$i 状态码次数:$GREEN_COL ${STATUS[$i]} $NONE_COL"
else
echo -e "$i 状态码次数:$GREEN_COL 0 $NONE_COL"
fi
done
#显示每个 IP 的访问次数
line
for i in ${!IP[@]}
do
printf "%-15s 的访问次数为: $GREEN_COL%s$NONE_COL\n" $i ${IP[$i]}
done
echo

#显示访问量大于 500 的 URI
echo -e "$GREEN_COL 访问量大于 500 的 URI:$NONE_COL"
for i in "${!URI[@]}"
do
if [ ${URI["$i"]} -gt 500 ];then
echo "-----------------------------------"
echo "$i"
echo "${URI[$i]}次"
echo "-----------------------------------"
fi
done