十分钟带你学会 Shell 脚本

小七学习网,助您升职加薪,遇问题可联系:客服微信【1601371900】 备注:来自网站

作为一名后端开发者, 需要经常操作 Linux 服务器。面对日常的一些运维部署工作,如果不会编写 Shell 脚本,如何做到快速完成任务?如何比普通开发者更牛叉呢?跟着我,十分钟带你轻松学会 Shel…

作为一名后端开发者, 需要经常操作 Linux 服务器。面对日常的一些运维部署工作,如果不会编写 Shell 脚本,如何做到快速完成任务?如何比普通开发者更牛叉呢?跟着我,十分钟带你轻松学会 Shell 脚本,超实用的 Shell 模板,工作中直接套用即可,就是这么简单,赶紧行动吧!

你会学习如下内容:

  • Shell 脚本的基本概念以及常用语法
  • 常用的 Shell 工具(重点)
  • 超实用!开箱即用的 Shell 脚本,拿好了


前言

本篇文章没有太多的理论知识,主要分为基础语法案例、常用工具型命令(重点:帮助我们完成复杂需求)、工作中常见的需求(实战案例有源码,工作中可以直接套用),建议花十分钟阅读一遍收藏即可,当工作中需要编写 Shell 脚本直接套用案例中的脚本模板,足可满足后端开发的大部分需求。

作为一名后端程序员,如果不掌握基础的 Shell 脚本,那么运维编写的一些简单的脚本根本无法看懂,也不便于与运维进行沟通交流。掌握 Shell,可以帮助我们提高日常工作效率,比如快速构建部署项目、管理集群、监控服务器、定时清理日志文件或管理服务器等等。

概述

Shell 是由 C 语言编写而成,外号俗称壳。开发者如果想操作 Linux 系统内核,必须通过 Shell 脚本进行交互,解释和执行用户命令,不可以绕过 Shell 直接操作 Linux 内核。Shell 是一门强大的编程语言,容易上手功能强大。

在这里插入图片描述

Shell 解析器

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

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 \"王二\"    ;;    esac
for 循环

案例:从 1 加到 100。

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

案例:从 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,因此要*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        #########################################################        #计算文档每行出现的数字个数,并计算整个文档的数字总数        ########################################################        #使用 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

小七学习网,助您升职加薪,遇问题可联系:客服微信【1601371900】 备注:来自网站

免责声明: 1、本站信息来自网络,版权争议与本站无关 2、本站所有主题由该帖子作者发表,该帖子作者与本站享有帖子相关版权 3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和本站的同意 4、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责 5、用户所发布的一切软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。 6、您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 7、请支持正版软件、得到更好的正版服务。 8、如有侵权请立即告知本站(邮箱:1099252741@qq.com,备用微信:1099252741),本站将及时予与删除 9、本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章和视频仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。