学习 Dvdwizard 源码

2010年12月26日 星期日
/usr/local/share/dvdwizard/dvdwizardrc

包括异常处理、创建按纽、建立目录和文件、清除临时目录等通用函数,关注的地方:
1、处理异常:

error_out()
{
	cat << EOF
信息提示
EOF
	if [ -e "$LOGFILE" ]; then
		cat << EOF
Here are the last 10 lines of the log ($LOGFILE):
-------------------------------------------------------
EOF
		tail -n 10 "$LOGFILE"
	fi
	
	exit 1
} >&2

包括 here 文档, 函数的输出重定向至标准错误输出

2、break N 跳出循环似乎包括 if... then ... fi 结构

	while [  $i -lt $# ]; do
	   	let "i+=1"
	    eval passed_arg=\${$i}
	    if [ "$passed_arg" == "-C" -o "$passed_arg" == "--config-file" ]; then
	    	let "i+=1"
		    eval confFile=\${$i}
	     	if [ -f "$confFile" -a -r "$confFile" ]; then
	      		:
	        else
	        	echo "Specified Config-File $confFile not found or no read permission. Aborting" >&2
	         	exit 1
	        fi
			break 2
	  	fi
	done

3、if 块、循环块等重定向的使用

	if [ "$IM_VERSION3" \< "6.3.5" ]; then
		echo "dvdwizard NEEDS at least ImageMagick Version 6.3.5 or higher"
	    echo "See $thisscript -h for more infos"
	    exit 1
	fi >&2
	for c in $TRANSLUCENT $TITLECOLOR $HEADCOLOR $TEXTCOLOR $NORMCOLOR $HICOLOR $SELCOLOR $ACTCOLOR; do
		if [ "${c:0:1}" == "#" -o "${c:0:3}" == "rgb" ]; then
			:
		else
			echo "$colorlist" | grep ^"${c}"$ >/dev/null
			if [ $? -ne 0 ]; then
				echo "Invalid color $c specified. Please check config file."
				exit 1
			fi
		fi 
	done >&2

4、eval 的大量使用增加程序的灵活性
eval maxtxt=\"\$txt_$i\"

用法:这里$i 是个变量,如果不用 eval 命令,像这样: maxtxt=$txt_$i ,maxtxt 的值为$txt_变量的值(这里没有这个变量,所以值是空字符串)联接 $i 的值,那么 maxtxt 的值就只等于 $i 的值了。如: i=2; txt_2=test; echo $txt_$i # 2 而不是 test 了。加了 eval 后,会先计算 $i 的值是2,那么最后的结果就是 $txt_2 等于 test 了。

疑问:
能不能用面向对象的思维用 Bash 脚本编程呢?

如 Operator.sh
{
var1;
var2;
init();
do1();
do2();
destroy();
}

在主程序中调用这个 operator.sh

operator1=new operator.sh;
operator1.do1();
operator1.do2();

这样做有没有什么好处呢?有没有什么坏处呢?有必要吗?可以试试,把 dvdwizard 里面的这个通用函数库作成一个对象的类,里面的函数就是对象可以进行的操作,初始化就是读入配置文件,初始化各种变量;注销就是删除临时目录文件等后续操作。
当然,像真正的面向对象语言里面的继承、重载什么的就不要去尝试了。只使用单一的类来试。
参考资料: A New Object-Oriented Programming Language: sh https://www.usenix.org/publications/library/proceedings/bos94/full_papers/haemer.a

/usr/local/bin/dvdwizard
结构:
定义一些内部的函数,调用其它功能脚本和通用脚本(dvdwizardrc)里的函数
主要处理流程

关注的部分:
1、

	if ! let x=$LOOP+0 ; then
		if [ ! $LOOP -eq 0 ]; then
			echo "Playback mode $LOOP (-L|--loop \$LOOP) is not numeric"
			error_out
		fi
	fi

这里 if let x=$a ; 是用算术结果进行测试,如果 x=0 则测试为假,否则为真,但是如果没有 let 这个代表算术赋值的语句,不管x=0还是多少,都是当作字符串处理,除非 x 为空字符串,测试都为真。

2、
把目录下的文件名作为元素封装入一个数组中
eval cshots=( $(ls -QS "$BASEDIR/cpics/VTS$i") )

3、
写入分行字符串进入文本文件的用法(here 文档重定向到文件)

 cat << EOF >> "$XMLFILE"
        
           
          
            
              
               call vmgm menu entry title; 
            
          
        
EOF

4、
指定优先级别运行程序同时记录日志和处理错误
nice -n $NICE dvdauthor -x "$XMLFILE" >> "$LOGFILE" 2>&1 || error_out

5、
读取大量参数的用法:

while [ -n "$*" ]; do
    case "$1" in
  		-C|--config-file)
	    	shift
			# -C and it's following parm already processed in set_defaults()
    	    shift
	  	;;
		-o|--output)
 	    	shift
	    	DESTDIR="$1"
	    	shift
  		;;
...
    esac
done

因为命令调用的格式是 dvdwizard -C /path/to/configfile -o /path/to/outputfile ... 这样的形式,所以 shift 一次去掉参数选项,剩下的参数值读入变量,读入后,再 shift 一次去掉已经读了的参数值。

6、好像是一个重启进行过程中某一步骤的方法,有需要时再详细学习

case "$restart" in
	dvdcpics)
		call_dvdcpics
	    call_mk_vmgm
    	call_mk_vtsm
	    call_author
	;;
	mk_vmgm)
		call_dvdcpics_upd
		call_mk_vmgm
	    call_mk_vtsm
    	call_author
	;;
	mk_vtsm)
		call_dvdcpics_upd
		call_mk_vtsm
	    call_author
	;;
...

从这个脚本可以看出,非面向对象的写法确实难以控制代码量比较大的程序,如这个 dvdwizard 里面读入参数的脚本就用了 1078-754=324 行代码!而且,我看其中获取视频片断信息的代码出现在了这个主脚本中,同时也有一个专门获取片断信息的脚本 mpgprobe ,为什么不用一个类比如叫作 detector 的,要获取片断信息时统一叫这个 detector 的对象去做然后报告相关的信息。

所以,对于大型的 shell 脚本程序,可以尝试用面向对象的方式来编程,应该可以增加代码的可读性和程序的健壮性的。尽量使用短脚本来完成特定的任务。

然后,从# Main Processing 开始,主程序调用的脚本文件依次是:
check_tools(dvdwizardrc)
checkParms(dvdwizard)
mk_tmpdvd(dvdwizardrc),里面调用了chaptercheck 脚本
然后依次是调用dvdwizard 里面的函数
call_dvdcpics(dvdcpics)
call_mk_vmgm(mk_vmgm)
call_mk_vtsm(mk_vtsm)
call_author (dvdwizard)
cleanup_tmpdir(dvdwizardrc)

可以看出,里面的调用关系比较混乱,脚本之间互相调用,主控脚本里有的调用自身的函数,有的调用脚本,等等。耦合度太高。应该可以有办法把这些脚本用面向对象的方式好好组织一下。

大概把 dvdwizard 的源码浏览了一下,里面的具体用法没有细看,以后有必要时再参考。

A New Object-Oriented Programming Language: sh https://www.usenix.org/publications/library/proceedings/bos94/full_papers/haemer.a
浏览了一下,不太明白,以后有需要时再参考。

注意:把用 Shell 写的程序封装得好一点,耦合度小一些,每个脚本完成的功能单一一些,维护容易一些。用面向对象的思维方式来写 Shell 脚本。