zoukankan      html  css  js  c++  java
  • shell 的选项解析

    引言

    目前在做嵌入式开发,经常要把程序 tftp 到设备上调试运行,打算写个脚本简化这些步骤,但系统所带 busybox 还是老旧的1.01版,不少 shell 特性都不支持,如 getopts。无奈,就写个老旧的脚本顶上吧~

    目标

    1. 脚本支持选项配置,如 -xfd /root/sbin app1 app2 .68,即,把文件 app1,app2 从 192.168.0.68 主机上传到设备路径 /root/sbin下,然后后台执行

    2. 支持二级选项,如 -Lef,即,记录日志到 stderr 或 file

    3. 可复用性,方便 copy 到别处用;可扩展性,方便增删选项支持;(所以尽量把各步骤封装成函数模块了);尽量使用内置命令

    4. 用来以后复习 shell 常用语法

    #!/bin/sh
    
    ### 功能 ### ## Tftp files from host to device ## wrap tftp functions with some handy options ## able to work with busybox
    1.01 (ash) or above ### 默认配置 ### rda="192.168.0.128" ## 主机 ip rdir="" ## 目标文件夹 run=0 ## 是否运行 fork=0 ## 是否后台运行
    DEBUG=1 ## 调试开关 #### 用法 #### if [ $# -le 0 ] || [ $1 = '?' ]; then echo "Usage: ${0##*/} [option] files_to_upload [.host_ip_tail]" echo "option: -d [dest_path] 移动文件至目的路径" echo " -x 运行" echo " -f 后台运行" echo "default: ${0##*/} files $rda" exit 1 fi ##############

    实现

    1. 处理主流程

    先把参数中的各选项识别出来,然后分别处理,最后处理剩余参数

    标注说明红粗字体的为函数,高亮部分为复用及扩展时需要修改的部分

    ## 选项解析函数
    parse_opt()
    {
       while local arg="$1"
             [ -n "$arg" ]  ## 遍历命令行参数
       do
          local oargs  ## 保存非选项参数
          local shift_step=1  ## 参数左移步进,用来遍历参数
          case $arg in  ## case 支持通配符(Globbing),可用来做参数匹配
          -*  )  ## 1.选项匹配
                local opt="${arg#-}"
                opt_need_arg $opt  ## 看看选项是否需要参数
                if [ $? -gt 0 ]; then
                   let shift_step++  ## 假设选项只支持带一个参数
                   local the_arg="$2"  ## 所带参数
                   handle_opts $opt "$the_arg"  ## 选项处理函数
                else
                   handle_opts $opt  ## 选项处理
                fi
                ;;
          .[0-9]* )  ## 2.特殊参数
    local tail=${arg#?} if [ $tail -le 255 ]; then ## host ip rda="192.168.0.$tail" else echo "Error: invalid host ip!" && exit 7 fi ;; * ) ## 3.其它参数 oargs="$oargs $arg" dmsg "oargs=$oargs" ## dmsg 为调试函数 ;; esac dmsg "shift_step=$shift_step > $# ?" shift $((shift_step)) ## 命令行参数左移 done handle_others $oargs ## 最终参数处理函数 return 0 }

    因为 shell 中变量作用域默认是全局的,所以函数中变量都作了 local 声明,方便复用

    2. 选项处理函数

    opt_need_arg()  ## 返回选项所支持的参数个数
    {
       dmsg "opt_need_arg $*"
       [ $(expr index "$1" d) -gt 0 ] && return 1   ## 选项 d 需要 1 个参数
       return 0
    }
    
    handle_opts()
    {
       dmsg "handle_opts $*"
       local opts="$1"  ## 选项字符串列表,如 xfd
       local sub_pos=2  ## 选项子串位置
       while local c1=$(expr substr "$opts" 1 1)  ## 得到列表中第一个字符
             [ -n "$c1" ]
       do
          dmsg "c1=$c1"
          case $c1 in
          L | l ) local bopts="ef"  ## 二级选项
               chk_bind_opt "${opts#?}" $bopts  ## 二级选项检查函数,返回当前二级选项个数
               local ret=$?
               if [ $ret -le 0 -o $ret -gt ${#bopts} ]; then  ## 错误处理:若返回值为0,或超出所支持的选项个数
                  echo "Error: wrong use of -$c1!"
                  exit 11  ## 返回值比较随意。。
               else
                  let sub_pos+=$ret  ## 选项左移步进
                  dmsg "sub_pos=$sub_pos"
                  [ $(expr index "$opts" f) -gt 0 ] && log2f=1  ## 二级选项处理
    [ $(expr index "$opts" e) -gt 0 ] && log2e=1 ## 二级选项处理
    fi ;; d ) if [ -z "$2" ] || [ "$opts" != "$c1" ]; then ## 错误处理:若选项需要参数,则该选项必须位于列表末位
    echo "Error: wrong use of -$c1!" exit 6 else rdir="$2" fi return 0 ;; f ) fork=1;; x ) run=1;; * ) echo "Error: unkown opt \"-$c1\"!" exit 2 ;; esac opts="$(expr substr "$opts" $sub_pos ${#opts})" ## 选项字符串左移 sub_pos=2 dmsg "opts=$opts" done return 0 }
    chk_bind_opt()  ## 二级选项检查函数,主要用来查错
    {
       dmsg "chk_bind_opt $1 $2"
       local opt2chk="$1"
       local optlist="$2"
       local num=0
       while local c1=$(expr substr "$opt2chk" 1 1)
             [ -n "$c1" -a $(expr index "$optlist" $c1) -gt 0 ]
       do
          dmsg "c1=$c1"
          let num++
          opt2chk="${opt2chk#?}"
       done
       dmsg "num=$num"
       return $num  ## return see -1 as option
    }

     3. 最后的自定义参数处理函数

    handle_others()
    {
    path="$(pwd)" ## 默认上传到当前路径
    if [ -n "$rdir" -a "$path" != "$rdir" ]; then
    path="$rdir"
    mv=1
    fi
    for file in $* ## 文件挨个处理 do dmsg $file echo "tftp -gr $file $rda ..." if tftp -gr $file $rda; then chmod a+x $file if [ "$mv" -eq 1 ]; then echo "moving $file to $path ..." mv $file $path ## 其实 mv 支持批量移动 fi if [ $run -gt 0 ]; then
    killall -q $file
    [ $fork -gt 0 ] && ("$path/$file" &) || "$path/$file" ## if-else 的简写形式 fi echo else exit 21 fi done }

    4. 调试函数

    dmsg()
    {
       [ -n "$DEBUG" ] && echo "$1"  ## 若 DEBUG 有值则打印参数 1
    }

    5. 跑起来

    parse_opt "$@"  ## 看上去孤独了点,可以考虑解除其函数封装
    
    exit 0

    总结

    1. 特殊参数的处理,可以考虑移到 oargs 的首部,从而在 handle_others() 中优先处理,省的污染 parse_opt();二级选项的处理没做,也许以后用得上

    2. 不支持 basename 命令,用 ${0##*/};不支持${string:position:length} 提取子串,用 expr substr $string $position $length

    3. 一些技巧:while 可以有多个判断条件,但根据最后一个做决定;if-else 的列表形式,有时比较简洁方便;case 支持通配符

    参考文献:

    [1]. Mendel Cooper,杨春敏、黄毅(译),高级 Bash Shell 编程指南,2006-05.  [注]:本文所有技巧均源于此书

    附录

    ## Usage of tftp in busybox 1.01 ##
    ## tftp [option] HOST [port]
    ## option: -g  Get file
    ##         -p  Put file
    ##         -l FILE  Local file
    ##         -r FILE  Remote file
    ##         -b SIZE
  • 相关阅读:
    15_门面模式
    14_责任链模式
    13_观察者模式
    12_状态模式
    11_策略模式
    10_命令模式
    09_适配器模式
    08_装饰者模式
    07_代理模式
    linux邮件服务器postfix配置实例
  • 原文地址:https://www.cnblogs.com/lookbackinside/p/2552087.html
Copyright © 2011-2022 走看看