Linux Shell

基本语法

第一行

第一行必须是 #!/bin/sh。

注释

一行开头为 #。

接收参数

脚本文件“copy.sh”,其内容如下:

m=$1
n=$2
echo $m-$n

执行命令:“sh copy.sh 111 222”;输出 111-222

格式化输出日期

curdate="`date +%Y%m%d%H%M%S`"
echo $curdate

执行结果:20230123105058

exist

退出当前shell脚本,一般来说,返回0表示执行成功,其他值表示没有执行成功。

exist 0    # 返回0
exist 1    # 返回1

变量

变量命名

shell 变量的命名规则如下:开头是一个字母或下划线,后面可以接任意长度的字母、数字或下划线符号,变量名的字符长度并无限制(Bourne shell中)。不过为了兼容性(一些早期的shell里变量名是有长度限制的),一般还是不要超过255个字符。另外,Linux区分大小写。当用户自己定义变量的时候,要注意变量名不能与 shell 中的关键字重名。

变量赋值

变量名=值

注意:赋值语句两边不能有空格(即 “=” 号两边不能有空格)。等号右边若有空格的话,需要加上引号(单引号或双引号都是可以的)。shell 中可以在变量名前加上 $ 字符来取变量的值。

定义变量

定义单变量:

p_name='kang'

使用单变量:

echo  $p_name'.js'    # 输出kang.js
echo  $p_name.js      # 输出kang.js
cp  $p_name.js  copy.js;

系统变量

pwd=$PWD      # 当前目录
user=$USER    # 当前用户
echo $pwd
echo $user

运行脚本后输出:

/home/rainman/test
rainman

数组

 #!/bin/bash

nums=(29 100 13 8 91 44)
echo ${nums[@]}  # 输出所有数组元素
nums[10]=66         # 给第10个元素赋值(此时会增加数组长度)
echo ${nums[*]}   # 输出所有数组元素
echo ${nums[4]}   # 输出第4个元素

获取数组长度

利用@或*,可以将数组扩展成列表,然后使用#来获取数组元素的个数,格式如下:

${#array_name[@]}
${#array_name[*]}

其中 array_name 表示数组名。两种形式是等价的,选择其一即可。示例如下:

#!/bin/bash

nums=(29 100 13)
echo ${#nums[*]} # 输出3

# 向数组中添加元素
nums[10]="http://c.biancheng.net/shell/"
echo ${#nums[@]} # 输出4

数组拼接

拼接数组的思路是:先利用@或*,将数组扩展成列表,然后再合并到一起。具体格式如下:

array_new=(${array1[@]}  ${array2[@]})
array_new=(${array1[*]}  ${array2[*]})

两种方式是等价的,选择其一即可。其中,array1 和 array2 是需要拼接的数组,array_new 是拼接后形成的新数组。完整示例如下:

#!/bin/bash
array1=(23 56)
array2=(99 "https://www.baidu.com/")
array_new=(${array1[@]} ${array2[*]})
echo ${array_new[@]}  # 也可以写作 ${array_new[*]}

运行结果:23 56 99 https://www.baidu.com/

删除数组元素

在 Shell 中,使用 unset 关键字来删除数组元素,具体格式如下:

unset array_name[index]

其中,array_name 表示数组名,index 表示数组下标。如果不写下标,而是写成下面的形式:

unset array_name

那么就是删除整个数组,所有元素都会消失。

#!/bin/bash
arr=(23 56 99 "https://www.baidu.com/")
unset arr[1]
echo ${arr[@]}

unset arr
echo ${arr[*]}

运行结果:23 99 https://www.baidu.com/

算术运算

expr命令求值

使用 expr 命令对算术表达式求值,常见的命令如下:

表达式

说明

expr1 | expr2

若 expr1 非零,则等于 expr1 ,否则等于 expr2。

expr1 & expr2

只要有一个表达式为零,则等于零,否则等于 expr1。

expr1 = expr2

等于(与 == 是同义的),若两式相等则结果为1,不等结果为0

expr1 > expr2

大于

expr1 >= expr2

大于等于

expr1 < expr2

小于

expr1 <= expr2

小于等于

expr1 != expr2

不等于

expr1 + expr2

expr1 - expr2

expr1 * expr2

expr1 / expr2

整除

expr1 % expr2

取余

注意:在 expr 命令所支持的操作符中,“|、&、<、<=、>、>=、 * ” 这几个需要用 符进行转义再使用。此外,表达式的各字符之间需要用空格隔开。使用方法如下:

  #!/bin/bash
  
  a=5;b=6;c=0
  echo $(expr $a | $c)       # 输出 5
  echo $(expr $b & $c)       # 输出 0
  echo $(expr $a & $b)       # 输出 5
  echo $(expr $a <= $b)      # 输出 1
  echo $(expr $a * $b)       # 输出 30
  echo $(expr $a = 2)         # 输出 1   exit 0 

逻辑符号

$(( ... ))求值

使用 $(( ... )) 的方式对算术表达式求值。

expr 虽然功能强大,但是上面已经提到,在进行一些运算的时候,需要使用 符来进行转义,这对于阅读代码的人来说并不友好。另一方面,expr 命令执行起来其实很慢,因为它需要调用一个新的 shell 来处理 expr 命令。更新更好的一种做法是使用 $((...)) 扩展的方式。只需要将准备求值的表达式放在 $((...)) 的括号中即可进行简单的算术求值。且,所有支持 $$(( ... )) 的 shell,都可以让用户在提供变量名称时,无须前置 $$$$(( ... )) 的 shell,都可以让用户在提供变量名称时,无须前置 $$$ 符。用一段代码演示一下用法:

 #!/bin/bash
 
 a=5;b=6
 
 echo $(($a + $b))  # 输出 11 。在变量名前加上 $,这在shell中一般是取变量值的意思
 echo $((a + b))            # 输出 11 。可见,变量前不加 $ 也是可以的,为了简便,后面的代码就不加 $ 了 
 echo $((a | b))            # 输出 7 。这里的 | 是按位或操作符
 echo $((a || b))           # 输出 1 。这里的 || 是逻辑或操作符
 echo $((a & b))            # 输出 4 。这里的 & 是按位与操作符
 echo $((a && b))          # 输出 1 。这里的 && 是逻辑与操作符
 echo $((a * b))             # 输出 30
 echo $((a == b))           # 输出 0 exit 0

字符串

字符串可以由单引号' '包围,也可以由双引号" "包围,也可以不用引号。它们之间的区别:

拼接

字符串拼接连接、合并。

#!/bin/bash

name="Shell"
url="https://www.baidu.com/"

str1=$name$url                                      # 中间不能有空格
str2="$name $url"                                  # 如果被双引号包围,那么中间可以有空格
str3=$name": "$url                                 # 中间可以出现别的字符串
str4="$name: $url"                                 # 这样写也可以
str5="${name}Script: ${url}index.html"  # 这个时候需要给变量名加上大括号

echo $str1
echo $str2
echo $str3
echo $str4
echo $str5

截取

格式

说明

${string: start :length}

从 string 字符串的左边第 start 个字符开始,向右截取 length 个字符。

${string: start}

从 string 字符串的左边第 start 个字符开始截取,直到最后。

${string: 0-start :length}

从 string 字符串的右边第 start 个字符开始,向右截取 length 个字符。

${string: 0-start}

从 string 字符串的右边第 start 个字符开始截取,直到最后。

${string#*chars}

从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。

${string##*chars}

从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。

${string%*chars}

从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 左边的所有字符。

${string%%*chars}

从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 左边的所有字符。

条件判断

test 命令可以处理 shell 脚本中的各类工作。它产生的不是一般的输出,而是可使用的退出状态。test 命令通过接受各种不同的参数,来控制要执行哪种测试。在许多系统上,test 命令与 [ 命令的作用其实是一样的,使用 [ 命令的时候,一般在结尾加上 ] 符号,使代码更具可读性。另外,需要注意一点的是,在使用 [ 命令时,[ 符号与被检查的语句之间应该留有空格shell 中通常使用 test 命令来产生控制结构所需要的条件,根据 test 命令的退出码决定是否需要执行后面的代码。

test 命令可以使用的条件类型有三类:字符串比较、算术比较和与文件有关的条件测试。

字符串比较

表达式

结果

string1 = string2

如果两个字符串相同则结果为真

string1 != string2

如果两个字符串不同则结果为真

-n string

如果字符串不为空则结果为真

-z string

如果字符串为空(null),则结果为真

使用方法如下:

str1="tongye"
str2="ttyezi"

# 用 test 命令,test 语句的结果将作为 if 的判断条件,结果为真即条件为真,则执行 if 下面的语句
if test "$str1" = "$str2" ; then
    ....
fi

# 用 [ 命令的话,可以这样,注意 [ 与表达式之间要有空格
if [ "$str1" != "$str2" ] ; then
    ....
fi    if [ -n "$str1" ] ; then    ....fi

使用字符串比较的时候,必须给变量加上引号 " " ,避免因为空字符或字符串中的空格导致一些问题。实际上,对于条件测试语句里的变量,都建议加上双引号,能做字符串比较的时候,不要用数值比较。

算术比较

算术比较

结果

expr1 -eq expr2

如果两个表达式相等,则结果为真

expr1 -ne expr2

如果两个表达式不相等,则结果为真

expr1 -gt expr2

如果 expr1 > expr2 ,则结果为真

expr1 -ge expr2

如果 expr1 >= expr2 ,则结果为真

expr1 -lt expr2

如果 expr1 < expr2,则结果为真

expr1 -le expr2

如果 expr1 <= expr2,则结果为真

!expr

如果表达式为假,则结果为真

使用方法如下:

num1=2
num2=3

if [ "$num1" -eq "$num2" ] ; then
    ...
fi

if [ "$num1" -le "$num2" ] ; then
    ....
fi

注意算术比较和字符串比较之间的不同之处,字符串比较比较的是两个字符串,数字也是能组成字符串的,因此,当我们使用字符串比较的方式和数字比较的方式来比较两串数字的时候,结果会有些不同。案例如下:

  #!/bin/bash
  
  val1="1"
  val2="001"
  val3="1 "                        # 字符串 val3 在 1 的后面还有一个空格                                                                            
  
  [ "$val1" = "$val2" ]
  echo $?        # 使用字符串比较,退出码为 1,说明两个字符串不相等
  
  [ "$val1" -eq "$val2" ]
  echo $?        # 使用数值比较,退出码为 0,说明两个数值相等
  
  [ "$val1" = "$val3" ]
  echo $?        # 退出码为 1
  
  [ "$val1" -eq "$val3" ]
  echo $?        # 退出码为 0  exit 0

需要注意的是,如果在编写代码时,变量没有加上双引号,上述程序的结果又会不同,仅对 val3 进行取值,将会忽略该字符串中的空格,则第三个表达式的退出码将为 0 。这也说明了在变量两边加上双引号的重要性。

文件条件测试

文件条件测试

结果

-d file

如果文件是一个目录,则结果为真

-e file

如果文件存在,则结果为真。注意,历史上 -e 选项不可移植,所以通常使用的是 -f 选项

-f file

如果文件存在且为普通文件,则结果为真

-g file

如果文件的 set-group-id 位被设置,则结果为真

-r file

如果文件可读,则结果为真

-s file

如果文件大小不为 0 ,则结果为真

-u file

如果文件的 set-user-id 为被设置,则结果为真

-w file

如果文件可写,则结果为真

-x file

如果文件可执行,则结果为真

用一个例子演示一下:

#!/bin/bash

if [ -f /bin/bash ] ; then
    echo "file /bin/bash exists"
fi

if [ -d /bin/bash ] ; then
  echo "/bin/bash is a directory"
else
  echo "/bin/bash is not a directory"
fiexit 0

流程控制

if语句

"["和"]"前后的空格必须有,否则提示错误。

m="kang2"
if [ "$m" == 'kang' ]; then
    echo 'kang'
elif [ $m == 'kang2' ]; then
    echo 'kang2'
else
    echo 'no'
fi

示例:判断文件夹

if [ -d './js' ]; then
 echo 'js是文件夹'
fi

case语句

与其他编程语言中的 case 语句类似, shell 中的 case 语句也可以用来进行模式匹配,语法如下:

case variable in
    pattern [ | pattern ] ... ) statements;;
    pattern [ | pattern ] ... ) statements;;
    ...
esac

关于 case 的语法,有以下几点需要说明一下:

#!/bin/bash

read -p "please keyin a word:" -t 5 word

case $word in
    [a-z] | [A-Z] ) echo "You have keyin a letter";;
    [1-9] ) echo "You have keyin a number";;                                                
    * ) echo "Unknow input"
esac

exit 0

这段代码从键盘输入一个字符,然后进行匹配,判断这个字符是字母还是数字,都不是的话返回未知输入。

for语句

循环:for/do/done。注意循环项是以“空格”拆分的字符串。

foreach形式:

name="rain man's blog"
for loop in $name; do
    echo $loop;
done

自定义步长循环:

for ((初始值; 限定值; 执行步长 ))
do
    # 程序段
done

# 例如
for (( i = 1; i < ${number}; i = i + 1 ))
do
    # 程序段
done
 #!/bin/bash
 
 for name in tongye wuhen xiaodong wufei laowang
 do
     echo $name
 done
                                                                                             
 exit 0
# 依次输出:tongye wuhen xiaodong wufei laowang

while与until语句

如果你需要进行循环操作而是先不知道需要循环的次数,可以使用 while 循环,while 循环的语法如下:

while condition
do
    statements
done

until 循环语句的功能与 while 一样,不同的是对于条件判断结果的处理上。until 循环的语法如下:

until condition
do
    statements
done

在 while 和 until 语句中,condition 是判断条件,不同的是,while 语句中,若判断条件为真,则执行循环体;until 语句中,若判断条件为真,则停止执行循环体。

 #!/bin/bash
 
 i=1
 
 while [ "$i" -le 10 ]                                                                       
 do
     read -p "please keyin a number:" i
 done
  9 
 10 echo "$i"
 11 
 12 exit 0

这段代码从键盘中输入一个数字,直到输入数值大于 10,退出循环并打印最后输入的那个值。

高级命令

输出重定向

示例:

#!/bin/bash
 
for str in "test1" "test2" "test3"
do
        echo $str >>demo.txt  # 将输入结果以追加的方式重定向到文件
done
[localhost]$ ls -l >demo.txt  # 重定向
[localhost]$ cat demo.txt    # 查看文件内容

自定义函数

$? 获取函数的返回值。

#!/bin/bash
 
# 得到两个数相加的和
function add(){
    return `expr $1 + $2`
}

add 23 50  # 调用函数
echo $?     # 获取函数返回值

常用脚本

检测两台服务器指定目录下的文件一致性

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

定时清空文件内容,定时记录文件大小

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

检测网卡流量,并按规定格式记录在日志中

#!/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,"	","input:","	",$5*1000*8,"bps","
",$2,"	","output:","	",$6*1000*8,"bps"}'
echo "####################"
#因为执行sar命令需要59秒,因此不需要sleep
done

杀死所有脚本

#!/bin/bash
################################################################
#有一些脚本加入到了cron之中,存在脚本尚未运行完毕又有新任务需要执行的情况,
#导致系统负载升高,因此可通过编写脚本,筛选出影响负载的进程一次性全部杀死。
################################################################
ps aux|grep 指定进程名|grep -v grep|awk '{print $2}'|xargs kill -9

从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

监测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

批量修改文件名

# touch article_{1..3}.html
# ls
article_1.html  article_2.html  article_3.html

# 目的:把article改为bbs
# 方法1
for file in $(ls *html); do
    mv $file bbs_${file#*_}
    # mv $file $(echo $file |sed -r 's/.*(_.*)/bbs1/')
    # mv $file $(echo $file |echo bbs_$(cut -d_ -f2)
done

# 方法2
for file in $(find . -maxdepth 1 -name "*html"); do
     mv $file bbs_${file#*_}
done

# 方法3
rename article bbs *.html

统计当前目录中以.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

扫描主机端口状态

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

输入数字运行相应命令

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

Expect实现SSH免交互执行命令

Expect是一个自动交互式应用程序的工具,如telnet,ftp,passwd等。需先安装expect软件包。

# 将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 "yesr"; exp_continue}
    "password:" {send "$passwdr"}
}
expect "$user@*"  {send "$cmdr"}
expect "$user@*"  {send "exitr"}
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

监控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

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

根据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

添加脚本开机自启动

# 将脚本移动到/etc/rc.d/init.d目录
mv test.sh /etc/rc.d/init.d/test.sh

# 赋予可执行权限
chmod +x /etc/rc.d/init.d/test.sh

# 添加脚本到开机自动启动项目中
cd /etc/rc.d/init.d
chkconfig --add test.sh
chkconfig test.sh on
展开阅读全文

页面更新:2024-04-29

标签:下标   遍历   数组   变量   脚本   元素   命令   方式   文件   日志

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top