首页 > 代码库 > Linux shell脚本编程详解及应用实例

Linux shell脚本编程详解及应用实例

什么是shell脚本?

1.shell脚本:是一种解释型语言,不需要提前进行编译,只需将代码转化成中间代码,边解释边运行,执行效率稍逊于编译型语言,跨平台性好.而编译型语言则需要提前进行编译转化为二进制文件,靠近底层硬件执行效率高,可移植性差.
2.shell的首行严格来说使用shebang机制:由#和!构成的字符序列,在类unix系统中程序的载入器将其后的内容,当做解释器的指令,并将载有shebang文件路径作为解释器的参数,且予以调用.

shell及其他解释型语言的一般格式?

 #!/bin/bash            #shell脚本的首行格式
 #!/usr/bin/python  #python脚本的格式
 #!/usr/bin/perl        #perl脚本的格式
 #Description:      #"#"开头的单个井字号是注释符,其后内容不当做脚本程序执行
 COMMAND            #脚本的编程内容部分,命令的堆砌,单一命令的组合完成复杂任务

怎样让脚本运行起来?

1.绝对路径(全路径)   #/usr/local/test.sh             需要执行权限
2.相对路径          #在脚本文件路径下执行 ./test.sh 需要执行权限
3.bash test.sh      #bash /path/to/test.sh        不需要执行权限
  bash -n 语法结构检查
  bash -x 调试模式运行,显示每一步的执行过程

shell中变量是怎样的?

变量 : 内存中的地址空间.

变量的类型:

1.强类型:类似于java,c#严格区分数值型和字符型,之间不能进行隐式转换,因此不同类型之间不可以直接计算,可进行手动显式转换
2.弱类型:变量定义时不需要指定类型也可计算,自动识别数据类型,但出于严谨尽量指明类型否则也可能出现不明错误

变量的命名规则?

1.不能使用命令或者是脚本语句结构中出现的关键字
2.不能数字开头,只能使用数字,字母,下划线
3.不包含特殊字符

变量相关概念?

本地变量(全局变量):仅对当前的shell有效,对其子shell无效的变量
环境变量 : 仅对当前shell及子shell有效(子shell继承父shell的环境变量)
局部变量(私有变量) : 当前shell脚本或代码的某个片段生效(比如函数),bash脚本在执行时,会开启一个新的shell执行完毕后回到父shell,其中定义的变量仅对子shell生效

怎样使得定义的变量生效?

1.~]# . /etc/profile        # 点后跟定义变量文件
2.~]# source /etc/profile   #直接在当前shell生效

什么是位置变量?

 用来描述参数位置的变量.
 $0:代表脚本本身的名字
 $1:脚本的第一个参数
 $2:脚本的第二个参数
 ...
 ${10}:脚本的第10个参数    #注:从第十个开始以后要加大括号
 $*:脚本所有参数,以一个整体字符串存储
 $@:脚本所有参数,以单个参数作为一个整体存储
 $#:脚本所有参数的总个数

参数位移:

shift n:每处理一个参数参数整体向左位移n个,处于最前面的参数($0不会变),位移后被抛弃,不做处理

如何定义一个变量?

1.declare -i        #定义整型
        -x      #定义环境
        -r      #只读变量
2.export variable=expression #定义环境变量

变量如何赋值?

left=right 将右边的值赋值给左边的变量存储
"":双引号,弱引用其内的字符,可进行变量替换及命令替换
‘‘:单引号,强引用其内的字符,不可进行变量替换及命令替换

变量如何引用?

1.$variable     #$后跟变量名
2.${variable}   #可加大括号

如何在脚本中调用命令的执行结果?

1.$(command)    #命令的执行结果可赋值给变量
2.`command`     #反引号同样是命令的执行结果

怎样得知系统定义了哪些变量?

1.set           #查看系统定义的所有变量
2.env/printenv  #查看定义的所有环境变量

该怎样取消一定定义好的变量?

1.unset variable#取消或者删除变量
  注:variable=   #变量赋值为空不等同于变量被取消

查看bashell的版本?

echo $BASH_VERSION

几个常见的环境变量:

MAIL:存储mail文件
MAILCHECK:隔多长时间巡检一次并发送mail
SHLVL:当前处于多少层shell
RANDOM:软件模拟实现随机数变量

shell如何进行算术运算?

shell 运算符:

 %  :  取余,取模  
 \* :  乘  
 ** :  乘方  
 +  :  加法  
 -  :  减法  
 += :  加等,在自身基础上加上第二个变量  
 -= :  减等,在自身基础上减去第二个变量  
 *= :  乘等,第一个变量乘第二个变量后再赋值给第一个变量  
 /= :  除等  
 %= :  取余等  
 i++:  先运算i的值后将i加1后赋值给i  
 ++i:  先将i的值加1后运算
shell 运算表达式:
1.let num=算术表达式     let sum=8+8
2.expr 8 + 8            expr 8 \* 8     #乘法需要转义符
3.var=$( expression )   sum=$( 8 + 8 )
4.var=$(( expression))  sum=$(( 8 + 8 ))
5.echo "8+8"|bc         回显一个算术表达式,通过管道传递给bc计算机
逻辑运算:
true    1   永真
false   0   永假
&&:与运算:全真为真
||:或运算:全假为假
! :非运算:非真即假
以命令的执行成功为真
以命令的执行失败为假

如何得知命令是否成功or失败?

$?:变量存储上一条命令的执行执行退出状态返回码

状态返回码:

0 :    返回为0,则为成功执行  
1-255: 为失败返回,失败返回可能退出的原因有很多所以需要不同的状态码标识  
127:   命令未找到,系统的默认设置  
130:   命令ctrl+c终止的状态返回码  
exit   num:自定义脚本执行退出状态返回码,可以加在脚本可能退出的位置,用于判断脚本退出原因

shell脚本中如何测试某些条件是否符合我的需求?

条件测试:

1.test expression   test 1 -eq 1
2.[ expression ]    [ 1 -eq 1 ] 一般条件测试
3.[[ expression ]]  [[ 1 -eq 1 ]] 高级条件测试,支持扩展的正则表达式

1.整数测试: 用字符符号来测试数字

-eq : 等于            用法:[[ 1 -eq 1 ]]
-gt : 大于
-lt : 小于
-ge : 大于等于
-le : 小于等于
-ne : 不等于

字符串测试: 用运算符号来测试字符

=~  : 左侧的字符是否能够被右侧的pattern匹配到,左侧的范围要大于等于右侧的条件
==  : 是否相同,可使用通配符及正则表达式
!=  : 是否不同,
\>  : 根据ascii码表顺序进行比较,与sort程序不同
\<  : 注意:大于小于要进行转义,否则shell可能会解释为重定向
-z  : 其后的字符串是否为空,比较的是字符串的长度
-n  : 其后的字符是否不为空,比较的是字符串的长度

文件测试: 针对文件的测试(单目测试),比如文件的类型等

-e  : 是否存在
-d  : 是否是目录
-O  : 是否是文件属主
-G  : 是否是文件的默认组与当前用户名相同
-d  : 目录
-e  : 存在(也可以用 -a)
-f  : 普通文件
-L  : 符号连接(也可以用 -h)
-p  : 命名管道
-r  : 可读
-s  : 非空
-S  : 套接字
-w  : 可写
-N  : 从上次读取之后已经做过修改

文件的比较测试:(双目测试)

-nt : 比较file1是否比file2新
-ot : 比较file1是否比file2旧
-ef : 比较file1是否和file2是否是同一文件

联合测试:将多个条件与,或,非的逻辑关系组合在一起

-a  : 与   优先级最末
-o  : 或   优先级次之
!   : 非   优先级最优

shell脚本中的条件判断语句?

1.if-then语句 适和一个条件产生两种不同的条件,或者判断一个条件是否符合需求,并执行相应的命令

    if-then语句一般格式:
        if  expression  ;then   #如果条件为真则执行statement1
            statement1
        else                    #否则执行statement2
            statement2
        fi                      #判断结束符

2.while语句 当条件成立时执行循环,当条件不成立时退出,并返回一个不为0的退出状态码(当型循环)

    (1).while语句的一般格式:

        while  [[ test expression ]];do #当条件成立时执行循环体1,当条件不成立时退出循环
            statement1
        done>> output.txt               #while语句循环结束符,可在其后重定向循环输出结果至文件

   (2).while语句的死循环格式:

        while  true;do                  #其中true为永真
            statement1
        done

   (3).while语句读取文件:

        while  read line;do             #通过read命令将文件的内容赋值给line变量存储
            statement1
        done<input.file                 #通过管道将文件传递入循环处理

3.until语句:与while刚好相反,当条件满足时退出,不满足执行.

        until [[ test expression ]];do  #条件不满足时执行循环体,满足即退出.适用于未知循环次数情况
            statement1
        done

4.ase语句:适合检测多个条件的情况

    case语句一般格式:
        case variable in                                #将某变量或参数传递给下面的语句
            pattern1 | pattern2) commands1;;            #参数是否符合条件1或2,执行相应命令
            pattern3) command2;;                        #如果不符合条件则继续判断下一个参数
            *)  command3;;                              #星号*捕获所有不匹配的值
        esac                                            #case语句的结束符

怎样去控制循环,在符合某一条件时就退出循环,或者符合某种情况时跳过这个参数去处理下一个参数?

   (1) break命令跳出(停止)循环

    1.跳出单个循环
        在for循环嵌套if-then语句满足条件则break
    2.跳出内部循环
        两个循环嵌套可用if-then语句跳出内部循环
    3.跳出外部循环
        在内部循环停止外部循环:
        break n   n指定跳出的层级数

   (2) continue命令:符合某种条件跳过符合条件的这个参数继续判断下一个参数,而不退出循环

    通过下面的例子很容易了解她的妙用:
        for i in `seq 1 20`;do                      #for循环传递1到20的数字
         if [[ $i -gt 5 ]] && [[ $i -lt 10 ]];then  #如果传递的数字大于5小于10则执行下一句
            continue                                #配合if语句,符合if的参数跳过循环体的处理
         fi
            echo $i                                 #显示送给循环体的参数
        done  
            注:如果是循环多层嵌套,可跳过多级循环:continue n  跳过n层

shell脚本编写的实例应用.怎样利用shell脚本解决实际问题?

   1.先简单了解脚本编写形式.编写脚本计算/etc/passwd 的20和10个用户的用户ID 的和(仅实现要求)?

    #!/bin/bash        #首行shebang机制
    #                  #注释符,其后的内容不做执行代码解释
    #从/etc/passwd文件中取出第10个用户uid并赋值给uid1
    uid1=`cat -n /etc/passwd|egrep -o [^" "].* |egrep -e "^10\>" -e "^20\>" | cut -d: -f3 | head -1`
    #从/etc/passwd文件中取出第20个用户uid并赋值给uid2
    uid2=`cat -n /etc/passwd|egrep -o [^" "].* |egrep -e "^10\>" -e "^20\>" | cut -d: -f3 | tail -1`
        let uidsum=$uid1+$uid2                          #将取得的两个值进行算术运算
            echo "user10 and user10 uidsum is $uidsum"  #执行完成打印回显一段话

   2.编写脚本计算以/etc/profile 和/etc/fstab作为参数,文件中的的空白行的总行数?

    #!/bin/bash
    if [ $# -eq 0 ];then        #如果执行脚本时没有添加参数则提示下面的回显信息
        echo -e "Usage:COMMAND ARG1 ARG2  \n      Ener your two ARGs,please!"
      else                          #否则执行下面的处理
        file1=` grep -c "^$" $1`    #取出参数1文件的所有空白行的总数赋值file1
        file2=`grep -c  "^$" $2`    #取出参数2文件的所有空白行的总数赋值file2
        sumspace=$(( $file1 + $file2 )) #将两个行数相加
        echo "/etc/profile and /etc/inittab sumspace is $sumspace"   #回显结果
    fi

   3.将lastb命令中的中ip取出写入/etc/hosts.deny需要后台运行(对于登录失败次数超过15次的恶意攻击封ip)?

    #!/bin/bash
    #
    #捕获当前正在登录用户ip地址,在这地方我用了两种方式,当做可信任用户,1.正在登录2.登陆过
    #allowip=`w -i  | egrep -o  "\<([0-2]?[0-9]{1,2}\.){3}[0-9]{1,3}\>" | uniq`
    #捕获已经成功登录过的用户IP地址
    allowip=`last -i | grep  -v  "0.0.0.0"|egrep  -o  "\<([0-2]?[0-9]{1,2}\.){3}[0-9]{1,3}\>"  | uniq` 
    for i in `echo $allowip|xargs -n1`;do   #循环比较成功过的用户ip地址是否在白名单中,不在则加入,注:添加黑名单时先确保自己在白名单之中(重要)
    if [[ -z `grep $i /etc/hosts.allow` ]];then#判断该ip地址白名单中是否存在
        echo "sshd: $i">>/etc/hosts.allow      #不存在则写入
    fi
    done
    #捕获所有的失败登录ip地址及次数
    count_all=`lastb -i -n 5000|egrep -v "\<tty[0-9]?\>"|egrep -o "\<([0-2]?[0-9]{1,2}\.){3}[0-9]{,3}"| uniq -c| sort -nr`
    #将捕获的失败ip及次数,传递给循环判断
    echo $count_all|xargs -n2 |  while read line;do
    #失败次数及ip分离
    loginfailcount=`echo $line | awk ‘{print $1}‘`
    ipaddress=`echo $line | awk ‘{print $2}‘`
    #判断失败的ip是否在已经登录过的ip列表中,存在则判断下一个失败ip,排除允许登录的用户产生的误操作
    if [[ $allowip =~ $ipaddress ]] ;then  
        continue
       elif [ $loginfailcount -gt 15 ] ;then    #判断失败次数的是否超过阀值
        egrep -q $ipaddress /etc/hosts.allow    #判断超过阀值的ip是否在白名单中添加过,是则判断下一个失败ip地址
       if   [ $? -eq 0 ] ;then #如果在白名单中
        continue               #跳过这个ip
       else
        echo "sshd: $ipaddress">/etc/hosts.deny #将超过阀值的失败登录ip加入黑名单
       fi
    fi
    done

   4.编写脚本实现,执行一个脚本带有不确定个数的参数,将这些参数以前面加序号的方式一一列举出?

    #!/bin/bash
    count=1                             #定义一个初始变量
    while [[ -n $1 ]];do                #判断第一个参数是否不为空,是则执行循环体
            echo  "$count = $1"         #回显序号=参数的形式
            count=$(( $count + 1 ))     #每执行一次循环,序号+1
            shift                       #每执行一次循环体将参数向前移动一个
    done

   5.给脚本传递一个或多个用户名作为参数,显示该用户是管理员还是系统用户或是普通用户(centos 7),并显示其默认shell?

    #!/bin/bash
    #
    #这里我用的是id命令作为判断依据,也可用/etc/passwd这个文件中的字段来判断
    #由于id命令的某些特性,需要考虑输入的参数为数字的情况,id会将数字当做uid来判断
    for i in $@;do                #将每个参数传递给变量i
    uid=`id -u $i 2>/dev/null`    #注意id -u <已存在用户的uid号,则返回该uid用户uid>,用户名转化为uid
    user=`id -un $i 2>/dev/null`  #将该uid转化为用户名
    getshell()                    #这里我用到了函数来获得用户的默认shell
    {
            #需要考虑两种情况
            #1.用户名和uid相同 
            #2.用户名和uid不同
        if [[ $uid -ne $user ]];then     #如果输入参数是数字,且和uid相同则回显用户名及默认shell
            echo  "user :$user shell is `cat /etc/passwd | egrep "^($user:)" | cut -d: -f 7 `"
        else                #否则输入的是数字但和uid不同,则证明该数字不是uid,而是以数字命名的用户
            echo  "user :$i shell is `cat /etc/passwd | egrep "^(${i}:)" | cut -d: -f 7 `"
        fi
    }
       if ! `id $i &>/dev/null` ;then       #判断这个用户是否存在,不存在则回显下面的提示
            echo -e "user :\033[31m$i\033[0m is not exist!"
       elif [ $uid -eq 0 ] ;then            #如果uid=0则回显该用户为管理员
            echo -e "user :\033[31m$i\033[0m is \033[32m$user\033[0m Administrator"
            getshell                        #这里直接调用moreshell的方法
       elif [ $uid -gt 0 -a $uid -lt 1000 ] ;then  #判断uid是否大于0小于1000是则为系统用户
            echo -e "user :\033[31m$i\033[0m is \033[32m$user\033[0m OS user"
            getshell                        #显示默认shell
       elif [ $uid -ge 1000 ] ;then         #大于1000则是普通用户
            echo -e "user :\033[31m$i\033[0m is \033[32m$user\033[0m Common user"
            getshell
       fi
    done

 

就先到这里了,有哪个地方错误请多多指教,希望linuxer在学习中遇到的一些坑多多拿来出交流,让探索这一领域的伙伴少走一些弯路! 

                  我的51cto博客:http://cityx.blog.51cto.com/

本文出自 “老城小叙” 博客,请务必保留此出处http://cityx.blog.51cto.com/9857477/1916354

Linux shell脚本编程详解及应用实例