高效编写Bash脚本的技巧
总结了10个实用技巧,帮助提高脚本的效率和可靠性,具体包括:
多写注释:在脚本中添加注释,以帮助理解脚本的不同部分。
当运行失败时使脚本退出:使用
set -o errexit
或set -e
,在命令失败时退出脚本。当Bash用未声明变量时使脚本退出:使用
set -o nounset
或set -u
,避免使用未声明的变量。使用双引号来引用变量:防止由于空格或通配符导致的不必要匹配。
在脚本中使用函数:通过函数使代码模块化,提高可读性和可重用性。
字符串比较时用
=
而不是==
:在Bash中,=
和==
是等价的,但推荐使用=
。用
$(command)
而不是反引号command
进行命令代换:推荐使用$(command)
,因为它更清晰。用
readonly
声明静态变量:声明变量为只读,防止其值被修改。环境变量用大写字母命名,自定义变量用小写:遵循命名约定,避免变量名冲突。
总是对长脚本进行调试:在脚本执行前进行调试,以便于修正错误。
相关例子
多写注释
举例:在脚本中添加注释,例如:
# This script updates the system packages update_system() { sudo apt-get update sudo apt-get upgrade -y }
好处:增加代码的可读性,便于维护和理解。
问题:没有注释的脚本难以理解,特别是在团队协作中,其他成员可能难以快速上手。
当运行失败时使脚本退出
举例:使用
set -e
来确保脚本在命令失败时立即退出:set -e ./some_command || true # 这个命令失败不会退出脚本
好处:防止错误的累积和传播,提高脚本的健壮性。
问题:不立即退出可能导致后续命令基于错误的前提执行,造成更严重的错误。
当Bash用未声明变量时使脚本退出
举例:使用
set -u
来避免使用未声明的变量:set -u echo "$undeclared_var" # 这将导致脚本退出
好处:避免使用未初始化的变量,减少潜在的bug。
问题:使用未声明的变量可能导致不可预测的行为或错误的输出。
使用双引号来引用变量
举例:使用双引号来防止变量展开或通配符匹配:
names="TecMint FOSSMint" echo "$names" # 正确:"TecMint FOSSMint" echo $names # 错误:可能分割成两个单词
好处:确保变量按照预期的方式被引用,防止意外的单词分割。
问题:不使用双引号可能导致变量值中的空格或特殊字符被错误解释。
在脚本中使用函数
举例:将重复的代码块封装成函数:
check_root() { if [ "$(id -u)" != "0" ]; then echo "This script must be run as root" >&2 exit 1 fi }
好处:提高代码的模块化和重用性,简化脚本结构。
问题:不使用函数可能导致代码重复,难以维护和扩展。
字符串比较时用
=
而不是==
举例:使用单等号进行字符串比较:
if [ "$value1" = "$value2" ]; then echo "Values are equal" fi
好处:简化语法,避免混淆。
问题:使用
==
在某些shell中可能不被识别,导致语法错误。哈哈,虽然简单场景用 [ ] 和 = 更保证可移植性和安全性。 但是这个我还是更喜欢用 [[ ]] 和 == ;
用
$(command)
而不是反引号进行命令代换举例:使用
$()
进行命令替换:user=$(whoami) # 正确 user=`whoami` # 错误:不建议使用
好处:提高代码的可读性和一致性。
问题:反引号在复杂表达式中可能导致错误,且不如
$()
易读。
用
readonly
声明静态变量举例:使用
readonly
来防止变量被修改:readonly config_path="/etc/config"
好处:确保配置值在脚本中保持不变,防止意外的修改。
问题:不声明为只读可能导致变量在脚本中被错误地修改。
环境变量用大写字母命名,自定义变量用小写
举例:遵循命名约定:
export PATH="/usr/bin:/bin" local script_path="$(dirname "$0")"
好处:避免与系统环境变量冲突,提高变量名的可读性。
问题:不遵循命名约定可能导致变量名冲突或混淆。
总是对长脚本进行调试
举例:使用
bash -n script.sh
来检查脚本中的语法错误:bash -n my_script.sh
好处:提前发现潜在的错误,避免在生产环境中出现问题。
问题:不进行调试可能导致脚本在实际运行时出现未预料的错误,影响系统的稳定性。
遵循这些技巧可以编写出更加健壮、可维护和易于理解的Bash脚本。
用nice指定shell脚本在运行过程中的优先级
在Linux系统中,脚本、程序、命令等可能同一时刻需要运行,而CPU在一个时刻只能运行其中的一个,这个时候就牵扯到先后的问题,换言之,也就是优先级。
系统中,进程的优先级一般包括实时优先级和静态优先级,在任何时刻,实时进程的优先级高于普通进程,实时进程的优先级范围0~99,普通进程的优先级范围100~139,因此,数值越小,优先级越高。
1 查看Linux中进程的优先级
[root@achao ~]# ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 0 2935 2928 0 80 0 - 29180 do_wai pts/0 00:00:00 bash
0 R 0 3071 2935 0 80 0 - 38328 - pts/0 00:00:00 ps
ps命令能够显示当前系统中正在运行的进程,并且能够显示进程的详细信息,其中,PRI和NI是和进程优先级相关的字段。PRI的默认取值范围是0~99,进程的优先级涉及到进程调度相关的知识,此处不做展开。本篇旨在使用。
2 使用nice指定优先级
NICE值是进程优先级的修正值,NICE和优先级的关系如下:
PRI值=PRI值(旧值)+NICE值
进程最终的优先级是PRI和NICE值的和,NICE值可以是正数,也可以是负数,当NICE值为正时,会使得进程优先级降低,反之,可以提高进程优先级。
NICE取值范围-20~19。
设置进程NICE值使用nice命令,介绍如下
[root@achao ~]# nice --help
Usage: nice [OPTION] [COMMAND [ARG]...]
Run COMMAND with an adjusted niceness, which affects process scheduling.
With no COMMAND, print the current niceness. Niceness values range from
-20 (most favorable to the process) to 19 (least favorable to the process).
Mandatory arguments to long options are mandatory for short options too.
-n, --adjustment=N add integer N to the niceness (default 10)
--help display this help and exit
--version output version information and exit
3 正常执行shell脚本,并查看
[root@achao libai]# ls
demo.sh
[root@achao libai]# cat demo.sh
#!/bin/bash
while((1))
do
sleep 1
done
[root@achao libai]# ./demo.sh &
[1] 3376
[root@achao libai]# ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 0 2935 2928 0 80 0 - 29213 do_wai pts/0 00:00:00 bash
0 S 0 3376 2935 0 80 0 - 28320 do_wai pts/0 00:00:00 demo.sh
0 S 0 3386 3376 0 80 0 - 27014 hrtime pts/0 00:00:00 sleep
0 R 0 3387 2935 0 80 0 - 38328 - pts/0 00:00:00 ps
不指定nice值执行shell脚本PID值是3376
4 使用nice指定优先级执行shell脚本
[root@achao libai]# nice -10 ./demo.sh &
[2] 3573
[root@achao libai]# ps -elf | grep demo.sh | grep -v grep
0 S root 3376 2935 0 80 0 - 28320 do_wai 01:39 pts/0 00:00:00 /bin/bash ./demo.sh
0 S root 3573 2935 0 90 10 - 28320 do_wai 01:42 pts/0 00:00:00 /bin/bash ./demo.sh
我们发现NI和PRI的值发生了变化,因此使用nice命令可以直接对进程执行的优先级产生影响,该影响在进行进程调度的时候,会发生很大的作用,不知道宝子们在生产环境中是否遇见过呢?阿超第一次遇见的时候是在shell脚本中的,当时也是一脸懵,只能悄悄查资料学习理解。
5 renice的语法格式
renice的范围是[-20~19]
[root@achao libai]# renice --help
Usage:
renice [-n] <priority> [-p|--pid] <pid>...
renice [-n] <priority> -g|--pgrp <pgid>...
renice [-n] <priority> -u|--user <user>...
Options:
-g, --pgrp <id> interpret argument as process group ID
-n, --priority <num> specify the nice increment value
-p, --pid <id> interpret argument as process ID (default)
-u, --user <name|id> interpret argument as username or user ID
-h, --help display help text and exit
-V, --version display version information and exit
在运行的过程中,如果需要提高或者降低优先级,也是有办法的,这个时候就用renice命令就可以完成。
6 使用renice重置优先级
使用kill -9结束3376进程
[root@achao libai]# kill -9 3376
[1]- Killed ./demo.sh
[root@achao libai]# ps -elf | grep demo.sh | grep -v grep
0 S root 3573 2935 0 90 10 - 28320 do_wai 01:42 pts/0 00:00:00 /bin/bash ./demo.sh
[root@achao libai]# renice 15 -p 3573
3573 (process ID) old priority 10, new priority 15
[root@achao libai]# ps -elf | grep demo.sh | grep -v grep
0 S root 3573 2935 0 95 15 - 28320 do_wai 01:42 pts/0 00:00:00 /bin/bash ./demo.sh
注意:renice命令也是只能通过影响进程的NICE值来改变进程的优先级,而不能直接改变进程的优先级。
一键生成SSL证书:详解Shell脚本实现及OpenSSL应用
OpenSSL简介
OpenSSL是一个强大的开源工具包,用于实现SSL和TLS协议以及提供加密功能。它广泛应用于Web服务器,用于加密HTTPS通信。在本脚本中,我们将使用OpenSSL来生成自签名的CA(证书颁发机构)证书以及服务器证书。
脚本分析
接下来,我们将逐行分析这个Shell脚本,了解它是如何工作的。
CERT_INFO=(
[00]="/O=heaven/CN=ca.god.com"
[01]="cakey.pem"
[02]="cacert.pem"
[03]=2048
[04]=3650
[05]=0
[10]="/C=CN/ST=hubei/L=wuhan/O=Central.Hospital/CN=master.liwenliang.org"
[11]="master.key"
[12]="master.crt"
[13]=2048
[14]=365
[15]=1
[16]="master.csr"
[20]="/C=CN/ST=hubei/L=wuhan/O=Central.Hospital/CN=slave.liwenliang.org"
[21]="slave.key"
[22]="slave.crt"
[23]=2048
[24]=365
[25]=2
[26]="slave.csr"
)
这段定义了一个名为CERT_INFO
的数组,包含了生成证书所需的各种信息,包括证书主题、密钥文件名、证书文件名、密钥长度、有效期、序列号以及CSR(证书签名请求)文件名。
COLOR="echo -e \\E[1;32m"
END="\\E[0m"
DIR=/data
cd $DIR
这几行定义了用于输出彩色文本的命令,并设置了工作目录为/data
。
for i in {0..2};do
if [ $i -eq 0 ] ;then
openssl req -x509 -newkey rsa:${CERT_INFO[${i}3]} -subj ${CERT_INFO[${i}0]} \
-set_serial ${CERT_INFO[${i}5]} -keyout ${CERT_INFO[${i}1]} -nodes -days ${CERT_INFO[${i}4]} \
-out ${CERT_INFO[${i}2]} &>/dev/null
else
openssl req -newkey rsa:${CERT_INFO[${i}3]} -nodes -subj ${CERT_INFO[${i}0]} \
-keyout ${CERT_INFO[${i}1]} -out ${CERT_INFO[${i}6]} &>/dev/null
openssl x509 -req -in ${CERT_INFO[${i}6]} -CA ${CERT_INFO[02]} -CAkey ${CERT_INFO[01]} \
-set_serial ${CERT_INFO[${i}5]} -days ${CERT_INFO[${i}4]} -out ${CERT_INFO[${i}2]} &>/dev/null
fi
$COLOR"**************************************生成证书信息 **************************************"$END
openssl x509 -in ${CERT_INFO[${i}2]} -noout -subject -dates -serial
echo
done
这是脚本的核心部分,通过一个循环,分别为CA和两个服务器生成证书。
对于CA(
$i -eq 0
),使用openssl req -x509
命令直接生成自签名的X.509证书。对于服务器证书,首先使用
openssl req
生成CSR,然后使用openssl x509
根据CSR和CA证书生成最终的服务器证书。
chmod 600 *.key
这行代码将所有密钥文件的权限设置为600,确保只有文件所有者可以读写密钥文件,增加安全性。
action "证书生成完成"
这行假设有一个名为action
的函数,用于显示操作结果。虽然脚本中没有定义这个函数,但你可以根据需要自行实现,例如通过简单的echo
命令来输出完成信息。
使用脚本
保存脚本:将上述脚本保存为
generate_certs.sh
。赋予权限:运行
chmod +x generate_certs.sh
,使脚本具有执行权限。执行脚本:运行
./generate_certs.sh
,脚本将自动生成所需的证书和密钥。
注意事项
确保在运行脚本之前,你已经安装了OpenSSL。
根据你的实际需求,可能需要调整证书的有效期、密钥长度等参数。
在生产环境中,建议使用受信任的CA颁发的证书,而不是自签名证书。
结语
通过这个简单的Shell脚本,你可以轻松地为你的服务器生成SSL证书,无论是用于开发环境还是测试环境。当然,对于生产环境,建议使用更严格的证书管理策略,并确保定期更新证书,以保障通信安全。希望这篇文章对你有所帮助,如果你有任何问题或建议,欢迎在评论区留言。
18 个一线工作中常用 Shell 脚本
1、检测两台服务器指定目录下的文件一致性
2、定时清空文件内容,定时记录文件大小
3、检测网卡流量,并按规定格式记录在日志中
4、计算文档每行出现的数字个数,并计算整个文档的数字总数
5、从 FTP 服务器下载文件
6、连续输入5个100以内的数字,统计和、最小和最大
7、监测 Nginx 访问日志 502 情况,并做相应动作
8、将结果分别赋值给变量
9、批量修改文件名
10、统计当前目录中以 .html 结尾的文件总大
11、扫描主机端口状态
12、输入数字运行相应命令
13、Expect 实现 SSH 免交互执行命令
14、监控 httpd 的进程数,根据监控情况做相应处理
15、批量修改服务器用户密码
16、iptables 自动屏蔽访问网站频繁的IP
17、根据web访问日志,封禁请求量异常的IP,如IP在半小时后恢复正常,则解除封禁
18、判断用户输入的是否为IP地址
1、检测两台服务器指定目录下的文件一致性
#!/bin/bash
#####################################
#检测两台服务器指定目录下的文件一致性
#####################################
#通过对比两台服务器上文件的md5值,达到检测一致性的目的
dir=/data/web
b_ip=192.168.88.10
#将指定目录下的文件全部遍历出来并作为md5sum命令的参数,进而得到所有文件的md5值,并写入到指定文件中
find $dir -type f|xargs md5sum > /tmp/md5_a.txt
ssh $b_ip "find $dir -type f|xargs md5sum > /tmp/md5_b.txt"
scp $b_ip:/tmp/md5_b.txt /tmp
#将文件名作为遍历对象进行一一比对
for f in `awk '{print 2} /tmp/md5_a.txt'`
do
#以a机器为标准,当b机器不存在遍历对象中的文件时直接输出不存在的结果
if grep -qw "$f" /tmp/md5_b.txt
then
md5_a=`grep -w "$f" /tmp/md5_a.txt|awk '{print 1}'`
md5_b=`grep -w "$f" /tmp/md5_b.txt|awk '{print 1}'`
#当文件存在时,如果md5值不一致则输出文件改变的结果
if [ $md5_a != $md5_b ]
then
echo "$f changed."
fi
else
echo "$f deleted."
fi
done
2、定时清空文件内容,定时记录文件大小
#!/bin/bash
################################################################
#每小时执行一次脚本(任务计划),当时间为0点或12点时,将目标目录下的所有文件内
#容清空,但不删除文件,其他时间则只统计各个文件的大小,一个文件一行,输出到以时#间和日期命名的文件中,需要考虑目标目录下二级、三级等子目录的文件
################################################################
logfile=/tmp/`date +%H-%F`.log
n=`date +%H`
if [ $n -eq 00 ] || [ $n -eq 12 ]
then
#通过for循环,以find命令作为遍历条件,将目标目录下的所有文件进行遍历并做相应操作
for i in `find /data/log/ -type f`
do
true > $i
done
else
for i in `find /data/log/ -type f`
do
du -sh $i >> $logfile
done
fi
3、检测网卡流量,并按规定格式记录在日志中
#!/bin/bash
#######################################################
#检测网卡流量,并按规定格式记录在日志中
#规定一分钟记录一次
#日志格式如下所示:
#2019-08-12 20:40
#ens33 input: 1234bps
#ens33 output: 1235bps
######################################################3
while :
do
#设置语言为英文,保障输出结果是英文,否则会出现bug
LANG=en
logfile=/tmp/`date +%d`.log
#将下面执行的命令结果输出重定向到logfile日志中
exec >> $logfile
date +"%F %H:%M"
#sar命令统计的流量单位为kb/s,日志格式为bps,因此要*1000*8
sar -n DEV 1 59|grep Average|grep ens33|awk '{print $2,"\t","input:","\t",$5*1000*8,"bps","\n",$2,"\t","output:","\t",$6*1000*8,"bps"}'
echo "####################"
#因为执行sar命令需要59秒,因此不需要sleep
done
4、计算文档每行出现的数字个数,并计算整个文档的数字总数
#!/bin/bash
#########################################################
#计算文档每行出现的数字个数,并计算整个文档的数字总数
########################################################
#使用awk只输出文档行数(截取第一段)
n=`wc -l a.txt|awk '{print $1}'`
sum=0
#文档中每一行可能存在空格,因此不能直接用文档内容进行遍历
for i in `seq 1 $n`
do
#输出的行用变量表示时,需要用双引号
line=`sed -n "$i"p a.txt`
#wc -L选项,统计最长行的长度
n_n=`echo $line|sed s'/[^0-9]//'g|wc -L`
echo $n_n
sum=$[$sum+$n_n]
done
echo "sum:$sum"
杀死所有脚本
#!/bin/bash
if [ $# -ne 1 ]; then
echo "Usage: $0 filename"
fi
dir=$(dirname $1)
file=$(basename $1)
ftp -n -v << EOF # -n 自动登录
open 192.168.1.10 # ftp服务器
user admin password
binary # 设置ftp传输模式为二进制,避免MD5值不同或.tar.gz压缩包格式错误
cd $dir
get "$file"
EOF
5、从 FTP 服务器下载文件
#!/bin/bash
if [ $# -ne 1 ]; then
echo "Usage: $0 filename"
fi
dir=$(dirname $1)
file=$(basename $1)
ftp -n -v << EOF # -n 自动登录
open 192.168.1.10 # ftp服务器
user admin password
binary # 设置ftp传输模式为二进制,避免MD5值不同或.tar.gz压缩包格式错误
cd $dir
get "$file"
EOF
6、连续输入5个100以内的数字,统计和、最小和最大
#!/bin/bash
COUNT=1
SUM=0
MIN=0
MAX=100
while [ $COUNT -le 5 ]; do
read -p "请输入1-10个整数:" INT
if [[ ! $INT =~ ^[0-9]+$ ]]; then
echo "输入必须是整数!"
exit 1
elif [[ $INT -gt 100 ]]; then
echo "输入必须是100以内!"
exit 1
fi
SUM=$(($SUM+$INT))
[ $MIN -lt $INT ] && MIN=$INT
[ $MAX -gt $INT ] && MAX=$INT
let COUNT++
done
echo "SUM: $SUM"
echo "MIN: $MIN"
echo "MAX: $MAX"
用户猜数字
#!/bin/bash # 脚本生成一个 100 以内的随机数,提示用户猜数字,根据用户的输入,提示用户猜对了,
# 猜小了或猜大了,直至用户猜对脚本结束。
# RANDOM 为系统自带的系统变量,值为 0‐32767的随机数
# 使用取余算法将随机数变为 1‐100 的随机数num=$[RANDOM%100+1]echo "$num"
# 使用 read 提示用户猜数字
# 使用 if 判断用户猜数字的大小关系:‐eq(等于),‐ne(不等于),‐gt(大于),‐ge(大于等于),
# ‐lt(小于),‐le(小于等于)
while :
do
read -p "计算机生成了一个 1‐100 的随机数,你猜: " cai
if [ $cai -eq $num ]
then
echo "恭喜,猜对了"
exit
elif [ $cai -gt $num ]
then
echo "Oops,猜大了"
else
echo "Oops,猜小了"
fi
done
7、监测 Nginx 访问日志 502 情况,并做相应动作
假设服务器环境为 lnmp,近期访问经常出现 502 现象,且 502 错误在重启 php-fpm 服务后消失,因此需要编写监控脚本,一旦出现 502,则自动重启 php-fpm 服务。
#场景:
#1.访问日志文件的路径:/data/log/access.log
#2.脚本死循环,每10秒检测一次,10秒的日志条数为300条,出现502的比例不低于10%(30条)则需要重启php-fpm服务
#3.重启命令为:/etc/init.d/php-fpm restart
#!/bin/bash
###########################################################
#监测Nginx访问日志502情况,并做相应动作
###########################################################
log=/data/log/access.log
N=30 #设定阈值
while :
do
#查看访问日志的最新300条,并统计502的次数
err=`tail -n 300 $log |grep -c '502" '`
if [ $err -ge $N ]
then
/etc/init.d/php-fpm restart 2> /dev/null
#设定60s延迟防止脚本bug导致无限重启php-fpm服务
sleep 60
fi
sleep 10
done
8、将结果分别赋值给变量
应用场景:希望将执行结果或者位置参数赋值给变量,以便后续使用。
方法1:
for i in $(echo "4 5 6"); do
eval a$i=$i
done
echo $a4 $a5 $a6
方法2:将位置参数192.168.1.1{1,2}拆分为到每个变量
num=0
for i in $(eval echo $*);do #eval将{1,2}分解为1 2
let num+=1
eval node${num}="$i"
done
echo $node1 $node2 $node3
# bash a.sh 192.168.1.1{1,2}
192.168.1.11 192.168.1.12
方法3:
arr=(4 5 6)
INDEX1=$(echo ${arr[0]})
INDEX2=$(echo ${arr[1]})
INDEX3=$(echo ${arr[2]})
9、批量修改文件名
示例:
# touch article_{1..3}.html
# lsarticle_1.html article_2.html article_3.html
方法1:
for file in $(ls *html); do
mv $file bbs_${file#*_}
# mv $file $(echo $file |sed -r 's/.*(_.*)/bbs\1/')
# mv $file $(echo $file |echo bbs_$(cut -d_ -f2)
方法2:
for file in $(find . -maxdepth 1 -name "*html"); do
mv $file bbs_${file#*_}done
方法3:
# rename article bbs *.html
把一个文档前五行中包含字母的行删掉,同时删除6到10行包含的所有字母
1)准备测试文件,文件名为2.txt
第1行1234567不包含字母
第2行56789BBBBBB
第3行67890CCCCCCCC
第4行78asdfDDDDDDDDD
第5行123456EEEEEEEE
第6行1234567ASDF
第7行56789ASDF
第8行67890ASDF
第9行78asdfADSF
第10行123456AAAA
第11行67890ASDF
第12行78asdfADSF
第13行123456AAAA
2)脚本如下:
#!/bin/bash
###############################################################
把一个文档前五行中包含字母的行删掉,同时删除6到10行包含的所有字母
##############################################################
sed -n '1,5'p 2.txt |sed '/[a-zA-Z]/'d
sed -n '6,10'p 2.txt |sed s'/[a-zA-Z]//'g
sed -n '11,$'p 2.txt
#最终结果只是在屏幕上打印结果,如果想直接更改文件,可将输出结果写入临时文件中,再替换2.txt或者使用-i选项
10、统计当前目录中以 .html 结尾的文件总大
方法1:
# find . -name "*.html" -exec du -k {} \; |awk '{sum+=$1}END{print sum}'
方法2:
for size in $(ls -l *.html |awk '{print $5}'); do
sum=$(($sum+$size))
done
echo $sum
11、扫描主机端口状态
#!/bin/bash
HOST=$1
PORT="22 25 80 8080"
for PORT in $PORT; do
if echo &>/dev/null > /dev/tcp/$HOST/$PORT; then
echo "$PORT open"
else
echo "$PORT close"
fi
done
用 shell 打印示例语句中字母数小于 6 的单词
#示例语句:
#Bash also interprets a number of multi-character options.
#!/bin/bash
##############################################################
#shell打印示例语句中字母数小于6的单词
##############################################################
for s in Bash also interprets a number of multi-character options.
do
n=`echo $s|wc -c`
if [ $n -lt 6 ]
then
echo $s
fi
done
12、输入数字运行相应命令
#!/bin/bash
##############################################################
#输入数字运行相应命令
##############################################################
echo "*cmd menu* 1-date 2-ls 3-who 4-pwd 0-exit "
while :
do
#捕获用户键入值
read -p "please input number :" n
n1=`echo $n|sed s'/[0-9]//'g`
#空输入检测
if [ -z "$n" ]
then
continue
fi
#非数字输入检测
if [ -n "$n1" ]
then
exit 0
fi
break
done
case $n in
1)
date
;;
2)
ls
;;
3)
who
;;
4)
pwd
;;
0)
break
;;
#输入数字非1-4的提示
*)
echo "please input number is [1-4]"
esac
13、Expect 实现 SSH 免交互执行命令
Expect是一个自动交互式应用程序的工具,如telnet,ftp,passwd等。
需先安装expect软件包。
方法1:EOF 标准输出作为 expect 标准输入
#!/bin/bash
USER=root
PASS=123.com
IP=192.168.1.120
expect << EOFset timeout 30spawn ssh $USER@$IP expect { "(yes/no)" {send "yes\r"; exp_continue} "password:" {send "$PASS\r"}
}
expect "$USER@*" {send "$1\r"}
expect "$USER@*" {send "exit\r"}
expect eof
EOF
方法2:
#!/bin/bash
USER=root
PASS=123.com
IP=192.168.1.120
expect -c "
spawn ssh $USER@$IP
expect {
\"(yes/no)\" {send \"yes\r\"; exp_continue}
\"password:\" {send \"$PASS\r\"; exp_continue}
\"$USER@*\" {send \"df -h\r exit\r\"; exp_continue}
}"
方法3:将expect脚本独立出来
登录脚本:
# cat login.exp
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set passwd [lindex $argv 2]
set cmd [lindex $argv 3]
if { $argc != 4 } {
puts "Usage: expect login.exp ip user passwd"
exit 1
}
set timeout 30
spawn ssh $user@$ip
expect {
"(yes/no)" {send "yes\r"; exp_continue}
"password:" {send "$passwd\r"}
}
expect "$user@*" {send "$cmd\r"}
expect "$user@*" {send "exit\r"}
expect eof
执行命令脚本:写个循环可以批量操作多台服务器
#!/bin/bash
HOST_INFO=user_info.txt
for ip in $(awk '{print $1}' $HOST_INFO)
do
user=$(awk -v I="$ip" 'I==$1{print $2}' $HOST_INFO)
pass=$(awk -v I="$ip" 'I==$1{print $3}' $HOST_INFO)
expect login.exp $ip $user $pass $1
done
Linux主机SSH连接信息:
# cat user_info.txt
192.168.1.120 root 123456
创建10个用户,并分别设置密码,密码要求10位且包含大小写字母以及数字,最后需要把每个用户的密码存在指定文件中 。
#!/bin/bash
##############################################################
#创建10个用户,并分别设置密码,密码要求10位且包含大小写字母以及数字
#最后需要把每个用户的密码存在指定文件中
#前提条件:安装mkpasswd命令
##############################################################
#生成10个用户的序列(00-09)
for u in `seq -w 0 09`
do
#创建用户
useradd user_$u
#生成密码
p=`mkpasswd -s 0 -l 10`
#从标准输入中读取密码进行修改(不安全)
echo $p|passwd --stdin user_$u
#常规修改密码
echo -e "$p\n$p"|passwd user_$u
#将创建的用户及对应的密码记录到日志文件中
echo "user_$u $p" >> /tmp/userpassword
done
14、监控 httpd 的进程数,根据监控情况做相应处理
#!/bin/bash
###############################################################################################################################
#需求:
#1.每隔10s监控httpd的进程数,若进程数大于等于500,则自动重启Apache服务,并检测服务是否重启成功
#2.若未成功则需要再次启动,若重启5次依旧没有成功,则向管理员发送告警邮件,并退出检测
#3.如果启动成功,则等待1分钟后再次检测httpd进程数,若进程数正常,则恢复正常检测(10s一次),否则放弃重启并向管理员发送告警邮件,并退出检测
###############################################################################################################################
#计数器函数
check_service()
{
j=0
for i in `seq 1 5`
do
#重启Apache的命令
/usr/local/apache2/bin/apachectl restart 2> /var/log/httpderr.log
#判断服务是否重启成功
if [ $? -eq 0 ]
then
break
else
j=$[$j+1]
fi
#判断服务是否已尝试重启5次
if [ $j -eq 5 ]
then
mail.py
exit
fi
done
}
while :
do
n=`pgrep -l httpd|wc -l`
#判断httpd服务进程数是否超过500
if [ $n -gt 500 ]
then
/usr/local/apache2/bin/apachectl restart
if [ $? -ne 0 ]
then
check_service
else
sleep 60
n2=`pgrep -l httpd|wc -l`
#判断重启后是否依旧超过500
if [ $n2 -gt 500 ]
then
mail.py
exit
fi
fi
fi
#每隔10s检测一次
sleep 10
done
15、批量修改服务器用户密码
Linux主机SSH连接信息:旧密码
# cat old_pass.txt
192.168.18.217 root 123456 22
192.168.18.218 root 123456 22
内容格式:IP User Password Port
SSH远程修改密码脚本:新密码随机生成
#!/bin/bash
OLD_INFO=old_pass.txt
NEW_INFO=new_pass.txt
for IP in $(awk '/^[^#]/{print $1}' $OLD_INFO); do
USER=$(awk -v I=$IP 'I==$1{print $2}' $OLD_INFO)
PASS=$(awk -v I=$IP 'I==$1{print $3}' $OLD_INFO)
PORT=$(awk -v I=$IP 'I==$1{print $4}' $OLD_INFO)
NEW_PASS=$(mkpasswd -l 8) # 随机密码
echo "$IP $USER $NEW_PASS $PORT" >> $NEW_INFO
expect -c "
spawn ssh -p$PORT $USER@$IP
set timeout 2
expect {
\"(yes/no)\" {send \"yes\r\";exp_continue}
\"password:\" {send \"$PASS\r\";exp_continue}
\"$USER@*\" {send \"echo \'$NEW_PASS\' |passwd --stdin $USER\r exit\r\";exp_continue}
}"
done
生成新密码文件:
# cat new_pass.txt
192.168.18.217 root n8wX3mU% 22
192.168.18.218 root c87;ZnnL 22
16、iptables 自动屏蔽访问网站频繁的IP
场景:恶意访问,安全防范
1)屏蔽每分钟访问超过200的IP
方法1:根据访问日志(Nginx为例)
#!/bin/bash
DATE=$(date +%d/%b/%Y:%H:%M)
ABNORMAL_IP=$(tail -n5000 access.log |grep $DATE |awk '{a[$1]++}END{for(i in a)if(a[i]>100)print i}')
#先tail防止文件过大,读取慢,数字可调整每分钟最大的访问量。awk不能直接过滤日志,因为包含特殊字符。
for IP in $ABNORMAL_IP; do
if [ $(iptables -vnL |grep -c "$IP") -eq 0 ];
then
iptables -I INPUT -s $IP -j DROP
fi
done
方法2:通过TCP建立的连接
#!/bin/bash
ABNORMAL_IP=$(netstat -an |awk '$4~/:80$/ && $6~/ESTABLISHED/{gsub(/:[0-9]+/,"",$5);{a[$5]++}}END{for(i in a)if(a[i]>100)print i}')
#gsub是将第五列(客户端IP)的冒号和端口去掉
for IP in $ABNORMAL_IP; do
if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then
iptables -I INPUT -s $IP -j DROP
fi
done
2)屏蔽每分钟SSH尝试登录超过10次的IP
方法1:通过lastb获取登录状态:
#!/bin/bash
DATE=$(date +"%a %b %e %H:%M") #星期月天时分 %e单数字时显示7,而%d显示07
ABNORMAL_IP=$(lastb |grep "$DATE" |awk '{a[$3]++}END{for(i in a)if(a[i]>10)print i}')
for IP in $ABNORMAL_IP; do
if [ $(iptables -vnL |grep -c "$IP") -eq 0 ];
then
iptables -I INPUT -s $IP -j DROP
fi
done
方法2:通过日志获取登录状态
#!/bin/bash
DATE=$(date +"%b %d %H")
ABNORMAL_IP="$(tail -n10000 /var/log/auth.log |grep "$DATE" |awk '/Failed/{a[$(NF-3)]++}END{for(i in a)if(a[i]>5)print i}')"
for IP in $ABNORMAL_IP; do
if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then
iptables -A INPUT -s $IP -j DROP
echo "$(date +"%F %T") - iptables -A INPUT -s $IP -j DROP" >>~/ssh-login-limit.log
fi
done
17、根据web访问日志,封禁请求量异常的IP,如IP在半小时后恢复正常,则解除封禁
#!/bin/bash
####################################################################################
#根据web访问日志,封禁请求量异常的IP,如IP在半小时后恢复正常,则解除封禁
####################################################################################
logfile=/data/log/access.log
#显示一分钟前的小时和分钟
d1=`date -d "-1 minute" +%H%M`
d2=`date +%M`
ipt=/sbin/iptables
ips=/tmp/ips.txt
block()
{
#将一分钟前的日志全部过滤出来并提取IP以及统计访问次数
grep '$d1:' $logfile|awk '{print $1}'|sort -n|uniq -c|sort -n > $ips
#利用for循环将次数超过100的IP依次遍历出来并予以封禁
for i in `awk '$1>100 {print $2}' $ips`
do
$ipt -I INPUT -p tcp --dport 80 -s $i -j REJECT
echo "`date +%F-%T` $i" >> /tmp/badip.log
done
}
unblock()
{
#将封禁后所产生的pkts数量小于10的IP依次遍历予以解封
for a in `$ipt -nvL INPUT --line-numbers |grep '0.0.0.0/0'|awk '$2<10 {print $1}'|sort -nr`
do
$ipt -D INPUT $a
done
$ipt -Z
}
#当时间在00分以及30分时执行解封函数
if [ $d2 -eq "00" ] || [ $d2 -eq "30" ]
then
#要先解再封,因为刚刚封禁时产生的pkts数量很少
unblock
block
else
block
fi
18、判断用户输入的是否为IP地址
方法1:
#!/bin/bash
function check_ip(){
IP=$1
VALID_CHECK=$(echo $IP|awk -F. '$1< =255&&$2<=255&&$3<=255&&$4<=255{print "yes"}')
if echo $IP|grep -E "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$">/dev/null; then
if [ $VALID_CHECK == "yes" ]; then
echo "$IP available."
else
echo "$IP not available!"
fi
else
echo "Format error!"
fi
}
check_ip 192.168.1.1
check_ip 256.1.1.1
方法2:
#!/bin/bash
function check_ip(){
IP=$1
if [[ $IP =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
FIELD1=$(echo $IP|cut -d. -f1)
FIELD2=$(echo $IP|cut -d. -f2)
FIELD3=$(echo $IP|cut -d. -f3)
FIELD4=$(echo $IP|cut -d. -f4)
if [ $FIELD1 -le 255 -a $FIELD2 -le 255 -a $FIELD3 -le 255 -a $FIELD4 -le 255 ]; then
echo "$IP available."
else
echo "$IP not available!"
fi
else
echo "Format error!"
fi
}
check_ip 192.168.1.1
check_ip 256.1.1.1
增加版:
加个死循环,如果IP可用就退出,不可用提示继续输入,并使用awk判断。
#!/bin/bash
function check_ip(){
local IP=$1
VALID_CHECK=$(echo $IP|awk -F. '$1< =255&&$2<=255&&$3<=255&&$4<=255{print "yes"}')
if echo $IP|grep -E "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" >/dev/null; then
if [ $VALID_CHECK == "yes" ]; then
return 0
else
echo "$IP not available!"
return 1
fi
else
echo "Format error! Please input again."
return 1
fi
}
while true; do
read -p "Please enter IP: " IP
check_ip $IP
[ $? -eq 0 ] && break || continue
done