Linux Shell
基本语法
第一行
第一行必须是 #!/bin/sh。
- 它不是注释,#!/bin/sh 是对shell的声明,说明你所用的是那种类型的shell及其路径所在
- 如果没有声明,则脚本将在默认的shell中执行,默认shell是由用户所在的系统定义为执行shell脚本的shell
- 如果脚本被编写为在Kornshell ksh中运行,而默认运行shell脚本的为C shell csh,则脚本在执行过程中很可能失败
- 所以建议大家就把 #!/bin/sh 当成C 语言的main函数一样,写shell必须有,以使shell程序更严密
注释
一行开头为 #。
接收参数
脚本文件“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
数组
- Shell 并且没有限制数组的大小,理论上可以存放无限量的数据
- Shell 数组元素的下标也是从 0 开始计数
- 获取数组中的元素要使用下标[ ],下标可以是一个整数,也可以是一个结果为整数的表达式、
- 下标必须大于等于 0
- 常用的 Bash Shell 只支持一维数组,不支持多维数组
#!/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
逻辑符号
- 命令1 && 命令2:如果左边的“命令1”执行成功,那么右边的“命令2”才会被执行
- 命令1 || 命令2:与&&相反。如果“命令1”未执行成功,那么就执行“命令2”
$(( ... ))求值
使用 $(( ... )) 的方式对算术表达式求值。
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 的语法,有以下几点需要说明一下:
- case 语句以 case 作为开头,以 esac 作为结尾
- case 语句的每个模式行都是以双分号 ;; 结尾的
- 一个模式行可以合并匹配多个模式,使用 | 符作为分隔
- 一个模式行可以执行多条语句,各语句之间可以使用单分号 ; 隔开,这也是为什么每行的结尾要使用双分号 ;; 作为结束标志的原因
- case 语句支持使用正则表达式作为匹配项,这使得 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,退出循环并打印最后输入的那个值。
高级命令
输出重定向
- 标准输出重定向command >file:以覆盖的方式,把 command 的正确输出结果输出到 file 文件中command >>file:以追加的方式,把 command 的正确输出结果输出到 file 文件中
- 标准错误输出重定向command 2>file:以覆盖的方式,把 command 的错误信息输出到 file 文件中command 2>>file:以追加的方式,把 command 的错误信息输出到 file 文件中
- 正确输出和错误信息同时保存command >file 2>&1:以覆盖的方式,把正确输出和错误信息同时保存到同一个文件(file)中command >>file 2>&1:以追加的方式,把正确输出和错误信息同时保存到同一个文件(file)中command >file1 2>file2:以覆盖的方式,把正确输出结果输出到 file1 文件中,把错误信息输出到 file2 文件中command >>file1 2>>file2: 以追加的方式,把正确输出结果输出到 file1 文件中,把错误信息输出到 file2 文件中
示例:
#!/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,"\t","input:","\t",$5*1000*8,"bps","\n",$2,"\t","output:","\t",$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/.*(_.*)/bbs\1/')
# 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 "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
监控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