您的位置:首页 > 运维架构 > Shell

一段评价程序平均运行时间的shell脚本&shell脚本编写注意事项

2012-06-02 11:56 645 查看
#!/bin/bash

usage()
{
echo "$0 -c command [-r repeat time]"
}

# average_time head_match_string filename
average_time()
{

head=$1
filename=$2
minutes=`sed -n "s/^$head.* \([0-9.]\+\)m.*/\1/p" $filename`
minute=0
for i in $minutes
do
minute=`echo "scale=5;$minute+$i" | bc`
done

seconds=`sed -n "s/^$head.*m\([0-9.]\+\)s/\1/p" $filename`
second=0
for i in $seconds
do
second=`echo "scale=5;$second+$i" | bc`
done

second=`echo "scale=3;(($minute*60+$second)/($repeat_time*1.0))" | bc`

second=`echo $second | sed 's/^\./0&/'`
second=`echo $second | sed 's/$/s/'`

echo $second

}

command="x"
repeat_time="10"

while [ $# -gt 0 ]
do
case $1 in
-c)        command=$2
shift
;;
-h)        usage
exit 0
;;
-r)        repeat_time=$2
shift
;;
*)        echo unexpected arg $1
exit 1
esac

shift
done

if `test "$command" == 'x'`; then
usage
exit 1
fi

rm -rf time.temp

i=0
while [ $i -lt $repeat_time ]
do
(eval "time "$command) 2>>time.temp
i=$((i+1))
done

real_average_time=`average_time "real" "time.temp"`
user_average_time=`average_time "user" "time.temp"`
sys_average_time=`average_time "sys" "time.temp"`

echo "repeate command '$command' for $repeat_time times"
echo "average_real_time = $real_average_time"
echo "average_user_time = $user_average_time"
echo "average_sys_time = $sys_average_time"


很久没有写shell脚本了。现在流行的是python,perl,甚至ruby。脚本语言的语法更简单明了,字符串操作,正则表达式操作,数值计算,shell可以说是样样比不上。shell的用法晦涩难懂,又缺乏很全面且可以快速定位的文档。即便写这段小小的代码,也花了我两个小时的时间,一半时间在google,一半时间在testing。

这段程序的功能正如标题所说,是重复运行一个程序,用time记录其每次运行的时间,最后计算一个平均时间。不论实际如何,至少从理论上来说,平均值更为合理一些。

程序虽短,但我却遇到了shell编程的种种障碍和需要注意的地方。这里总结一下:

1. 首行用#!/bin/bash而非#!/bin/sh。因为shell有许多中,如ubuntu中sh其实是dash(一个比bash更轻便快捷的shell,相应的,它的功能要比bash少很多)。把执行shell统一成流行的bash,而非任系统摆布,大概是我唯一能做的脚本可移植性方面的努力(^_^)!。

2. 变量赋值是两边都要紧贴等号。比如"a=1"。如果你想学其它语言那样增加一下可读性,写成了"a = 1”,那等待你的必将是漫长的debug过程。

3. 熟悉条件循环控制语句,函数写法,命令行参数传递等基本语法规则,或者尽可能按照书本或文档中的样例来写。在shell中,for语句和while语句的功能区分很大,一个是对数组遍历,一个是通用的循环控制,后者更常用些。函数的返回值用return传递,但只能传整数,见鬼,还是echo出来,传得更方便些。

4. 数值计算。如果你只需要整数的,用expr就可以了,expr可以简写成类似$((a+b))的形式。如果需要更强大的,那就用bc命令吧。scale=4是设置小数点后保留4位,如果不设依然是整数。bc的返回值如果在(-1, 1) 范围内,通常把小数点前的0省去,这个还是在外部自己加吧。粗略看过bc的文档,发现它已经悄然发展出了一个编程语言的雏形,有各种控制语句,函数等等。bc的作者真是野心勃勃啊!

5. 字符串和正则表达式处理。简单的字符串操作就不提了,可以用shell内置语法来解决。复杂一些的,shell找来了3个帮手:grep, sed, awk。grep只能帮你找到你所需的行,至于更精确的,抱歉。awk的功能强大,但它是基于字段处理的,而且对于我这样的菜鸟来说,也太复杂了些。sed则是一个好帮手,虽然我没有像perl或者python中那样灵活地操纵正则表达式去匹配,但从上面的程序中可以看出,虽然无法提取出匹配的内容,但可以将字符串中不匹配的内容全部替换掉,这也不失为一种曲线救国的策略。用info
sed命令可以看到详细的sed帮助,我建议应尽快定位到它正则表达式的一节,否则sed独具特色的正则表达式编写方法一定会让你焦头烂额的(没有\d, \w,匹配一到多个字符用\+,匹配内容用\( \)圈起来,等等)

6. 执行变量中的命令。如果你的命令是用参数的形式传进来的,它一定是放在一个变量中。你想要执行存放在变量中的命令,比如$command。直接在一行的开头写上$command?,大多数时候可以,但我的经验是如果$command中有对标准输入输出的重定向,重定向的文件会打不开。不论原因如何,解决方案是用eval来执行$command中的命令。这是个不错而优雅的方法。

横观几种脚本语言。perl坚持了它在创建之初对正则表达式和结构化编程的追求,像一位纯洁专业的修道士;python则是在兼具了底层操作与面向对象的特点,号称用对象封装了一切的代码复杂性(以及罪恶),支持代码的无限扩展,像一位野心勃勃的多面手;而shell,则坚持了它简单古板的语法,而且麾下一众小弟(bc,sed,awk等等),像一位古板讲道义的黑道大哥。不管如何,shell脚本是伴随unix和linux成长起来的,使用越多,就越感受到unix的思想(管道,众多的小命令,文本,等等)。如果习惯了shell脚本,相信它编写起来要比其它脚本语言更为简单方便;但如果确实不适应,用perl和python等脚本语言也无妨,关键是更快更好地完成任务。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: