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-p1.xml
Date: Sat, 06 Oct 2007 17:41:43
Message-Id: E1IeDVg-0000mf-HJ@stork.gentoo.org
1 r0bertz 07/10/06 17:32:16
2
3 Added: bash-by-example-p1.xml
4 Log:
5 added doc/zh_cn/articles/bash-by-example-p1.xml
6
7 Revision Changes Path
8 1.1 xml/htdocs/doc/zh_cn/articles/bash-by-example-p1.xml
9
10 file : http://sources.gentoo.org/viewcvs.py/gentoo/xml/htdocs/doc/zh_cn/articles/bash-by-example-p1.xml?rev=1.1&view=markup
11 plain: http://sources.gentoo.org/viewcvs.py/gentoo/xml/htdocs/doc/zh_cn/articles/bash-by-example-p1.xml?rev=1.1&content-type=text/plain
12
13 Index: bash-by-example-p1.xml
14 ===================================================================
15 <?xml version='1.0' encoding="UTF-8"?>
16
17 <!-- $Header -->
18 <!-- English CVS version 1.7 -->
19 <!DOCTYPE guide SYSTEM "/dtd/guide.dtd">
20
21 <guide link="/doc/zh_cn/articles/bash-by-example-p1.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 <author title="译者">
28 <mail link="night2008@×××××.com">苏永恒</mail>
29 </author>
30 <abstract>
31 通过学习如何运用bash脚本语言进行编程,你每天和linux的交互将变的更有意思和有生产力,同时你也将能够构建那些你所知和喜欢的标准UNIX概念(诸如管道和重定向)。在这三部分的系列中,Daniel Robbins将教你如何通过bash例程进行编程。他将讲述非常基本的内容(这使它成为入门者优秀的系列)并在后续的系列中引入更加高级的特征。
32 </abstract>
33
34 <!-- The original version of this article was published on IBM developerWorks,
35 and is property of Westtech Information Services. This document is an updated
36 version of the original article, and contains various improvements made by the
37 Gentoo Linux Documentation team -->
38 <version>1.3</version>
39 <date>2005-10-09</date>
40
41 <chapter>
42 <title>Bourne again shell(bash)基础编程</title>
43 <section>
44 <title>简介</title>
45 <body>
46
47 <p>
48 你可能会奇怪为什么你应该学习Bash编程。那么,这里有两个令人信服的理由:
49 </p>
50
51 </body>
52 </section>
53 <section>
54 <title>你已经开始运行它了</title>
55 <body>
56
57 <p>
58 如果检查一下,你可能将会发现你现在正在运行bash。即使你改变了你默认的shell,bash也可能仍在你系统的某个地方运行,因为它是标准的Linux shell并被用做多种用途。由于bash已经运行,你运行的任何附加的bash脚本拥有固有的内存高效性,这是因为它们和任何已经运行的bash进程共享内存。如果你已经运行了些东西来做这项工作,并且做的很好,那还为什么要载入一个500K的解释器呢?
59 </p>
60
61 </body>
62 </section>
63 <section>
64 <title>你正在使用它</title>
65 <body>
66
67 <p>
68 不仅是因为你已经运行了bash,另外你还在每天的基本工作中与bash进行交互。它就在那里,所以学习如何最大限度的使用它就变得很有意义。这么做将是你的bash之旅更加有意思和具有创制性。但是为什么你应该学习bash编程呢?很简单,因为你已经在思考运行命令,复制粘贴文件,以及管道和重定向输出。那你是否应该学习这种语言,以便使用和利用那些已熟悉和喜爱的强大省时的概念呢?命令shell开启了UNIX系统的潜能,而bash就是Linux shell。它是你和机器之间的高等级的纽带。随着你的bash知识的增长,同时你将能自动的增加你在Linux和UNIX下的生产力──就是这么简单。
69 </p>
70
71 </body>
72 </section>
73 <section>
74 <title>Bash的困惑</title>
75 <body>
76
77 <p>
78 通过错误的方法学习bash会是一个非常困惑的过程。很多新手敲入<c>man bash</c>来查看bash的man页,但令人头疼的是只面临一些shell函数特别简明和科技性的解释。其他人通过敲入<c>info bash</c>(来查看GNU的信息文档),但可能是man页的翻版,也可能(如果他们幸运的话)仅是一些稍微友好的信息文档出现。
79 </p>
80
81 <p>
82 然而这些对于初学者来说在一定程度上是令人沮丧的,标准的bash文档不可能是适合于每一个人的所有东西和迎合所有已经对一般的shell编程十分熟悉的人。毋庸置疑的,在man页上有很多优秀的技术性的信息,但是它对于初学者来说帮助是有限的。
83 </p>
84
85 <p>
86 这是这个系列存在的原因。在它里面,我将向你展示如何有效的使用bash编程结构,以便你能够编写你自己的脚本。与科技性的说明不同的是,我将通过易懂的英文向你们讲解,使你不仅知道这是什么,而且知道你应该在什么时候有效的使用它。到这个三部分的系列之后,你将能够编写你自己的精巧的bash脚本,并能够达到一个能够很舒服的使用bash和通过读(和理解)标准的bash文档来增进你的知识的层次上。那就让我们开始吧!
87 </p>
88
89 </body>
90 </section>
91 <section>
92 <title>环境变量</title>
93 <body>
94
95 <p>
96 在bash和几乎所有其他shell下,用户可以定义环境变量,它们被以ASCII字符的形式存储在内部。环境变量最有用的地方在于它们是UNIX作业模型中的一个标准部分。这意味着环境变量并非是shell脚本所独有,而是同样可以为标准的编译程序所使用。当我们在bash下"export"一个环境变量,任何之后我们运行的程序都可以读取我们的设定,而不管它是否是不是一个shell脚本。一个很好的例子是<c>vipw</c>命令,它通常允许root去编辑系统密码文件。通过设定<c>EDITER</c>环境变量来命名你喜欢的文本编辑器,你可以设定vipw去使用而不是vi,特别是如果你习惯于xemacs而又实在讨厌vi。
97 </p>
98
99 <p>
100 在bash下定义一个环境变量的标准方法是:
101 </p>
102
103 <pre caption="定义环境变量">
104 $ <i>myvar='This is my environment variable!'</i>
105 </pre>
106
107 <p>
108 上面的命令定义了一个叫"myvar"的环境变量并包含"This is my environment variable!"字符串。上面有很多是需要我们注意的:首先,在"="号两侧没有空格,任何空格都将导致一个错误产生(可以试一下看看)。第二要注意的是虽然在定义一个词时我们可以省略引号,但当环境变量的值多于一个词(包含空格或制表符)时,引号却是必须的。
109 </p>
110
111 <note>
112 要想获得关于如何在bash中使用引号的更加详尽的信息,请参考bash man页中"QUOTING"一节。特殊字符序列由其它值"扩展"(替换)确实使 bash 中字符串的处理变得复杂。本系列将只讲述最常用的引用功能。
113 </note>
114
115 <p>
116 第三,当我们通常可以用双引号代替单引号,在上面的例子中这样做会导致一个错误。为什么呢?因为用单引号禁用了一个bash称为扩展的特性,其中特殊字符和字符序列的值可以互换。比如,"!"字符是历史扩展字符,它通常由之前敲入的命令所替换。(在这个系列的文章中,我们不能讲述历史扩展,那是在我们bash编程中不常用到它。要获得相关的更多信息,请看bash man页中的"HISTORY EXPANSION"部分。)尽管这个类似于宏的功能很便利,但我们现在只想在环境变量后面加上一个简单的感叹号,而不是宏。
117 </p>
118
119 <p>
120 现在让我们看一下一个人如何运用环境变量。下面是例子:
121 </p>
122
123 <pre caption="运用环境变量">
124 $ <i>echo $myvar</i>
125 This is my environment variable!
126 </pre>
127
128 <p>
129 通过在我们的环境变量前加上一个$,我们可以使bash来用myvar的值来取代它。在bash的术语中,这被成为"变量扩展"。但是,如果我们试过下面的会怎样:
130 </p>
131
132 <pre caption="首先使用变量扩展">
133 $ <i>echo foo$myvarbar</i>
134 foo
135 </pre>
136
137 <p>
138 我们希望这能echo "fooThis is my environment variable!bar",但是它却没有起作用。哪里出错了呢?简单的说,bash的变量扩展很容易让人迷惑。它不能判别我们要扩展的变量是$m,$my,$myvar,$myvarbar等等。我们如何才能明确的清楚的告诉bash我们提到的是哪个变量呢?请试一下这个:
139 </p>
140
141 <pre caption="合适的变量扩展">
142 $ <i>echo foo${myvar}bar</i>
143 fooThis is my environment variable!bar
144 </pre>
145
146 <p>
147 你可以看到,当环境变量名没有很清晰的和周围的文本分开时,我们可以把它们放在一对括号内。然而$myvar可以很快的打出来并在大部分时候都能工作,${myvar}在几乎所有的情况下都能正确的解析出来。除此之外,它们都做同样的事情,在以下的这个系列中这两种形式的变量扩展你将都能看到。当你的环境变量没有和你周围的文本用空白(空格或制表符)分开时,你将希望记住使用更加复杂的带括号的形式。
148 </p>
149
150 <p>
151 还记的我们还提到过我们可以"导出"变量。当我们导出一个环境变量时,它将自动的可以在任何随后执行或可执行的脚本中使用。Shell脚本可以运用shell的内建环境变量支持"获取"环境变量,而C程序可以运用getenv()函数来调用。这里是一些你应该敲入并编译的C代码的例子──它都可以使我们从C的视角来理解环境变量:
152 </p>
153
154 <pre caption="myvar.c──一个C程序环境变量的例子">
155 #include &lt;stdio.h&gt;
156 #include &lt;stdlib.h&gt;
157
158 int main(void) {
159 char *myenvvar=getenv("EDITOR");
160 printf("The editor environment variable is set to %s\n",myenvvar);
161 }
162 </pre>
163
164 <p>
165 保存上面的源代码到一个叫<path>myenv.c</path>的文件,然后通过发出下面指令编译它:
166 </p>
167
168 <pre caption="编译上面的源代码">
169 $ <i>gcc myenv.c -o myenv</i>
170 </pre>
171
172 <p>
173 现在在你的文件夹里将有一个可执行的程序,当你运行它时,如果有,将打印出<c>EDITOR</c>的环境变量值。这是当我在我的机器上运行它时的结果:
174 </p>
175
176 <pre caption="运行上面的程序">
177 $ <i>./myenv</i>
178 The editor environment variable is set to (null)
179 </pre>
180
181 <p>
182 Hmmm...因为环境变量<c>EDITOR</c>没有设定任何值,C程序得到的是一个空字符串。让我们试着将它设为一个特定的值:
183 </p>
184
185 <pre caption="用特定的值试验">
186 $ <i>EDITOR=xemacs</i>
187 $ <i>./myenv</i>
188 编辑器的环境变量被设为(空)
189 </pre>
190
191 <p>
192 然而你可能会希望myenv会打印值"xemacs",它并非很管用,因为我们没有export<c>EDITOR</c>环境变量。这次,我们将让它起作用:
193 </p>
194
195 <pre caption="同样的程序在导出变量后">
196 $ <i>export EDITOR</i>
197 $ <i>./myenv</i>
198 编辑器的环境变量被设为xemacs
199 </pre>
200
201 <p>
202 因此,你已经用自己的眼睛看到另外一种方法(正如我们的C程序例子)直到环境变量被导出才能看到它。顺便一提,如果你喜欢,你可以用一行定义和输出一个环境变量,就像下面一样:
203 </p>
204
205 <pre caption="用一个命令定义和导出环境变量">
206 $ <i>export EDITOR=xemacs</i>
207 </pre>
208
209 <p>
210 它的工作和两行的版本是一样的。这是一个好的时机去展示如何用<c>unset</c>去除一个环境变量。
211 </p>
212
213 <pre caption="去除变量">
214 $ <i>unset EDITOR</i>
215 $ <i>./myenv</i>
216
217 </pre>
218
219 </body>
220 </section>
221 <section>
222 <title>拆分字符串综述</title>
223 <body>
224
225 <p>
226 拆分字符串──它是将原始字符串拆分成小一点的,切片──是一个在你平常每天的shell脚本执行中的任务。很多时候,shell脚本需要采用全限定路径,并找到结束的文件或目录。虽然可以用bash编码实现(而且有趣),但标准<c>basename</c> UNIX可执行程序可以极好地完成此工作:
227 </p>
228
229 <pre caption="运用basename">
230 $ <i>basename /usr/local/share/doc/foo/foo.txt</i>
231 foo.txt
232 $ <i>basename /usr/home/drobbins</i>
233 drobbins
234 </pre>
235
236 <p>
237 <c>basename</c>是一个拆分字符串的及其简便的工具。与它相关的被称为<c>dirname</c>返回<c>basename</c>丢弃的"另"一部分路径。
238 </p>
239
240 <pre caption="运用dirname">
241 $ <i>dirname /usr/local/share/doc/foo/foo.txt</i>
242 /usr/local/share/doc/foo
243 $ <i>dirname /usr/home/drobbins/</i>
244 /usr/home
245 </pre>
246
247 <note>
248 <c>dirname</c>和<c>basename</c>都不考虑磁盘上的任何文件和文件夹;它们是纯粹的字符串操作命令。
249 </note>
250
251 </body>
252 </section>
253 <section>
254 <title>命令替换</title>
255 <body>
256
257 <p>
258 需要知道一个很简便的操作,如何创建一个包含可执行命令结果的环境变量。这是很容易做的:
259 </p>
260
261 <pre caption="创建一个包含命令结果的环境变量">
262 $ <i>MYDIR=`dirname /usr/local/share/doc/foo/foo.txt`</i>
263 $ <i>echo $MYDIR</i>
264 /usr/local/share/doc/foo
265 </pre>
266
267 <p>
268 我们上面所做的被称为<e>命令替换</e>。在这个例子中有很多值得注意的。在第一行,我们简单的将命令用反引号括起。这些不是标准的单引号,而是通常在键盘上位于Tab键之上的单引号。我们用bash的交互命令语法来做同样的事情:
269 </p>
270
271 <pre caption="交互命令替换语法">
272 $ <i>MYDIR=$(dirname /usr/local/share/doc/foo/foo.txt)</i>
273 $ <i>echo $MYDIR</i>
274 /usr/local/share/doc/foo
275 </pre>
276
277 <p>
278 正如我们所见,bash提供了很多执行相同操作的方法。运用命令替换,我们可以将任何命令和命令管道放在<e>` `</e>或<e>$( )</e>之间并把它指定为环境变量。多方便啊!这里是一个如何在命令替换中使用管道的例子:
279 </p>
280
281 <pre caption="管道命令替换">
282 $ <i>MYFILES=$(ls /etc/| grep pa)</i>
283 $ <i>echo $MYFILES</i>
284 pam.d passwd
285 </pre>
286
287 </body>
288 </section>
289 <section>
290 <title>像个专家一样拆分字符串</title>
291 <body>
292
293 <p>
294 尽管<c>basename</c>和<c>dirname</c>是很棒的工具,但我们可能时常需要更加专业的字符串"拆分"操作而不是仅仅的标准的路径名处理。当我们需要更强的说服力时,我们可以利用bash内建的变量扩展功能。我们已经使用了标准的变量扩展,就像:${MYVAR}。但是bash还可以自己执行一些便利的字符串拆分。请看一下这些例子:
295 </p>
296
297 <pre caption="字符串拆分示例">
298 $ <i>MYVAR=foodforthought.jpg</i>
299 $ <i>echo ${MYVAR##*fo}</i>
300 rthought.jpg
301 $ <i>echo ${MYVAR#*fo}</i>
302 odforthought.jpg
303 </pre>
304
305 <p>
306 在第一个例子中,我们敲入${MYVAR##*fo}。那么它确切的意思是什么呢?基本上,在${ }中,我们敲入环境变量的名字,两个##和一个通配符("*fo")。然后,bash获取<c>MYVAR</c>,从字符串"foodforthought.jpg"的起始处查找匹配通配符"*fo"的最长的子串并将其从字符串的开始处拆分。刚开始这有一点难以掌握,为了感受一下这个特殊的"##"选项是如何工作的,让我们一步步探讨bash是如何完成这个扩展的。首先,它从"foodforthought.jpg的开始处开始搜索匹配"*fo"通配符的子串。这里是它检查到的子串:
307 </p>
308
309 <pre caption="子串检查">
310 f
311 fo MATCHES *fo
312 foo
313 food
314 foodf
315 foodfo MATCHES *fo
316 foodfor
317 foodfort
318 foodforth
319 foodfortho
320 foodforthou
321 foodforthoug
322 foodforthought
323 foodforthought.j
324 foodforthought.jp
325 foodforthought.jpg
326 </pre>
327
328 <p>
329 寻找到匹配的字符串后,你可以看到bash找到两个。它选择了最长匹配,从原始字符串的开始处出去,然后返回结果。
330 </p>
331
332 <p>
333 上面的第二种形式的变量扩展和第一种表现的完全相同,只是它只运用一个"#"──并且bash执行了一个几乎相同的过程。正如我们的第一个例子一样,它检查乐同样的一些子串,除了bash从我们的原始字符串中移除了最短匹配并返回结果。所以,它一检查到"fo"子串,它就从我们的字符串中移除"fo"并返回"odforthought.jpg"。
334 </p>
335
336 <p>
337 这看起来很神秘,所以我们将向你展示一个简单的方法去记住这个功能。当搜索最长匹配时,运用##(因为##比#要长)。当搜索最短匹配时,用#。看,也并不是那么难去记忆!等等,怎样记住应该使用'#'字符来从字符串起始处移除呢?很简单!注意一下在US键盘上,shift-4是"$",它是bash变量扩展字符。在键盘上,紧靠在"$"左边是"#"。所以,你可以看到"#"是在"$"的"起始处的",因此(根据我们的记忆法),"#"从字符串的起始处移除字符。你可能还想知道如何从字符串的末尾移除字符。如果猜到我们用的字符是US键盘上紧靠在"$"右边的"%",正确!这里有几个简单的例子来解释如何拆分字符串的末尾部分:
338 </p>
339
340 <pre caption="">
341 $ <i>MYFOO="chickensoup.tar.gz"</i>
342 $ <i>echo ${MYFOO%%.*}</i>
343 chickensoup
344 $ <i>echo ${MYFOO%.*}</i>
345 chickensoup.tar
346 </pre>
347
348 <p>
349 正如你所见,除了将匹配通配符从字符串末尾去除之外,%和%%变量扩展的选项和#和##没有什么不同。注意:如果你希望从末尾移除一个特殊的子串,则不能运用"*"字符:
350 </p>
351
352 <pre caption="从末尾移去字符">
353 <i>MYFOOD="chickensoup"</i>
354 $ <i>echo ${MYFOOD%%soup}</i>
355 chicken
356 </pre>
357
358 <p>
359 在这个例子中,既然只有一个匹配,我们使用"%%"或"%"也就不重要了。同时记住,如果你忘记了是使用"#"还是"%",请看键盘上的3,4,5键并猜出来。
360 </p>
361
362 <p>
363 我们可以运用其它形式的变量扩展来选择一个特殊的子串,它是基于一个特定的字符串偏移和长度。请尝试在bash下敲入下面几行:
364 </p>
365
366 <pre caption="选择一个特殊的子字符串">
367 $ <i>EXCLAIM=cowabunga</i>
368 $ <i>echo ${EXCLAIM:0:3}</i>
369 cow
370 $ <i>echo ${EXCLAIM:3:7}</i>
371 abunga
372 </pre>
373
374 <p>
375 这种形式的字符串拆分将十分简便;只需简单的用冒号分开指定字符的起始字符和子串的长度。
376 </p>
377
378 </body>
379 </section>
380 <section>
381 <title>应用字符串拆分</title>
382 <body>
383
384 <p>
385 现在我们已经学了拆分字符串的所有内容,让我们写一个简单的小shell脚本。我们的脚本将接受单个文件作为参数并打印出它是否是一个压缩档,它将在文件的结尾寻找模式".tar"。下面就是:
386 </p>
387
388 <pre caption="mytar.sh──一个示例脚本">
389 #!/bin/bash
390
391 if [ "${1##*.}" = "tar" ]
392 then
393 echo This appears to be a tarball.
394 else
395 echo At first glance, this does not appear to be a tarball.
396 fi
397 </pre>
398
399 <p>
400 要运行这个脚本,将它输入到几个叫<path>mytar.sh</path>,并敲入<c>chmod 755 mytar.sh</c>使它可执行。然后如下给他一个压缩档做试验:
401 </p>
402
403 <pre caption="尝试这个脚本">
404 $ <i>./mytar.sh thisfile.tar</i>
405 This appears to be a tarball.
406 $ <i>./mytar.sh thatfile.gz</i>
407 At first glance, this does not appear to be a tarball.
408 </pre>
409
410 <p>
411 好,它工作了,但是它不是很实用。在我们将它变得实用之前,先让我们看看上面用的"if"语句。其中,我们有一个布尔表达式。在bash中,"="比较操作检查字符串是否相等。但是这布尔表达式真正测试的是什么呢?让我们看看左边。通过我们所学习的字符串拆分,"${1##*.}"将从包含环境变量"1"的字符串起始处移除"*."的最长匹配,并返回结果。这将返回文件中最后一个"."之后的所有部分。很明显,如果一个文件以".tar"结尾,我们将获取"tar"作为结果,条件也就为真。
412 </p>
413
414 <p>
415 你可能会吃惊环境变量"1"是在第一个位置。非常简单──$1是这个脚本的第一个命令行参数,$2是第二个,如此等等。好的,现在我们已经回顾乐这个函数,我们可以看一下"if"语句了。
416 </p>
417
418 </body>
419 </section>
420 <section>
421 <title>If语句</title>
422 <body>
423
424 <p>
425 像大多语言一样,bash有它自己的条件类型。当使用它们时,请准照上面的格式;它是使"if"和"then"在不同的行,"else"和结束处必须的"fi"和它们水平对齐。这使代码易于阅读和调试。除了"if,else"形式,"if"语句还有很多其它形式:
426 </p>
427
428 <pre caption="if语句的基本结构">
429 if [ condition ]
430 then
431 action
432 fi
433 </pre>
434
435 <p>
436 只有当condition为真的,它才会执行一个动作,否则它不执行任何动作并继续执行直到"fi"的行。
437 </p>
438
439 <pre caption="在继续fi之后的命令之前检查状况">
440 if [ condition ]
441 then
442 action
443 elif [ condition2 ]
444 then
445 action2
446 .
447 .
448 .
449 elif [ condition3 ]
450 then
451
452 else
453 actionx
454 fi
455 </pre>
456
457 <p>
458 上面的"elif"序列将连续测试每一个condition并执行第一个真的condition所相应的动作。如果没有condition是真,它将执行"else"动作,如果有一个为真,它将继续执行所有接下来的"if,elif,else"语句。
459 </p>
460
461 </body>
462 </section>
463 <section>
464 <title>下一次</title>
465 <body>
466
467 <p>
468 现在我们已经涵盖了大部分的基本的bash功能,是时候加快脚步准备书写一些真正的脚本了。在下一部分中,我将讲述循环结构,函数,命名空间和其它重要的话题。接下来我们将准备写更多的复杂的脚本。在第三篇文章中,我们将重点集中在非常复杂的脚本和函数上,正如很多bash脚本设计选项一样。到时候再见!
469 </p>
470
471 </body>
472 </section>
473 </chapter>
474
475 <chapter>
476 <title>资源</title>
477 <section>
478 <title>有用的链接</title>
479 <body>
480
481 <ul>
482 <li>
483 请阅读<uri link="http://www.gentoo.org/doc/zh_cn/articles/bash-by-example-p2.xml">Bash示例:第二部分</uri>
484 </li>
485 <li>
486 请阅读<uri link="http://www.gentoo.org/doc/zh_cn/articles/bash-by-example-p3.xml">Bash示例:第三部分</uri>
487 </li>
488 <li>
489 访问<uri link="http://www.gnu.org/software/bash/bash.html">GUN's bash home page</uri>
490 </li>
491 </ul>
492
493 </body>
494 </section>
495 </chapter>
496 </guide>
497
498
499
500 --
501 gentoo-commits@g.o mailing list