美文网首页Shell
shell脚本编程

shell脚本编程

作者: SRE1 | 来源:发表于2019-04-07 16:37 被阅读0次

    bash的配置文件

    用户登录系统后,会启动一个接口程序,也称shell程序,一般默认是bash,而这个程序的启动,需要读取配置文件,完成环境的配置,以便用户在该接口程序下方便的执行一系列操作

    配置文件的种类:

    根据配置文件中定义的内容,将配置文件分为两大类

    profile类:为交互式登录的shell进程提供配置

    用于定义环境变量,启动需要执行的命令或者脚本

    • /etc/profile: 全局设置

    • /etc/profile.d/*.sh

    • ~/.bash_profile: 个人设置(仅对当前用户有效)

    bashrc类:为非交互式登录的shell进程提供配置

    用于定义命令别名或本地变量

    • /etc/bashrc:全局设置
    • ~/.bashrc:个人设置

    配置文件的加载顺序

    交互式登录

    直接通过某终端输入账号和密码后登录打开的shell进程;
    使用su命令:su - USERNAME, 或者使用 su -l USERNAME执行的登录切换;

    顺序:/etc/profile-->/etc/profile.d/*.sh-->/.bash_profile-->~/.bashrc-->/etc/bashrc

    非交互式登录

    su USERNAME执行的登录切换;
    图形界面下打开的终端;
    运行脚本

    顺序:~/.bashrc-->/etc/bashrc-->/etc/profile.d/*.sh

    • 命令行中定义的特性,例如变量和别名作用域为当前shell进程的生命周期;
    • 配置文件定义的特性,只对随后新启动的shell进程有效;

    让通过配置文件定义的特性立即生效:
    (1) 通过命令行重复定义一次;
    (2) 让shell进程重读配置文件;

    ~]# source /PATH/FROM/CONF_FILE
    ~]# . /PATH/FROM/CONF_FILE


    shell脚本编程

    过程式编程,解释运行,依赖于外部程序文件运行

    编程语言的分类:根据运行方式

    • 编译运行:源代码 --> 编译器 (编译)--> 程序文件;
    • 解释运行:源代码 --> 运行时启动解释器,由解释器边解释边运行;

    根据其编程过程中功能的实现是调用库还是调用外部的程序文件:

    编程模型:过程式编程语言,面向对象的编程语言

    过程式:以指令为中心来组织代码,数据是服务于代码;

    • 顺序执行
    • 选择执行
    • 循环执行
      代表:C,bash

    对象式:以数据为中心来组织代码,围绕数据来组织指令;
    类(class):实例化对象,method;
    代表:Java, C++, Python

    数据类型:字符型,数值型
    弱类型:字符型

    shell脚本格式:

    脚本文件的第一行,顶格:给出shebang(解释器路径),用于指明解释执行当前脚本的解释器程序文件

    常见的解释器:
    #!/bin/bash
    #!/usr/bin/python
    #!/usr/bin/perl

    注意:代码需要加注释,如空白行,缩进,所有#开头的为注释行

    文本编程器:nano
    行编辑器:sed
    全屏幕编程器:nano, vi, vim

    shell脚本是命令的堆积,但很多命令不具有幂等性,需要用程序逻辑来判断运行条件是否满足,以避免其运行中发生错误;

    运行脚本:

    • 赋予执行权限,并直接运行此程序文件;
      chmod +x /PATH/TO/SCRIPT_FILE
      /PATH/TO/SCRIPT_FILE

    • 直接运行解释器,将脚本以命令行参数传递给解释器程序;
      bash /PATH/TO/SCRIPT_FILE

    注意:脚本中的空白行会被解释器忽略;
    脚本中,除了shebang,余下所有以#开头的行,都会被视作注释行而被忽略;此即为注释行;
    shell脚本的运行是通过运行一个子shell进程实现的;

    练习1:写一个脚本,实现如下功能;
    (1) 显示/etc目录下所有以大写p或小写p开头的文件或目录本身
    (2) 显示/var目录下的所有文件或目录本身,并将显示结果中的小写字母转换为大写后显示
    (3) 创建临时文件/tmp/myfile.XXXX

    #!/bin/bash
    ls -d /rtc/[pP]*
    ls -d /var/* | 'a-z' 'A-Z'
    mktemp /tmp/myfile.XXXX
    

    ctrl+o:保存
    ctrl+x:退出

    脚本的状态返回值:

    默认是脚本中执行的最后一条件命令的状态返回值

    [root@centos7 ~]# id usr3 &> /dev/null && exit 0 || useradd usr3
    logout
    There are stopped jobs.
    useradd: user 'usr3' already exists
    

    自定义状态退出状态码:
    exit [n]:n为自己指定的状态码;
    注意:shell进程遇到exit时,即会终止,因此,整个脚本执行即为结束;

    shell脚本的参数传递

    位置参数变量

    执行脚本和执行命令一样,在脚本后加上多个参数,用空格隔开即可,例如myscript.sh argu1 argu2

    引用方式:

    \$1, \$2, ..., \${10}, ${11}, ...

    例如:

    ~]#vim sum.sh
    #!/bin/bash
    #
    echo $[$1+$2]
    
    轮替:

    shift [n]:位置参数轮替;

    ~]#vim shift.sh
    #!/bin/bash
    #
    echo "First pos argu: $1"
    shift
    echo "Second pos argu: $1"
    ~]# bash shift.sh one two
    
    
    ~]#vim shift.sh
    #!/bin/bash
    #
    echo "First and second pos argu: $1, $2"
    shift 2
    echo "Third pos argu: $1"
    ~]# bash shift.sh one two three              
    

    练习:写一脚本,通过命令传递两个文本文件路径给脚本,计算其空白行数之和

    ~]# vim lines.sh
       #!/bin/bash
       #
       file1_lines=$(grep "^$" $1 | wc -l)
       file2_lines=$(grep "^$" $2 | wc -l)
    
       echo "Total blank lines: $[$file1_lines+$file2_lines]"    
    ~]# bash lines.sh /etc.fstab /etc/issue
    

    特殊变量

    符号 含义
    $0 脚本文件路径本身
    $# 脚本参数的个数
    $* 所有参数
    $@ 所有参数

    shell脚本的用户交互

    用户交互:通过键盘输入数据,从而完成变量赋值操作;
    read[option] [name...]
    -p“提示字符”
    -t # 限定输入时间

    #!/bin/bash
    #
    read -p "Enter a username: " name
    [ -z "$name" ] && echo "a username is needed." && exit 2
    
    read -p "Enter password for $name, [password]: " password
    [ -z "$password" ] && password="password"
    
    if id $name &> /dev/null; then
       echo "$name exists."
    else
       useradd $name
       echo "$password" | passwd --stdin $name &> /dev/null
       echo "Add user $name finished."
    fi            
    

    shell脚本的语法检测

    • bash -n FILE,智能检测出语法错误,不能检测出逻辑错误
    • bash -x FILE 调试执行

    bash脚本编程之算术运算: +,-,*,/, **, %

    算术运算格式:

    • let VAR=算术运算表达式
    • VAR=$[算术运算表达式]
    • VAR=$((算术运算表达式))
    • VAR=$(expr $ARG1 $OP $ARG2)

    注意:乘法符号在有些场景中需要使用转义符;

    变量赋值
    增强型赋值:

    变量做某种算术运算后回存至此变量中;
    let i=$i+#
    let i+=#

    如:+=,-=,*=, /=, %=

    自增:
    VAR=$[$VAR+1]
    let  VAR+=1
    let  VAR++
    
    自减:
    VAR=$[$VAR-1]
    let  VAR-=1
    let  VAR--
    

    练习:
    1、写一个脚本
    计算/etc/passwd文件中的第10个用户和第20个用户的id号之和;

    id1=$(head -10  /etc/passwd | tail -1  | cut  -d:  -f3)
    id2=$(head -20   /etc/passwd | tail -1  | cut  -d:  -f3)
    

    2、写一个脚本
    计算/etc/rc.d/init.d/functions和/etc/inittab文件的空白行数之和;

    grep "^[[:space:]]*$"   /etc/rc.d/init.d/functions | wc -l
    

    3、编写脚本,实现自动添加三个用户,并计算三个用户的UID之和

    #!/bin/bash
    #
    id_sum=0
    [ $# -lt 3 ] && echo "请输入三个用户:" && exit 1
    for i in $* ;do
    useradd $i && echo "${i}创建成功"
    id_num=$(id -u $i)
    id_sum=$[${id_sum}+${id_num}]
    done
    echo "三个用户uid之和为: ${id_sum}"
    
    [root@centos7 ~]# bash useradd.sh 1 2 3
    useradd: invalid user name '1'
    useradd: invalid user name '2'
    useradd: invalid user name '3'
    三个用户uid之和为: 6
    

    常用测试条件及控制语句

    条件测试语句

    判断某需求是否满足,需要由测试机制来实现

    如何编写测试表达式以实现所需的测试:

    • 执行命令,并利用命令状态返回值来判断;

      0:成功
      1-255:失败

    • 测试表达式

      test EXPRESSION
      [ EXPRESSION ]
      [[ EXPRESSION ]]

    注意:EXPRESSION两端必须有空白字符,否则为语法错误;

    bash的测试类型

    数值测试:
    符号 含义
    -eq 是否等于
    -ne 是否不等于
    -gt 是否大于
    -ge 是否大于等于
    -lt 是否小于
    -le 是否小于等于
    字符串测试
    符号 含义
    == 是否等于
    > 是否大于
    < 是否小于
    != 是否不等于
    =~ 左侧的字符串是否被右侧的patter所匹配
    -z “string” 判断指定的字符创是否为空
    -n “string” 判断指定的字符串是否不空

    注意

    1. 字符创比较尽量定义在双括号中
    2. 比较对象是变量时,最好加双引号(变量为空时,该测试会出现问题)
    文件测试:
    • 存在性测试
    • -a FILE
    • -e FILE

    文件存在性测试
    存在则为真,否则则为假

    符号 含义
    -b FILE 文件是否存在,且为块设备文件
    -c FILE 文件是否存在,且为字符设备
    -d FILE 文件是否存在,且为目录文件
    -f FILE 文件是否存在,且为普通文件
    -h FILE 是否存在,且为符号链接
    -p FILE 是否存在,且为命名管道文件
    -S FILE 是否存在,且为套接字文件

    文件权限测试

    符号 含义
    -r FILE 是否存在,且对当前用户可读
    -w FILE 是否存在,且对当前用户可写
    -x FILE 是否存在,且对当前用户可执行

    特殊权限测试

    符号 含义
    -g FILE 是否存在,且拥有sgid权限
    -u FILE 是否存在,且拥有suid权限
    -k FILE 是否存在,且拥有sticky权限
    双目测试
    符号 含义
    FILE1 -ef FILE2 file1与file2是否为指向同一个文件系统的相同inode的硬链接
    FILE1 -ne FILE2 file1是否新于file2
    FILE1 -ot FILE2 file1是否旧于file2
    文件是否有内容:

    -s FILE:是否有内容;
    时间戳:
    -N FILE:文件自从上一次读操作后是否被修改过;

    从属关系测试:
    符号 含义
    -O FILE 当前用户是否为文件的属主
    -G FILE 当前用户是否属于文件的属组
    组合测试条件

    逻辑运算

    • 命令的逻辑运算

      COMMAND1 && COMMAND2

      COMMAND1 || COMMAND2

      !COMMAND

    • 表达式的逻辑运算

      EXPRESSION1 -a EXPRESSION2

      EXPRESSION1 -o EXPRESSION2

      !EXPRESSION

    练习:将当前主机名称保存至hostName变量中;主机名如果为空,或者为localhost.localdomain,则将其设置为www.magedu.com

    hostName=$(hostname)
    
    [ -z "$hostName" -o "$hostName" == "localhost.localdomain" -o "$hostName" == "localhost" ] && 
    hostname [www.magedu.com](http://www.magedu.com/)
    

    控制语句

    if选择执行语句
    • 单分支
    if  测试条件; then
        代码分支
    fi
    

    例如:测试/tmp目录下是否有test文件,如果有则告知用户,已存在

    source-shell
    if [[ -a /tmp/test ]];then
        echo "test is existing"
    fi
    
    • 双分支
    if  测试条件; then
           条件为真时执行的分
    else
           条件为假时执行的分支
    fi
    

    例如:检查/tmp目录下是否有名为test的普通文件,没有则创建

      source-shell
    if [[ -f /tmp/test ]];then
            echo "test is existing"
            else
                touch /tmp/test
    fi
    
    • 多分支
     if  测试条件1;then
           条件1为真时执行的分支
    elif  测试条件2;then
           条件2为真时执行的分支
    ......
    else
           上述条件都不满足时的执行分支
    fi
    

    注意:即便多个条件可能同时都能满足,分支只会执行中其中一个,首先测试为“真”

    示例:脚本参数传递一个文件路径给脚本,判断此文件的类型;

    #!/bin/bash
    #
    if [ $# -lt 1 ]; then
         echo "At least on path."
         exit 1
    fi
    
    if ! [ -e $1 ]; then
         echo "No such file."
         exit 2
    fi
    
    if [ -f $1 ]; then
          echo "Common file."
    elif [ -d $1 ]; then
          echo "Directory."
    elif [ -L $1 ]; then
          echo "Symbolic link."
    elif [ -b $1 ]; then
          echo "block special file."
    elif [ -c $1 ]; then
          echo "character special file."
    elif [ -S $1 ]; then
          echo "Socket file."
    else
          echo "Unkown."
    fi    
    

    注意:if语句可嵌套;

    练习1:通过命令行参数给定一个用户名,判断其ID号是偶数还是奇数

    #!/bin.bash
    if [ $# -lt 1 ];then
    echo "Must give a username."
    fi
    num=$(id -u $1)
    let num2=$num%2
    #echo "num is $num"
    #echo "num2 is $num2"
    if [ $num2 -eq 1 ];then
    echo "the uid is "odd number
    else
    echo "the uid is even number"
    fi
    

    练习2:写一个脚本
    (1) 传递一个参数给脚本,此参数为用户名;
    (2) 根据其ID号来判断用户类型:
    0: 管理员
    1-999:系统用户
    1000+:登录用户

    #!/bin/bash
    #
    [ $# -lt 1 ] && echo "At least on user name." && exit 1
    
    ! id $1 &> /dev/null && echo "No such user." && exit 2
    
    userid=$(id -u $1)
    
    if [ $userid -eq 0 ]; then
         echo "root"
    elif [ $userid -ge 1000 ]; then
         echo "login user."
    else
         echo "System user."
    fi                                        
    

    练习3:写一个脚本
    (1) 列出如下菜单给用户:
    disk: show disks info;
    mem: show memory info;
    cpu: show cpu info;
    ( * ) quit;
    (2) (2) 正确的选择则给出相应的信息;否则,则提示重新选择正确的选项;

    #!/bin/bash
    #
    cat << EOF
    (disk) show disks info
    (mem) show memory info
    (cpu) show cpu info
    (*) QUIT
    EOF
    
    read -p "Your choice: " option
    
    while [ "$option" != "cpu" -a "$option" != "mem" -a "$option" != "disk" -a "$option" != "quit" ]; do
                        echo "cpu, mem, disk, quit"
                        read -p "Enter your option again: " option
    done
    
    if [[ "$option" == "disk" ]]; then
          fdisk -l /dev/[sh]d[a-z]
    elif [[ "$option" == "mem" ]]; then
          free -m
    elif [[ "$option" == "cpu" ]];then
          lscpu
    else
          echo "Unkown option."
          exit 3
    fi
    
    case语句
    case  $VARAIBLE(变量引用)  in  
    PAT1)
         分支1
          ;;
    PAT2)
         分支2
          ;;
    ...
    *)
         分支n
          ;;
    esac
    

    case支持glob风格的通配符:

    *:任意长度的任意字符;
    ?:任意单个字符;
    []:范围内任意单个字符;
    a|b:a或b;

    示例:写一个服务框架脚本
    $lockfile, 值/var/lock/subsys/SCRIPT_NAME

    (1) 此脚本可接受start, stop, restart, status四个参数之一;
    (2) 如果参数非此四者,则提示使用帮助后退出;
    (3) start,则创建lockfile,并显示启动;stop,则删除lockfile,并显示停止;restart,则先删除此文件再创建此文件,而后显示重启完成;status,如果lockfile存在,则显示running,否则,则显示为stopped.

    #!/bin/bash
    #
    # chkconfig: - 50 50
    # description: test service script
    #
    prog=$(basename $0)
    lockfile=/var/lock/subsys/$prog
    
    case $1  in
    start)
          if [ -f $lockfile ]; then
              echo "$prog is running yet."
          else
             touch $lockfile
             [ $? -eq 0 ] && echo "start $prog finshed."
          fi
          ;;
    stop)
          if [ -f $lockfile ]; then
               rm -f $lockfile
               [ $? -eq 0 ] && echo "stop $prog finished."
          else
             echo "$prog is not running."
          fi
          ;;
    restart)
          if [ -f $lockfile ]; then
               rm -f $lockfile
               touch $lockfile
               echo "restart $prog finished."
         else
               touch -f $lockfile
               echo "start $prog finished."
          fi
          ;;
    status)
          if [ -f $lockfile ]; then
             echo "$prog is running"
         else
             echo "$prog is stopped."
          fi
          ;;
    *)
         echo "Usage: $prog {start|stop|restart|status}"
         exit 1
    esac
    

    循环执行: 将一段代码重复执行0、1或多次;

    进入条件:

    for:列表元素非空;
    while:条件测试结果为“真”
    unitl:条件测试结果为“假”

    退出条件:每个循环都应该有退出条件,以有机会退出循环;

    for:列表元素遍历完成;
    while:条件测试结果为“假”
    until:条件测试结果为“真

    for循环:

    两种格式:

    • 遍历列表
    • 控制变量

    遍历列表:

    for  VARAIBLE  in  LIST; do
         循环体
    done
    

    进入条件:只要列表有元素,即可进入循环;
    退出条件:列表中的元素遍历完成;

    示例:

    #!/bin/bash
    #
    for username in user21 user22 user23; do
        if id $username &> /dev/null; then
            echo "$username exists."
        else
            useradd $username && echo "Add user $username finished."
        fi
    done
    

    LISTT的生成方式:

    (1) 直接给出;
    (2) 整数列表
    (a) {start..end}
    (b) seq [start [incremtal]] last
    (3) 返回列表的命令
    (4) glob
    (5) 变量引用
    @,*
    ...

    示例:求100以内所有正整数之和;

    #!/bin/bash
    #
    declare -i sum=0
    
    for i in {1..100}; do
                echo "\$sum is $sum, \$i is $i"
                sum=$[$sum+$i]
    done
    
    echo $sum
    

    示例:判断/var/log目录下的每一个文件的内容类型

    #!/bin/bash
    #
    for filename in /var/log/*; do
         if [ -f $filename ]; then
            echo "Common file."
    elif [ -d $filename ]; then
            echo "Directory."
    elif [ -L $filename ]; then
            echo "Symbolic link."
    elif [ -b $filename ]; then
            echo "block special file."
    elif [ -c $filename ]; then
            echo "character special file."
    elif [ -S $filename ]; then
            echo "Socket file."
    else
            echo "Unkown."
         fi                    
    done
    
    for循环的特殊用法:
    for  ((控制变量初始化;条件判断表达式;控制变量的修正语句)); do
                    循环体
                done
    

    控制变量初始化:仅在循环代码开始运行时执行一次;
    控制变量的修正语句:每轮循环结束会先进行控制变量修正运算,而后再做条件判断;

    示例:求100以内所有正整数之和

    #!/bin/bash
    #
    declare -i sum=0
    
    for ((i=1;i<=100;i++)); do
        let sum+=$i
    done
    
    echo "Sum: $sum."
    

    示例:用for循环检测10.0.0.1/24网段存活的IP地址

    #!/bin/bash
    #
    declare -i a=0
    for a in {1..255};do
    if ping -w 2 -c 2 10.0.0.$a &>/dev/null;then
    echo "10.0.0.$a is up"
    else
    echo "10.0.0.$a is down"
    fi
    done
    
    while循环:
    while  CONDITION; do
         循环体
         循环控制变量修正表达式
    done
    

    进入条件:CONDITION测试为”真“
    退出条件:CONDITION测试为”假“

    示例:求100以内所有正整数之和

    #!/bin/bash
    #
    declare -i sum=0
    declare -i i=1
    
    while [ $i -le 100 ]; do
         let sum+=$i
         let i++
    done
    
    echo $sum
    
    循环控制语句:

    continue:提前结束本轮循环,而直接进入下一轮循环判断

    while  CONDITION1; do
              CMD1
                        ...
          if  CONDITION2; then
              continue
          fi
              CMDn
             ...
    done
    

    示例:求100以内所有偶数之和

    #
    declare -i evensum=0
    declare -i i=0
    
    while [ $i -le 100 ]; do
        let i++
        if [ $[$i%2] -eq 1 ]; then
                continue
        fi
        let evensum+=$i
    done
    
    echo "Even sum: $evensum"   
    

    break:提前跳出循环

    while  CONDITION1; do
              CMD1
              ..
         if  CONDITION2; then
              break
         fi
    done
    

    创建死循环:

    while true; do
              循环体
    done
    

    退出方式:
    某个测试条件满足时,让循环体执行break命令;

    示例:求100以内所奇数之和

    #!/bin/bash
    #
    declare -i oddsum=0
    declare -i i=1
    
    while true; do
         let oddsum+=$i
         let i+=2
         if [ $i -gt 100 ]; then
               break
        fi
    
    done
    
    sleep命令:

    delay for a specified amount of time
    sleep NUMBER

    练习:每隔3秒钟到系统上获取已经登录用户的用户的信息;其中,如果logstash用户登录了系统,则记录于日志中,并退出;

    #!/bin/bash
    #
    while true; do
          if who | grep "^logstash\>" &> /dev/null; then
              break
          fi
              sleep 3
    done
    
    echo "$(date +"%F %T") logstash logged on" >> /tmp/users.log    
    
    #!/bin/bash
    #
    until who | grep "^logstash\>" &> /dev/null; do
             sleep 3
             done
    
    echo "$(date +"%F %T") logstash logged on" >> /tmp/users.log   
    
    while循环的特殊用法(遍历文件的行):
    while  read  VARIABLE; do
              循环体;
    done  <  /PATH/FROM/SOMEFILE
    

    依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将基赋值给VARIABLE变量;

    示例:找出ID号为偶数的用户,显示其用户名、ID及默认shell

    #!/bin/bash
    #
    while read line; do
        userid=$(echo $line | cut -d: -f3)
        username=$(echo $line | cut -d: -f1)
        usershell=$(echo $line | cut -d: -f7)
    
        if [ $[$userid%2] -eq 0 ]; then
              echo "$username, $userid, $usershell."
        fi
    done < /etc/passwd        
    

    示例:用while循环检测10.0.0.1/24网段存活的IP地址

    #!/bin/bash
    #
    declare -i a=0
    declare -i b=0
    declare -i c=0
    
    while [ $a -le 255 ];do
    if ping -w 2 -c 2 10.0.0.$a &>/dev/null;then
    echo "10.0.0.$a is up"
    else
    echo "10.0.0.$a is down"
    fi
    let a++
    done
    
    until循环:
    until  CONDITION; do
         循环体
         循环控制变量修正表达式
    done
    

    进入条件:CONDITION测试为”假“
    退出条件:CONDITION测试为”真“

    示例:求100以内所有正整数之和

    #!/bin/bash
    #
    declare -i sum=0
    declare -i i=1
    
    until [ $i -gt 100 ]; do
         let sum+=$i
         let i++
    done
    
    echo $sum            
    

    练习:打印九九乘法表
    1X1=1
    1X2=2 2X2=4
    1X3=3 2X3=6 3X3=9
    外循环控制乘数,内循环控制被乘数;

    #!/bin/bash
    #
    for j in {1..9}; do
          for i in $(seq 1 $j); do
                echo -n -e "${i}X${j}=$[${i}*${j}]\t"
          done
          echo
    done    
    
    #!/bin/bash
    #
    for ((j=1;j<=9;j++)); do
             for ((i=1;i<=j;i++)); do
                     echo -e -n "${i}X${j}=$[${i}*${j}]\t"
             done
             echo
    done              
    

    写一个脚本:
    ping命令去查看172.16.1.1-172.16.67.1范围内的所有主机是否在线;在线的显示为up, 不在线的显示down,分别统计在线主机,及不在线主机数;
    分别使用for, while循环实现。

    #!/bin/bash
    #
    declare -i uphosts=0
    declare -i downhosts=0
    
    for i in {1..67}; do
         if ping -W 1 -c 1 172.16.$i.1 &> /dev/null; then
                 echo "172.16.$i.1 is up."
                 let uphosts+=1
         else
                 echo "172.16.$i.1 is down."
                 let downhosts+=1
          fi
    done
    
    echo "Up hosts: $uphosts, Down hosts: $downhosts."        
    
    #!/bin/bash
    #
    declare -i uphosts=0
    declare -i downhosts=0
    declare -i i=1
    
    hostping() {
              if ping -W 1 -c 1 $1 &> /dev/null; then
                       echo "$1 is up."
                       return 0
              else
                       echo "$1 is down."
                       return 1
              fi
    }
              while [ $i -le 67 ]; do
                    hostping 172.16.$i.1
                    [ $? -eq 0 ] && let uphosts++ || let downhosts++
                    let i++
    done
    
    echo "Up hosts: $uphosts, Down hosts: $downhosts."                    
    

    写一个脚本,实现:
    能探测C类、B类或A类网络中的所有主机是否在线;

    #!/bin/bash
    #
    cping() {
            local i=1
            while [ $i -le 254 ]; do
                    if ping -W 1 -c 1 $1.$i &> /dev/null; then
                            echo "$1.$i is up"
                    else
                            echo "$1.$i is down."
                    fi
                    let i++
    done
    }
    bping() {
            local j=0
            while [ $j -le 255 ]; do
                    cping $1.$j
                    let j++
    done
    }
    aping() {
            local x=0
            while [ $x -le 255 ]; do
                    bping $1.$x
                    let x++
    done
    }
    

    函数:function

    过程式编程:代码重用

    • 模块化编程
    • 结构化编程

    把一段独立功能的代码当作一个整体,并为之一个名字;命名的代码段,此即为函数;

    注意:定义函数的代码段不会自动执行,在调用时执行;所谓调用函数,在代码中给定函数名即可;
    函数名出现的任何位置,在代码执行时,都会被自动替换为函数代码;

    语法风格一:

    function  f_name  {
             ...函数体...
    }
    

    语法风格二:

    f_name()  {
             ...函数体...
    }
    

    函数的生命周期:每次被调用时创建,返回时终止;
    其状态返回结果为函数体中运行的最后一条命令的状态结果;

    自定义状态返回值,需要使用:return
    return [0-255]
    0: 成功
    1-255: 失败

    示例:给定一个用户名,取得用户的id号和默认shell

    #!/bin/bash
    #
    userinfo() {
           if id "$username" &> /dev/null; then
                 grep "^$username\>" /etc/passwd | cut -d: -f3,7
           else
                 echo "No such user."
           fi
    }
    
    username=$1
    userinfo
    
    username=$2
    userinfo        
    

    示例2:服务脚本框架(函数形式)

    #!/bin/bash
    #
    # chkconfig: - 50 50
    # description: test service script
    #
    prog=$(basename $0)
    lockfile=/var/lock/subsys/$prog
    
    start() {
            if [ -f $lockfile ]; then
                   echo "$prog is running yet."
            else
                   touch $lockfile
                   [ $? -eq 0 ] && echo "start $prog finshed."
            fi
    }
    
    stop() {
            if [ -f $lockfile ]; then
                   rm -f $lockfile
                   [ $? -eq 0 ] && echo "stop $prog finished."
            else
                   echo "$prog is not running."
            fi
    }
    status() {
            if [ -f $lockfile ]; then
                   echo "$prog is running"
            else
                   echo "$prog is stopped."
            fi
    }
    
    usage() {
                   echo "Usage: $prog {start|stop|restart|status}"
    }
    
    case $1 in
    start)
            start ;;
    stop)
            stop ;;
    restart)
            stop
            start ;;
    status)
            status ;;
    *)
            usage
            exit 1 ;;
    esac
    
    函数返回值:

    函数的执行结果返回值:
    (1) 使用echo或printf命令进行输出;
    (2) 函数体中调用的命令的执行结果;

    函数的退出状态码:
    (1) 默认取决于函数体中执行的最后一条命令的退出状态码;
    (2) 自定义:return

    函数可以接受参数:

    传递参数给函数:
    在函数体中当中,可以使用$1,$2, ...引用传递给函数的参数;还可以函数中使用$*或$@引用所有参数,$#引用传递的参数的个数;
    在调用函数时,在函数名后面以空白符分隔给定参数列表即可,例如,testfunc arg1 arg2 arg3 ...

    示例:添加10个用户,添加用户的功能使用函数实现,用户名做为参数传递给函数

    #!/bin/bash
    #
    # 5: user exists
    
    addusers() {
             if id $1 &> /dev/null; then
                    return 5
             else
                    useradd $1
                    retval=$?
                    return $retval
             fi
    }
    
         for i in {1..10}; do
                    addusers ${1}${i}
                    retval=$?
              if [ $retval -eq 0 ]; then
                    echo "Add user ${1}${i} finished."
              elif [ $retval -eq 5 ]; then
                    echo "user ${1}${i} exists."
              else
                    echo "Unkown Error."
              fi
    done
    
    变量作用域:

    局部变量:作用域是函数的生命周期;在函数结束时被自动销毁;
    定义局部变量的方法:local VARIABLE=VALUE
    本地变量:作用域是运行脚本的shell进程的生命周期;因此,其作用范围为当前shell脚本程序文件;

    示例程序:

    #!/bin/bash
    #
    name=tom
    
    setname() {
           local name=jerry
           echo "Function: $name"
    }
    
    setname
    echo "Shell: $name"
    
    函数递归:

    函数直接或间接调用自身

    n*(n-1)!=n*(n-1)*(n-2)!=
    
    #!/bin/bash
    #
    fact() {
           if [ $1 -eq 0 -o $1 -eq 1 ]; then
                echo 1
           else
                echo $[$1*$(fact $[$1-1])]
           fi
    }
    
    fact $1                    
    
    斐波那契数列:1,1,2,3,5,8,13,21,...
    
    #!/bin/bash
    #
    fab() {
          if [ $1 -eq 1 ]; then
                  echo -n "1 "
          elif [ $1 -eq 2 ]; then
                  echo -n "1 "
          else
                  echo -n "$[$(fab $[$1-1])+$(fab $[$1-2])] "
           fi
    }
    
           for i in $(seq 1 $1); do
                  fab $i
          done
     echo
    

    数组:

    程序=指令+数据
    数据:变量、文件

    变量:存储单个元素的内存空间

    数组:存储多个元素的连续的内存空间
    数组名:整个数组只有一个名字;
    数组索引:编号从0开始;
    数组名[索引]
    ${ARRAY_NAME[INDEX]}

    注意:bash-4及之后的版本,支持自定义索引格式,而不仅仅是0,1,2,...数字格式;
    此类数组称之为“关联数组”

    [root@promote ~]# world[us]="america"
    [root@promote ~]# echo ${world[us]}
    america
    [root@promote ~]# world[uk]="united kingdom"
    [root@promote ~]# echo ${world[uk]}
    united kingdom
    
    声明数组:
    • declare -a NAME:声明索引数组
    • declare -A NAME:声明关联数组
    数组中元素的赋值方式:

    (1) 一次只赋值一个元素
    ARRAY_NAME[INDEX]=value

    [root@promote ~]# animal[0]=pig
    [root@promote ~]# animal[1]=dog
    [root@promote ~]# echo $animal
    pig
    [root@promote ~]# echo ${animal[0]}
    pig
    [root@promote ~]# echo ${animal[1]}
    dog
    

    (2) 一次赋值全部元素
    ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)

    [root@promote ~]# weekday=("Monday" "Tuesday" "Wednesday")
    [root@promote ~]# echo ${weekday[1]}
    Tuesday
    

    (3) 只赋值特定元素;
    ARRAY_NAME=([0]="VAL1" [3]="VAL4" ...)
    注意:bash支持稀疏格式的数组

    [root@promote ~]# sword=([0]="Yitianjian" [3]="Longquan")
    [root@promote ~]# echo ${sword[3]}
    Longquan
    

    (4) read -a ARRAY_NAME

    [root@promote ~]# read -a Jiangsu
    ljiawen wangchenlu lalala
    [root@promote ~]# echo ${Jiangsu[2]}
    lalala
    

    引用数组中的元素:${ARRAY_NAME[INDEX]}
    注意:引用时,只给数组名,表示引用下标为0的元素;

    数组的长度(数组中元素的个数):

    ${#ARRAY_NAME[*]}
    ${#ARRAY_NAME[@]}

    [root@promote ~]# echo ${#Jiangsu[*]}
    3
    [root@promote ~]# echo ${#animal[@]}
    2
    

    示例:生成10个随机数,并找出其中的最大值

    #!/bin/bash
    #
    declare -a  rand
    declare -i max=0
    
    for i in {0..9}; do
           rand[$i]=$RANDOM
           echo ${rand[$i]}
           [ ${rand[$i]} -gt $max ] && max=${rand[$i]}
    done
    
    echo "MAX: $max"            
    

    练习:写一个脚本
    定义一个数组,数组中的元素是/var/log目录下所有以.log结尾的文件;统计其下标为偶数的文件中的行数之和

    #!/bin/bash
    #
    declare -a files
    files=(/var/log/*.log)
    declare -i lines=0
    for i in $(seq 0 $[${#files[*]}-1]); do
        if [ $[$i%2] -eq 0 ]; then
              let lines+=$(wc -l ${files[$i]} | cut -d' ' -f1)
        fi
    done
    
    echo "Lines: $lines."
    
    引用数组中的所有元素:

    ${ARRAY_NAME[*]}
    ${ARRAY_NAME[@]}

    数组元素切片:

    ${ARRAY_NAME[@]:offset:number}
    offset:要路过的元素个数;
    number:要取出的元素个数;省略number时,表示取偏移量之后的所有元素;

    [root@promote ~]# files=(/etc/[Pp]*)
    [root@promote ~]# echo ${files[*]}
    /etc/pam.d /etc/passwd /etc/passwd- /etc/pkcs11 /etc/pki /etc/plymouth /etc/pm /etc/polkit-1 /etc/popt.d /etc/postfix /etc/ppp /etc/prelink.conf.d /etc/printcap /etc/profile /etc/profile.d /etc/protocols /etc/python
    [root@promote ~]# echo ${files[@]:2:3}
    /etc/passwd- /etc/pkcs11 /etc/pki
    

    向非稀疏格式数组中追加元素:
    ARRAY_NAME[${#ARRAY_NAME[*]}]=

    删除数组中的某元素:
    unset ARRAY[INDEX]

    关联数组:
    declare -A ARRAY_NAME
    ARRAY_NAME=([index_name1]="value1" [index_name2]="value2" ...)

    bash的内置字符串处理工具:

    字符串切片:

    ${var:offset:number}取字符串的子串;
    取字符趾的最右侧的几个字符:${var: -length}

    注意:冒号后必须有一个空白字符

    [root@promote ~]# name=jerry
    [root@promote ~]# echo ${name: 2: 2}
    rr
    [root@promote ~]# echo ${name: 2}
    rry
    [root@promote ~]# echo ${name: -4}
    erry
    

    基于模式取子串:

    ${var#*word}:其中word是指定的分隔符;
    功能:自左而右,查找var变量所存储的字符串中,第一次出现的word分隔符,删除字符串开头至此分隔符之间的所有字符;

    [root@promote ~]# mypath="/etc/init.d/functions"
    [root@promote ~]# echo ${mypath#*/}
    etc/init.d/functions
    

    ${var##*word}:其中word是指定的分隔符;
    功能:自左而右,查找var变量所存储的字符串中,最后一次出现的word分隔符,删除字符串开头至此分隔符之间的所有字符;

    [root@promote ~]# mypath="/etc/init.d/functions"
    [root@promote ~]# echo ${mypath##*/}
    functions
    

    ${var%word*}:其中word是指定的分隔符;
    功能:自右而左,查找var变量所存储的字符串中,第一次出现的word分隔符,删除此分隔符至字符串尾部之间的所有字符;

    [root@promote ~]# mypath="/etc/init.d/functions"
    [root@promote ~]# echo ${mypath%/*}
    /etc/init.d
    

    ${var%%word*}:其中word是指定的分隔符;
    功能:自右而左,查找var变量所存储的字符串中,最后一次出现的word分隔符,删除此分隔符至字符串尾部之间的所有字符;

    [root@promote ~]# mypath="/etc/init.d/functions"
    [root@promote ~]# echo ${mypath%%/*}
    [root@promote ~]# mypath="etc/init.d/functions"
    [root@promote ~]# echo ${mypath%%/*}
    etc
    

    示例:

    [root@promote ~]# url=http://www.magedu.com:80
    [root@promote ~]# echo ${url##*:}
    80
    [root@promote ~]# echo ${url%%:*}
    http
    

    查找替换:

    ${var/PATTERN/SUBSTI}:查找var所表示的字符串中,第一次被PATTERN所匹配到的字符串,将其替换为SUBSTI所表示的字符串;

    ${var//PATTERN/SUBSTI}:查找var所表示的字符串中,所有被PATTERN所匹配到的字符串,并将其全部替换为SUBSTI所表示的字符串;

    [root@promote ~]# userinfo="root:x:0:0:root admin:/root:/bin/chroot"
    [root@promote ~]# echo ${userinfo/root/ROOT}
    ROOT:x:0:0:root admin:/root:/bin/chroot
    [root@promote ~]# echo ${userinfo//root/ROOT}
    ROOT:x:0:0:ROOT admin:/ROOT:/bin/chROOT
    

    ${var/#PATTERN/SUBSTI}:查找var所表示的字符串中,行首被PATTERN所匹配到的字符串,将其替换为SUBSTI所表示的字符串;

    ${var/%PATTERN/SUBSTI}:查找var所表示的字符串中,行尾被PATTERN所匹配到的字符串,将其替换为SUBSTI所表示的字符串;

    注意:PATTERN中使用glob风格和通配符;

    [root@promote ~]# userinfo="root:x:0:0:root admin:/root:/bin/chroot"
    [root@promote ~]# echo ${userinfo/%r??t/ROOT}
    root:x:0:0:root admin:/root:/bin/chROOT
    

    查找删除:

    ${var/PATTERN}:以PATTERN为模式查找var字符串中第一次的匹配,并删除
    ${var//PATERN}:以PATTERN为模式查找var字符串中所有的匹配,并删除
    ${var/#PATTERN:以PATTERN为模式查找var字符串中所有被行首匹配的,并删除
    ${var/%PATTERN}:以PATTERN为模式查找var字符串中所有被行尾匹配的,并删除

    字符大小写转换:

    ${var^^}:把var中的所有小写字符转换为大写
    ${var,,}:把var中的所有大写字符转换为小写

    [root@promote ~]# url=http://www.magedu.com:80
    [root@promote ~]# echo ${url^^}
    HTTP://WWW.MAGEDU.COM:80
    [root@promote ~]# myurl=${url^^}
    [root@promote ~]# echo $myurl
    HTTP://WWW.MAGEDU.COM:80
    [root@promote ~]# echo ${myurl,,}
    http://www.magedu.com:80
    

    变量赋值:

    ${var:-VALUE}:如果var变量为空,或未设置,那么返回VALUE;否则,则返回var变量的值;

    [root@promote ~]# echo hi
    hi
    [root@promote ~]# echo $hi
    
    [root@promote ~]# echo ${hi:-world}
    world
    [root@promote ~]# hi="america"
    [root@promote ~]# echo ${hi:-world}
    america
    

    ${var:=VALUE}:如果var变量为空,或未设置,那么返回VALUE,并将VALUE赋值给var变量;否则,则返回var变量的值;

    [root@promote ~]# hi="america"
    [root@promote ~]# echo ${hi:=world}
    america
    [root@promote ~]# unset hi
    [root@promote ~]# echo ${hi:=world}
    world
    [root@promote ~]# echo $hi
    world
    

    ${var:+VALUE}:如果var变量不空,则返回VALUE;
    ${var:?ERROR_INFO}:如果var为空,或未设置,那么返回ERROR_INFO为错误提示;否则,返回var值;

    信号捕捉:

    trap命令
    格式:trap 'COMMAND' SIGNALS

    列出信号:
    trap -l
    kill -l

    [root@promote ~]# trap -l
     1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP
     6) SIGABRT  7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1
    11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
    16) SIGSTKFLT   17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
    21) SIGTTIN 22) SIGTTOU 23) SIGURG  24) SIGXCPU 25) SIGXFSZ
    26) SIGVTALRM   27) SIGPROF 28) SIGWINCH    29) SIGIO   30) SIGPWR
    31) SIGSYS  34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
    38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
    43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
    48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
    53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
    58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
    63) SIGRTMAX-1  64) SIGRTMAX    
    

    man 7 signal
    解释每个信号的功用

    常可以进行捕捉的信号:

    HUP, INT

    示例:

    #!/bin/bash
    #
    declare -a hosttmpfiles
    trap  'mytrap'  INT
    
    mytrap()  {
             echo "Quit"
             rm -f ${hosttmpfiles[@]}
             exit 1
    }
    
    for i in {1..50}; do
            tmpfile=$(mktemp /tmp/ping.XXXXXX)
            if ping -W 1 -c 1 172.16.$i.1 &> /dev/null; then
                  echo "172.16.$i.1 is up" | tee $tmpfile
            else
                  echo "172.16.$i.1 is down" | tee $tmpfile
            fi
            hosttmpfiles[${#hosttmpfiles[*]}]=$tmpfile
            done
    
    rm -f ${hosttmpfiles[@]}
    
    SIGHUP、SIGQUIT、SIGTERM、SIGINTERRUPT的区别
    • SIGHUP: 无需关闭进程而让其重读配置
    • SIGQUIT: 终止进程,由QUIT字符(通常是Ctrl-)来控制,进程在因收到SIGQUIT退出时会产生core文件
    • SIGTERM: 程序结束(terminate)信号,该信号可以被阻塞和处理。通常用来要求程序自己正常退出
    • SIGNTERRUPT: 允许信号中断系统调用

    在bash中使用ACSII颜色

    \033[31m hello \033[0m

    ##m:
    左侧#:
    3:前景色
    4:背景色
    右侧#:颜色种类
    1, 2, 3, 4, 5, 6, 7

    #m:
    加粗、闪烁等功能;

    多种控制符,可组合使用,彼此间用分号隔开

    示例:

    dialog命令:可实现窗口化编程

    各窗体控件使用方式

    [root@promote ~]# dialog --msgbox hello 17 30

    如何获取用户选择或键入的内容?
    默认,其输出信息被定向到了错误输出流;

    相关文章

      网友评论

        本文标题:shell脚本编程

        本文链接:https://www.haomeiwen.com/subject/duysiqtx.html