程序首先會執行菜單選擇程序,然後根據用戶輸入的命令,再去執行相應的函數。
# 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"
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"
read menu_choice
return
}
# Now the application proper
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 recognized";;
esac
done
# Tidy up and leave
rm -f $temp_file
echo "Finished"
exit 0
幾個重要函數的實現
1、count_cds函數
在標題文件和曲目文件中,每一項以一行存儲,所以只要統計有多少行數就可以了。程序通過命令:
set $(wc -l $title_file)
num_titles=$1
實現。
2、 find_cd函數
find函數需要一個參數,如果這個參數是y,他會在你輸入主鍵找到一個CD唱片後詢問你是否要查看這張CD的曲目。不過這個參數是固定配置的,方便程序員修改程序吧,用戶不能自己配置。
程序中用到了環境變量IFS,這裏介紹一下。
IFS(Internal Field Separator),是內部字段分隔符。
例:
它會把IFS中設置的字符替換爲空格(分隔符)。
CD成片程序中是這樣用的:
IFS=","
read cdcatnum cdtitle cdtype cdac < $temp_file
IFS=" "
temp_file文件中是以“,”號分割的字符串。
3、 list_tracks函數
# 搜索含變量cdcatnum的內容到temp_file,^代表指向一行的開頭。
grep "^${cdcatnum}," $tracks_file > $temp_file
if [ "$num_tracks" = "0" ]; then
echo no tracks found for $cdtitle
else {
echo
echo "$cdtitle :-"
echo
# 用","分隔符分割temp_file中的字符串,並輸出從第二段開始後面的
cut -f 2- -d , $temp_file
echo
# 如果PAGER沒有定義或者爲空,則返回more,否則返回PAGER。 # PAGER這個變量包含了瀏覽文件內容的程序的路徑(例如less或者more)。
} | ${PAGER:-more}
fi
4、add_record_tracks函數
while [ "$cdttitle" != "q" ]
if [ -n "$cdttitle" ] ; then
這個函數裏用的最多的是字符串的匹配,要注意的是"["的右,"]"的左,"="的左右都要留空格。
以下是整數,字符串以及文件操作的匹配符:
比較操作
|
整數操作
|
字符串操作
|
相同
|
-eq
|
=或==
|
不同
|
-ne
|
!= |
小於
|
-lt
|
\< (ASCII)
|
小於等於
|
-le
|
|
大於
|
-gt
|
\> (ASCII)
|
大於等於
|
-ge
|
|
|
|
-z (字符串爲空)
|
|
|
-n (字符串不爲空)
|
文件操作符
|
-e
|
-f
|
-s
|
-d
|
-r
|
-w
|
-x
|
說明
|
文件已經存在
|
文件是普通文件
|
文件大小不爲零
|
文件是一個目錄
|
文件對當前用戶可以讀取
|
文件對當前用戶可以寫入
|
文件對當前用戶可以執行
|
5、shell腳本的默認變量
$# 傳入腳本的命令行參數個數
$* 所有命令行參數值,在各個參數值之間留有空格(作爲單個字符串)
$0 命令本身(shell文件名)
$1 第一個命令行參數
${10} 第10個命令行參數
$? 返回值
$$ 腳本的進程ID
$@ 所有位置參數(每個作爲獨立的字符串)
CD唱片的shell實現的源碼(程序源碼來自《linux程序設計(第四版)》一書)
1 #!/bin/bash
2
3 # Very simple example shell script for managing a CD collection.
4 # Copyright (C) 1996-2007 Wiley Publishing Inc.
5
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the
8 # Free Software Foundation; either version 2 of the License, or (at your
9 # option) any later version.
10
11 # This program is distributed in the hopes that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 # Public License for more details.
15
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc.
18 # 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 # The first thing to do is to ensure that some global variables that we'll be using
21 # throughout the script are set up. We set the title and track files and a temporary file.
22 # We also trap Ctrl-C, so our temporary file is removed if the user interrupts the script.
23
24 menu_choice=""
25 current_cd=""
26 title_file="title.cdb"
27 tracks_file="tracks.cdb"
28 temp_file=/tmp/cdb.$$
29 trap 'rm -f $temp_file' EXIT
30
31 # Now we define our functions, so that the script, executing from the top line, can find
32 # all the function definitions before we attempt to call any of them for the first time.
33 # To avoid rewriting the same code in several places, the first two functions are simple
34 # utilities.
35
36 get_return() {
37 echo -e "Press return \c"
38 read x
39 return 0
40 }
41
42 get_confirm() {
43 echo -e "Are you sure? \c"
44 while true
45 do
46 read x
47 case "$x" in
48 y | yes | Y | Yes | YES )
49 return 0;;
50 n | no | N | No | NO )
51 echo
52 echo "Cancelled"
53 return 1;;
54 *) echo "Please enter yes or no" ;;
55 esac
56 done
57 }
58
59 # Here, we come to the main menu function, set_menu_choice.
60 # The contents of the menu vary dynamically, with extra options being added if a CD entry
61 # has been selected. Note that echo -e may not be portable to some shells.
62
63 set_menu_choice() {
64 clear
65 echo "Options :-"
66 echo
67 echo " a) Add new CD"
68 echo " f) Find CD"
69 echo " c) Count the CDs and tracks in the catalog"
70 if [ "$cdcatnum" != "" ]; then
71 echo " l) List tracks on $cdtitle"
72 echo " r) Remove $cdtitle"
73 echo " u) Update track information for $cdtitle"
74 fi
75 echo " q) Quit"
76 echo
77 echo -e "Please enter choice then press return \c"
78 read menu_choice
79 return
80 }
81
82 # Two more very short functions, insert_title and insert_track for adding to the database files.
83 # Though some people hate one-liners like these, they help make other functions clearer
84 # They are followed by the larger add_record_track function that uses them.
85 # This function uses pattern matching to ensure no commas are entered (since we're using commas
86 # as a field separator), and also arithmetic operations to increment the current track number
87 # as tracks are entered.
88
89 insert_title() {
90 echo $* >> $title_file
91 return
92 }
93
94 insert_track() {
95 echo $* >> $tracks_file
96 return
97 }
98
99 add_record_tracks() {
100 echo "Enter track information for this CD"
101 echo "When no more tracks enter q"
102 cdtrack=1
103 cdttitle=""
104 while [ "$cdttitle" != "q" ]
105 do
106 echo -e "Track $cdtrack, track title? \c"
107 read tmp
108 cdttitle=${tmp%%,*}
109 if [ "$tmp" != "$cdttitle" ]; then
110 echo "Sorry, no commas allowed"
111 continue
112 fi
113 if [ -n "$cdttitle" ] ; then
114 if [ "$cdttitle" != "q" ]; then
115 insert_track $cdcatnum,$cdtrack,$cdttitle
116 fi
117 else
118 cdtrack=$((cdtrack-1))
119 fi
120 cdtrack=$((cdtrack+1))
121 done
122 }
123
124 # The add_records function allows entry of the main CD information for a new CD.
125
126 add_records() {
127 # Prompt for the initial information
128
129 echo -e "Enter catalog name \c"
130 read tmp
131 cdcatnum=${tmp%%,*}
132
133 echo -e "Enter title \c"
134 read tmp
135 cdtitle=${tmp%%,*}
136
137 echo -e "Enter type \c"
138 read tmp
139 cdtype=${tmp%%,*}
140
141 echo -e "Enter artist/composer \c"
142 read tmp
143 cdac=${tmp%%,*}
144
145 # Check that they want to enter the information
146
147 echo About to add new entry
148 echo "$cdcatnum $cdtitle $cdtype $cdac"
149
150 # If confirmed then append it to the titles file
151
152 if get_confirm ; then
153 insert_title $cdcatnum,$cdtitle,$cdtype,$cdac
154 add_record_tracks
155 else
156 remove_records
157 fi
158
159 return
160 }
161
162 # The find_cd function searches for the catalog name text in the CD title file, using the
163 # grep command. We need to know how many times the string was found, but grep only returns
164 # a value telling us if it matched zero times or many. To get around this, we store the
165 # output in a file, which will have one line per match, then count the lines in the file.
166 # The word count command, wc, has whitespace in its output, separating the number of lines,
167 # words and characters in the file. We use the $(wc -l $temp_file) notation to extract the
168 # first parameter from the output to set the linesfound variable. If we wanted another,
169 # later parameter we would use the set command to set the shell's parameter variables to
170 # the command output.
171 # We change the IFS (Internal Field Separator) to a , (comma), so we can separate the
172 # comma-delimited fields. An alternative command is cut.
173
174 find_cd() {
175 if [ "$1" = "n" ]; then
176 asklist=n
177 else
178 asklist=y
179 fi
180 cdcatnum=""
181 echo -e "Enter a string to search for in the CD titles \c"
182 read searchstr
183 if [ "$searchstr" = "" ]; then
184 return 0
185 fi
186
187 grep "$searchstr" $title_file > $temp_file
188
189 set $(wc -l $temp_file)
190 linesfound=$1
191
192 case "$linesfound" in
193 0) echo "Sorry, nothing found"
194 get_return
195 return 0
196 ;;
197 1) ;;
198 2) echo "Sorry, not unique."
199 echo "Found the following"
200 cat $temp_file
201 get_return
202 return 0
203 esac
204
205 IFS=","
206 read cdcatnum cdtitle cdtype cdac < $temp_file
207 IFS=" "
208
209 if [ -z "$cdcatnum" ]; then
210 echo "Sorry, could not extract catalog field from $temp_file"
211 get_return
212 return 0
213 fi
214
215 echo
216 echo Catalog number: $cdcatnum
217 echo Title: $cdtitle
218 echo Type: $cdtype
219 echo Artist/Composer: $cdac
220 echo
221 get_return
222
223 if [ "$asklist" = "y" ]; then
224 echo -e "View tracks for this CD? \c"
225 read x
226 if [ "$x" = "y" ]; then
227 echo
228 list_tracks
229 echo
230 fi
231 fi
232 return 1
233 }
234
235 # update_cd allows us to re-enter information for a CD. Notice that we search (grep)
236 # for lines that start (^) with the $cdcatnum followed by a ,, and that we need to wrap
237 # the expansion of $cdcatnum in {} so we can search for a , with no whitespace between
238 # it and the catalogue number. This function also uses {} to enclose multiple statements
239 # to be executed if get_confirm returns true.
240
241 update_cd() {
242 if [ -z "$cdcatnum" ]; then
243 echo "You must select a CD first"
244 find_cd n
245 fi
246 if [ -n "$cdcatnum" ]; then
247 echo "Current tracks are :-"
248 list_tracks
249 echo
250 echo "This will re-enter the tracks for $cdtitle"
251 get_confirm && {
252 grep -v "^${cdcatnum}," $tracks_file > $temp_file
253 mv $temp_file $tracks_file
254 echo
255 add_record_tracks
256 }
257 fi
258 return
259 }
260
261 # count_cds gives us a quick count of the contents of our database.
262
263 count_cds() {
264 set $(wc -l $title_file)
265 num_titles=$1
266 set $(wc -l $tracks_file)
267 num_tracks=$1
268 echo found $num_titles CDs, with a total of $num_tracks tracks
269 get_return
270 return
271 }
272
273 # remove_records strips entries from the database files, using grep -v to remove all
274 # matching strings. Notice we must use a temporary file.
275 # If we tried to do this,
276 # grep -v "^$cdcatnum" > $title_file
277 # the $title_file would be set to empty by the > output redirection before the grep
278 # had chance to execute, so grep would read from an empty file.
279
280 remove_records() {
281 if [ -z "$cdcatnum" ]; then
282 echo You must select a CD first
283 find_cd n
284 fi
285 if [ -n "$cdcatnum" ]; then
286 echo "You are about to delete $cdtitle"
287 get_confirm && {
288 grep -v "^${cdcatnum}," $title_file > $temp_file
289 mv $temp_file $title_file
290 grep -v "^${cdcatnum}," $tracks_file > $temp_file
291 mv $temp_file $tracks_file
292 cdcatnum=""
293 echo Entry removed
294 }
295 get_return
296 fi
297 return
298 }
299
300 # List_tracks again uses grep to extract the lines we want, cut to access the fields
301 # we want and then more to provide a paginated output. If you consider how many lines
302 # of C code it would take to re-implement these 20-odd lines of code, you'll appreciate
303 # how powerful a tool the shell can be.
304
305 list_tracks() {
306 if [ "$cdcatnum" = "" ]; then
307 echo no CD selected yet
308 return
309 else
310 grep "^${cdcatnum}," $tracks_file > $temp_file
311 num_tracks=$(wc -l $temp_file)
312 if [ "$num_tracks" = "0" ]; then
313 echo no tracks found for $cdtitle
314 else {
315 echo
316 echo "$cdtitle :-"
317 echo
318 cut -f 2- -d , $temp_file
319 echo
320 } | ${PAGER:-more}
321 fi
322 fi
323 get_return
324 return
325 }
326
327 # Now all the functions have been defined, we can enter the main routine.
328 # The first few lines simply get the files into a known state, then we call the menu
329 # function, set_menu_choice, and act on the output.
330 # When quit is selected, we delete the temporary file, write a message and exit
331 # with a successful completion condition.
332
333 rm -f $temp_file
334 if [ ! -f $title_file ]; then
335 touch $title_file
336 fi
337 if [ ! -f $tracks_file ]; then
338 touch $tracks_file
339 fi
340
341 # Now the application proper
342
343 clear
344 echo
345 echo
346 echo "Mini CD manager"
347 sleep 1
348
349 quit=n
350 while [ "$quit" != "y" ];
351 do
352 set_menu_choice
353 case "$menu_choice" in
354 a) add_records;;
355 r) remove_records;;
356 f) find_cd y;;
357 u) update_cd;;
358 c) count_cds;;
359 l) list_tracks;;
360 b)
361 echo
362 more $title_file
363 echo
364 get_return;;
365 q | Q ) quit=y;;
366 *) echo "Sorry, choice not recognized";;
367 esac
368 done
369
370 # Tidy up and leave
371
372 rm -f $temp_file
373 echo "Finished"
374
375 exit 0