Gentoo Archives: gentoo-commits

From: "Le Zhang (r0bertz)" <r0bertz@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] gentoo commit in xml/htdocs/doc/zh_cn/articles: bash-by-example-p2.xml
Date: Wed, 10 Oct 2007 17:53:11
Message-Id: E1Iffac-0007rs-P5@stork.gentoo.org
1 r0bertz 07/10/10 17:43:22
2
3 Added: bash-by-example-p2.xml
4 Log:
5 added xorg-config.xml articles/bash-by-example-p2.xml
6
7 Revision Changes Path
8 1.1 xml/htdocs/doc/zh_cn/articles/bash-by-example-p2.xml
9
10 file : http://sources.gentoo.org/viewcvs.py/gentoo/xml/htdocs/doc/zh_cn/articles/bash-by-example-p2.xml?rev=1.1&view=markup
11 plain: http://sources.gentoo.org/viewcvs.py/gentoo/xml/htdocs/doc/zh_cn/articles/bash-by-example-p2.xml?rev=1.1&content-type=text/plain
12
13 Index: bash-by-example-p2.xml
14 ===================================================================
15 <?xml version='1.0' encoding="UTF-8"?>
16
17 <!-- $Header: /var/cvsroot/gentoo/xml/htdocs/doc/zh_cn/articles/bash-by-example-p2.xml,v 1.1 2007/10/10 17:43:22 r0bertz Exp $ -->
18 <!-- English CVS version 1.6 -->
19 <!DOCTYPE guide SYSTEM "/dtd/guide.dtd">
20
21 <guide link="/doc/zh_cn/articles/bash-by-example-p2.xml" disclaimer="articles" lang="zh_cn">
22 <title>Bash示例,第二部分</title>
23
24 <author title="作者">
25 <mail link="drobbins@g.o">Daniel Robbins</mail>
26 </author>
27
28 <author title="译者">
29 <mail link="night2008@×××××.com">苏永恒</mail>
30 </author>
31
32 <abstract>
33 在他那篇关于bash的介绍性的文章中,Daniel Robbins描述了脚本语言的一些基本概念和使用bash的原因。在本篇中,即第二部分,Daniel接着前面的讲述继续讨论bash的诸如条件(if-then)语句,循环语句等等bash的基本结构。
34 </abstract>
35
36 <!-- The original version of this article was published on IBM developerWorks,and is property of Westtech Information Services. This document is an updated version of the original article, and contains various improvements made by the Gentoo Linux Documentation team -->
37
38 <version>1.4</version>
39 <date>2005-10-09</date>
40
41 <chapter>
42 <title>更多bash编程基础</title>
43 <section>
44 <title>接收参数</title>
45 <body>
46
47 <p>
48 让我们从一个操作命令行参数的基本技巧开始,然后再看bash的基本编程结构。
49 </p>
50
51 <p>
52 在前面的<uri link="/doc/zh_cn/articles/bash-by-example-p1.xml">介绍性的文章</uri>的示例程序中。我们使用了环境变量“$1”,他引用第一个命令行参数。类似的,你可以使用“$2”,“$3”等等来引用传入你的脚本中的第二个和第三个参数。下面是一个例子:
53 </p>
54
55 <pre caption="引用传入脚本的参数">
56 #!/usr/bin/env/bash
57
58 echo name of script is $0
59 echo first argument is $1
60 echo second argumeng is $2
61 echo seventeenth argument is $17
62 echo number of argument is $#
63 </pre>
64
65 <p>
66 这个例子有两个小的细节需要解释。第一,“$0”将扩展成从命令行调用的脚本的名称,而“$#”则将扩展成传入脚本的参数的数量。试验上面的脚本,并通过传入不同类型的命令行参数理解它是怎样工作的。
67 </p>
68
69 <p>
70 有时候一次引用所有的命令行参数是很有用的。为了达到此目的,bash实现了“$@”变量,它扩展了所有用空格隔开的命令行参数。我们在稍后的“for”循环中,我们将看到使用它的例子。
71 </p>
72
73 </body>
74 </section>
75 <section>
76 <title>Bash编程结构</title>
77 <body>
78
79 <p>
80 如果你曾用过诸如C,Pascal,Python或Perl过程语言编程,那么你对像“if”语句,“for”循环和此类的标准的编程结构应该比较熟悉。对于大多数这些标准结构,Bash也有自己的版本。在下面的几段中,我将介绍一些bash结构并演示这些结构与它们在其他你所熟知的编程语言中的不同之处。
81 </p>
82
83 </body>
84 </section>
85 <section>
86 <title>方便的条件语句</title>
87 <body>
88
89 <p>
90 如果你用C语言编写过文件相关的代码,你应该知道比较一个特定的文件是否比另一个要新通常要花费很大气力。这是因为C语言没有用来执行这种比较的内建语法;那么两个stat()的调用和两个stat结构体就应该用来手工执行这种比较了。相反,bash有标准的内建文件比较操作符,所以确定“<path>/tmp/myfile</path>是否可读”和判断“<c>$myvar</c>是否大于4”一样容易。
91 </p>
92
93 <p>
94 下表中列出了大多数常用的bash比较操作符。你将还可以找到如何正确使用每一个选项的例子。例子要紧跟在“if”语句后面。例如:
95 </p>
96
97 <pre caption="Bash比较操作符">
98 if [ -z "$myvar" ]
99 then
100 echo "myvar is not defined"
101 fi
102 </pre>
103
104 <p>
105 有时一种特定的比较操作可以有很多不同的方法来实现。例如,下面的两段代码实现了同样的功能:
106 </p>
107
108 <pre caption="进行比较的两种方法">
109 if [ "$myvar" -eq 3 ]
110 then
111 echo "myvar equals 3"
112 fi
113
114 if [ "$myvar" = "3" ]
115 then
116 echo "$myvar" = "3" ]
117 then
118 echo "myvar equals 3"
119 fi
120 </pre>
121
122 <p>
123 上面两个比较做了同样的事情,但是第一个使用了算术比较操作符,然而第二个则使用了字符串比较操作符。
124 </p>
125
126 </body>
127 </section>
128 <section>
129 <title>字符串比较说明</title>
130 <body>
131
132 <p>
133 大多数时候你可以不使用括起字符串和字符串变量的双引号,但这并非是个好主意。为什么呢?因为你的环境变量刚好有个空格或制表符在里面,这时bash将不能区分。从而导致代码运行异常。这里是一个糟糕的比较的例子。
134 </p>
135
136 <pre caption="糟糕的比较的例子">
137 if [ $myvar = "foo bar oni" ]
138 then
139 echo "yes"
140 fi
141 </pre>
142
143 <p>
144 在上面的例子中,如果myvar等于“foo”,代码将正常运行而且不会打印出任何东西。然而,如果myvar等于“foo bar oni”,代码将出错并返回以下错误:
145 </p>
146
147 <pre caption="变量包含空格导致的错误">
148 [: too many arguments
149 </pre>
150
151 <p>
152 在这种情况下,“$myvar”(等于“foo bar oni”)中的空格使bash混淆了。在bash扩展了“$myvar”之后,这个比较变成了:
153 </p>
154
155 <pre caption="最终的比较">
156 [ foo bar oni = "foo bar oni"
157 </pre>
158
159 <p>
160 因为环境变量没有放在双引号中,bash认为你方括号中参数太多。你可以通过用双引号将字符串参数括起来轻松的解决这个问题。记住,如果你习惯于在所有的字符参数和环境变量都用双引号括起来,你将能解决很多类似的编程错误。“foo bar oni”比较应该这样些:
161 </p>
162
163 <pre caption="正确的比较书写的方法">
164 if [ "$myvar" = "foo bar oni" ]
165 then
166 echo "yes"
167 fi
168 </pre>
169
170 <p>
171 以上的代码将如期运行并不会有什么不快的意外结果出现。
172 </p>
173
174 <note>
175 如果你希望你的环境变量被扩展,你应该将它们括在双引号里面,而不是单引号里。单引号是禁用变量(和历史)扩展的。
176 </note>
177
178 </body>
179 </section>
180 <section>
181 <title>循环结构</title>
182 <body>
183
184 <p>
185 好,我们已经讲述了条件结构,现在浏览一下bash的循环结构。我们将从一个标准的“for”循环开始。下面是一个基本的例子:
186 </p>
187
188 <pre caption="基本的例子">
189 #!/usr/bin/env bash
190
191 for x in one two three four
192 do
193 echo number $x
194 done
195
196 <comment>输出:</comment>
197 number one
198 number two
199 number three
200 number four
201 </pre>
202
203 <p>
204 这是怎么发生的呢?在“for”循环中的“for x”部分我们定义了一个叫“$x”的新的环境变量(又称循环控制变量),它被成功的设定为值“one”,“two”,“there”和“four”。在每一次赋值之后,循环的主体(在“do”和“done”之间的代码)将立即执行。在循环主体中,与其他环境变量一样,我们使用标准的变量扩展句法来引用循环控制变量“$x”。还应该注意的是“for”循环总是接收在“in”语句之后的某种类型的字符表。在这里我们指定了四个英文单词,但是字符表也能引用也能引用磁盘文件甚至文件通配符。请看下面演示如何使用标准shell通配符的例子:
205 </p>
206
207 <pre caption="使用标准的shell通配符">
208 #!/usr/bin/env bash
209
210 for myfile in /etc/r*
211 do
212 if [ -d "myfile" ]
213 then
214 echo "$myfile (dir)"
215 else
216 echo "$myfile"
217 fi
218 done
219 输出:
220
221 /etc/rc.d (dir)
222 /etc/resolv.conf
223 /etc/resolv.conf
224 /etc/rpc
225 </pre>
226
227 <p>
228 上面的代码遍历了<path>/etc</path>下所有以“r”开头的文件。为了这么做,bash首先获取通配符/etc/r*并将它扩展,然后在执行循环之前将它用字符串<path>/etc/rc.d</path><path>/etc/resolv.conf</path> <path>/etc/resolv.conf~</path><path>/etc/rpc</path>替换。每一次循环,“-d”条件操作符就会被用来执行两个不同的操作来确定myfile是一个文件夹不是。如果是,则在输出行后加一个“(dir)”。
229 </p>
230
231 <p>
232 在字符表中我们同样可以使用多通配符甚至多环境变量:
233 </p>
234
235 <pre caption="多通配符和环境变量">
236 for x in /etc/r??? /var/lo* /home/drobbins/mystuff/* /tmp/${MYPATH}/*
237 do
238 cp $x /mnt/mydira
239 done
240 </pre>
241
242 <p>
243 Bash将在所有正确的位置上进行通配符和变量扩展,并可能建立一个很长的字符表。
244 </p>
245
246 <p>
247 虽然我们所有的通配符扩展的例子都使用绝对路径,但是你同样像下面一样可以使用相对路径:
248 </p>
249
250 <pre caption="使用相对路径">
251 for x in ../* mystuff/*
252 do
253 echo $x is a silly file
254 done
255 </pre>
256
257 <p>
258 在上例中,bash演示了相对与当前目录的通配符扩展,也即在命令行中使用相对路径。仔细研究一下通配符扩展你会发现如果你在你的通配符中使用绝对路径,bash将把你的通配符扩展成一串却对路径。相反,bash将在随后的字符序列中使用相对路径。如果你只是简单的引用当前目录下的文件(例如,如果你敲入<c>for x in *</c>),文件的结果列表中不会附加任何路径信息。记住前面的路径信息可以通过执行<c>basename</c>剥除,如下所示:
259 </p>
260
261 <pre caption="用basename剥除前面的路径">
262 for x in /var/log/*
263 do
264 echo `basename $x` is a file living in /var/log
265 done
266 </pre>
267
268 <p>
269 当然,在脚本的命令行参数上执行循环通常是很方便的。正如文章开始提到的,这是一个如何使用“$@”变量的例子:
270 </p>
271
272 <pre caption="使用$@变量的例子">
273 #!/usr/bin/env bash
274
275 for thing in "$@"
276 do
277 echo you typed ${thing}.
278 done
279
280 <comment>输出</comment>
281
282 $ allargs hello there you silly
283 you typed hello.
284 you typed three.
285 you typed you.
286 you typed silly.
287 </pre>
288
289 </body>
290 </section>
291 <section>
292 <title>Shell算术</title>
293 <body>
294
295 <p>
296 在学习第二中循环结构之前,我们最好先熟悉如何执行shell算术。是的,你可以用shell结构来执行简单的整数运算。只需将特定的算术表达式用一个“$((”和一个“))”括起来即可,然后bash将计算这个表达式。下面是例子:
297 </p>
298
299 <pre caption="bash中的计算">
300 $ <i>echo $(( 100 / 3 ))</i>
301 33
302 $ <i>myvar="56"</i>
303 $ <i>echo $(( $myvar + 12 ))</i>
304 68
305 $ <i>echo $(( $myvar - $myvar ))</i>
306 0
307 $ <i>myvar=$(( $myvar + 1 ))</i>
308 $ <i>echo $myvar</i>
309 57
310 </pre>
311
312 <p>
313 现在你已经熟悉算术操作的执行了,也是时间介绍其他两个循环结构,“while”和“until”。
314 </p>
315
316 </body>
317 </section>
318 <section>
319 <title>更多循环结构:“while”和“until”</title>
320 <body>
321
322 <p>
323 只有当特定的条件为真,“while”语句将执行,其格式如下:
324 </p>
325
326 <pre caption="While条件模板">
327 while [ condition ]
328 do
329 statements
330 done
331 </pre>
332
333 <p>
334 “While”语句通常用来循环特定次数,就如下面的例子中循环了10次:
335 </p>
336
337 <pre caption="语句循环10次">
338 myvar=0
339 while [ $myvar -ne 10 ]
340 do
341 echo $myvar
342 myvar=$(( $myvar + 1 ))
343 done
344 </pre>
345
346 <p>
347 我们可以看到算术扩展的使用导致条件语句为假,并使循环结束。
348 </p>
349
350 <p>
351 “Until”语句提供了和“While”语句相反的功能:它们不断重复执行直到一个特定的条件为假。下面是一个实现和前面“while”循环同样功能的“until”循环:
352 </p>
353
354 <pre caption="Until循环示例">
355 myvar=0
356 until [ $myvar -eq 10 ]
357 do
358 echo $myvar
359 myvar=$(( $myvar + 1 ))
360 done
361 </pre>
362
363 </body>
364 </section>
365 <section>
366 <title>Case语句</title>
367 <body>
368
369 <p>
370 “Case”语句是另外一个简便的条件结构。这是一个示例片断:
371 </p>
372
373 <pre caption="Case语句示例片断">
374 case "${x##*.}" in
375 gz)
376 gzunpack ${SROOT}/${x}
377 ;;
378 bz2)
379 bz2unpack ${SPOOT}/{x}
380 *)
381 echo "Archive format not recognized.
382 exit
383 ;;
384 esac
385 </pre>
386
387 <p>
388 上面,bash首先扩展了“${x##*.}”。在代码中,“$x”是一个文件名,而“${x##*.}”则剥去了文件名除句末除最后点号后面之外的文本。然后,bash用所得的结果与“)”后列出的值进行比较。此时,“${x##*.}”和“gz”进行比较,然后是“bz2”最后和“*”。如果“${x##*.}”与这些之中的字符串或模式比配,在“)”之后的行将马上执行,直到“;;”,然后bash就继续执行末尾“esac”之后的行。如果没有模式或字符串与之匹配,代码将不被执行;然而在这个特定的代码片断中,至少会又一个代码区域将被执行,因为“*”模式将获取任何不匹配“gz”或“bz2”的模式。
389 </p>
390
391 </body>
392 </section>
393 <section>
394 <title>函数与命名空间</title>
395 <body>
396
397 <p>
398 在bash中,和其他诸如Pascal和C等过程语言一样,你甚至可以定义函数。在bash中,函数甚至可以接收参数,使用的是一个和脚本接收命令行参数类似的系统。让我们看一个函数定义的例子,然后再从那里继续:
399 </p>
400
401 <pre caption="函数定义的例子">
402 tarview() {
403 echo -n "Displaying contents of $1 "
404 if [ ${1##*.} = tar ]
405 then
406 echo "(uncompressed tar)"
407 tar tvf $1
408 elif [ ${1##*.} =gz ]
409 then
410 echo "(gzip-compressed tar)
411 tar tzvf $1
412 elif [ ${1##*.} =bz2 ]
413 then
414 echo "(bzip2-compressed tar)"
415 cat $1 | bzip2 -d | tar tvf -
416 fi
417 }
418 </pre>
419
420 <note>
421 另外,上面的代码还可以使用一个“case”语句编写,你能把它写出来吗?
422 </note>
423
424 <p>
425 上面,我们定义了一个叫“tarview”的函数,它接收一个参数,某种类型的压缩档文件。当这个函数执行时,它判断这个参数是压缩档的哪种类型(是没有解压缩的gzip压缩档还是bzip压缩档),并打印出一行信息然后列出压缩档的目录。上面的函数应该如此调用(在敲入,粘贴或找到该函数后,可以从脚本或是命令行调用它):
426 </p>
427
428 <pre caption="调用上面的函数">
429 $ <i>tarview shorten.tar.gz</i>
430 Displaying contents of shorten.tar.gz (gzip-compressed tar)
431 drwxr-xr-x ajr/abbot 0 1999-02-27 16:17 shorten-2.3a/
432 -rw-r--r-- ajr/abbot 1143 1997-09-04 04:06 shorten-2.3a/Makefile
433 -rw-r--r-- ajr/abbot 1199 1996-02-04 12:24 shorten-2.3a/INSTALL
434 -rw-r--r-- ajr/abbot 839 1996-05-29 00:19 shorten-2.3a/LICENSE
435 ....
436 </pre>
437
438 <p>
439 正如你所见,参数可以通过使用与引用命令行参数同样的机制在函数定义内部引用。此外,“$#”宏将被扩展成包含的参数的数目。唯一可能不完全相同的是变量“$0”,它将被扩展成字符串“bash”(如果通过shell交互运行函数)或调用函数的脚本名称。
440 </p>
441
442 <note>
443 交互的使用它们:不要忘了像上面那样的函数可以放在你的~/.bashrc或~/.bash_profile中,以便你可以使用bash的任何时候都能使用它。
444 </note>
445
446 </body>
447 </section>
448 <section>
449 <title>命名空间</title>
450 <body>
451
452 <p>
453 通常状况下,你需要在函数内部建立环境变量。但同时,也有一个技巧需要知道。在大多数的编译语言(像C),当你在函数中建立变量时,它会被放置在单独的命名空间中。所以,如果你要在C中定义一个叫myfunction的函数,并定义一个叫“x”的变量,任何叫作“x”的全局(函数之外的)变量将不会受它影响,从而消除了侧面影响。
454 </p>
455
456 <p>
457 虽然这在C中是正确的,但在bash中却是不对的。在bash中,无论你是否是在函数内部定义了一个环境变量,它都会被加入到全局的命名空间中。这意味着它将覆盖函数之外的其他全局变量,甚至在函数退出后它还依然存在:
458 </p>
459
460 <pre caption="bash中的变量操纵">
461 #!/usr/bin/env bash
462
463 myvar="hello"
464
465 myfunc(){
466
467 myvar="one two three"
468 for x in $myvar
469 do
470 echo $x
471 done
472 }
473
474 myfunc
475
476 echo $myvar $x
477 </pre>
478
479 <p>
480 当这个脚本运行后,它将输出“one two three three”,从而展示了在函数中定义的“$myvar”是如何替代全局变量“$myvar”的和循环控制变量“$x”是如何在函数退出之后继续存在的(并且会替代任何之前定义的全局的“$x”)。
481 </p>
482
483 <p>
484 在这个简单的例子中,这个错误很容易可以找到,并能通过使用其它变量名来改正。然而,这不是正确的方法,解决此问题的最好方法是通过使用“local”命令,在一开始就预防影响全局变量的可能性。当我们使用“local”在函数内部创建变量时,它们将被放在局部的命名空间中,并且不会影响任何全局变量。这里演示了如何实现上述代码,以便不会覆盖全局变量:
485 </p>
486
487 <pre caption="确保没有全局变量将被覆盖">
488 #!/usr/bin/env bash
489
490 myvar="hello"
491
492 myfunc() {
493 local x
494 local myvar="one two three"
495 for x in $myvar
496 do
497 echo $x
498 done
499 }
500
501 myfunc
502
503 echo $myvar $x
504 </pre>
505
506 <p>
507 此函数将输出“hello”──没有覆盖全局变量“$myvar”,“$x”在myfunc之外也不会继续存在。在函数的第一行中,我们创建了以后要使用的局部变量x,而在第二个例子(local myvar="one two three")中,我们创建了局部变量 myvar并为它赋值。既然我们不能使用"for local x in $myvar",用第一种形式来将循环控制变量定义为局部变量是很方便的。此函数不影响任何全局变量,所以使用这种方法设计所有的函数是值得鼓励的。只有当你在明确希望要修改全局变量时,才不应该使用“local”.
508 </p>
509
510 </body>
511 </section>
512 <section>
513 <title>结语</title>
514 <body>
515
516 <p>
517 现在我们已经学了bash大部分基本功能,现在要看一下如何开发基于bash的应用程序。到时再见!
518 </p>
519
520 </body>
521 </section>
522 </chapter>
523
524 <chapter>
525 <title>资源</title>
526 <section>
527 <title>有用的链接</title>
528 <body>
529
530 <ul>
531 <li>
532 请阅读<uri link="/doc/zh_cn/articles/bash-by-example-p1.xml">Bash示例:第一部分</uri>。
533 </li>
534 <li>
535 请阅读<uri link="/doc/zh_cn/articles/bash-by-example-p3.xml">Bash示例:第三部分</uri>。
536 </li>
537 <li>
538 请访问<uri link="http://www.gnu.org/software/bash/bash.html">GNU的bash首页</uri>"
539 </li>
540 </ul>
541
542 </body>
543 </section>
544 </chapter>
545 </guide>
546
547
548
549 --
550 gentoo-commits@g.o mailing list