#!/bin/bash
# Very simple example shell script for managing a CD collection.
# Copyright (C) 1996-2007 Wiley Publishing Inc.
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
# This program is distributed in the hopes that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.
# 675 Mass Ave, Cambridge, MA 02139, USA.
# The first thing to do is to ensure that some global variables that we'll be using
# throughout the script are set up. We set the title and track files and a temporary file.
# We also trap Ctrl-C, so our temporary file is removed if the user interrupts the script.
menu_choice=""
current_cd=""
title_file="title.cdb"
tracks_file="tracks.cdb"
temp_file=/tmp/cdb.$$
# 定义一个EXIT信号的处理函数
trap 'rm -f $temp_file' EXIT
# Now we define our functions, so that the script, executing from the top line, can find
# all the function definitions before we attempt to call any of them for the first time.
# To avoid rewriting the same code in several places, the first two functions are simple
# utilities.
# 用户输入的返回内容存储到x
get_return() {
echo -e "Press return \c"
read x
return 0
}
# 返回0作为正常情况 非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
}
# Here, we come to the main menu function, set_menu_choice.
# The contents of the menu vary dynamically, with extra options being added if a CD entry
# has been selected. Note that echo -e may not be portable to some shells.
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"
# 如果有选择过某个CD记录 就展示下面几个选择
if [ "$cdcatnum" != "" ]; then
echo " l) 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"
# 读取用户输入的内容保存到menu_choice中
read menu_choice
return
}
# Two more very short functions, insert_title and insert_track for adding to the database files.
# Though some people hate one-liners like these, they help make other functions clearer
# They are followed by the larger add_record_track function that uses them.
# This function uses pattern matching to ensure no commas are entered (since we're using commas
# as a field separator), and also arithmetic operations to increment the current track number
# as tracks are entered.
# 把函数的所有参数直接追加到目标文件尾部
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"
cdtrack=1
cdttitle=""
while [ "$cdttitle" != "q" ]
do
echo -e "Track $cdtrack, track title? \c"
read tmp
cdttitle=${tmp%%,*}
# 输入的内容中不能带有,号 否则截断之后2个值不等
if [ "$tmp" != "$cdttitle" ]; then
echo "Sorry, no commas allowed"
continue
fi
if [ -n "$cdttitle" ] ; then
if [ "$cdttitle" != "q" ]; then
# 插入曲目
insert_track $cdcatnum,$cdtrack,$cdttitle
fi
else
cdtrack=$((cdtrack-1))
fi
cdtrack=$((cdtrack+1))
done
}
# The add_records function allows entry of the main CD information for a new CD.
# CD 词条记录的字段为 CD的编号 CD的标题 CD的类型 这条CD存储的曲目的作者
add_records() {
# Prompt for the initial information
echo -e "Enter catalog name \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%%,*}
# Check that they want to enter the information
echo About to add new entry
echo "$cdcatnum $cdtitle $cdtype $cdac"
# If confirmed then append it to the titles file
# 根据用户的选择判断是否插入新数据还是删除
if get_confirm ; then
# 插入CD 直接写入title文件中
insert_title $cdcatnum,$cdtitle,$cdtype,$cdac
# 增加曲目
add_record_tracks
else
# 删除刚才输入的曲目数据
remove_records
fi
return
}
# The find_cd function searches for the catalog name text in the CD title file, using the
# grep command. We need to know how many times the string was found, but grep only returns
# a value telling us if it matched zero times or many. To get around this, we store the
# output in a file, which will have one line per match, then count the lines in the file.
# The word count command, wc, has whitespace in its output, separating the number of lines,
# words and characters in the file. We use the $(wc -l $temp_file) notation to extract the
# first parameter from the output to set the linesfound variable. If we wanted another,
# later parameter we would use the set command to set the shell's parameter variables to
# the command output.
# We change the IFS (Internal Field Separator) to a , (comma), so we can separate the
# comma-delimited fields. An alternative command is cut.
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
# 统计搜索到的行数
set $(wc -l $temp_file)
linesfound=$1
case "$linesfound" 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
# 展示这个cd下的所有曲目
list_tracks
echo
fi
fi
return 1
}
# update_cd allows us to re-enter information for a CD. Notice that we search (grep)
# for lines that start (^) with the $cdcatnum followed by a ,, and that we need to wrap
# the expansion of $cdcatnum in {} so we can search for a , with no whitespace between
# it and the catalogue number. This function also uses {} to enclose multiple statements
# to be executed if get_confirm returns true.
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 && {
# 把没有匹配到的行重新覆盖到原文件 实现删除某个CD的效果 然后让用户重新输入cd内容
grep -v "^${cdcatnum}," $tracks_file > $temp_file
mv $temp_file $tracks_file
echo
add_record_tracks
}
fi
return
}
# count_cds gives us a quick count of the contents of our database.
# 统计对应的行数即可
count_cds() {
set $(wc -l $title_file)
num_titles=$1
set $(wc -l $tracks_file)
num_tracks=$1
echo found $num_titles CDs, with a total of $num_tracks tracks
get_return
return
}
# remove_records strips entries from the database files, using grep -v to remove all
# matching strings. Notice we must use a temporary file.
# If we tried to do this,
# grep -v "^$cdcatnum" > $title_file
# the $title_file would be set to empty by the > output redirection before the grep
# had chance to execute, so grep would read from an empty file.
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 && {
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 again uses grep to extract the lines we want, cut to access the fields
# we want and then more to provide a paginated output. If you consider how many lines
# of C code it would take to re-implement these 20-odd lines of code, you'll appreciate
# how powerful a tool the shell can be.
list_tracks() {
if [ "$cdcatnum" = "" ]; then
echo no CD selected yet
return
else
grep "^${cdcatnum}," $tracks_file > $temp_file
num_tracks=$(wc -l $temp_file)
if [ "$num_tracks" = "0" ]; then
echo no tracks found for $cdtitle
else {
echo
echo "$cdtitle :-"
echo
# 处理每一行 -d , 以,号分割 处理从第二个开始的域 -f 2-
cut -f 2- -d , $temp_file
echo
} | ${PAGER:-more} # 没有定义pager就用more来展示
fi
fi
get_return
return
}
# Now all the functions have been defined, we can enter the main routine.
# The first few lines simply get the files into a known state, then we call the menu
# function, set_menu_choice, and act on the output.
# When quit is selected, we delete the temporary file, write a message and exit
# with a successful completion condition.
# 声明完所有变量和函数之后脚本的主体逻辑如下:
# 清除之前创建的临时文件
rm -f $temp_file
# 如果文件不存在就创建一个新title文件
if [ ! -f $title_file ]; then
touch $title_file
fi
# 如果文件不存在就创建一个新tract文件
if [ ! -f $tracks_file ]; then
touch $tracks_file
fi
# Now the application proper
# 清除屏幕输出
clear
echo
echo
echo "Mini CD manager"
sleep 1
# quit退出值默认是n 如果变成y则下面的循环终止 脚本执行结束
quit=n
while [ "$quit" != "y" ];
do
# 进入菜单选择函数
set_menu_choice
# 根据选择的项执行不同的函数
case "$menu_choice" in
# 增加一条CD记录
a) add_records;;
# 删除一条CD记录
r) remove_records;;
# 查找某个CD记录
f) find_cd y;;
# 更新某个CD记录
u) update_cd;;
# 统计多少个CD
c) count_cds;;
# 列出所有的曲目记录
l) list_tracks;;
b)
echo
more $title_file
echo
get_return;;
# 退出应用
q | Q ) quit=y;;
# 默认情况 不识别的输入
*) echo "Sorry, choice not recognized";;
esac
done
# Tidy up and leave
# 清除临时文件 退出应用
rm -f $temp_file
echo "Finished"
exit 0
参考资料:
《Linux程序设计第四版》
总结: 熟悉变量的声明、赋值、取值、格式化,函数,控制条件,其他命令,信号处理,函数返回值的使用,输入输出的使用,是一个不错的练习。