• Shell Script


    Shell Script

    作者:Danbo     日期:2015-7-3
    什么是Shell:Shell是一个命令解析器,它在OS的最外层,负责直接与用户对话。
    shell在系统四大层次所处的位置:(从内向外)
    1.硬件
    2.Kernel
    3.Shell
    4.外围应用程序
    什么是Shell脚本:当命令或语句不在命令行执行,而是通过一个程序文件执行时,该程序就被称为Shell脚本或Shell程序。类似DOS下的批处理。通过命令、变量和流程控制语句等有机的结合起来,就形成了一个功能强大的Shell脚本。

    示例1::清空日志的三种方法:把所有命令堆积起来形成了脚本示例。
    echo " " >/var/log/messages
    >/var/log/messages
    cat /dev/null >/var/log/messages
    echo "Logs cleaned up"

    示例2:有判断的shell的脚本
    #!/bin/bash
    ROOT_UID=0
    LOG_DIR=/var/log
    if [ "$UID" -ne "ROOT_UID" ]; then
       echo "You must be root to run this script"
       exit 1
    fi

    cd $LOG_DIR || {
        echo "Cannot change to necessary directory." >&2
        exit 1
    }

    cat /dev/null > messages
    echo "Logs cleaned up."
    exit 0        #注意返回0表示成功,返回1表示失败。

    Shell脚本是弱类型语言,较为通用的shell有标准的sh和csh,其中sh已被bash shell取代了。Shell相较于php、python、perl的差别:shell的优势在于吃力OS底层的业务(有大量的系统命令做支撑,2000多个命令,比grep、awk、sed )。比如一键安装、报警脚本、shel开发快速。

    规范的shell脚本开头。
    #!/bin/bash
    #!又称为幻数,在执行bash及哦啊本的时候,内核会根据它来确定哪一程序来解析脚本中的内容。这一行必须在脚本的顶端的第一行,比如不是第一行则是注释。
    我们发现sh本质上是bash的软链接:

    [root@localhost ~]# which sh
    /bin/sh
    [root@localhost ~]# ll /bin/sh
    lrwxrwxrwx 1 root root 4 Jun 29 17:03 /bin/sh -> bash

    Shell脚本的执行
    当Shell脚本以非交互的方式运行时,它会先检查环境变量env,该便来个支出一个环境文件(.bashrc)后,从该变量环境变量文件开始执行,当读取了env文件后,Shell才开始执行Shell脚本的内容。

    Shell脚本的执行有3中方式
    ①.bash script-name 或 sh script-name  #当脚本本身没有x权限或脚本开始没有指定解析器。
    ②.path/script-name 或 ./script-name
    ③.source script-name 或 . script-name
    使用source或者"."的话,可以将子shell中的变量传递到父shell中
    例如:

    [root@localhost uestc]# echo 'userdir=`pwd`' >test.sh
    [root@localhost uestc]# cat test.sh
    userdir=`pwd`
    [root@localhost uestc]# sh test.sh
    [root@localhost uestc]# echo $userdir

    [root@localhost uestc]# . ./test.sh
    [root@localhost uestc]# echo $userdir
    /uestc

    这是为什么呢?我们当前执行脚本的窗口是一个shell(通过echo $$来查看当前窗口的shell)。而test.sh有处于另外一个shell中 。因此当我们执行sh test.sh后,虽然tesh.sh中已经定义了userdir,但是无法将其传递到父shell(当前窗口所处的shell)中来,而. /test.sh执行的方式就可以。

    变量

    变量可分为环境变量(全局变量)和局部变量(本地变量)。
    我们设置环境变量在用户家目录的.bash_profile文件中,或者/etc/bashrc,或者/etc/profile,或者/etc/profile环境变量可以在创建他们的shell和子shell,他们通常被称为全局变量以区别局部变量。通常环境变量应该大写。环境变量可以使用export内置命令或者declare -x导出为全局变量。取消本地变量命令:unset 变量名
    本地变量是在用户当前的shell生存期的脚本中使用:定义方式为:locate UESTC
    定义变量的时候:"" '' 不加引号区别
    不加引号:内容一般为简单连续的数字、字符串、路径名等
    单引号:输出变量时引号里面是什么就输出什么
    双引号:双引号中的变量会经过解析然后再输出
    注意再awk则相反:双引号原样输出,单引号解析后再输出

    [root@localhost uestc]# UESTC=123456
    [root@localhost uestc]# awk 'BEGIN{print "$UESTC"}'
    $UESTC
    [root@localhost uestc]# awk 'BEGIN{print '$UESTC'}'
    123456

    把命令定义为变量
    cmd=`date +%F`或者 cmd=$(date +%F)
    当我们对文件进行打包压缩备份时为显示什么时间备份的,我们可以将打包时间写到文件名当中
    tar -zcvf etc_$(date +%F)_backup.tar.gz /etc

    Shell特殊变量

    位置变量


    $0:获得当前执行Shell脚本的文件名,包括完整路路径;只取名字:basename $0;只取路径:dirname $0

    [root@localhost uestc]# cat sh.sh
    #!/bin/bash
    echo $0
    [root@localhost uestc]# cd scripts/
    [root@localhost scripts]# sh /uestc/sh.sh
    /uestc/sh.sh

    $n:获得当前指定行的Shell脚本第n个参数值,n=1,2,3,4....9当大于10的时候就需要括起来$(10);

    [root@localhost scripts]# cat 1.sh
    #!/bin/bash
    echo $1 $2 $3 $4 $5 $6 $7 $8 $9 $(10)
    [root@localhost scripts]# sh 1.sh 1 2 3 4  //即取后面的形式
    1 2 3 4

    $*:获得当前Shell的所有参数,并将所有命令行参数视为单个字符串
    $#:获得当前Shell命令行中参数的总个数
    $@:获得当前Shell的所有参数,每个参数还是独立的。
    我们通过实例来看一下其作用

    #!/bin/bash
    case "$1" in
           start)
                   start
                   ;;
            stop)
                    stop
                    ;;

             statue)

                     status portmap
                     ;;

              restart|reload)
                      restart

                      ;;
              condrestart)

                      [ -f /var/lock/subsys/portmap ] && restart || :
                      ;;
               *)
                      echo "Usage:$0{start|stop|status|restart|reload|condrestart}"
                      exit 1

    esac

    我们这里看一下$#的作用

    [root@localhost scripts]# set -- "I am " an uestc student
    [root@localhost scripts]# echo $#
    4

    我们这里看一下$*与$@的区别

    [root@localhost scripts]# for i in "$@";do echo $i; done
    I am
    an
    uestc
    student
    [root@localhost scripts]# for i in "$*";do echo $i; done
    I am an uestc student

    进程状态变量


    $$:获取当前shell的进程号
    $!:获得Shell最后运行后台的PID
    $?:或者执行上一个指令的返回值(0为成功;非零为失败)
    $_:在此之前执行命令或脚本的最后一个参数

    [root@localhost ~]# cat ba.sh
    #!/bin/bash
    cd /etc
    tar -zcvf service.tar.gz ./services >/dev/null 2>&1
    [ $? -eq 0 ] && echo Success || echo Failed

    我们这里总结一下$?返回值的意义:
    0 成功
    2 权限拒绝 
    126 找到命令但是无法执行
    127 未找打目标命令
    大于128 命令被系统强制结束

    另一个例子:
    #!/bin/bash
    echo $$ >/uestc/a.log
    while true 
    do 
        uptime > /dev/null 2>&1
        sleep 2
    done
    此时我们cat a.log此时我们就能看到当前脚本的PID

    [root@localhost ~]# sh ba1.sh &
    [1] 13038
    [root@localhost ~]# cat a.log
    13038

    Bash内部命令变量及shift实践讲解

    有些内部命令在目录列表是看不到的,可以通过man bash来查看,常见的有:echo、eval、exec、 export、read、shift、exit和点(.)
    shift语句按如下方式重新命令所有的位置参数变量,即$2成为$1;$3成为$2。在程序中每使用一次shift作用是所有的位置参数依次向左移动一个位置并且是位置参数$#减1,知道其值减到为0
    某些脚本加了一些选项再接参数。
    eval(evalargs)读入参数args并将他们组合成一个新的命令,然后执行。
    比如:eval command-line
    其中command-line是在终端上键入的一条普通命令行。然而当在它前面放上eval时,其结果是shell在执行命令行之前扫描它两次。如:
    pipe="|"
    eval ls $pipe wc -l
    shell第1次扫描命令行时,它替换出pipe的值|,接着eval使它再次扫描命令行,这时shell把|作为管道符号了。

    exec:当Shell执行到exec语句,不会去创建新的子进程,而是转去执行指定的命令,当指定的明林执行完时,该进程(也就是最初的Shell)就终止了,所以Shell程序中exec后面的语句将不再被执行。

    Shell变量的字串常用操作
    ${#string} #返$string的长度 //echo ${string} | wc -m,但是这种技术比#string计数多一个

    [root@localhost ~]# string=uestc
    [root@localhost ~]# echo ${#string}
    5
    [root@localhost ~]# echo $string | wc -m
    6

    ${string:position}:提取含有position关键字的字符串
    ${string:position:length}:从position之后开始提取长度为length的字串
    ${string#substring}:从变量string开头开始删除最短匹配substring字串
    ${string##substring}:从变量string开头开始删除最长匹配substring字串
    ${string%substring}:从变量string结尾开始删除最短匹配substring字串
    ${string%%substring}:从变量strig结尾开始删除最长匹配substring字串
    ${string/substring/replace}:使用$replace,来代替第一个匹配$substring字串
    ${string/#substring/replace}:如果string前缀匹配substring,就用replace来替代/substring

    比如我们批量改名:
    我们首先将要创建的名字写入到一个文件当中,比如:a.log
    然后使用:for f in `cat a.log`; do touch $f ; done创建文件
    我们需要将每个文件后面的finished去掉,该如何做?脚本如下:

    #!/bin/bash
    for f in `ls *.jpg`
    do
    mv $f `echo ${f%finished*}.jpg`
    done

    我们将后缀jpg改为JPG,我们先通过mv命令实现

    #!/bin/bash
    for f in `ls *.jpg`
    do
    mv $f `echo ${f/%jpg/JPG}`
    done

    通过sed命令也可以实现:

    #!/bin/bash
    for f in `ls *.JPG`
    do
    mv $f `echo $f | sed 's/JPG/jpg/g'`
    done

    通过rename是最简单的实现改名的方式:
    rename "finished" '' *  第二种:rename .jpg .JPG *
    其用法如下:
    rename from to file
    from:需要替换或需要处理的字符;
    to:把前面from替换为to;
    file:代替换的文件,可以用*处理所有文件。

    扩展其他变量

    ${value:-word}:如果变量名存在且非空,则返回变量的值。否则,word字符串用途:字符变量未定义,则返回默认值。 范例:${vlaue:-word},如果value未定义,则表达式的值为word。
    ${value:=word}如果变量名非空,则返回变量值。否则,设置这个变量值为word,并返回气值。用途,如果:如果变量未定义,则设置变量为默认值,并返回默认值 。范例:${value:=word},如果value为定义,则设置value值为word,返回表达式的值也为word。
    ${value:?message}:如果变量赋值的话正常替换,否则将消息message送到标准错误输出。
    实例:
    path1="/uestc"
    rm -rf ${path:-/tmp/}
    这样之后就比较保险了,即使目录不存在会删除tmp目录内的临时文件。
    在脚本执行之前,我们可以使用sh -x来对脚本进行调试。
    生产常见中我们可以看这两个系统脚本:
    /etc/init.d/httpd
    /etc/init.d/crond
    我们再学习脚本的时候可以多看一些系统脚本文件,可以给系统脚本写备注。

    变量长度相关

    三种计算字符串长度的方法:
    1.echo {#char}
    2.echo ${char} | wc -c   #这种计算方式会多计算一个换行字符" ",比实际多1。
    3.echo $(expr length "$char")

    程序执行效率问题

    1.time for i in $(seq 11111);do count=${#chars};done;

     

    real    0m0.743s
    user    0m0.727s
    sys     0m0.007s

     

    2.time for i in $(seq 11111);do count=`echo ${char} | wc -c`;done;
    real    0m28.207s
    user    0m6.423s
    sys     0m21.683s

     

    3.time for i in $(seq 11111);do count=`echo $(expr length "$chars")`;done;
    real    0m28.004s
    user    0m6.218s
    sys     0m21.150s

    我们通过time+要执行的命令发现运用bash内置的命令进行执行操作时效率最高。

    双括号(())运算的示例
    请你设计一个加、减、乘、除等功能的计算器。通过名列给你行的传参的方式
    #!/bin/bash
    echo $(($1$2$3))
    这个其实是通过把后面的表达式传递到前面的脚本中执行,就是简答的把3个参数排在一起就OK了

    Let变量的数值运算

    [root@localhost ~]# i=2
    [root@localhost ~]# let i=i+8
    [root@localhost ~]# echo $i
    10
    [root@localhost ~]# i=1
    [root@localhost ~]# i=i+1
    [root@localhost ~]# echo $i
    i+1

    提示:let i=i+8 等同于((i+8)),但是后者的效率更高。

    变量的数值运算与特殊应用expr命令

    expr命令一般用于整数值,但也可用于字符串,用来求表达式变量的值,同时expr也是一个手工命令行计算器。
    我们之前用户expr来计算字符串的长度:echo $(expr length "$char") //双引号可以不加
    1.expr也可以用在计算:
    expr 2 + 2
    expr 2 * 2
    注意运算符与数字之前都有空格。、

    2.expr在循环中可以用于增量计算。首先循环初始化为0,然后循环值加1,反引号的用法为命令替换。最基本的一种是从expr命令接受输入并将之放入循环变量。
    例如:给自变量i+1

    [root@localhost ~]# i=0
    [root@localhost ~]# i=`expr $i + 1`
    [root@localhost ~]# echo $i
    1

    3.expr $[$a+$b]表达式形式,其中$a$b可为整数值
    expr $[2+3]   #中括号数字与运算符之前可以不加空格
    5
    以上这种书写形式符号两边不需要加空格。expr将其后的串解释为表达式并计算其值,运算符前需要空格。

    4.其他特殊用法:此时我们注意到ssh-cpoy-id脚本
    if expr "$1" : ".*.pub";then   #匹配*.pub格式的文件如果是则为真。例如:
    expr "uestc.pub" : ".*.pub"
    9     #返回非0值为真,如果返回值为0则为假。我们注意到这个非0值其实是返回前面字符个数。

    5.为了更方面阅读我们在纪委再加上两个语句:expr "uestc.pub" : ".*.pub"  && echo MATCH || echo Not-MATCH
    通过expr判断变量是否为整数。
    #!/bin/bash
    read -p "Please input an integer:" a
    expr $a + 0 >&/dev/null
    [ $? -eq 0 ] && echo INIT||echo Chars
    这种判断上输入数字是否为整数的一种重要的方式

    6.通过expr计算字符串的长度
    expr length "$UESTC"

    变量的数值计算

    bc命令的用法
    1)、echo 3+4 | bc
    7

    2)、我们要计算1+2+3+...+10也可以通过管道接bc
    seq -s "+" 10 |bc
    55
    3)、保留小数点问题:通过命令scale=n

    [root@localhost ~]# echo "scale=3;4.35/2.11"|bc
    2.061

    4)、进制转换obase=n
    echo "obase=2; 8" | bc   #这里是将十进制的8转化为二进制。

    [root@localhost ~]# echo "obase=2;8" | bc
    1000

    5)、typeset -i A=1 B=3
    A=A+B
    echo $A
    4
    不过这种方式很少见

    6.$[]这种方式前面我们使用expr讲过了

    [root@localhost ~]# echo $[1+2]
    3

    不过我们还是推荐使用$(())这样的用法比较好

    Shell内置变量read的用法

    最常用的-p:prompt:设置提示信息
    -t timeout设置输入等待的时间,单位为秒
    read -t 10 -p "Please input two number:" a b #10s后不输入退出
    还有另外一种方法:
    echo -n "Please input two number:"
    read a b

    我们写个脚本对read读入的是不是数字进行判断

    #!/bin/bash
    while true
    do
    read -p "Please input two number:" a b
    expr $a + 0 >/dev/null
    [ $? -ne 0 ] && continue
    expr $b + 0 >/dev/null
    [ $? -ne 0 ] && continue || break
    done

    echo "a-b=$(($a-$b))"
    echo "a+b=$[$a+$b]"

    将上面改为命令行传参的方式并优化

    #!/bin/bash
    a="$1"
    b="$2"
    Usage(){
    echo "USAGE:sh $0 num1 num2"
    exit 1
    }

    if [ $# -ne 2 ];then
    Usage
    fi

    expr $1 + 0 >&/dev/null
    [ $? -ne 0 ] && Usage
    expr $2 + 0 >&/dev/null
    [ $? -ne 0 ] && Usage

    echo "a+b=$(($a+$b))"
    echo "a+b=$[$a+$b]"

    条件测试的多种方式

    在bash的各种流程控制结构中通常要进行各种测试,然后根据测试结果执行不同的操作,优势也会通过与if等条件语句相结合,使我们可以方便的完成判断。
    格式1:test <测试表达式>
    格式2:[ <测试表达式> ]
    格式3:[[ <测试表达式> ]]
    说明:格式1有格式2等价,格式3为扩展test命令

    常用的文件测试操作符号:
    -f 文件存在
    -d 目录存在
    -s 文件存在且非空
    -e 文件存在即为真
    -r 文件存在且可读
    -w 文件存在且可写
    -x 文件存在且可执行
    -L 文件存在且为链接
    f1 -nt f2 文件1比文件2更新
    f1 -ot f2 文件1比文件2更久

    我们可以通过查看/etc/init.d/nfs这个脚本来学习这些文件操作符的使用

    test -f file && echo true||echo false
    [ -f file ] && echo true|| echo false
    [[ -f file && -d folder ]] && echo true||echo false

    注意:&&或者||只能用在[]之间与[[]]之内。不过他们之间是可以相互转换的。
    并且-o -a 可以单[]中

    字符串测试操作符

    作用:比较两个字符串是否相同,字符串长度是否为零,是否为null
    -z "字符串"  #若串长度为0则真,-z可以立即为zero
    -n "字符串"  #若串的长度不会0则为真
    "串1"="串2"  #相等为真,此时也可以使用符号==代替=
    "串1"!="串2"  #不相等为真
    注意:以上字符串测试操作符号一定要用""引起来!!!!

    整数二元比较操作符

    注意我们看下面这个实例:

    [root@localhost ~]# [ 2>1 ] && echo OK||echo False
    False
    [root@localhost ~]# [ 2<1 ] && echo OK||echo False
    False
    为什么?我们必须注意当使用单中括号时必须使用转义字符:

    [root@localhost ~]# [ 2<1 ] && echo OK||echo False
    OK

    所以说单括号我们最好不要使用符号表示法:
    [ 2 -lt 1 ] && echo OK||echo False
    OK

    所以我们在[]中使用的比较  在(())和[[]]中
    -eq                                       ==
    -ne                                       !=
    -gt                                        >
    -ge                                       >=
    -lt                                          <
    -le                                         <=

    逻辑操作符
    在[]使用的逻辑操作符     在[[]]中使用的逻辑操作符
    -a                                       &&
    -o                                        ||
    !                                          !

    我们看一下一下示例
    [ -f "$UESTC" ] $$echo 1||echo 0  #这个是条件表达式的用法:返回1为真返回0为假,这点状态变量的$?不同
    file1=/etc/services; file2=/etc/rc.local
    echo $file1 $file2
    [ -f "file1" ] && echo 1||echo 0

    一般系统脚本中使用中会用到大量的判断语句
    [ -r /etc/ssysconfig/network ] && . /etc/sysconfig/network

    判断条件后执行多条命令语句,也即是我们刚开始经shell所用到的语句:
    [ 判断 ] || {命令1 命令2 命令3}
    其用法实例:cd $LOG_DIR||{echo "Cannot change to necessary directory" >&2 exit 1}
    对于上面的例子我们可以这样写:
    [ 3 -ne 3 ] ||{

           echo "I am a UESTC student"
           echo "I am a GOOD man"
           exit 1
    }

    如果要写在一行中,每个命令还需要分号结尾。
    脚本中编写实例:[ 3 -ne 3 ]||{echo "I am a UESTC Student";echo "LAAL";}
    命令行中我们可以用:[ 3 -ne 3 ]||(echo s="1";echo "2")
    还有一个例子:[ $ERROR -eq 0 ] && echo "jdk安装成功" || (echo "jdk安装失败,请检查" && exit 1)

    我们在nfs中找到相关的应用:

    [ -n "$RPCNFSDARGS" -a "$NFSD_MODULE" !="noload" ] && { 
        [ -x /sbin/modprobe ] && /sbin/modprobe nfsd
    }
    其中空格的使用有两个注:符号两边需要有空格,中括号内需要有空格
    字符串测试bind系统启动脚本举例:named
    if [ -n "$ROOTDIR" ]; then
       ROOTDIR=`echo $ROOTDIR | sed 's#//*#/#g; s#/$##'` ;
       rdl=`/usr/bin/readlink $ROOTDIR`;
       if [ -n "$rdl" ]; then
          ROOTDIR="rdl";
       fi
    fi
    举例:
    a1=10;a2=13
    [ $a1 -eq $a2 ] && echo 1||echo 0  #注意这里$a1 $a2虽然是数字,但是我们也可以把其当做字符串来处理。
    0
    利用Shell制作单级及多级菜单
    显示单号我们用echo 显示多行我们用cat
    #!/bin/bash
    cat <<END
             1.[INSTALL LAMP]
             2.[INSTALL LANP]
             3.[INSTALL NFS]
             4.[INSTALL RSYNC]
             Please input which you want to install:
    END
    read a
    [ $a -eq 1 ] && {
    cat <<END
              1.[INSTALL APACHE]
              2.[INSTALL MYSQL]
              3.[INSTALL PHP]
              4.[Back]
    END
    }
    read "a1"
    [ $a1 -eq 1 ] && {
    echo "You want to install: Apache"
    }
    [ $a1 -eq 2 ] && {
    echo "You want to install: MYSQL"
    }
    [ $a1 -eq 3 ] && {
    echo "You want to install: PHP"
    }
    这样就实现了多级显示的功能。

    if语句

    单分支结构
    if [条件]; then
       command
    fi
    双分支结构

  • 相关阅读:
    spring中Bean的懒加载
    ActiveMQ学习教程
    Maven中解决jar包冲突的三种方式
    Spring的日志管理
    mybatis使用collection查询集合属性规则
    springcloud之Feign伪装
    springcloud之Hystrix熔断入门
    springcloud的负载均衡Ribbon入门
    springcloud的Eureka启动时报错:Connection refused: connect
    springboot启动过程中出现You must configure either the server or JDBC driver (via the serverTimezone configuration
  • 原文地址:https://www.cnblogs.com/danbo/p/4618855.html
Copyright © 2020-2023  润新知