7.SHELL脚本编程

1. shell脚本的基本结构

1.1 shell脚本的格式

  • 指明解释器类型

    通常被称为shebang,常见的有sh、bash、zsh,如#!/bin/bash指明此脚本使用/bin/bash来解释执行

    格式:#!加解释器的路径

    #!/bin/bash
    #!/usr/bin/python
    #!/usr/bin/perl
    
  • 注释

    单行注释以#开头,多行注释以:<<!开头以!结尾

    在用户家目录新建.vimrc文件,自定义shell脚本模板vim ~/.vimrc

    #设置tab键长度等于4个空格 set expandtab
    #将tab键替换为4个空格 set tabstop=4
    set et
    set ts=4
    autocmd BufNewFile *.sh exec ":call SetTitle()"
    func SetTitle()
        if expand("%:e") == 'sh'
        call setline(1,"#!/bin/bash")
        call setline(2,"")
        call setline(3,"#********************************************************************")
        call setline(4,"#FileName:     ".expand("%"))
        call setline(5,"#Author:       John")
        call setline(6,"#Date:         ".strftime("%F %T"))
        call setline(7,"#Description:  The test script")
        call setline(8,"#********************************************************************")
        endif
    endfunc
    

1.2 shell脚本的调试

  • 语法错误调试

    使用bash -n调试,该命令不会真正执行shell脚本,只是输出语法错误

  • 逻辑错误调试

    使用basn -x调试,会逐条执行shell脚本,并输出,该命令会真正执行脚本

  • 命令拼写错误

    脚本中的命令拼写有误,也可以使用bash -x调试

2. 变量

2.1 变量介绍

  • 变量分类

    内置变量:

    用户自定义变量:

  • 命名规则

    变量命名:字符数字下划线(数字不开头),常用大写字母表示,如VAR_NAME

    函数命名:函数名一般用小写,如check_network()

2.2 变量赋值

  1. 格式:NAME=VALUE等号左右不能有空格
  • 直接字符串:VALUE是字符串,单引号、双引号、不加引号都可以,没有区别

    NAME=root NAME='root' NAME="root"

  • 变量引用:VALUE是引用其他变量的值,双引号、不加引号都可以

    NAME=$USER NAME="$USER"

  • 命令引用:VALUE是其他命令的输出结果,反引号、$()都可以

    ``NAME=COMMAND``` NAME=$(COMMAND)`

  1. 范例:使用变量引用赋值时,改变其中一个变量的值后,另一个保持不变

    [root@centos ~]# NAME=John
    [root@centos ~]# VAR_NAME=$NAME
    [root@centos ~]# echo $NAME $VAR_NAME
    John John
    [root@centos ~]# NAME=Dana
    [root@centos ~]# echo $NAME $VAR_NAME
    Dana John
    
    [root@centos ~]# NAME=John
    [root@centos ~]# VAR_NAME=$NAME
    [root@centos ~]# echo $NAME $VAR_NAME
    John John
    [root@centos ~]# VAR_NAME=Allen
    [root@centos ~]# echo $NAME $VAR_NAME
    John Allen
    
  2. 范例:不加引号单行输出,双引号多行输出

    [root@centos ~]# seq 3
    1
    2
    3
    [root@centos ~]# NUM=`seq 3`
    
    [root@centos ~]# echo $NUM 
    1 2 3
    [root@centos ~]# echo "$NUM" 
    1
    2
    3
    

2.3 变量查找与删除

  • 查找:set显示所有变量

    [root@centos ~]# set |grep NAME
    HOSTNAME=centos
    LOGNAME=root
    NAME=John
    VAR_NAME=Allen
    
  • 删除:unset NAME1 NAME2 NAME3删除指定变量

    [root@centos ~]# unset VAR_NAME
    

2.4 普通变量

普通变量仅在当前进程中有效,即定义在命令行只在当前终端有效,定义在脚本中只在当前脚本有效

2.5 环境变量

定义在父进程中的环境变量,子进程可以使用

  • 环境变量声明和赋值

    # 方法一:常用
    export name=VALUE
    # 方法二:不常用
    declare -x name=VALUE
    
  • 环境变量的引用

    $NAME不支持拼接下划线,${NAME}支持拼接下划线

    #!/bin/bash
    NAME1=value
    NAME2=new
    #不拼接下划线,输出valuenew
    echo $NAME1$NAME2
    #拼接下划线,输出value_new
    echo ${NAME1}_$NAME2
    
    #错误拼接,只输出new,因为找不到叫NAME1_的变量名
    #echo $NAME1_$NAME2
    
  • 显示所有环境变量

    envprintenvexportdeclare -x 这四个命令都可以

    [root@centos ~]# env
    HOSTNAME=centos
    SHELL=/bin/bash
    SSH_CLIENT=172.32.115.1 4946 22
    USER=root
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
    ..
    
  • 删除变量

    [root@centos ~]# unset VAR_NAME
    
  • 范例:子进程使用父进程中定义的环境变量

    vim main_proc.sh父进程中定义环境变量,并调用子进程脚本

    #!/bin/bash
    export MAIN_VAR="John"
    echo "main_proc pid is $BASHPID , MAIN_VAR is $MAIN_VAR"
    ./sub_proc.sh
    

    vim sub_proc.sh子进程中使用父进程中定义的环境变量

    #!/bin/bash
    SUB_VAR=$MAIN_VAR
    echo "sub_proc pid is $BASHPID , SUB_VAR is '$SUB_VAR' and ppid is $PPID"
    

    脚本执行结果

    [root@centos ~]# ./main_proc.sh 
    main_proc pid is 1186 , MAIN_VAR is John
    sub_proc pid is 1187 , SUB_VAR is 'John' and ppid is 1186
    
  • 范例:显示系统信息脚本

    vim system_info.sh

    #!/bin/bash
    GREEN="\033[1;32m"
    BLUE="\033[1;33m"
    RED="\033[1;31m"
    END="\033[0m"
    echo -e "$GREEN-----------------Host systeminfo---------------$END"
    echo -e "${BLUE}HOSTNAME:  $RED`hostname`$END"
    echo -e "${BLUE}IPADDR:    $RED` ifconfig enp0s8|grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' |head -n1`$END"
    echo -e "${BLUE}OSVERSION: $RED`cat /etc/redhat-release`$END"
    echo -e "${BLUE}KERNEL:    $RED`uname -r`$END"
    echo -e "${BLUE}CPU:       $RED`lscpu|grep 'Model name'|tr -s ' '|cut -d ' ' -f 3-8`$END"
    echo -e "${BLUE}MEMORY:    $RED`free -h|grep Mem|tr -s ' ' : |cut -d : -f2`$END"
    echo -e "${BLUE}DISK:      $RED`lsblk |grep '^sd' |tr -s ' ' |cut -d " " -f4`$END"
    echo -e "$GREEN-----------------------------------------------$END"
    
  • 范例:计算文本中所有人员的年龄总和

    vim file.txt

    num1=20
    num2=18
    num3=33
    

    vim add_age.sh

    #!/bin/bash
    VAR=`cut -d "=" -f 2 file.txt |tr '\n' '+'|grep -Eo ".*[0-9]+"`
    RES=$[VAR]
    echo $RES
    
    #练习的脚本
    #a=`cat file.txt |cut -d "=" -f 2 |head -1`
    #b=`cat file.txt |cut -d "=" -f 2 |head -2|tail -1`
    #c=`cat file.txt |cut -d "=" -f 2 |tail -1`
    #echo $a $b $c
    #res=$[a+b+c]
    #echo $res;
    

2.6 只读变量

只能声明定义,但后续不能修改和删除,当前进程有效(命令行定义退出终端失效、脚本中定义的脚本执行结束后失效)

  • 声明只读变量

    #方式一
    readonly NAME
    #方式二
    declare -r NAME
    
  • 查看只读变量

    #方式一
    readonly [-p]
    #方式二
    declare -r
    

2.7 位置变量

在bash shell中内置的变量,执行脚本时传入的参数sh test.sh arg1 arg2,可以理解为调用有入参的方法

  • 参数含义

    $1, $2, ... 对应第1个、第2个等参数,shift [n]换位置
    $0 当前执行脚本的名称,包括路径
    $* 传递给脚本的所有参数,全部参数合为一个字符串
    $@ 传递给脚本的所有参数,每个参数为独立字符串
    $# 传递给脚本的参数的个数
    注意:$@ $* 只在被双引号包起来的时候才会有差异
    
  • 清空所有位置变量

    set --
    
  • 范例:各个参数演示案例./position.sh a b c

    #!/bin/bash
    echo "\$1=$1,\$2=$2,\$3='$3'"
    #引用变量时$11表示第一个入参拼接字符1,${11}表示第11个入参
    echo "\$11='$11',\$11='${11}'"
    #统计入参的个数$#
    echo "参数个数$#"
    
  • 范例:移动文件vim mv.sh sh mv.sh a.txt b.txt

    #!/bin/bash
    mv $* /tmp/
    
  • 范例:"$*"作为一个字符串、$*``$@作为单个字符串

    #!/bin/bash
    # vim num.sh
    echo "arg num is $#"  
    
    # vim test.sh
    #   "$*"作为整体,入参个数为1
    ./num.sh "$*"
    #   $*作为个体,入参个数为3
    ./num.sh $*
    
    #   $@作为个体,入参个数为3
    ./num.sh "$@"
    
    
    #脚本执行命令 sh test.sh a b c
    

2.8 退出状态码变量

状态码:保存在$? ,上一条命令的执行结果,

  • 0 代表成功

  • 1到255 代表失败

  • 范例:

    [root@centos ~]# curl www.baidu.com &> /dev/null 
    [root@centos ~]# echo $?
    

    也可以指定脚本中的退出状态码

    #指定退出状态码为37,使用echo $?的输出结果为37
    exit 37
    

2.9 脚本安全和set

  • 变量$-:当前shell的默认配置,通过查看

    [root@centos ~]# echo $-
    himBH
    
    
    h:hashall,打开选项后,Shell 会将命令所在的路径hash下来,避免每次都要查询。通过set +h将h选项关闭
    i:interactive-comments,包含这个选项说明当前的 shell 是一个交互式的 shell。所谓的交互式shell,在脚本中,i选项是关闭的
    m:monitor,打开监控模式,就可以通过Job control来控制进程的停止、继续,后台或者前台执行等
    B:braceexpand,大括号扩展
    H:history,H选项打开,可以展开历史列表中的命令,可以通过!感叹号来完成,例如“!!”返回上最近的一个历史命令,“!n”返回第 n 个历史命令
    
  • set命令:脚本相关配置

    该配置只在当前进程有效,命令行是当前终端,子进程无效

    #查看配置 set -o
    [root@centos ~]# set -o
    allexport          off
    braceexpand        on
    emacs              on
    errexit            off
    errtrace           off
    

    配置在脚本中只对当前脚本有效,脚本执行结束后失效

    #!/bin/bash
    #脚本中遇到一条错误,立即结束脚本,后续命令不再执行
    set -e
    #禁止使用未定义的变量,不设置时可以使用未定义遍历,未定义变量的值为空(如 $UNVAR/* 被视为/*)
    set -o
    
    # -o option 显示,打开或者关闭选项(打开选项:set -o 选项;关闭选项:set +o 选项)
    # -x 当执行命令时,打印命令及其参数,类似 bash -x
    

2.10 格式化输出

  • 格式:printf "格式" "文本1" "文本2"

  • 常见格式

    %s 字符串
    %f 浮点格式
    %b 相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会转义
    %c ASCII字符,即显示对应参数的第一个字符
    %d %i 十进制整数
    %o 八进制值
    %u 不带正负号的十进制值
    %x 十六进制值(a-f)
    %X 十六进制值(A-F)
    %% 表示%本身
    
    
  • 常见转移字符

    \a 警告字符,通常为ASCII的BEL字符
    \b 后退
    \f 换页
    \n 换行
    \r 回车
    \t 水平制表符
    \v 垂直制表符
    \  表示\本身
    
    
  • 范例

    [root@centos ~]# printf "%s\n" 1 2 3 4
    1
    2
    3
    4
    [root@centos ~]# printf "%f\n" 1 2 3 4
    1.000000
    2.000000
    3.000000
    4.000000
    
    
    #.2f 表示保留两位小数
    [root@centos ~]# printf "%.2f\n" 1 2 3 4
    1.00
    2.00
    3.00
    4.00
    [root@centos ~]# printf "(%s)" 1 2 3 4;echo ""
    (1)(2)(3)(4)
    [root@centos ~]# printf " (%s) " 1 2 3 4;echo ""
     (1)  (2)  (3)  (4) 
    [root@centos ~]# printf "%s %s\n" 1 2 3 4
    1 2
    3 4
    [root@centos ~]# printf "%s %s %s\n" 1 2 3 4
    1 2 3
    4  
    
    #%-10s 表示宽度10个字符,左对齐
    [root@centos ~]# printf "%-10s %-10s %-4s %s \n" 姓名 性别 年龄 体重 小明 男 20 70 小红 女 18 50
    姓名     性别     年龄 体重 
    小明     男        20   70 
    小红     女        18   50 
    
    #将十进制的17转换成16进制数
    [root@centos ~]# printf "%X" 17
    11[root@centos ~]# 
    
    #将十六进制C转换成十进制
    [root@centos ~]# printf "%d\n" 0xC
    12
    
    #加颜色输出
    [root@centos ~]# VAR="welcome to world";printf "\033[31m%s\033[0m\n" $VAR
    welcome
    to
    world
    [root@centos ~]# VAR="welcome to world";printf "\033[31m%s\033[0m\n" "$VAR"
    welcome to world
    
    

3. 运算符与条件判断

3.1 算术运算符

  • bash中算术运算需要特定的格式,否则就当成了字符串进行运算

    #常用的3种格式
    var=$[算术表达式]
    let var=算术表达式
    var=$((算术表达式))
    #不常用,计算乘法需要转义
    var=$(expr arg1 arg2 arg3 ...)
    
    
    [root@centos ~]# var=$[2 * 3];echo $var
    6
    [root@centos ~]# var=$[2*3];echo $var
    6
    [root@centos ~]# var=$(expr 2 \* 3);echo $var
    6
    
  • bash中的算术运算只支持整数运算

    [root@centos ~]# echo $[5/2]
    2
    
  • 内置随机变量$RANDOM取值范围:0-32767

    #$RANDOM%n  生成0到n-1的随机数是###
    #生成0-4的随机数
    [root@centos ~]# echo $[$RANDOM%5]
    
    #生成7到22的随机数
    [root@centos ~]# echo $[$RANDOM%16+7]
    

3.2 逻辑运算符

  • 真假:true``false 1``0

  • 与:&,意思 true&false为false(假)

  • 或:|,或者 1&0为1(真)

  • 非:!,不是!false为真

  • 异或:,将值用二进制表示,逐位对比,不同为真(01为真),相同为假(1^1为假)

    [root@centos ~]# echo $[12^8]
    4
    
    # 1100  12的二进制
    # 1000  8的二进制
    # -------------
    # 0100  4的二进制
    
  • 短路与、短路或:根据第一个条件的结果与或判断是否要执行第二个条件,判断是否短路

3.3 条件判断

判断条件表达式的返回结果是真是假,需要特定的格式

  • 常见格式

    #常用格式,[ E ]与表达式之间必须有空格
    [ EXPRESSION ]
    #格式,效果等同于[  ]
    test EXPRESSION
    #不常用,当表达式需要使用通配符或扩展正则时才使用 [[ "$VAR" == 通配符 ]]  [[ "$VAR" =~ 扩展正则表达式 ]]
    [[ EXPRESSION ]]
    
  • 变量表达式判断

    [ -v VARNAME ]:判断变量名为VARNAME的变量是否声明(即set命令查看是否有该变量)

    #未声明,结果为假
    [root@centos ~]# [ -v VARNAME ]
    [root@centos ~]# echo $?
    1
    #已声明,结果为真
    [root@centos ~]# VARNAME=
    [root@centos ~]# [ -v VARNAME ]
    [root@centos ~]# echo $?
    0
    
  • 数值表达式判断

    格式[ $arg1 OP $arg2 ],OP是操作符([ $arg1 -ne $arg2 ]),常见如下

    -eq 等于 equal
    -ne 不等于 not-equal
    
    -gt 大于 greater-than
    -ge 大于等于 greater-than-or-equal
    
    -lt 小于 less-than
    -le 小于等于 less-than-or-equal
    

    注意:[ ]前后有空格,切arg前面的$必须有

    [root@centos ~]# a=10;b=35
    [root@centos ~]# [ $a -gt $b ];echo $?
    1
    [root@centos ~]# [ $a -lt $b ];echo $?
    0
    #变量名前面必须有$,否则报错
    [root@centos ~]# [ a -ne b ]
    -bash: [: a: integer expression expected
    
  • 字符串表达式判断

    格式[ OP $STR ][ $STR1 OP $STR2 ] OP是操作符(如[ -n $STR ][ $STR1 = $STR2 ]),常见如下

    -z "STRING":是否为空 (未声明、声明未赋值、声明赋空值、声明赋空格)均为空

    -n "STRING":是否不空

    [root@centos ~]# [ -z $STR1 ];echo $?
    0
    [root@centos ~]# STR1=
    [root@centos ~]# [ -z $STR1 ];echo $?
    0
    [root@centos ~]# STR1=''
    [root@centos ~]# [ -z $STR1 ];echo $?
    0
    [root@centos ~]# STR1=' '
    [root@centos ~]# [ -z $STR1 ];echo $?
    0
    [root@centos ~]# STR1='abc'
    [root@centos ~]# [ -z $STR1 ];echo $?
    1
    

    =!=><: 注意,比较的是ascii码,切操作符两边要有空格(没空格是赋值的格式)

    [root@centos ~]# STR1="abc";STR2="ahc"
    [root@centos ~]# [ $STR1 = $STR2 ];echo $?
    1
    [root@centos ~]# STR1="abc";STR2="abc"
    [root@centos ~]# [ $STR1 = $STR2 ];echo $?
    0
    #不规范格式
    [root@centos ~]# [ $STR1=$STR2 ]
    
    #变量最好使用双引号,不然识别不了变量中的空格
    [root@centos ~]# STR="THIS IS STRING"
    [root@centos ~]# [ "$STR" ]
    [root@centos ~]# [ $STR ]
    -bash: [: IS: binary operator expected
    # 就相当于
    [root@centos ~]# [ "THIS IS STRING" ]
    [root@centos ~]# [ THIS IS STRING ]
    -bash: [: IS: binary operator expected
    
  • 通配符与扩展正则表达式判断[[ EXPR ]]

    [[ "$VAR" == 通配符表达式 ]][[ "$VAR" != 通配符表达式 ]]

    [root@centos ~]# FILE=test.log
    [root@centos ~]# [[ "$FILE" == *.log ]];echo $?
    0
    [root@centos ~]# [[ "$FILE" != *.log ]];echo $?
    1
    

    [[ "$VAR" =~ 扩展正则表达式 ]]

    [root@centos ~]# FILE=test.log
    [root@centos ~]# [[ "$FILE" =~ \.log$ ]];echo $?
    0
    [root@centos ~]# N=100
    [root@centos ~]# [[ "$N" =~ ^[0-9]+$ ]];echo $?
    0
    [root@centos ~]# N=AA100
    [root@centos ~]# [[ "$N" =~ ^[0-9]+$ ]];echo $?
    1
    
    #IP地址正则表达式:^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$
    [root@centos ~]# IP=192.68.7.2
    [root@centos ~]# [[ "$IP" =~ IP正则表达式 ]];echo $?
    0
    [root@centos ~]# IP=192.68.7.255
    [root@centos ~]# [[ "$IP" =~ IP正则表达式 ]];echo $?
    0
    [root@centos ~]# IP=192.68.7.256
    [root@centos ~]# [[ "$IP" =~ IP正则表达式 ]];echo $?
    1
    
  • 文件判断

    help test:查看所有判断参数含义

    	# 文件存在判断
          -a FILE        True if file exists.
          -e FILE        True if file exists.
          -s FILE        True if file exists and is not empty.
          
    	#文件类型判断
          -f FILE        True if file exists and is a regular file.
          -d FILE        True if file is a directory.
          -b FILE        True if file is block special.
          -c FILE        True if file is character special.
          -h FILE        True if file is a symbolic link.
          -L FILE        True if file is a symbolic link.
          -p FILE        True if file is a named pipe.
          -S FILE        True if file is a socket.
          -t FD          True if FD is opened on a terminal.
          
    	# 文件权限判断
          -r FILE        True if file is readable by you.
          -w FILE        True if the file is writable by you.
          -x FILE        True if the file is executable by you.
          -u FILE        True if the file is set-user-id.
          -g FILE        True if file is set-group-id.
          -k FILE        True if file has its `sticky' bit set.
          
          -O FILE        True if the file is effectively owned by you.
          -G FILE        True if the file is effectively owned by your group.
          -N FILE        True if the file has been modified since it was last read.
          
        # 字符串判断、变量声明判断
          -z "$STR"		 判断是否为空
          -n "$STR"		 非空判断
          -v VARNAME	 判断变量是否声明(不加$)
    
  • 组合使用参数

    [ EXPR1 -a EXPR2 ]-a 并且 (and),EXPR1 EXPR2均成立为真,如[ -n "$pid" -a -z "$force" ]

    [ EXPR1 -o EXPR2 ]-o 或者 (or),EXPR1 EXPR2有一个真即为真,如 [ -n "$pid" -o -z "$force" ]

    [ ! EXPR ]! 非,如[ ! -x /bin/cgexec ]

    COMMAND1 && COMMAND2&& 短路与,并且,如[ -z "$gotbase" ] && base=${1##*/}

    COMMAND1 || COMMAND2|| 短路或,或者

    ! COMMAND! 非,取反

  • 范例:ping主机连通性测试

    [root@centos ~]#cat ping.sh
    #!/bin/bash
    IP=172.32.115.1
    ping -c1 -W1 $IP &> /dev/null && echo "$IP is up" || { echo "$IP is
    unreachable"; exit; }
    echo "Script is finished"
    [root@centos ~]#sh ping.sh
    172.32.115.1 is up
    Script is finished
    
  • 范例:磁盘空间利用率监控

    [root@centos8 ~]#cat disk_check.sh
    #!/bin/bash
    WARNING=80
    SPACE_USED=`df|grep '^/dev/sd'|tr -s ' ' %|cut -d% -f5|sort -nr|head -1`
    [ "$SPACE_USED" -ge $WARNING ] && echo "disk used is $SPACE_USED,will be full" | mail -s diskwaring root
    
  • 范例:摘自系统脚本cat /etc/init.d/functions,查看各个参数的使用

    # 片段一
    if [ $PPID -ne 1 -a -z "$SYSTEMCTL_SKIP_REDIRECT" ] && \
            [ -d /run/systemd/system ] ; then
        case "$0" in
        /etc/init.d/*|/etc/rc.d/init.d/*)
            _use_systemctl=1
            ;;
        esac
    fi
    
    # 片段二
        # Save basename.
        [ -z "$gotbase" ] && base=${1##*/}
    
        # See if it's already running. Look *only* at the pid file.
        __pids_var_run "$base" "$pid_file"
    
        [ -n "$pid" -a -z "$force" ] && return
    
        # make sure it doesn't core dump anywhere unless requested
        corelimit="ulimit -S -c ${DAEMON_COREFILE_LIMIT:-0}"
    
        # if they set NICELEVEL in /etc/sysconfig/foo, honor it
        [ -n "${NICELEVEL:-}" ] && nice="nice -n $NICELEVEL"
    
        # if they set CGROUP_DAEMON in /etc/sysconfig/foo, honor it
        if [ -n "${CGROUP_DAEMON}" ]; then
            if [ ! -x /bin/cgexec ]; then
                echo -n "Cgroups not installed"; warning
                echo
            else
                cgroup="/bin/cgexec";
                for i in $CGROUP_DAEMON; do
                    cgroup="$cgroup -g $i";
                done
            fi
        fi
    

3.4 小括号和大括号

  • 查看帮助:man bash搜索\(list\)\{list;\}

           (list) list  is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below).  Variable
                  assignments and builtin commands that affect the shell's environment do  not  remain  in  effect
                  after the command completes.  The return status is the exit status of list.
    
           { list; }
                  list  is  simply executed in the current shell environment.  list must be terminated with a new‐
                  line or semicolon.  This is known as a group command.  The return status is the exit  status  of
                  list.   Note  that  unlike the metacharacters ( and ), { and } are reserved words and must occur
                  where a reserved word is permitted to be recognized.  Since they do not cause a word break, they
                  must be separated from list by whitespace or another shell metacharacter.
    
  • 小括号: (CMD;CMD;CDM)会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境

    [root@centos init.d]# echo $BASHPID
    944
    [root@centos init.d]# (echo $BASHPID)
    1125
    
    [root@centos init.d]# NAME=John;(echo $NAME;NAME=Sara;echo $NAME);echo $NAME
    John
    Sara
    John
    
    # umask临时生效,可以使用括号
    [root@centos init.d]# umask;(umask 555;umask);umask
    0022
    0555
    0022
    
  • 大括号:{ CMD;CMD;CMD; }不会启子shell, 在当前shell中运行,会影响当前shell环境

    [root@centos init.d]# echo $BASHPID
    944
    [root@centos init.d]# { echo $BASHPID; }
    944
    
    [root@centos init.d]# NAME=John;{ echo $NAME;NAME=Sara;echo $NAME; };echo $NAME
    John
    Sara
    Sara
    

3.5 键盘输入read

  • 格式:read [options] [name ...],常见options

    -p "hell,world"		指定提示语言
    -s 					静默输入,一般用于密码
    -n num 				指定输入的字符长度N
    -d ' '				指定任意结束字符,遇到该字符即停止输入
    -t num 				TIMEOUT为N秒
    
  • 范例:

    #不指定变量接收read输入,默认保存在REPLY变量中
    [root@centos ~]# read -p "what's your name:"
    what's your name:John
    [root@centos ~]# echo $REPLY 
    John
    
    #可以一次指定多个变量接收多个read输入,但是不建议使用多个
    [root@centos ~]# read -p "what's your name:" NAME1 NAME2 
    what's your name:Sara Jane
    [root@centos ~]# echo $NAME1 $NAME2
    Sara Jane
    
  • 范例:功能菜单 vim readtest.sh

    #!/bin/bash
    
    cat <<EOF
    
    ######################################################
    # 各函数功能说明如下:                               #
    # 1.check_network:	检查发布主机到其他主机的网络 #
    # 2.init_selinux:	关闭selinux		     #
    # 3.init_firewalld:	关闭防火墙		     #
    # 4.init_yum:		配置yum			     #
    # 5.init_hosts:	配置主机名与IP映射关系	     #
    ######################################################
    
    EOF
    
    read -p "please choose num:" MENU
    [ "$MENU" -eq 1 ] && echo "num is $MENU:check_network"
    [ "$MENU" -eq 2 ] && echo "num is $MENU:init_selinux"
    [ "$MENU" -eq 3 ] && echo "num is $MENU:init_firewalld"
    [ "$MENU" -eq 4 ] && echo "num is $MENU:init_yum"
    [ "$MENU" -eq 5 ] && echo "num is $MENU:init_hosts"
    
  • 范例:面试题考点,管道中的命令是运行在子进程中

    [root@centos ~]# echo "John" "Sara" | read NAME1 NAME2;echo "$NAME1 is a boy,$NAME2 is a girl."
     is a boy, is a girl.
    
    
    [root@centos ~]# echo "John" "Sara" | (read NAME1 NAME2;echo "$NAME1 is a boy,$NAME2 is a girl.")
    John is a boy,Sara is a girl.
    

3.6 bash的配置文件

  • 按作用范围分类

    全局配置:对所有登录用户生效

    /etc/profile/etc/profile.d/*.sh/etc/bashrc

    个人配置:对当前登录用户生效

    ~/.bash_profile~/.bashrc

  • 按登录方式分类

    交互式登录,即直接登录,配置文件执行顺序

    #当存在冲突的配置时,理论上靠后的文件会覆盖前面的
    /etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile --> ~/.bashrc -->/etc/bashrc
    

    非交互式登录,包括su命令执行脚本等任何可以开启子进程shell的命令,配置文件执行顺序

    #当存在冲突的配置时,理论上靠后的文件会覆盖前面的
    /etc/profile.d/*.sh --> /etc/bashrc -->~/.bashrc
    
  • 按功能分来

    profile类:定义环境变量、运行命令或脚本

    全局:/etc/profile, /etc/profile.d/*.sh
    个人:~/.bash_profile
    

    bashrc类:定义本地变量、函数、别名

    全局:/etc/bashrc
    个人:~/.bashrc
    
  • 配置生效命令

    #两种命令都可以,原理是source和.执行时都不会创建子进程,直接在当前shell中执行,所以会根据配置文件中的配置去更新shell环境,从而配置生效
    source /etc/bashrc
    . /etc/profile
    #也可以退出重新登录shell,因为登录时会加载配置文件,从而配置生效
    
  • 退出时配置文件

    #用户退出时,会执行该配置文件中的命令,默认为空,可以自定义一下操作
    ~/.bash_logout
    

4. 流程控制

4.1 条件判断if

  • 格式:包括单分支、双分支、多分支

    if COMMANDS; then
    	COMMANDS
    #elif COMMANDS; then 
    #	COMMANDS
    #... 
    else
    	COMMANDS
    fi
    
  • 范例:摘自系统脚本

    if [ -x /usr/bin/id ]; then
        if [ -z "$EUID" ]; then
            # ksh workaround
            EUID=`/usr/bin/id -u`
            UID=`/usr/bin/id -ru`
        fi
        USER="`/usr/bin/id -un`"
        LOGNAME=$USER
        MAIL="/var/spool/mail/$USER"
    fi
    
    # Path manipulation
    if [ "$EUID" = "0" ]; then
        pathmunge /usr/sbin
        pathmunge /usr/local/sbin
    else
        pathmunge /usr/local/sbin after
        pathmunge /usr/sbin after
    fi
    
  • 范例:vim if_bmi.sh

    #!/bin/bash
    
    read -p "请输入身高(m为单位): " HIGH
    if [[ ! "$HIGH" =~ ^[0-2].?[0-9]{,2}$ ]];then
    	echo "输入错误的身高"
    	exit 1
    fi
    
    read -p "请输入体重(kg为单位): " WEIGHT
    if [[ ! "$WEIGHT" =~ ^[0-9]{1,3}$ ]];then
    	echo "输入错误的体重"
    	exit 1
    fi
    
    BMI=`echo $WEIGHT/$HIGH^2|bc`
    if [ $BMI -le 18 ] ;then
    	echo "你太瘦了,多吃点"
    elif [ $BMI -lt 24 ] ;then
    	echo "身材很棒!"
    else
    	echo "你太胖了,注意节食,加强运动"
    fi
    

4.2 条件判断case

  • 格式

    # PATTERN通配符,$VARNAME支持拼接其他字符串 如 ":${PATH}:"
    case "$VARNAME" in
    PATTERN)
    	COMMANDS
    	;;
    PATTERN)
    	COMMANDS
    	;;
    *)
    	COMMANDS
    	;;
    esac
    
  • 支持统配符匹配PATTERN

    * 	任意长度任意字符
    ? 	任意单个字符
    []  指定范围内的任意单个字符
    | 	或,如 a或b
    
  • 范例

    #摘自系统脚本profile
    pathmunge () {
        case ":${PATH}:" in
            *:"$1":*)
                ;;
            *)
                if [ "$2" = "after" ] ; then
                    PATH=$PATH:$1
                else
                    PATH=$1:$PATH
                fi
        esac
    }
    
    #摘自系统脚本function
    # Evaluate shvar-style booleans
    is_true() {
        case "$1" in
        [tT] | [yY] | [yY][eE][sS] | [tT][rR][uU][eE] | 1)
            return 0
            ;;
        esac
        return 1
    }
    
    
    # A function to start a program.
    daemon() {
        # Test syntax.
        local gotbase= force= nicelevel corelimit
        local pid base= user= nice= bg= pid_file=
        local cgroup=
        nicelevel=0
        while [ "$1" != "${1##[-+]}" ]; do
            case $1 in
            '')
                echo $"$0: Usage: daemon [+/-nicelevel] {program}" "[arg1]..."
                return 1
                ;;
            --check)
                base=$2
                gotbase="yes"
                shift 2
                ;;
            --pidfile=?*)
                pid_file=${1#--pidfile=}
                shift
                ;;
            [-+][0-9]*)
                nice="nice -n $1"
                shift
                ;;
            *)
                echo $"$0: Usage: daemon [+/-nicelevel] {program}" "[arg1]..."
                return 1
                ;;
          esac
        done
    }
    
  • 范例:功能菜单 vim readtest.sh

    #!/bin/bash
    
    cat <<EOF
    
    ######################################################
    # 各函数功能说明如下:                               #
    # 1.check_network:	检查发布主机到其他主机的网络 #
    # 2.init_selinux:	关闭selinux		     #
    # 3.init_firewalld:	关闭防火墙		     #
    # 4.init_yum:		配置yum			     #
    # 5.init_hosts:	配置主机名与IP映射关系	     #
    ######################################################
    
    EOF
    
    read -p "please choose num:" MENU
    case "$MENU" in
    1)
    	echo "num is $MENU:check_network"
    	;;
    2)
    	echo "num is $MENU:init_selinux"
    	;;
    3)
    	echo "num is $MENU:init_firewalld"
    	;;
    4)
    	echo "num is $MENU:init_yum"
    	;;
    5)
    	echo "num is $MENU:init_hosts"
    	;;
    *)
    	echo "input is error"
    	;;
    esac
    
posted @ 2022-06-14 23:35  人间丶迷走  阅读(88)  评论(0编辑  收藏  举报