从这里开始

书写格式

#!/bin/bash
# 解释器

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

echo "Hello World!"
# 单行注释

执行方式

  1. 脚本没有执行权限
    [sujx@LEGION:~]$ bash first.sh
    Hello World!
    [sujx@LEGION:~]$ source first.sh
    Hello World!
  2. 脚本有可执行权限
    [sujx@LEGION:~]$ chmod +x first.sh
    [sujx@LEGION:~]$ ./first.sh
    Hello World!
    [sujx@LEGION:~]$ /home/sujx/first.sh
    Hello World!
  3. 开启子进程执行
    [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. 不开启子进程执行
    [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输出

[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输出

[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. 查看账户信息
----------------------

输入

[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. $(),实现命令替换
    [sujx@docker ~]$ echo "当前账户登录数量:`who | wc -l`"
    当前账户登录数量:1
    [sujx@docker ~]$ echo "当前账户登录数量:$(who | wc -l)"
    当前账户登录数量:1

千变万化的变量

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

  1. 定义变量等号两边不能有空格
  2. 不能包含特殊符号、空格
  3. 不能以数字开头
  4. unset取消变量设定
[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计算器
#!/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中可以使用;(顺序执行) &&(与,仅当前序程序成功执行才会执行) ||(非前序程序执行失败或者错误才会执行后序程序,为二选一)
# 空变量要用双引号,否则容易讲空格作为空变量的值
[sujx@docker ~]$ [ -n "$Jacob" ] && echo Y || echo N
N
[sujx@docker ~]$ [ -n $Jacob ] && echo Y || echo N
Y

整数的判断与比较

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

文件属性的判断与比较

[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语句

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

[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语句

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

# 现实字符的颜色、背景、显示方式
[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循环

# 机选双色球
[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仅支持一维索引数组和关联数组,对数组大小没有限制。

# 命名数组
[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环境中,执行命令,执行完成加载的命令后,继续执行脚本后续指令

函数

#!/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"
# 网段存活主机速查
[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编写的一个部署脚本

#!/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 核心编程指南⌋一书中的教学示例

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