前言
上篇实现了shell选项的简单解析,支持选项连写(-xfd)及带参选项(-d “目的路径”)。但在使用中发现仍有不便之处:经常需要上传多个文件到设备下的不同路径,若脚本只支持单路径,则只能按路径分批上传;其次,有些文件名比较长,输入麻烦,so,新的需求产生了~
1)支持对文件指定不同目的路径
2)考虑到不少情况下只是覆盖原文件,若目的路径可带文件名,则能利用shell的路径补全简化文件名的输入,即路径选项支持:-d /usr/bin/file_with_a_very_long_name
【补630】唉,人都是有惰性的,若功能顺应了惰性,则惰性会打蛇随棍上,反向推动功能演化,这从侧面反映了科技与欲望的关系,所以西方科技曾被视为奇技淫巧是有文化根据的,呵呵。 言归正传,使用中发现,若支持文件名补全,则对于长点的文件名,都会有使用特性 2)的心理倾向,导致选项经常像这样 -d /usr/bin/file1, -d /usr/bin/file2, -d /opt/file3,不停的输入 -d 很麻烦,因为输入时需寻找“-”在键盘上的位置,从人体工学的角度来说,符号“-”的I/O时间远大于其它普通字符,导致输入流水线断流,违背了GTD中及时清空大脑的要求,叔婶均无可忍之。这样吧,若参数里带“/”,则认为是目的路径,前面带不带选项“-d”都行
思考
1、可以修改原脚本,在流程处理中添加对新需求的支持。但这样做会得不偿失,因为原脚本功能单一,遵循标准的选项处理方式。直接修改会破坏模块封装,引起功能紊乱,导致难以复用。若标准化封装模块是块平直的砖,则功能添加版就是块更大的砖,但不甚平直,前者可建高楼,后者就只能盖屋,太高就 Hold 不住撒~
2、既然不改原模块,则考虑在原功能基础上实现。一个自然的想法是使新脚本支持批量选项,即 原选项1 分隔符 原选项2 分隔符 ...,这样的话,新脚本只需根据分隔符把各选项分割出来,然后传给标准模块处理即可,如, -xd /usr/bin snmp 分隔符 -d /opt/local webs ...,从而支持了多目的路径
3、对文件名补全的支持:与 2 类似,也要转为选项的标准形式来处理,即把 -d /usr/bin/snmp 转为 -d /usr/bin snmp,把粘在一块的扯明白喽~
4、其它考虑:多数情况下,都是从一台主机上传文件,所以约定,若最后一个参数为ip,则此ip作为所有选项的默认主机地址;
目标
支持多目的路径;支持带文件名路径
例: -xfd /usr/bin/linux-2.6.13-mips-snmpv2 webs分隔符 -d /opt/local wtp 分隔符... .68
解释:把文件linux-2.6.13-mips-snmpv2,webs从主机192.168.0.68上传到设备的/usr/bin目录下,并后台执行;把文件wtp从192.168.0.68上传到/opt/local下...
【补630】不要 -d 也支持,-xf /usr/bin/linux-2.6.13-mips-snmpv2 webs 分隔符 /opt/local wtp
实现
1. 分隔符的选择
感觉标准点的,如 --add,但字符多了点; 分号“;”?不可,shell会试图解释,先用逗号顶上吧
2. 如何分割选项
直观的方法是依次把两个连续分隔符间的内容取出,标准化处理后作为参数传出。shell的字符串处理功能不弱,看看有哪些趁手的工具先~
expr 命令,length求长度,index得索引,substr截取字符串,match根据正则表达式获得匹配串的长度和内容
index + substr 方式,通过index 得到两个连续分隔符的位置,substr根据位置截取中间,但index只能匹配到第一个字符
match,写个分隔符的正则,把之间的内容取出,但试了下,似乎是贪婪的方式哦,会取最大匹配
忽然发现以上思路有偏差,依赖直观而没脚踏实地,从机器扫描的方式来看,从左至右,碰到一个分隔符,就该认为一个项已取完,可以送出了,然后删掉此分隔符,继续扫描。
思路明确了,这里用子串削除来取得一项,${参数列表%%分隔符*},即右边取最大匹配,削掉丫的;至于格式的标准化,用for来选择处理
3. 代码
1 tftp_multi_da() 2 { 3 ## 1. 先处理默认主机 IP 4 local args_list="$*" 5 eval local last_arg=\"\${$#}\" ## 获得最后一个参数 6 dmsg "last_arg=$last_arg" 7 local tail="" 8 case $last_arg in .[0-9]* ) ## 是否 ip? 9 tail=$last_arg ## 保存默认 host ip 10 args_list="${*%${last_arg}}";; ## 保存后移除 11 esac 12 13 local delim="," ## 分隔符 14 local args="" ## 项中的单个参数 15 local the_end=0 ## 末项处理标识 16 while local seg="${args_list%%${delim}*}" ## 2. 分割出每项
18 local seg_b="" ## 用来传出的标准化项 19 [ "$the_end" -eq 0 ] ## 若末尾项还未处理(实现类似 do-while) 20 do 21 if [ -n "$seg" ]; then 22 dmsg "seg=$seg" 23 for args in $seg ## 3. 分割出每项中的参数,准备标准化 24 do 25 dmsg "args=$args" 26 case $args in 27 -* ) prev_opt="$args" 28 dmsg "prev_opt=$prev_opt" 29 ;; 30 * ) case $prev_opt in
-*d ) ## 根据上次保存的选项做处理 31 if [ ! -d "$args" ]; then 32 args="${args%/*} ${args##*/}" ## 分离路径和文件名 33 fi
;;
* ) if [ $(expr index "$args" "/") -gt 0 ];then ## 630需求
[ ! -d "${args}" ] && args="${args%/*} ${args##*/}"
args="-d ${args}"
fi
;; 34 esac 35 dmsg "seg_b=$seg_b" 36 prev_opt="" 37 ;; 38 esac 39 seg_b="$seg_b $args" ## 4. 生成标准化项 40 done 41 42 [ -n "$seg_b" ] && parse_opt $tail $seg_b ## 5. 最终处理(代入默认ip) 43 fi 44 if [ $(expr index "$args_list" "$delim") -eq 0 ]; then ## 已到最后一项 45 let the_end=the_end+1
17 else
args_list="${args_list#*${delim}}" ## 需要处理的参数列表 46 fi 47 dmsg "the_end=$the_end" && echo 48 done ## 处理循环结束 49 }
最后
运行:tftp_multi_da "$@"
收工:exit 0
参考文献
《Unix 编程艺术》