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
  • 相关阅读:
    spark 读取mongodb失败,报executor time out 和GC overhead limit exceeded 异常
    在zepplin 使用spark sql 查询mongodb的数据
    Unable to query from Mongodb from Zeppelin using spark
    spark 与zepplin 版本兼容
    kafka 新旧消费者的区别
    kafka 新生产者发送消息流程
    spark ui acl 不生效的问题分析
    python中if __name__ == '__main__': 的解析
    深入C++的new
    NSSplitView
  • 原文地址:https://www.cnblogs.com/lookbackinside/p/2552087.html
Copyright © 2011-2022 走看看