程序的原型来自于经典的<<Linux程序设计(第3版)>>,有用Shell编程实现的版本,有用C编程实现的版本。用C编程有多种方式,有用curses库实现的,有用ndbm数据库实现的,有用MySQL数据库实现的,后来还加了简单的图形界面(GTK+或Qt编写的)。程序都比较长,用到大量的库函数和系统调用。因此,这里我重点剖析了一下用Shell编程实现的版本,并做了一点小小的修改。Shell编程实现的程序最简单,也易于理解,分析起来方便。同样的功能用C来实现时可能需要大量的代码和一大堆的库函数调用,特别在涉及到输入输出的重定向时,由于Shell可以结合很多命令并用管道重定向,因此代码可能只需几行就可以了。
程序如下(看不清的话可以让浏览器增大字符的显示):
#! /bin/bash
# CD唱片应用程序,所有的函数原型如下:
# get_return(): 获取返回字符(回车,在Linux中为换行符'/n')
# get_confirm(): 获取确认字符(y,yes或n,no等)
# set_menu_choice(): 显示菜单
# insert_title(): 插入唱片信息
# insert_track(): 插入曲目信息
# add_record_tracks(): 添加曲目记录(可一次添加多个曲目)
# add_records(): 添加或唱片记录(根据确认也可以执行删除操作)
# find_cd(): 查找并显示唱片信息
# update_cd(): 更新唱片的所有曲目
# count_cds(): 统计唱片数目和曲目总数
# remove_records(): 从数据库中删除唱片
# list_tracks(): 列出指定唱片的所有曲目
menu_choice="" # 当前选中的菜单项
current_cd="" # 用户当前选中的唱片
title_file="title.cdb" # 唱片信息的数据库文件
tracks_file="tracks.cdb" # 曲目信息的数据库文件
temp_file=/tmp/cdb.$$ # 临时文件
trap "rm -f $temp_file" EXIT # 对Ctrl+C的中断处理
# 工具型函数:获取返回字符
get_return(){
echo -e "Press return /c"
read x
return 0
}
# 工具型函数:获取确认字符
get_confirm(){
echo -e "Are you sure? /c"
while true; do
read x
case "$x" in
y | yes | Y | Yes | YES )
return 0;;
n | no | N | No | NO )
echo
echo "Cancelled"
return 1;;
*) echo "Please enter yes or no";;
esac
done
}
# 主菜单函数:当用户选中某张CD唱片后,主菜单会多出几个选项
set_menu_choice(){
clear
echo "Options :-"
echo
echo " a) Add new CD"
echo " f) Find CD"
echo " c) Count the CDs and tracks in the catalog"
if [ "$cdcatnum" != "" ]; then # 当前唱片编号不为空,即用户选中了某张CD
echo " 1) List tracks on $cdtitle"
echo " r) Remove $cdtitle"
echo " u) Update track information for $cdtitle"
fi
echo " q) Quit"
echo
echo -e "Please enter choice then press return /c"
read menu_choice # 选择菜单项,并按回车
return
}
# 插入唱片信息
insert_title(){
echo $* >> $title_file
return
}
# 插入曲目信息
insert_track(){
echo $* >> $tracks_file
return
}
# 添加曲目记录:可一次添加多个曲目
add_record_tracks(){
echo "Enter track information for this CD"
echo "When no more tracks enter q" # 有多个曲目时用q表示结束
cdtrack=1 # 当前曲目的编号
cdttitle=""
while [ "$cdttitle" != "q" ]; do
echo -e "Track $cdtrack, track title? /c"
read tmp # 读入曲目的名称
cdttitle=${tmp%%,*} # 不能有逗号:截掉逗号及后面部分
if [ "$tmp" != "$cdttitle" ]; then
echo "Sorry, no commas allowed"
continue
fi
if [ -n "$cdttitle" ]; then # 若曲目名称不空且不是结束符q
if [ "$cdttitle" != "q" ]; then
insert_track $cdcatnum,$cdtrack,$cdttitle # 插入曲目记录:唱片目录编号、曲目编号、曲目名
fi
else # 否则曲目名称为空,编号不变(这里先减1,后面还会加1)
cdtrack=$((cdtrack-1))
fi
cdtrack=$((cdtrack+1)) # 编号加1
done
}
# 添加或唱片记录(根据确认也可以执行删除操作)
add_records(){
echo -e "Enter catalog number /c"
read tmp # 读入唱片的类别编号
cdcatnum=${tmp%%,*} # 不能有逗号
echo -e "Enter title /c"
read tmp # 读入唱片标题
cdtitle=${tmp%%,*}
echo -e "Enter type /c"
read tmp # 读入唱片类型
cdtype=${tmp%%,*}
echo -e "Enter artist/composer /c"
read tmp # 读入作曲家
cdac=${tmp%%,*}
echo "About to add new entry"
echo "$cdcatnum $cdtitle $cdtype $cdac"
if get_confirm; then # 若确认为插入(y,yes),则插入唱片记录
insert_title $cdcatnu,$cdtitle,$cdtype,$cdac
add_record_tracks # 在该唱片中插入曲目
else
remove_records # 否则确认为删除(n,no),则删除唱片信息
fi
return
}
# 查找并显示唱片信息
find_cd(){
if [ "$1" = "n" ]; then
asklist=n # 表示只显示唱片信息,不显示其下的所有曲目信息
else
asklist=y # 表示显示唱片及其中的所有曲目信息
fi
cdcatnum=""
echo -e "Enter a string to search for in the CD titles /c"
read searchstr
if [ "$searchstr" = "" ]; then
return 0;
fi
grep "$searchstr" $title_file > $temp_file # 从数据库查找指定标题的唱片信息,放到temp_file文件中
set $(wc -1 $temp_file) # 统计唱片数量,wc统计文件的行数、单词数、字符数,输出的第1列就是行数信息
linefound=$1
case "$linefound" in
0) echo "Sorry, nothing found" # 没有找到唱片记录
get_return
return 0
;;
1) ;;
2) echo "Sorry, not unique." # 找到两条相同的唱片记录
echo "Found the following"
cat $temp_file
get_return
return 0
;;
esac
IFS=","
read cdcatnum cdtitle cdtype cdac < $temp_file # 读取找到的唱片信息的各个数据段
IFS=""
if [ -z "$cdcatnum" ]; then
echo "Sorry, could not extract catalog field from $temp_file"
get_return
return 0
fi
# 显示各个数据段
echo
echo "Catalog number: $cdcatnum"
echo "Title: $cdtitle"
echo "Type: $cdtype"
echo "Artist/Composer: $cdac"
echo
get_return
if [ "$asklist" = "y" ]; then
echo -e "View tracks for this CD? /c"
read x # 问是否显示该唱片下的曲目信息
if [ "$x" = "y" ]; then
echo
list_tracks
echo
fi
fi
return 1
}
# 更新唱片的所有曲目
update_cd(){
if [ -z "$cdcatnum" ]; then
echo "You must select a CD first"
find_cd n
fi
if [ -n "$cdcatnum" ]; then
echo "Current tracks are :-"
list_tracks
echo
echo "This will re-enter the tracks for $cdtitle"
get_confirm && { # 语句块,当get_confirm返回true时才会执行
grep -v "^${cdcatnum}," $tracks_file > $temp_file # 删除该唱片下的所有曲目
mv $temp_file $tracks_file
echo
add_record_tracks # 输入新的曲目
}
fi
return
}
# 统计唱片数目和曲目总数
count_cds(){
set $(wc -1 $title_file)
num_titles=$1
set $(wc -1 $tracks_file)
num_tracks=$1
echo "found $num_titles CDs, with a total of $num_tracks tracks"
get_return
return
}
# 从数据库中删除唱片
remove_records(){
if [ -z "$cdcatnum" ]; then
echo "You must select a CD first"
find_cd n
fi
if [ -n "$cdcatnum" ]; then
echo "You are about to delete $cdtitle"
get_confirm && { # 语句块:当get_confirm返回true时才会执行
# grep -v表示输出与正则表达式不匹配的行
grep -v "^${cdcatnum}," $title_file > $temp_file # 删除指定的唱片
mv $temp_file $title_file
grep -v "^${cdcatnum}," $tracks_file > $temp_file # 删除该唱片下的所有曲目
mv $temp_file $tracks_file
cdcatnum=""
echo "Entry removed"
}
get_return
fi
return
}
# 列出指定唱片的所有曲目
list_tracks(){
if [ "$cdcatnum" = "" ]; then
echo "no CD selected yet"
return
else
grep "^${cdcatnum}," $tracks_file > $temp_file
num_tracks=$(wc -1 $temp_file) # 统计指定唱片有多少曲目
if [ "$num_tracks" = "0" ]; then
echo "no tracks found for $cdtitle"
else { # 把整个语句块的输出用管道重定向到more命令
echo
echo "$cdtitle :-"
echo
cut -f2- -d, $temp_file # 列出每一行从第2个域(指定逗号为分隔符)开始的部分
echo
} | ${PAGER:-more} # 通过more命令按页输出
fi
fi
get_return
return
}
# 主程序
rm -f $temp_file
if [ ! -f $title_file ]; then
touch $title_file # 唱片数据库文件
fi
if [ ! -f $tracks_file ]; then
touch $title_file # 曲目数据库文件
fi
clear
echo
echo
echo "Mini CD manager"
sleep 1
quit=n
while [ "$quit" != "y" ]; do
set_menu_choice # 调用主菜单函数,根据输出作相应操作
case "$menu_choice" in
a) add_records;;
r) remove_records;;
f) find_cd y;;
u) update_cd;;
c) count_cds;;
l) list_tracks;;
b)
echo
more $title_file
echo
get_return;;
q | Q) quit=y;;
*) echo "Sorry, choice not recognize";;
esac
done
rm -f $temp_file
echo "Finished"
exit 0