shell自动化编程详解
一、shell自动化编程-基础与变量
1.环境准备
- 修改vimrc文件,达到控制vim创建,编辑文件的动作 给文件添加注解
- 当前用户家目录下 ~/.vimrc
- 放在/etc/vimrc
sh
set ignorecase
set ignorecase
autocmd BufNewFile *.py,*.cc,*.sh,*.java exec ":call SetTitle()"
func SetTitle()
if expand("%:e") == 'sh'
call setline(1, "#!/bin/bash")
call setline(2,"########################################################")
call setline(3, "# File Name:".expand("%"))
call setline(4, "# Version:V1.0")
call setline(5, "# Author:liux")
call setline(6, "# Desc:")
call setline(7,"########################################################")
endif
endfunc
2.shell脚本执行方式
- 通过sh或bash执行脚本 执行脚本最常用的方式
- 通过.(点)或source
- !符号含义 用于指定脚本默认的命令解释器
3.书写脚本每次用户登录后显示系统的基本信息
sh
vim 02.sys_login_info.sh
#!/bin/bash
########################################################
# File Name:02.sys_login_info.sh
# Version:V1.0
# Author:liux
# Desc:用户登录后显示系统基本信息
########################################################
#1.赋值
sys_hostname=`hostname`
sys_ip_addrs=`hostname -I`
sys_mem_total=`free -h |awk 'NR==2{print $2}'`
sys_mem_free=`free -h |awk 'NR==2{print $NF}'`
sys_load=`uptime |awk '{print $(NF-2),$(NF-1),$NF}'`
#2.输出
cat <<EOF
主机名: ${sys_hostname}
ip地址: ${sys_ip_addrs}
总内存: ${sys_mem_total}
可用内存:${sys_mem_free}
系统负载:${sys_load}
EOF
#创建软链接到指定目录 用户登录会加载/etc/profile.d/目录下以.sh结尾的文件
ln -s /server/scripts/devops-shell/02.sys_login_info.sh /etc/profile.d/sys_login_info.sh
4.特殊变量
4.1特殊变量-位置变量
| 变量 | 含义 | 应用场景 |
|---|---|---|
| $n | 执行脚本后面的第n个参数 n表示数字 | 命令行与脚本内部桥梁 |
| $0 | 获取脚本的名字 | 用于输出脚本的格式或帮助的时候。用于错误提示输出帮助 |
| $# | 统计脚本的参数个数 | 一般与判断结合,检查脚本参数个数 |
| $@ | 循环取出脚本所有参数 | 数组中或循环中 |
| $* | 取出脚本所有参数 | 数组中或循环中 |
4.2特殊变量-状态变量
| 变量 | 含义 | 应用场景 |
|---|---|---|
| $? | 上一个命令、脚本的返回值。0表示正确,非0即错误 | 一般用于判断检查命令结果 |
| $$ | 当前脚本的pid | 一般写在脚本中获取脚本pid |
| $! | 上一个脚本/命令的pid |
4.3特殊变量-变量子串
- ${#parameter} 统计字符长度(变量中有多少个字符)
二、shell自动化编程-运算符-条件表达式-if
1.运算方法
| 运算的命令/符号 | 说明 | 应用场景 |
|---|---|---|
| awk | 可以进行计算,带小数,可以与shell脚本进行变量传递 | |
| bc | 带小数 | 一般计算都可以用bc.需要安装 |
| expr | 进行计算,整数 | 一般用于检查输入内容是否为数字 |
| let | 进行计算,整数,变量直接使用即可 | 用于计算i++ |
| $(()) | 进行计算,整数,变量直接使用即可 | |
| $[] | 进行计算,整数,变量直接使用即可 |
1.1 awk进行计算
sh
awk 'BEGIN{print 1/3}'
awk 'BEGIN{print 1/3*100}'
#在awk中使用shell变量
#awk -v选项用于创建或修改awk中的变量。 -v是shell脚本与awk桥梁
#在awk中各种变量直接使用即可,不要加上$n1,如果加上了会被awk识别为取列
awk -vn1=1 -vn2=3 'BEGIN{print n1/n2}'
1.2 bc用法
sh
#-l显示小数
echo 1/3 |bc -l
echo 2^10 |bc -l
1.3 expr
- 使用注意事项:使用空格,对*号转义
sh
[root@m01 ~]# expr 1+1
1+1
[root@m01 ~]# expr 1 + 1
2
[root@m01 ~]# expr 2 * 2
expr: 语法错误
[root@m01 ~]# expr 2 \* 2
4
[root@m01 ~]# expr 2 / 2
1
- 案例:对输入的内容检查是否为数字
sh
[root@m01 devops-shell]# vim 03.check-num.sh
#!/bin/bash
########################################################
# File Name:03.check-num.sh
# Version:V1.0
# Author:liux
# Desc:
########################################################
#1.vars
#num=$1
read -p "请输入数字:" num
#2.检查是否为数字
expr $num \* 0 + 1 &>/dev/null && echo "$num是数字"
expr $num \* 0 + 1 &>/dev/null || echo "$num不是数字"
1.4 let <math xmlns="http://www.w3.org/1998/Math/MathML"> ( ( ) ) (()) </math>(())[]
sh
n1=666
n2=999
let c=n1+n2
n1=666
n2=999
echo $((n1+n2))
n1=666
n2=999
e=$[n1+n2]
案例:计算传入的两个参数的值
sh
[root@m01 devops-shell]# vim 04.calc_num.sh
#!/bin/bash
########################################################
# File Name:04.calc_num.sh
# Version:V1.0
# Author:liux
# Desc:
########################################################
#1.vars
num1=$1
num2=$2
#2.检查是否位数字
expr $num1 \* 0 + $num2 \* 0 + 1 &>/dev/null || {
echo "Usage: $0 数字1 数字2,2个数字"
exit 1
}
#3.计算
awk -vn1=$num1 -vn2=$num2 'BEGIN{print n1+n2}'
awk -vn1=$num1 -vn2=$num2 'BEGIN{print n1-n2}'
awk -vn1=$num1 -vn2=$num2 'BEGIN{print n1*n2}'
awk -vn1=$num1 -vn2=$num2 'BEGIN{print n1/n2}'
2.条件表达式
- 大部分情况下使用[ ]进行判断
- 使用正则的时候用[[ ]]两对中括号
2.1条件表达式符号
| 符号 | 说明 |
|---|---|
| -f | file 判断是否为文件,是为真 |
| -d | dir 判断是否为目录,是为真 |
| -x | 判断是否有执行权限,是为真 |
| -s | 判断文件内容是否为空 ,非空为真 |
| -r | 是否有读权限 |
| -w | 是否有写权限 |
| -nt | 两个文件修改时间,是否更加新 |
| -ot | 两个文件修改时间,是否更加老 |
| -L | 软链接 |
| -e | 是否存在(任何类型文件) |
sh
#判断文件是否存在
[ -f /etc/hosts ] && echo "成立 " || echo "失败"
#检查目录是否存在
[ -d /etc/ ] && echo "成立 " || echo "失败"
#检查/etc/rc.d/rc.local是否有执行权限
[ -x /etc/rc.d/rc.local ] && echo "成立 " || echo "失败"
#ip命令是否有执行权限,如果没有则退出
[ -x /sbin/ip ] || exit 1
#-s检查文件内容是否为空,非空则为真
[ -s /etc/hosts ] && echo "成立 " || echo "失败"
注意:
&& 表示前面命令执行成功然后执行echo 成立。
|| 表示前面命令执行失败了,echo 失败
2.2对比字符串
| 字符串对比 | 说明 |
|---|---|
| "str1" = "str2" | str1等于str2,则为真 |
| -z "str" | zero 检查str字符串是否为空,空的则为真 |
sh
[ "$UID" != "0" ] && exit 4 #判断是否为root执行
[ -z "$str" ] && echo "成立 " || echo "失败"
2.3比大小(整数) [ ]
- -eq equal 等于
- ne not equal 不等于
- -gt great than 大于
- -ge great equal 大于等于
- -lt less than 小于
- -le less equal 小于等于
2.4逻辑判断
- 多个条件同时成立,或者,取反
| 条件表达式 | [ ] |
|---|---|
| 与(同时成立) | 条件1 -a 条件2 (and) |
| 或 | 条件1 -r 条件2 (or) |
| 非 | ! |
2.5正则 [[ ]]
sh
num=666
[[ $num =~ [0-9] ]] && echo 成立 || echo 失败
num=liux996
[[ $num =~ [0-9] ]] && echo 成立 || echo 失败
#前的正则,仅仅表示变量中只要有数字就行
#用正则精确匹配数字
[[ $num =~ ^[0-9]+$ ]] && echo 成立 ||echo 失败
#变量的值 开头结尾中间全是数字 连续数字
2.6 案例:比较两个数字的大小
sh
[root@m01 devops-shell]# vim compare_num.sh
#!/bin/bash
########################################################
# File Name:compare_num.sh
# Version:V1.0
# Author:liux
# Desc:
########################################################
#1.vars
num1=$1
num2=$2
#2.检查是否为数字
[[ $num1 =~ ^-?[0-9]+$ ]] || {
echo "$num1 不是数字"
exit 1
}
[[ $num2 =~ ^-?[0-9]+$ ]] || {
echo "$num2 不是数字"
echo 1
}
#3.比较大小
[ $num1 -gt $num2 ] && {
echo "$num1 大于 $num2"
}
[ $num1 -lt $num2 ] && {
echo "$num1 小于 $num2"
}
[ $num1 -eq $num2 ] && {
echo "$num1 等于 $num2"
}
#测试
[root@m01 devops-shell]# sh compare_num.sh 1 -1
1 大于 -1
3.if判断
- 单分支判断
sh
if 条件;then
满足条件后执行的内容。
fi
- 双分支判断
sh
if 条件;then
满足条件后执行的内容。
else
不满足条件执行的内容。
fi
- 多分枝判断
sh
if 条件;then
满足条件后执行的内容。
elif 条件;then #else if
满足elif条件,执行的内容。
elif 条件;then
满足elif条件,执行的内容。
else
不满足条件执行的内容。
fi
案例1:检查根分区磁盘空间使用率
sh
[root@m01 devops-shell]# vim 05.disk_check.sh
#!/bin/bash
#1.vars
root_usage_now=`df -h |awk -F "[ %]+" '$NF=="/" {print $(NF-1)}'`
root_usage_warning=80
#2.进行比较
if [ $root_usage_now -gt $root_usage_warning ];then
echo "磁盘空间不足,使用率是$root_usage_now%"
else
echo "磁盘空间足,使用率是$root_usage_now%"
fi
案例2:输出指定用户的信息
-
执行脚本输入用户名(参数/read)
-
判断用户是否存在,如果不存在则提示用户不存在,退出脚本.
-
如果用户存在输出用户的信息.
-
是否可以登录(命令解释器)
-
uid,gid(过滤)
-
用户家目录
-
最近1次登录情况
-
属于用户的文件(很多)
-
sh
[root@m01 devops-shell]# vim 06.secure_check_user.sh
#!/bin/bash
#1.输入用户名
read -p "请输入用户名" user
#2.检查用户是否存在
#判断输入变量不能为空
if [ "${user}x" = "x" ];then
echo "请输入用户名:"
exit 1
fi
#是否存在
id $user &>/dev/null
if [ $? -ne 0 ];then
echo "用户$user 不存在"
exit 2
fi
#3.用户信息
user_shell=`awk -F: -vname=$user '$1==name{print $NF}' /etc/passwd`
if [ "$user_shell" = "/bin/bash" ];then
if_login="可以登录"
else
if_login="无法登录"
fi
#uid,gid,家目录
user_ids=`awk -F: -vname=$user '$1==name{print $3,$4}' /etc/passwd`
user_homedir=`awk -F: -vname=$user '$1==name{print $(NF-1)}' /etc/passwd`
#最近登录信息
user_login_info=`lastlog |awk -vname=$user '$1==name'`
#用户文件信息
user_files=`find / -type f -user liux 2>/dev/null`
#4.输出
cat <<EOF
用户名:$user
是否可以登录:$if_login
用户uid,gid:$user_ids
用户家目录:$user_homedir
最近的登录情况:$user_login_info
用户的文件:$user_files
EOF
4.case语句
-
条件分支语句,一般用于实现有多种选择的脚本
-
case语句功能,可以通过if+elif+else形式进行替换
-
格式
sh
case "变量" in
1)
命令;;
2)
命令;;
*)
命令(保底的默认输出)
esac
5.shell编程-函数
- 格式
sh
#定义方式01 最完整
function lidao_show() {
内容
return n #函数的返回值
}
#定义方式02 精简写法 一般使用这一种.
lidao_show() {
命令
return n #函数的返回值
}
- 函数传参
| 参数 | shell脚本中 | 函数中 |
|---|---|---|
| $n | 脚本的第n个参数 | 函数的第n个参数 |
| $0 | 脚本的名字 | 脚本的名字 |
| $# | 脚本的参数个数 | 函数的参数个数 |
| <math xmlns="http://www.w3.org/1998/Math/MathML"> @ / @/ </math>@/* | 脚本的所有参数 | 函数的所有参数 |
案例:书写sersync服务的管理脚本
-
sh data_sync.sh start|stop|restart|status
-
需求
-
如果用户输入的是start,则运行sersync启动的命令。
-
如果用户输入的是stop,则运行关闭sersync的命令。
-
如果用户输入的是status,则显示sersync是否运行中,pid。
-
如果用户输入的是restart,则运行stop的命令,然后运行start的命令。
-
如果用户输入的是其他的内容,则提示输入错误,提示格式。
-
sh
[root@m01 devops-shell]# vim 07.data_sync.sh
#!/bin/bash
########################################################
# File Name:07.data_sync.sh
# Version:V1.0
# Author:liux
# Desc:sersync服务管理脚本
########################################################
#1.vars命令行传参
choice=$1
sersync_dir=/app/tools/sersync
srv_status=`ps -ef|grep sersync |egrep -v "grep|$0" |wc -l`
#2.书写功能对应的函数
start() {
if [ $srv_status -eq 0 ];then
${sersync_dir}/bin/sersync -rdo ${sersync_dir}/conf/confxml.xml &>/dev/null
else
echo "sersync is already running"
fi
}
stop() {
if [ $srv_status -eq 0 ];then
echo "sersync is already stopped"
else
pkill sersync
fi
}
restart() {
pkill sersync
${sersync_dir}/bin/sersync -rdo ${sersync_dir}/conf/confxml.xml &>/dev/null
}
status() {
if [ $srv_status -eq 0 ];then
echo "sersync is not running"
else
pid=`ps -ef|grep sersync |egrep -v "grep|$0" |awk '{print $2}'`
echo "sersync is running,pid is $pid"
fi
}
#3.case语句
main() {
case "$choice" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
status
;;
*)
echo "Usage:$0 {start|stop|restart|status}"
esac
}
main
#测试
sh 07.data_sync.sh status
6.必知必会命令
6.1 检查端口
sh
检查端口是否存在
ss -lntup |grep 80
netstat -lntup |grep 80
lsof -i:80
#检查ip、端口能否访问
telnet
nc
#-z 无io模式,用于检查端口是否连通。
nc -z 10.0.0.61 80
nmap
6.2 检查进程
sh
top ps
6.3 检查网络
sh
ping -c2 www.baidu.com
iftop
6.4 检查web 域名或ip能否访问
sh
curl/wget
#-v显示过程 -I获取响应头
#-s slient 安静模式 如果不使用默认输出下载进度
#-o curl的输出到指定位置的文件
#-w 按照指定格式与内容输出 %{http_code}状态码 更多格式 man
curl 搜索 variable
curl: -v -L -H -I -w -o
#-t 失败后,重复尝试次数、-T timeout 超时时间 -q 不显示wget输出 --spider 不下载文件,仅访问
wget -t 3 -T 1 -q --spider www.baidu.com
案例:检查指定地址和端口是否可以访问
- sh check_access.sh 10.0.0.61 22
- nc
sh
[root@m01 devops-shell]# cat 08.check_access.sh
#!/bin/bash
########################################################
# File Name:08.check_access.sh
# Version:V1.0
# Author:liux
# Desc:检查指定ip和端口是否可以访问
########################################################
#加载系统内置函数库
. /etc/init.d/functions
addr=$1
port=$2
#检查nc命令是否存在
check_nc() {
which nc &>/dev/null || {
yum -y install nc
}
}
#检查域名和ip是否合法
check_valid() {
[[ "$addr" =~ ^([0-9.]+|[a-z.]+)$ ]] || {
echo "输入的域名不合法,$addr"
exit 1
}
[[ "$port" =~ ^[0-9]+$ ]] || {
echo "输入的ip不合法,$port"
exit 1
}
}
#用nc命令检查
check_port() {
nc -z $addr $port
if [ $? -eq 0 ];then
action "可以访问,是通的" /bin/true
else
action "无法访问" /bin/false
fi
}
#调用函数
main() {
check_nc
check_valid
check_port
}
main
"=~" 用于判断左边字符串和右边的正则表达式是否匹配
正则表达式^表示起始,$表示结束,?表示0个或者1个
三、shell自动化编程-循环
1.for循环
- 最常用的for循环方式 大部分场景可用
sh
for 变量 in 候补清单(列表)
do
命令
done
#for循环一般搭配着{1..10} `seq 10` `ls /etc/`
- c语言格式for循环 对数组循环使用
sh
for((i=1;i<=10;i++))
do
echo $i
done
案例:使用for循环在/liux目录下通过随机小写10个字母加固定字符串liux批量创建10个html文件
-
例如:apquvdpqbk_liux.html
-
生成随机数
sh
#1.
uuidgen
#2. -l密码长度,-d数字数量,-s special 特殊字符 -C 大写字母 -c小写字母
mkpasswd -l 10 -d 0 -s 0 -C 0
#3.-c取反,-d删除 /dev/urandom 字符设备,生成随机字符
tr -cd 'a-z' </dev/urandom |head -c 10
#4.%N纳秒
date +%N |md5sum
#5
echo $RANDOM
- 代码
sh
[root@m01 devops-shell]# vim 09.touch_files.sh
#!/bin/bash
#1.vars
dir=/liux
#2.检查
[ ! -d $dir ] && mkdir -p $dir
#3.for循环
for n in {1..10}
do
file_name_pre=`tr -cd 'a-z' </dev/urandom |head -c 10`
file_name_full=${file_name_pre}_liux.html
touch ${dir}/${file_name_full}
done
2.while循环
- 加入条件
- 死循环
- 读取文件
2.1while循环通用格式
sh
while 条件
do
命令
done
#温馨提示: while循环只会在满足条件后运行
2.2案例:生成随机数,判断数字是什么
-
如果输入的数字比随机数大,提示大了,
-
如果输入数字比随机数小,提示小了,
-
如果等于提示恭喜
-
额外要求:
-
用了1-3次 超越了99.99%人
-
用了4-6次 超越80%的人
-
其他 超越了70%的人
-
sh
[root@m01 devops-shell]# vim 10.guess_num.sh
#!/bin/bash
#1.vars
num_rand=$(( RANDOM%100 ))
i=1
#2.检查输入的是否为数字
num_check() {
[[ $num_rand =~ ^[0-9]+$ ]] || {
echo "请输入数字"
continue
}
}
#3.排名功能
ranking() {
cnt=$((i-1))
if [ $cnt -le 3 ];then
echo "恭喜您,使用了$cnt次,超越了99.99%的用户"
elif [ $cnt -ge 4 -a $cnt -le 6 ];then
echo "恭喜您,使用了$cnt次,超越了80%的用户"
else
echo "恭喜您,使用了$cnt次,超越了70%的用户"
fi
}
#4.用户输入数字,判断
input_num_compare() {
read -p "请输入数字" num
num_check
let i++
if [ $num -gt $num_rand ];then
echo "很抱歉,猜大了"
elif [ $num -lt $num_rand ];then
echo "很抱歉,猜小了"
else
echo "恭喜猜对了"
ranking
exit
fi
}
#5.书写主函数
main() {
while true
do
input_num_compare
done
}
main
- while循环-读取文件内容
- 需要在脚本中读取文件内容,多行。此时可以选择3剑客或while循环
sh
#方式1:采用exec读取文件后,然后进入while循环处理。
exec<FILE
while read line
do
cmd
echo $line
done
#方式2:使用cat读取文件内容,然后通过管道进入 不适用于有变量传递场景使用。
cat FILE|while read line
do
cmd
echo $line
done
#方式3:在while循环结尾done通过输入重定向指定读取的文件。 推荐使用
while read line
do
cmd
done<FILE
3.do-until循环
- 无论条件是否满足,都会执行一次
sh
#直到型循环: 一直循环,直到条件不满足.
until 条件
do
命令
命令
....
done
until 话费是否充足
do
发短信
done
#先执行,再判断条件是否满足
4.循环控制语句
| 语句 | 含义 | 应用场景 |
|---|---|---|
| exit | 终止执行脚本,退出返回值 | 脚本结束加上exit |
| return | 放在函数中,终止执行函数,函数返回值 | 写在函数中,检查函数命令运行是否成功 |
| break | 终止循环(退出) | 需要在循环中退出循环 |
| continue | 终止(跳过)本次循环,进入下一次循环 | 要在循环中跳过某一次循环 |
四、shell编程-辅助功能
- 颜色
sh
for n in {30..48};do echo -e "\E[1;${n}mliux\E[0m" ; done
sh
#创建环境变量或写入脚本开头
export RED="\E[5;31m"
export GREEN="\E[1;32m"
export BLUE="\E[1;34m"
export END="\E[0m"
#永久使用/etc/profile中即可
#写为函数
redecho() {
echo -ne "\e[5;31m"
echo -n "$@"
echo -e "\e[0m"
#echo -e "\e[5;31m $@ \e[0m"
}
greenecho() {
echo -ne "\e[1;32m"
echo -n "$@"
echo -e "\e[0m"
}
blueecho() {
echo -ne "\e[1;34m"
echo -n "$@"
echo -e "\e[0m"
}
五、shell编程-数组
sh
names=(web01 web02 db01 nfs01 backup)
for n in ${names[@]}
do
ping -c 1 -W 1 $n
done
- shell数组赋值
| 赋值 | |
|---|---|
| 直接赋值 | array=(ip01 ip02 ip03 ip04 ) array=( cat ip.txt) #cat ip.txt 三剑客命令获取指定内容 |
| read命令赋值 | read -a -p "输入数组中内容:" array 可以创建数组,空格分割即可 |
- 案例:从键盘输入10个整数,求和、平均数。
sh
[root@m01 devops-shell]# vim 11.read_sum.sh
#!/bin/bash
#0.vars
. ./func.sh
sum=0
avg=0
i=1
tmp_file=/tmp/num.txt
#1.初始化,判断文件是否存在
init() {
[ -f $tmp_file ] && rm -f $tmp_file
return $?
}
#2.读取用户输入
read_num() {
while true
do
[ $i -gt 10 ] && break
read -p "请输入第${i}个数字:" num
[[ "$num" =~ ^[0-9]+$ ]] || continue
echo $num >>$tmp_file
let i++
done
return $?
}
#3.进行计算
num_calc() {
num_array=(`cat $tmp_file`)
for num in ${num_array[@]}
do
let sum+=num
done
return $?
}
#4.输出总和与平均数
num_print() {
avg=`echo "scale=2;$sum/10"|bc -l`
rc=$?
blueecho "用户输入的数字有:${num_array[@]}"
greenecho "10个数字的总和是$sum,平均数是$avg"
return $rc
}
#5.主函数
main() {
init
read_num
num_calc
num_print
}
main
六、shell编程-debug全流程
1.书写习惯
- 注释
- 变量:在脚本中尽量使用变量,变量命令规范,给变量加上注释
- 函数:代码中尽可能使用函数,增加说明
- 返回值:尽可能增加函数return功能,方便后期调试
- 参数与选项检查:尽可能增加exit 返回值的功能,方便后期调试
- 输出:书写代码的时候,可以多写一些echo用于在某些步骤中进行输出
- 缩进:代码注意缩进
2.调试方法
- -x 大部分情况使用 显示详细的执行过程
- 精确显示执行过程
- set -x 开启显示执行过程
- set +x 关闭显示箱子过程
- 注释法
- 输出关键变量
七、再战三剑客
1.sed与变量
sh
cat ip.txt
10.0.0.5
10.0.0.6
10.0.0.7
src=10.0.0
dst=172.16.1
sed "s#$src#$dst#g" ip.txt
172.16.1.5
172.16.1.6
172.16.1.7
2.awk判断
sh
awk '{if(NR<=5){print $0}}' /etc/passwd
awk '{
if(NR<=5){
print $0
}
}' /etc/passwd
3.awk循环
sh
awk 'BEGIN{for(i=1;i<=100;i++) {sum=sum+i} print sum }'
4.awk数组
- awk数组专用于统计与分析
- 去重统计次数
- 去重求和
- awk数组与shell数组区别
- awk数组:关联数组,下标啥都行
- shell数组:普通数组,下标数字,shell中也有关联数组
sh
awk 'BEGIN{array[0]="lidao996";array["lidao"]=996;array[110]="sos"; print array[0]}'
- awk专用于数组的循环
sh
for(n in 数组名字)
print n(数组下标),数组名字[n]
数组下标 对应的值
sh
vim awk-array.txt
url 次数
img.oldboylinux.cn 6
bbs.oldboylinux.cn 7
avi.oldboylinux.cn 99
mp4.oldboylinux.cn 88
#以url为下标,次数为元素值的数组,输出内容
awk 'NR>1{array[$1]=$2}END{ for( url in array) print url,array[url] }' awk-array.txt
- 去重统计次数
sh
vim url.txt
http://www.etiantian.org/index.html
http://www.etiantian.org/1.html
http://post.etiantian.org/index.html
http://mp3.etiantian.org/index.html
http://www.etiantian.org/3.html
http://post.etiantian.org/2.html
awk -F'/+' '{url[$2]=url[$2]+1}END{for(name in url) print name,url[name]}' url.txt
八、shell编程-安全基线检查脚本
1.身份鉴权
1.1配置口令复杂度
sh
#默认格式
[root@m01 ~]# grep pwquality /etc/pam.d/system-auth
password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
#修改
vim /etc/pam.d/system-auth
password requisite pam_pwquality.so try_first_pass local_users_only retry=3 difok=3 minlen=8 ucredit=-1 lcredit=-1 dcredit=-1 authtok_type=
#root用户也使用
enforce_for_root
#检查有没有配置密码复杂度
egrep -o 'minlen|[uld]credit' /etc/pam.d/system-auth |wc -l
minlen=8 至少8位
ucredit=-1 upper 大写字母 -号表示至少-1位 3 最多3位
lcredit=-1 lower 小写字母 -号表示至少-1位
dcredit=-1 digit 数字 -号表示至少-1
difok=3 与上个密码重复的内容
enforce_for_root 也限制root修改密码
1.2设置口令认证失败锁定次数
sh
vim /etc/pam.d/system-auth
#auth是属于认证部分
auth required pam_tally2.so onerr=fail deny=3 unlock_time=20
#注:解锁用户 pam_tally2 -u liux --reset
#温馨提示: 这个规则放在最上面.
#如果需要root 也错误并锁定则增加even_deny_root即可
#检查是否配置
grep pam_tally2.so /etc/pam.d/system-auth |egrep -o 'deny=|unlock_time=' |wc -l
1.3配置口令生存周期
- 是否配置新用户的密码过期
- 检查是否最近30天有用户密码要过期
sh
#新用户
#建议在/etc/login.defs文件中配置:
PASS_MAX_DAYS 90 #新建用户的密码最长使用天数
PASS_MIN_DAYS 0 #新建用户的密码最短使用天数
PASS_WARN_AGE 7 #新建用户的密码到期提前提醒天数
#命令修改已经存在的用户过期时间 -x最长使用天数 -w 过期提醒7天前
passwd -x 90 -n 0 -w 7 liux
#检查
chage -l liux
awk '/^PASS_MAX_DAYS/{print $2}' /etc/login.defs 或者
grep "^PASS_MAX_DAYS" /etc/login.defs |awk '{print $NF}'
awk '/^PASS_WARN_AGE/{print $2}' /etc/login.defs
#密码过期判断
#1. 可以登录系统 /bin/bash
awk -F: '$NF=="/bin/bash" {print $1}' /etc/passwd
#2. 获取用户密码过期时间chage -l 用户名看
chage -l root
#3. 过期时间与当前时间相减 (秒)
#4. 转换为天 与30天对比
#5. 与30对比即可
1.4配置使用ssh方式远程访问
sh
#使用ssh远程连接,禁用telnet
#检查点检查服务是否运行或开启即可.
telnet #方式关闭
ssh #方式开启
[root@lb01 ~]# systemctl is-active sshd telnet
active
unknown
1.5配置历史口令使用策略
sh
#最近5次的密码不能相同
password requisite pam_pwhistory.so remember=5
2.访问控制
2.1锁定无关用户
sh
#不用的用户,锁定,注释
#lock (root)
passwd -l
#解锁 (root)
passwd -u
#通过
passwd -S 用户名查看是否锁定
/etc/shadow 第2列是否!!叹号
#可以注释掉用户.
lp nuucp hpdb sync adm ftp games
2.2清楚uid为0的非root用户
sh
#检查是否存在 是否有uid是0 用户名不是root的用户.
awk -F: '$3==0 && $1!="root" {print $1}' /etc/passwd
2.3控制为授权的suid/sgid
- 取PATH变量存放的目录中的命令
sh
#-perm 按照文件权限查找
find ${PATH//:/} /tmp/ -perm /+s 2>/dev/null |xargs ls -ld --color
2.4控制任何人都有写权限的目录
- o上有w的目录
sh
find / -type d -perm /o+w |xargs ls -ld
2.5控制任何人都有写权限的文件
- o上有w的文件
sh
find / -type f -perm /o+w 2>/dev/null |egrep -v "^/sys|^/proc" |wc -l
2.6删除没有属主的文件
sh
find / -nouser -o -nogroup 2>/dev/null |wc -l
2.7控制异常或隐藏文件
sh
#查找以点开始的文件
find / -name ".*" |xargs ls -ld
3.安全审计
3.1配置使用ntp
sh
ntpdate ntp1.aliyun.com
4.入侵防护
4.1配置登录超时自动退出
sh
export TMOUT=3
epxort HISTSIZE=10
export HISTFILESIZE=10
4.2限制root账户远程登录
sh
PermitRootLogin no
九、脚本汇总
1.系统巡检脚本
sh
[root@m01 devops-shell]# vim sys_info.sh
#!/bin/bash
########################################################
# File Name:sys_info.sh
# Version:V1.0
# Author:liux
# Desc:系统巡检基本脚本
########################################################
. /server/scripts/devops-shell/func.sh
#1.基础信息
base_info() {
hostname=`hostname`
ip=`hostname -I`
blueecho "#################################"
blueecho "###########基本信息##############"
greenecho "主机名:$hostname"
greenecho "ip地址:$ip"
blueecho "#################################"
}
#2.负载
load_info() {
load1=`uptime |awk -F'[ ,]+' '{print $(NF-2)}'`
load5=`uptime |awk -F'[ ,]+' '{print $(NF-1)}'`
load15=`uptime |awk -F'[ ,]+' '{print $NF}'`
blueecho "#################################"
blueecho "###########负载信息##############"
greenecho "最近1分钟负载:$load1"
greenecho "最近5分钟负载:$load5"
greenecho "最近15分钟负载:$load15"
blueecho "#################################"
}
#3.内存
mem_info() {
mem_total=`free -h |awk 'NR==2{print $2}'`
mem_used=`free -h |awk 'NR==2{print ($3+$6)}'`
mem_used_percent=`free |awk 'NR==2{print ($3+$6)/$2*100"%"}'`
blueecho "#################################"
blueecho "###########内存信息##############"
greenecho "总计内存:$mem_total"
greenecho "已用内存:${mem_used}M"
greenecho "内存使用率:$mem_used_percent"
blueecho "#################################"
}
#4.swap
swap_info() {
swap_total=`free -h |awk 'NR==3{print $2}'`
swap_used=`free -h |awk 'NR==3{print ($3+$6)}'`
swap_used_percent=`free |awk 'NR==3{print ($3+$6)/$2*100"%"}'`
blueecho "#################################"
blueecho "###########swap信息##############"
greenecho "总计swap:$swap_total"
greenecho "已用swap:${swap_used}M"
greenecho "swap使用率:$swap_used_percent"
blueecho "#################################"
}
#5.磁盘
disk_info() {
disk_total=`fdisk -l |grep '/dev/[sv]d[a-z][::]' |wc -l`
root_size=`df -h | awk '$NF=="/"{print $2}'`
root_used_percent=`df -h | awk '$NF=="/"{print $(NF-1)}'`
blueecho "#################################"
blueecho "###########磁盘信息##############"
greenecho "总计硬盘个数:$disk_total"
greenecho "根分区大小:${root_size}"
greenecho "根分区使用率:$root_used_percent"
blueecho "#################################"
}
#6.进程
proc_info() {
proc_total=`top -bn1 |awk 'NR==2{print $2}'`
proc_running=`top -bn1 |awk 'NR==2{print $4}'`
proc_stopped=`top -bn1 |awk 'NR==2{print $8}'`
proc_zombie=`top -bn1 |awk 'NR==2{print $10}'`
blueecho "#################################"
blueecho "###########进程信息##############"
greenecho "进程总数:$proc_total"
greenecho "正在运行的进程数:${proc_running}"
greenecho "挂起的进程数:$proc_stopped"
greenecho "僵尸进程数:$proc_zombie"
blueecho "#################################"
}
main() {
base_info
load_info
mem_info
swap_info
disk_info
proc_info
}
main

2.安全基线检查脚本
sh
[root@m01 devops-shell]# vim project_linux_base_line_check.sh
#!/bin/bash
########################################################
# File Name:project_linux_base_line_check.sh
# Version:V1.0
# Author:liux
# Desc:安全基线检查脚本
########################################################
#1.加载函数
. /server/scripts/devops-shell/func.sh
printline() {
blueecho "------------$1--------------"
}
# -ne不等于0
check_root() {
[ $UID -ne 0 ] && {
echo "只能root用户使用该脚本"
exit 1
}
}
check_cmd() {
which wget nc ntpdate &>/dev/null
[ $? -ne 0 ] && {
echo "没有安装常用工具"
exit 2
}
}
#2.函数
#####身份鉴权#####
user_password_complex() {
printline 1.检查密码复杂度
pass_complex=`egrep -o 'minlen|[uld]credit' /etc/pam.d/system-auth |wc -l`
if [ $pass_complex -eq 4 ];then
greenecho "密码复杂度配置OK"
rc=0
else
redecho "未配置密码复杂度failed"
rc=1
fi
return $rc
}
user_password_lock() {
printline 2.检查密码认证失败锁定的配置
pass_lock=`grep pam_tally2.so /etc/pam.d/system-auth |egrep -o 'deny=|unlock_time=' |wc -l`
if [ $pass_lock -eq 2 ];then
greenecho "密码认证失败锁定配置OK"
rc=0
else
redecho "密码认证失败锁定配置failed"
rc=1
fi
return $rc
}
user_password_expire() {
printline 3.检查口令生存期配置
pass_max_days=`awk '/^PASS_MAX_DAYS/{print $2}' /etc/login.defs`
pass_warn_age=`awk '/^PASS_WARN_AGE/{print $2}' /etc/login.defs`
max_days=90
warn_age=7
if [ $pass_max_days -le $max_days -a $pass_warn_age -ge $warn_age ];then
greenecho "口令生存周期配置OK"
rc=0
else
redecho "口令生存周期未配置failed"
rc=1
fi
return $rc
}
check_user_password_expire() {
expire_days=30
printline 4.检查是否有口令最近${expire_days}天将要过期的用户
login_user=`awk -F: '$NF=="/bin/bash" {print $1}' /etc/passwd`
for name in $login_user
do
expire_status=`chage -l $name |awk -F: 'NR==2{print $2}'`
[ "$expire_status" = " never" ] && continue
time_expire_second=`date -d "$expire_status" +%s`
time_now_second=`date +%s`
time_leave_days=`echo "(${time_expire_second}-${time_now_second})/86400" |bc`
if [ $time_leave_days -le $expire_days ];then
redecho "用户${name}即将过期,你还有${time_leave_days}天"
fi
done
}
user_remote_access() {
printline 5.检查是否开启未加密传输服务
if [ nc -z localhost 23 &>/dev/null ];then
redecho "开启了未加密的telnet服务"
rc=1
else
greenecho "telnet服务已关闭"
rc=0
fi
}
user_password_history() {
printline 6.检查是否配置历史口令重复要求
pass_history=`grep pam_pwhistory.so /etc/pam.d/system-auth |grep -o remember |wc -l`
if [ $pass_history -eq 1 ];then
greenecho "历史口令重复策略配置OK"
rc=0
else
redecho "未配置历史口令策略failed"
rc=1
fi
return $rc
}
########访问控制########
deny_useless_user() {
printline 7.检查是否禁用无用用户
useless_user_cnt=`grep -v '^#' /etc/passwd |egrep 'lp|nuucp|hpdb|sync|adm|ftp|games' |wc -l`
if [ $useless_user_cnt -eq 0 ];then
greenecho "禁用了无用用户OK"
rc=0
else
redecho "未禁用无用用户failed"
rc=1
fi
return $rc
}
fake_root_user() {
printline 8.检查是否存在uid为0,用户名不是root的用户
fake_root=`awk -F: '$3==0 && $1!="root" {print $1}' /etc/passwd |wc -l`
if [ $fake_root -eq 0 ];then
greenecho "不存在这样的用户OK"
rc=0
else
redecho "存在这样的用户failed"
rc=1
fi
return $rc
}
check_uid_gid_file() {
printline 9.检查是否存在额外的uid,gid文件
uid_gid_file=`find ${PATH//:/ } /tmp/ -perm /+s 2>/dev/null|wc -l`
if [ $uid_gid_file -eq 23 ];then
greenecho "uid,gid文件OK"
rc=0
else
redecho "uid,gid文件failed"
rc=1
fi
return $rc
}
check_everybody_write_dir() {
printline 10.检查是否有过大权限的目录
dir_777=`find / -type d -perm /o+w |egrep -v '/tmp|/dev' |wc -l`
if [ $dir_777 -eq 0 ];then
greenecho "系统没有过大权限的目录OK"
rc=0
else
redecho "系统有大过大权限的目录failed"
rc=1
fi
return $rc
}
check_hidden() {
printline 11.检查是否有隐藏文件或目录
dir_list=`awk '$3~/xfs|ext[2-4]/{print $2}' /etc/fstab `
for dir in $dir_list
do
hidden_cnt=`find $dir -xdev -name ".*" |egrep -v "/sys/|/proc/" |wc -l`
greenecho "$dir 磁盘分区 隐藏文件的数量:$hidden_cnt"
done
}
#####安全审计#####
check_ntp() {
printline 12.检查是否配置了时间同步
ntp_sync=`crontab -l|grep ntpdate |wc -l`
if [ $ntp_sync -eq 1 ];then
greenecho "配置了时间同步OK"
rc=0
else
redecho "没有配置时间同步failed"
rc=1
fi
return $rc
}
#####入侵防护#####
check_env() {
printline 13.检查是否配置了安全的环境变量
if [ "$TMOUT"x != ""x -a $HISTSIZE -le 10 ];then
greenecho "配置了安全的环境变量OK"
rc=0
else
redecho "没有配置安全的环境变量failed"
rc=1
fi
return $rc
}
check_root_remote_login() {
printline 14.检查root是否可以远程登录
root_remote_login=`awk '/^PermitRootLogin/{print $2}' /etc/ssh/sshd_config`
if [ "$root_remote_login"x = "no"x ];then
greenecho "root禁用了远程登录功能OK"
rc=0
else
redecho "root开启了远程登录功能failed"
rc=1
fi
return $rc
}
#####身份鉴权#####
user() {
user_password_complex
user_password_lock
user_password_expire
check_user_password_expire
user_remote_access
user_password_history
}
#####访问控制#####
access() {
deny_useless_user
fake_root_user
check_uid_gid_file
check_everybody_write_dir
check_hidden
}
#####安全审计#####
audit() {
check_ntp
}
#####入侵防护#####
ruqin() {
check_env
check_root_remote_login
}
main() {
check_root
check_cmd
user
access
audit
ruqin
}
main
