shell脚本

什么是shell脚本

  • 提前设计可执行语句,用来完成特定任务的文件
  • 解释型程序顺序,批量执行                         

规范shell脚本的一般组成

-#!环境声明

-#注释文本

-可执行代码

一:编写一个批量处理shell脚本

  • 任务需求:依次输出以下系统信息

        -红帽系统版本,内核版本,当前的主机名

  • 转换成代码

        - cat /etc/redhat-release , uname -r  ,hostname

[root@server0 ~]# vim /root/sysinfo

#!/bin/bash
cat /etc/redhat-release
uname -r
hostname

添加执行权限。独立运行

[root@server0 ~]# chmod +x /root/sysinfo

[root@server0 ~]# /root/sysinfo
Red Hat Enterprise Linux Server release 7.0 (Maipo)
3.10.0-123.el7.x86_64
server0.example.com

变量

  • 基本概念

    • Shell变量的名称与C语言一样,由数字、字母、下划线组成,其中只能以字母或下划线开头
    • 变量可以为空值,null
    • 赋值时,=两边没有空格
    str="abc"
    a=1
    • 1
    • 2
    • 取变量的值时,在变量前面加$
    a=1
    echo $a => 1
    • 1
    • 2
    • 变量的值如果含空格,赋值时用双引号括起来
    str="Hello World!"
    • 1
    • 算数展开 $( (…) )
    i=5 j=6
    echo $((i+j))             => 11
    echo $(((i*j)-(i+j)))     => 19
    echo $((3>1))             => 1
    echo $((1>3))             => 0
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关命令

    • export:将shell变量输出为环境变量,或者将shell函数输出为环境变量

      • 打印当前环境变量
      export      => 打印当前进程的所有环境变量
      export -p   =>export
      • 1
      • 2
      • 修改当前进程的环境变量
      export DAVID="david"           => 设置环境变量DAVID,值为david
      export PATH=$PATH:/home/david  => 在PATH后面追加/home/david路径
      • 1
      • 2
    • readonly:定义只读shell变量和shell函数

      • 打印当前只读变量
      readonly      => 打印当前进程的所有只读变量
      readonly -p   => 同readonly
      • 1
      • 2
      • 设置当前进程的只读变量
      readonly DAVID="david"           => 设置只读变量DAVID,值为david
      • 1
    • env:为将要运行的程序设置环境变量

      • -i

        env -i A=1 B=2 awk 'xxx' file   # 用新的环境变量重新初始化环境变量,然后运行命令
                                        # 新环境变量只对将要运行的命令生效
        • 1
        • 2
      • -u

        env -u xxx awk 'xxx' file       # 删除某个环境变量,然后用新的环境变量运行命令
                                        # 新环境变量只对将要运行的命令生效
        • 1
        • 2
    • set:更改shell特性

      • -x 显示执行的语句
      • +x 不显示执行的语句
    • unset:删除已经定义的变量或函数
      • -f 删除函数
      • -v 删除变量
  • 参数展开

  • {}展开
str="Hello World!"
echo __$str__    => __
echo __${str}__  => __Hello World!__
  • 1
  • 2
  • 3
  • 未定义的变量会展开为null字符串

# 非常危险!

rm -fr /$UnDeclareVar   => rm -fr /
  • 1
  • 2
  • 3
  • 4
  • 替换运算符

    • ${varname:-word}
      实现:如果varname存在且非null,则返回其值;否则返回word
      用途:如果变量未定义,则返回一个默认值

      str1="Hello Shell!"
      echo ${str1:-Default} => Hello Shell!
      echo ${str2:-Default} => Default
      • 1
      • 2
      • 3
    • ${varname:=word}
      实现:如果varname存在且非null,则返回其值;否则设置varname=word,并返回varname
      用途:如果变量未定义,则将变量设为默认值并返回该变量

      str1="Hello Shell!"
      echo ${str1:=Default} => Hello Shell!
      echo ${str2:=Default} => Default
      echo $str2            => Default
      • 1
      • 2
      • 3
      • 4
    • ${varname:?message}
      实现:

      如果varname存在且非null,则返回其值;否则显示varname:message,并退出脚本
      如果省略message,显示默认信息"parameter null or not set"
      注:交互式Shell下不需要退出,不同Shell有不同行为
      
      • 1
      • 2
      • 3
      • 4

      用途:捕捉由于变量未定义而导致的错误

      str1="Hello Shell!"
      echo ${str1:?str1 is not set} => Hello Shell!
      echo ${str2:?str2 is not set} => line N: str2: str2 is not set
      echo ${str2:?}                => line N: str2: parameter null or not set
      • 1
      • 2
      • 3
      • 4
    • ${varname:+word}
      实现:如果varname存在且非null,则返回word;否则返回null
      用途:测试变量是否存在

      str1="Hello Shell!"
      echo ${str1:+1} => 1
      echo ${str2:+1} => (null)
      • 1
      • 2
      • 3
      • 位置参数
      • N1N>9
    • {10}
    • $#:命令行参数的个数
    • ,
      • @:一次表示所有参数
        • “$*”:将所有参数当做一个字符串
        • “$@”:将所有参数视为单独的个体
          echo "\$1 = ${1:-/dev/null}"
          ./test.sh       => $1 = /dev/null
          ./test.sh 1 2 3 => $1 = 1
          
          echo "arg num = $#"
          echo "\$* = $*"
          echo "\$@ = $@"
          printf "\"\$*\" = %s\n" "$*"
          printf "\"\$@\" = %s\n" "$@"
          
          ./test.sh 1 2 3 ==>
          arg num = 3
          $* = 1 2 3
          $@ = 1 2 3
          "$*" = 1 2 3
          "$@" = 1
          "$@" = 2
          "$@" = 3
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
      • 特殊变量

    变量 意义
    0 Shell程序名称
    # 参数个数
    @ 命令行参数,如果在”“内,则展开为一系列单独的参数
    * 命令行参数,如果在”“内,则把所有参数合并为一个字符串
    $ 当前进程ID
    PPID 父进程ID
    ? 前一个命令的退出状态
    ! 最近一个后台命令的进程编号
    HOME 用户根目录
    PATH 命令的查找路径
    PWD 当前工作目录
    LINENO 行号
    - 传给Shell的选项
    PS1 主要的命令提示字符串;默认为$
    PS2 行继续的提示字符串;默认为>
    PS4 以set -x设置的执行跟踪的提示字符串;默认为+
    LANG 当前local默认名称;其他LC_*变量会覆盖其值
    LC_ALL 当前local名称;会覆盖LANG和其他LC_*变量
    LC_COLLATE 用来排序字符的当前local名称
    LC_CTYPE 在模式匹配期间,用来确定字符类别的当前local名称
    LC_MESSAGES 输出信息的当前语言的名称
    ENV 仅用于交互式Shell;要读取和在启动时要执行的一个文件的完整路径名;XSI必须的变量
    IFS 内部的字段分隔器
    NLSPATH 在$LC_MESSAGES(XSI)所给定的信息语言里,信息目录的位置

    条件

    • 退出状态
      • 每一条命令,不管是内置的还是外部的,退出时均返回一个整数值
      • 0表示成功,非0表示失败,用$?访问
      • exit命令:传递一个退出值给调用者;如果没有提供退出值,默认为最后一个命令的退出状态
    • if语句

      if [[ condition ]]; then
          #statements
      elif [[ condition ]]; then
          #statements
      else
          #statements
      fi
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • test命令

      • 两种形式

        str1="abc"
        str2="abc"
        
        if test $str1 = $str2; then
            echo "str1 == str2"
        else
            echo "str1 != str2"
        fi
        
        
        # 下面的形式等同于上面的形式
        
        if [[ $str1 = $str2 ]]; then
            echo "str1 == str2"
        else
            echo "str1 != str2"
        fi
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
      • 运算符

      运算符 如果…则为真
      -e file file存在
      -s file file不为空
      -f file file是一般文件
      -d file file是目录
      -b file file是块设备文件
      -c file file是字符设备文件
      -p file file是命名管道(FIFO)
      -S file file是socket
      -h file file是符号链接
      -L file file是符号链接,同-h
      -r file file是可读的
      -w file file是可写入的
      -x file file是可执行的,或file是可被查找的目录
      -g file file有设置setgid位
      -u file file有设置setuid位
      string string不是null
      -n string string非null
      -z string string是null
      s1 = s2 字符串s1等于s2
      s1 != s2 字符串s1不等于s2
      n1 -eq n2 整数n1等于n2
      n1 -ne n2 整数n1不等于n2
      n1 -lt n2 整数n1小于n2
      n1 -gt n2 整数n1大于于n2
      n1 -le n2 整数n1小于等于n2
      n1 -ge n2 整数n1大于等于n2
      -t n 文件描述符n指向以终端
      
      # 在test中,所有变量展开都需要用引号括起来
      
      if[ -f "$file" ]  ==> 正确
      if[ -f $file ]    ==> 如果$file为空,shell的行为无法预料
      • 1
      • 2
      • 3
      • 4
      • 5
    • case语句

      case word in
          pattern )
              ;;      # 执行到;;结束
          * )         # 默认动作,相当于C语言中的default
              ;;
      esac
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    循环

    • for语句

      for i in words; do
          #statements
      done
      
      for i; do   #相当于for i in "$@"
          #statements
      done
      
      for (( i = 0; i < 10; i++ )); do
          #statements
      done
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • while与until语句

      while [[ condition ]]; do # condition为真时执行
          #statements
      done
      
      until [[ condition ]]; do # condition为假时执行
          #statements
      done
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • break与continue

      • break:跳出循环
      • continue 继续执行下一个循环
      • break与continue,都可以接受数值参数,控制语句跳出或继续执行多少个循环
      while [[ condition ]]; do
          while [[ condition ]]; do
              #statements
              break 2  # 跳出最外面的循环
          done
      done
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • shift

      • 依次处理命令行参数
      • 每次执行后,原来的1
    2的旧值取代,2
      • 3的旧值取代…,$#也会依次减1
      • 有一个可选参数,指定每一次移动几位,默认为1
      file= verbose= quiet= long=
      while [[ $# -gt 0 ]]; do                 # 依次处理命令行参数,没处理依次,$#都减1 
          case $1 in
              -f )
                  file=$2
                  echo "file = $file"
                  shift                        # 因为-f选项后面要跟文件名,所以还得再shift一遍
                  ;;
              -v )
                  verbose=true
                  quiet=false
                  echo "verbose = $verbose"
                  echo "quiet = $quiet"
                  ;;
              -q )
                  quiet=true
                  verbose=false
                  echo "verbose = $verbose"
                  echo "quiet = $quiet"
                  ;;
              -l )
                  long=true
                  echo "long = $long"
                  ;;
          esac
          shift
      done
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
    • getopts

      • 简化命令行参数处理
      • 第一个参数是所有合法参数的字符串;如果选项字母后面跟冒号,说明该选项后面需要一个参数,且是必填的;如果遇到需要参数的选项,将参数值放置到变量OPTARG中
      • OPTIND包含下一个要处理的参数的索引值,初始值为1
      • 第二个参数为变量名称,每次getopts时,该变量会被更新,值等于找到的选项字母;遇到不合法的选项时,该变量被置为?
      file= verbose= quiet= long= a=
      
      while getopts f:vqa:l opt
      do
          case $opt in
              f )
                  file=$OPTARG
                  echo "file = $file"
                  ;;
              a )
                  a=$OPTARG
                  echo "a = $a"
                  ;;
              v )
                  verbose=true
                  quiet=false
                  echo "verbose = $verbose"
                  echo "quiet = $quiet"
                  ;;
              q )
                  verbose=false
                  quiet=true
                  echo "verbose = $verbose"
                  echo "quiet = $quiet"
                  ;;
              l )
                  long=true
                  echo "long = $long"
                  ;;
          esac
      done
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31

    函数

    • 函数使用前必须先定义
    • 在脚本的起始处定义
    • 在一个独立文件里定义,在脚本中用”.”符号获取

      wait_for_user(){
          XXX
      }
      • 1
      • 2
      • 3
    • return

      • 从函数里返回值
      • 如果为指定参数,使用默认退出状态
        return $?  # 严谨的写法
        • 1
    • 函数参数

      • 在函数中,位置参数代表函数参数;当函数完成时,原来的命令行参数会恢复

    涉及到的命令

    • export
    • readonly
    • env
    • unset
    • exit
    • return
    • test
    • shift
    • getopts

    1 案例1:Shell脚本的编写及测试

    1.1 问题

    本例要求两个简单的Shell脚本程序,任务目标如下:

    1. 编写一个面世问候 /root/helloworld.sh 脚本,执行后显示出一段话“Hello World!!”
    2. 编写一个能输出系统信息的 /root/sysinfo 脚本,执行后依次输出当前红帽系统的版本信息、当前使用的内核版本、当前系统的主机名

    1.2 方案

    规范Shell脚本的一般组成:

    1. #! 环境声明(Sha-Bang)
    2. # 注释文本
    3. 可执行代码

    1.3 步骤

    实现此案例需要按照如下步骤进行。

    步骤一:编写helloworld.sh问候脚本

    1)编写脚本代码

    1. [root@server0 ~]# vim /root/helloworld.sh
    2. #!/bin/bash
    3. echo "Hello World !!"

    2)添加x执行权限

    1. [root@server0 ~]# chmod +x /root/helloworld.sh

    3)运行脚本测试

    1. [root@server0 ~]# /root/helloworld.sh
    2. Hello World !!

    步骤二:编写sysinfo系统信息报告脚本

    1)编写脚本代码

    1. [root@server0 ~]# vim /root/sysinfo
    2. #!/bin/bash
    3. cat /etc/redhat-release
    4. uname -r
    5. hostname

    2)添加x执行权限

    1. [root@server0 ~]# chmod +x /root/sysinfo

    3)运行脚本测试

    1. [root@server0 ~]# /root/sysinfo
    2. Red Hat Enterprise Linux Server release 7.0 (Maipo)
    3. 3.10.0-123.el7.x86_64
    4. server0.example.com

    2 案例2:重定向输出的应用

    2.1 问题

    本例要求编写一个脚本 /root/out.sh,功能特性如下:

    1. 执行此脚本显示 I love study !!
    2. 执行 /root/out.sh 2> err.log 应该没有显示,但是查看 err.log 文件的内容为 I love study !!

    2.2 方案

    屏幕输出文本的类别:

    • 标准输出(1):命令行执行正常的显示结果
    • 标准错误(2):命令行执行出错或异常时的显示结果

    将屏幕显示信息保存到文件:

    • cmd > file 、 cmd >> file
    • cmd 2> file 、 cmd 2>> file
    • cmd &> file 、cmd 2> file 1>&2

    使用1>&2或>&2操作,可以将命令行的标准输出编程标准错误。

    2.3 步骤

    实现此案例需要按照如下步骤进行。

    步骤:编写out.sh输出测试脚本

    1)编写脚本代码

    1. [root@server0 ~]# vim /root/out.sh
    2. #!/bin/bash
    3. echo "I love study !!" >&2

    2)添加x执行权限

    1. [root@server0 ~]# chmod +x /root/out.sh

    3)运行脚本测试

    1. [root@server0 ~]# /root/out.sh
    2. I love study !!
    3. [root@server0 ~]# /root/out.sh 2> err.log
    4. [root@server0 ~]# cat err.log
    5. I love study !!

    3 案例3:使用特殊变量

    3.1 问题

    本例要求编写一个脚本 /root/myuseradd,功能特性如下:

    1)此脚本可接收2个位置参数,能够按照下列格式执行:

    1. /root/myuseradd 用户名 密码

    2)此脚本执行后,能显示“一共提供了 $# 个参数”,然后在下一行显示“用户名是 $1,密码是 $2 ”,紧跟下一行开始输出对应文件的前几行内容。

    3.2 方案

    使用位置变量可以取得在执行脚本时提供的命令行参数:

    • 表示为 $n,n为序号
    • $1、$2、.. .. ${10}、${11}、.. ..

    使用预定义变量$#可以统计执行脚本时提供的位置变量个数。

    3.3 步骤

    实现此案例需要按照如下步骤进行。

    步骤一:编写 /root/myuseradd 添加用户的脚本

    1)编写脚本代码

    1. [root@server0 ~]# vim /root/myuseradd
    2. #!/bin/bash
    3. echo "一共提供了 $# 个参数"
    4. echo "用户名是 $1,密码是 $2 "
    5. useradd $1
    6. echo "$2" | passwd --stdin $1

    2)添加x执行权限

    1. [root@server0 ~]# chmod +x /root/myuseradd.sh

    步骤二:测试 /root/myuseradd 脚本

    1)测试添加用户 bob,密码设为 1234567

    1. [root@server0 ~]# /root/myuseradd bob 1234567
    2. 一共提供了 2 个参数
    3. 用户名是 bob,密码是 1234567
    4. 更改用户 bob 的密码 。
    5. passwd:所有的身份验证令牌已经成功更新。
    6. [root@server0 ~]# id bob
    7. uid=1002(bob) gid=1002(bob)=1002(bob)

    2)测试添加用户 jerry,密码设为 1234567

    1. [root@server0 ~]# /root/myuseradd jerry 1234567
    2. 一共提供了 2 个参数
    3. 用户名是 jerry,密码是 1234567
    4. 更改用户 jerry 的密码 。
    5. passwd:所有的身份验证令牌已经成功更新。
    6. [root@server0 ~]# id jerry
    7. uid=1003(jerry) gid=1003(jerry)=1003(jerry)

    4 案例4:编写一个判断脚本

    4.1 问题

    本例要求在虚拟机 server0 上创建 /root/foo.sh 脚本,任务目标如下:

    1. 当运行/root/foo.sh redhat,输出为fedora
    2. 当运行/root/foo.sh fedora,输出为redhat
    3. 当没有任何参数或者参数不是 redhat 或者 fedora时,其错误输出产生以下信息: /root/foo.sh redhat|fedora

    4.2 方案

    Shell脚本中执行条件测试的方式:

    • 任何一条命令行
    • test 测试表达式
    • [ 测试表达式 ]

    常用的test测试选项:

    • 文件状态检测 -f、-d、-e、-r、-w、-x
    • 整数值比较 -gt、-ge、-eq、-ne、-lt、-le
    • 字符串比较 ==、!=
    • 取反操作 !

    多分支if选择结构:

    1. if 条件测试操作1;then
    2. 命令序列1....
    3. elif 条件测试操作2;then
    4. 命令序列2....
    5. else
    6. 命令序列3....
    7. fi

    4.3 步骤

    实现此案例需要按照如下步骤进行。

    步骤一:编写foo.sh判断脚本

    1)编写脚本代码

    1. [root@server0 ~]# vim /root/foo.sh
    2. #!/bin/bash
    3. if [ $# -eq 0 ];then
    4. echo "/root/foo.sh redhat|fedora" >&2
    5. elif [ $1 = "redhat" ];then
    6.      echo "fedora"
    7. elif [ $1 = "fedora" ];then
    8.      echo "redhat"
    9. else
    10.      echo "/root/foo.sh redhat|fedora" >&2
    11. fi

    2)添加x执行权限

    1. [root@server0 ~]# chmod +x /root/foo.sh

    步骤二:测试foo.sh判断脚本

    1)测试提供正确参数的情况

    1. [root@server0 ~]# /root/foo.sh redhat
    2. fedora
    3. [root@server0 ~]# /root/foo.sh fedora
    4. Redhat

    2)测试提供非预期参数的情况

    1. [root@server0 ~]# /root/foo.sh ubuntu
    2. /root/foo.sh redhat|fedora

    3)测试不提供参数的情况

    1. [root@server0 ~]# /root/foo.sh
    2. /root/foo.sh redhat|fedora

    5 案例5:编写一个批量添加用户脚本

    5.1 问题

    本例要求在虚拟机 server0 上创建 /root/batchusers 脚本,任务目标如下:

    1. 此脚本要求提供用户名列表文件作为参数
    2. 如果没有提供参数,此脚本应该给出提示 Usage: /root/batchusers,退出并返回相应值
    3. 如果提供一个不存在的文件,此脚本应该给出提示 Input file not found,退出并返回相应值
    4. 新用户的登录Shell为 /bin/false,无需设置密码
    5. 列表测试文件:http://classroom/pub/materials/userlist

    5.2 方案

    单分支if选择结构:

    1. if 条件测试操作
    2. then
    3. 命令序列....
    4. fi

    脚本的退出状态:取决于退出前最后一条命令的 $? 值,或者“exit 整数值”指定。

    列表式for循环结构:

    1. for 变量名 in123 .. ..
    2. do
    3. 命令序列($变量名)
    4. done

    使用命令替换来获取命令结果:$(命令行)

    5.3 步骤

    实现此案例需要按照如下步骤进行。

    步骤一:编写batchusers批量添加用户脚本

    1)编写脚本代码

    1. [root@server0 ~]# vim /root/batchusers
    2. #!/bin/bash
    3. if [ $# -eq 0 ] ; then
    4. echo "Usage: /root/batchusers <userfile>" >&2
    5. exit 1
    6. fi
    7. if [ ! -f $1 ] ; then
    8. echo "Input file not found" >&2
    9. exit 2
    10. fi
    11. for name in $(cat $1)
    12. do
    13. useradd -s /bin/false $name
    14. done

    2)添加x执行权限

    1. [root@server0 ~]# chmod +x /root/batchusers

    步骤二:测试batchusers批量添加用户脚本

    1)下载用户列表测试文件:

    1. [root@server0 ~]# wget http://classroom/pub/materials/userlist -O /root/userlist
    2. .. ..
    3. 2016-11-27 17:23:32 (2.83 MB/s) -/root/userlist’ saved [27/27]
    4. [root@server0 ~]# cat /root/userlist                     //检查下载文件
    5. duanwu
    6. zhongqiu
    7. zhsan
    8. lisi

    2)实现批量添加用户:

    1. [root@server0 ~]# /root/batchusers /root/userlist
    2. [root@server0 ~]# id duanwu
    3. uid=1006(duanwu) gid=1006(duanwu) groups=1006(duanwu)

    3)测试其他异常处理:

    1. [root@server0 ~]# /root/batchusers                         //未提供列表文件
    2. Usage: /root/batchusers <userfile>
    3. [root@server0 ~]# echo $?
    4. 1
    5. [root@server0 ~]# /root/batchusers /root/userlist.txt        //提供的列表文件找不到
    6. Input file not found
    7. [root@server0 ~]# echo $?
    8. 2



阅读更多

没有更多推荐了,返回首页