作为一名后端程序员,如果不节制根本的 Shell 脚本,那么运维编写的一些大略的脚本根本无法看懂,也不便于与运维进行沟通互换。
节制 Shell,可以帮助我们提高日常事情效率,比如快速构建支配项目、管理集群、监控做事器、定时清理日志文件或管理做事器等等。

概述

Shell 是由 C 措辞编写而成,外号俗称壳。
开拓者如果想操作 Linux 系统内核,必须通过 Shell 脚本进行交互,阐明和实行用户命令,不可以绕过 Shell 直接操作 Linux 内核。
Shell 是一门强大的编程措辞,随意马虎上手功能强大。

Shell 解析器

Linux 中有几种常见的解析器,后面的模板都是利用 Bash(最常用的解析器)解析器进行编写,查看当前系统支持哪些解析器:

php调用shell脚本十分钟带你学会 Shell 剧本 CSS

cat /etc/shells

查看当前系统利用的 Shell 解析器:

echo $SHELL

根本语法与实操案例Shell 变量

对付后台开拓者,系统环境变量一定不会陌生,这里不做过多赘述。
Shell 变量分为两种:系统变量、自定义变量。

系统变量

常见的系统变量如下:

变量名

阐明

$PWD

脚本实行确当前所在目录

$UID

当前操作的系统用户 ID

\$\$

当前操浸染户的 PID

$#

当前脚本的参数个数

$

当前脚本的所有参数

$0

当前实行程序的名称

$n

当出路序的第 N 个参数

$HOME

当出路序的 home 目录

$USER

查询当出路序利用的操浸染户

自定义变量

1. 变量命令规则

变量名必须因此字母或下划线字符“_”开头,后面字母、数字或下划线字符。
牢记不用利用分外符号,给自己带来不必要的麻烦。

2. 查看当前 Shell 所有的环境变量

3. 编写自定义变量

# 变量名=值如:A=1 等号两边不要有空格,如果值中间存在空格,请利用单引或者双引号:A='张 三'# 撤销变量unset A# 定义静态变量,静态变量不可以二次赋值,静态变量不可以 unset 撤销readonly B=2

4. 变量的浸染域

普通的变量浸染域为当前的实行程序,程序外部不可利用当前定义的变量。
通过 export 可以把变量升级为全局环境变量,这样当前系统所有程序都可以利用这个环境变量。

创建测试脚本:

touch test.sh

赋值实行权限:

chmod u+x test.sh

编写脚本:

vim test.sh

定义全局脚本(脚本内容如下):

export user_name="张三"

#!/bin/bashecho $user_name

5. 由于定义了全局变量,以是实行脚本可以正常输出 \$user_name 变量的值,反之脚本中定义的局部变量,其它脚本中不可以正常输出结果。

./test.sh运算符

运算符的种类大致可以分为(直接上代码示例)4 种。

算数运算符

#!/bin/bash a=10 b=20 # 加法 val=`expr $a + $b` echo "a + b : $val" # 减法 val=`expr $a - $b` echo "a - b : $val" # 乘法 val=`expr $a \ $b` echo "a b : $val" # 除法 val=`expr $b / $a` echo "b / a : $val" # 取余 val=`expr $b % $a` echo "b % a : $val" # 即是 if [ $a == $b ] then echo "a 即是 b" fi if [ $a != $b ] then echo "a 不即是 b" fi关系运算符

#!/bin/bash a=10 b=20 # 即是 if [ $a -eq $b ] then echo "$a -eq $b : a 即是 b" else echo "$a -eq $b: a 不即是 b" fi # 不即是 if [ $a -ne $b ] then echo "$a -ne $b: a 不即是 b" else echo "$a -ne $b : a 即是 b" fi # 大于 if [ $a -gt $b ] then echo "$a -gt $b: a 大于 b" else echo "$a -gt $b: a 不大于 b" fi # 小于 if [ $a -lt $b ] then echo "$a -lt $b: a 小于 b" else echo "$a -lt $b: a 不小于 b" fi # 大于即是 if [ $a -ge $b ] then echo "$a -ge $b: a 大于或即是 b" else echo "$a -ge $b: a 小于 b" fi # 小于即是 if [ $a -le $b ] then echo "$a -le $b: a 小于或即是 b" else echo "$a -le $b: a 大于 b" fi布尔运算符

#!/bin/bash a=10 b=20 # ! 非运算,跟 java 一样 if [ $a != $b ] then echo "$a != $b : a 不即是 b" else echo "$a == $b: a 即是 b" fi # 与运算,跟 java 里面的 && 一样 if [ $a -lt 100 -a $b -gt 15 ] then echo "$a 小于 100 且 $b 大于 15 : 返回 true" else echo "$a 小于 100 且 $b 大于 15 : 返回 false" fi # 或运算,与 java 里面的 || 同理 if [ $a -lt 100 -o $b -gt 100 ] then echo "$a 小于 100 或 $b 大于 100 : 返回 true" else echo "$a 小于 100 或 $b 大于 100 : 返回 false" fi if [ $a -lt 5 -o $b -gt 100 ] then echo "$a 小于 5 或 $b 大于 100 : 返回 true" else echo "$a 小于 5 或 $b 大于 100 : 返回 false" fi字符串运算符

#!/bin/bash a="abc" b="efg" # 判断字符串是否相等 if [ $a = $b ] then echo "$a = $b : a 即是 b" else echo "$a = $b: a 不即是 b" fi # 判断字符串不相等 if [ $a != $b ] then echo "$a != $b : a 不即是 b" else echo "$a != $b: a 即是 b" fi # -n 判断字符串长度是否不为 0 if [ -n "$a" ] then echo "-n $a : 字符串长度不为 0" else echo "-n $a : 字符串长度为 0" fi # 与 -n 相反 if [ -z $a ] then echo "-z $a : 字符串长度为 0" else echo "-z $a : 字符串长度不为 0" fi # $ 表示检讨字符串是否为空 if [ $a ] then echo "$a : 字符串不为空" else echo "$a : 字符串为空" fi流程掌握

if else 不再做先容,上述运算符案例中有大量利用,对付后端开拓及其大略,流程掌握在程序用利用非常频繁。

case 语法直接套用

末了的 ) 表示默认模式,相称于 Java 中的 default,;; 表示命令序列结束,相称于 Java 中的 break。

!/bin/bash case $1 in "1") echo "张三" ;; "2") echo "李四" ;; ) echo "王二" ;; esacfor 循环

案例:从 1 加到 100。

#!/bin/bash s=0 for((i=0;i<=100;i++)) do s=$[$s+$i] done echo $swhile 循环

案例:从 1 加到 100。

#!/bin/bash s=0 i=1 while [ $i -le 100 ] do s=$[$s+$i] i=$[$i+1] done # 输出值 echo $s函数

Shell 脚本和其它编程措辞类似,分为系统函数和自定义函数。

系统函数

1. basename 基本语法

basename 路径 后缀

功能描述:basename 命令会删掉所有的前缀包括末了一个(‘/’)字符,然后将字符串显示出来。

不加后缀:

加后缀:

如果脚本中须要获取当前路径的后缀名称:

2. dirname 基本语法

dirname 文件绝对路径

功能描述:从给定的包含绝对路径的文件名中去除文件名(非目录的部分),然后返回剩下的路径(目录的部分)。

自定义函数

1. 基本语法:

[ function ] funname[()]{ Action; [return int;]}

2. 履历技巧

必须在调用函数地方之前,先声明函数,Shell 脚本是逐走运行。
不会像其它措辞一样先编译。
函数返回值,只能通过 $? 系统变量得到,可以显示加 return 返回,如果不加,将以末了一条命令运行结果,作为返回值。
return 后跟数值 n(0~255)。

3. 案例实操

函数无返回值:打算两个输入参数的和。

脚本源码:

#!/bin/bash function sum() { s=0 s=$[ $1 + $2 ] echo "$s" } # read 读取掌握台的输入,n1, n2 用于吸收输入内容,-p:指定读取值时的提示符; -t:指定读取值时等待的韶光(秒) read -p "Please input the number1: " n1; read -p "Please input the number2: " n2; # 调用方法 sum $n1 $n2;

函数有返回值:打算两个输入参数的和(函数返回值,只能通过$?系统变量得到)。

#!/bin/bash function sum() { # read 读取掌握台的输入,n1, n2 用于吸收输入内容,-p:指定读取值时的提示符; -t:指定读取值时等待的韶光(秒) read -p "Please input the number1: " n1; read -p "Please input the number2: " n2; return $(($n1+$n2)) } # 调用方法 sum echo "打算两个数字之和为 $? !"常用的 Shell 工具

下面列举的几个命令非常实用,命令的详细利用方法请阅读:Linux 命令大全,非常主要且命令参数太多,这里不做过多赘述。

awk:非常强大的文本分析功能,开拓中利用非常频繁。
sort:对文件进行排序,并将标准结果显示输出。
sed:sed 是一种流编辑器,一次处理一行内容。
cut:紧张用于剪切字符、字节,并输出结果。
开箱即用的 Shell 脚本

请用 Shell 脚本写出查找当前文件夹(/home)下所有的文本文件内容中包含有字符“shen”的文件名称。

grep -r "shen" /home | cut -d ":" -f 1

判断用户输入的是否为 IP 地址:

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

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

#!/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,因此要10008 sar -n DEV 1 59|grep Average|grep ens33|awk '{print $2,"\t","input:","\t",$510008,"bps","\n",$2,"\t","output:","\t",$610008,"bps"}' echo "####################" #由于实行 sar 命令须要 59 秒,因此不须要 sleep done

打算文档每行涌现的数字个数,并打算全体文档的数字总数:

#!/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_nsum=$[$sum+$n_n] done echo "sum:$sum"

杀去世所有脚本:

#!/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 访问日志 404 情形:

#场景: #1.访问日志文件的路径:/data/log/access.log #2.脚本去世循环,每 10 秒检测一次,10 秒的日志条数为 300 条,涌现 404 的比例不低于 10%(30 条)则须要重启 php-fpm 做事 #3.重启命令为:/etc/init.d/php-fpm restart #!/bin/bash ########################################################### #监测 Nginx 访问日志 404 情形,并做相应动作 ########################################################### log=/data/log/access.log N=30 #设定阈值 while :do #查看访问日志的最新 300 条,并统计 404 的次数 err=`tail -n 300 $log |grep -c '404" '` if [ $err -ge $N ] then /etc/init.d/php-fpm restart 2> /dev/null #设定 60s 延迟防止脚本 bug 导致无限重启 php-fpm 做事 sleep 60 fi sleep 10 done

iptables 自动屏蔽访问网站频繁的 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 fidone

方法 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

Expect 实现 SSH 免交互实行命令:

登录脚本: # 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/userpassworddone

扫描主机端口状态:

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