forked from bitchao/bitchao.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
616 lines (391 loc) · 738 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Cloudberry</title>
<subtitle>A site for writing life</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://shixiangwang.github.io/"/>
<updated>2018-02-26T13:07:13.115Z</updated>
<id>https://shixiangwang.github.io/</id>
<author>
<name>王诗翔</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>两天研习Python基础</title>
<link href="https://shixiangwang.github.io/2018/02/26/python-basics/"/>
<id>https://shixiangwang.github.io/2018/02/26/python-basics/</id>
<published>2018-02-26T13:01:41.000Z</published>
<updated>2018-02-26T13:07:13.115Z</updated>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p><strong>两天研习Python基础</strong>系列文章为“learn by example”编程课程的python部分,原英文Github仓库<a href="https://github.com/learnbyexample/Python_Basics" target="_blank" rel="noopener">点击此处</a>,中文Github仓库<a href="https://github.com/ShixiangWang/Python_Basics" target="_blank" rel="noopener">点击此处</a>,所有内容已发至简书(见章节部分)。</p><div class="github-widget" data-repo="ShixiangWang/Python_Basics"></div><a id="more"></a><p>离线学习请克隆:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/ShixiangWang/Python_Basics</span><br></pre></td></tr></table></figure><p>该系列仅作学习及参考使用,本人能力有限,很多专业术语在学习中,如果错误,还请指正。欢迎大家对仓库fork进行学习、补充和修改等等。</p><h1 id="Python-基础"><a href="#Python-基础" class="headerlink" title="Python 基础"></a>Python 基础</h1><p>Python介绍 - 语法、与shell命令工作、文件、文本处理等等…</p><ul><li>适合Python初学者一两天研习</li><li>更加完整的<a href="https://github.com/ShixiangWang/scripting_course/blob/master/Python_curated_resources.md" target="_blank" rel="noopener">Python整合资源列表</a> 包括初学者教程</li><li>更多相关资源,访问<a href="https://github.com/ShixiangWang/scripting_course" target="_blank" rel="noopener">scripting course</a></li></ul><p><br></p><h1 id="章节"><a href="#章节" class="headerlink" title="章节"></a>章节</h1><ul><li><a href="https://www.jianshu.com/p/043b22c53464" target="_blank" rel="noopener">介绍</a><ul><li>安装、Hello World示例、Python解释器、Python标准库</li></ul></li><li><a href="https://www.jianshu.com/p/cea74af54c19" target="_blank" rel="noopener">数值和字符串数据类型</a><ul><li>数值、字符串、常量和内置操作符</li></ul></li><li><a href="https://www.jianshu.com/p/fc17f347094e" target="_blank" rel="noopener">函数</a><ul><li>def、print函数,range函数, type函数,变量作用域</li></ul></li><li><a href="https://www.jianshu.com/p/01fff554603e" target="_blank" rel="noopener">获取用户输入</a><ul><li>整数输入、浮点输入、字符串输入</li></ul></li><li><a href="https://www.jianshu.com/p/982ce84fe274" target="_blank" rel="noopener">执行外部命令</a><ul><li>调用Shell命令、用扩展调用Shell命令、获取命令输出和重定向</li></ul></li><li><a href="https://www.jianshu.com/p/968e00a326e5" target="_blank" rel="noopener">控制结构</a><ul><li>条件检查, if, for, while, continue and break</li></ul></li><li><a href="https://www.jianshu.com/p/1e2d44b8d060" target="_blank" rel="noopener">列表</a><ul><li>列表变量赋值、切片和修改列表、复制列表、列表方法和杂项、循环、列表推导式、获取列表作为用户输入、随机从列表中获取元素</li></ul></li><li><a href="https://www.jianshu.com/p/d27050ef63f6" target="_blank" rel="noopener">序列、集合以及字典数据类型</a><ul><li>字符串、元组、集合、字典</li></ul></li><li><a href="https://www.jianshu.com/p/c1f7abc7371f" target="_blank" rel="noopener">文本处理</a><ul><li>字符串方法、正则表达式、模式匹配和提取、搜索和替换、编译正则表达式、正则表达式进一步阅读</li></ul></li><li><a href="https://www.jianshu.com/p/294031b710c0" target="_blank" rel="noopener">文件处理</a><ul><li>open函数、读入文件,写入文件</li></ul></li><li><a href="https://www.jianshu.com/p/a66b046d0c4a" target="_blank" rel="noopener">命令行参数</a><ul><li>已知参数数目、变长参数、在代码中使用程序名、命令行开关</li></ul></li><li><a href="https://www.jianshu.com/p/571bab3f422e" target="_blank" rel="noopener">意外处理和调试</a><ul><li>意外处理、语法检查、pdb、导入程序</li></ul></li><li><a href="https://www.jianshu.com/p/62a7bdbf27ba" target="_blank" rel="noopener">文档字符</a><ul><li>风格指南,回文示例</li></ul></li><li><a href="https://www.jianshu.com/p/c3ecc57adaf9" target="_blank" rel="noopener">测试</a><ul><li>assert语句、使用assert测试程序、使用unittest框架,使用unittest.mock、使用unittest.mock测试用户输入和程序输出、其他测试框架</li></ul></li><li><a href="https://www.jianshu.com/p/d8a4fd109b30" target="_blank" rel="noopener">练习</a></li><li><a href="https://www.jianshu.com/p/188366d88aa5" target="_blank" rel="noopener">进一步阅读</a><ul><li>没有涉及的标准主题,有用的编程链接,python扩展包</li></ul></li></ul><p><br></p><h1 id="电子书"><a href="#电子书" class="headerlink" title="电子书"></a>电子书</h1><ul><li>以电子书方式查看<a href="https://learnbyexample.gitbooks.io/python-basics/content/index.html" target="_blank" rel="noopener">gitbook</a></li><li>下载离线阅读 - <a href="https://www.gitbook.com/book/learnbyexample/python-basics/details" target="_blank" rel="noopener">链接</a></li></ul><p><br></p><h1 id="致谢"><a href="#致谢" class="headerlink" title="致谢"></a>致谢</h1><ul><li><a href="https://automatetheboringstuff.com/" target="_blank" rel="noopener">automatetheboringstuff</a> - 让我入门python</li><li><a href="https://www.reddit.com/r/learnpython/" target="_blank" rel="noopener">/r/learnpython/</a> - 帮助初学者和高手的有用论坛</li><li><a href="http://devup.in/" target="_blank" rel="noopener">Devs and Hackers</a> - helpful slack group</li><li><a href="https://www.reddit.com/r/india/search?q=Weekly+Coders%2C+Hackers+%26+All+Tech+related+thread+author%3Aavinassh&restrict_sr=on&sort=new&t=all" target="_blank" rel="noopener">Weekly Coders, Hackers & All Tech related thread</a> - 谢谢建议和评论</li></ul><p><br></p><h1 id="许可证"><a href="#许可证" class="headerlink" title="许可证"></a>许可证</h1><p>本工作基于<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" rel="noopener">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License</a></p>]]></content>
<summary type="html">
<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p><strong>两天研习Python基础</strong>系列文章为“learn by example”编程课程的python部分,原英文Github仓库<a href="https://github.com/learnbyexample/Python_Basics" target="_blank" rel="noopener">点击此处</a>,中文Github仓库<a href="https://github.com/ShixiangWang/Python_Basics" target="_blank" rel="noopener">点击此处</a>,所有内容已发至简书(见章节部分)。</p>
<div class="github-widget" data-repo="ShixiangWang/Python_Basics"></div>
</summary>
<category term="Python之歌" scheme="https://shixiangwang.github.io/categories/Python%E4%B9%8B%E6%AD%8C/"/>
<category term="python" scheme="https://shixiangwang.github.io/tags/python/"/>
</entry>
<entry>
<title>为markdown博文创建独立图片路径</title>
<link href="https://shixiangwang.github.io/2018/02/12/how-to-create-an-independent-figpath/"/>
<id>https://shixiangwang.github.io/2018/02/12/how-to-create-an-independent-figpath/</id>
<published>2018-02-12T07:17:33.000Z</published>
<updated>2018-02-12T08:01:16.707Z</updated>
<content type="html"><![CDATA[<p>在博文<a href="https://shixiangwang.github.io/2018/02/06/how-to-write-rmd-documents-in-hexo-system/">怎么在hexo博客系统中用Rmarkdown写文章</a>中我介绍了如何使用我创建的函数自动生成<code>Rmarkdown</code>文档并将其转换为<code>markdown</code>博文。文章并没有具体讲生成图片的过程,我在前一篇文章<a href="https://shixiangwang.github.io/2018/02/07/how-to-do-group-survival-analysis/">怎么对连续变量分组并进行生存分析</a>写作时发现用hexo系统存在一些小问题:<code>hexo</code>生成<code>public</code>静态网页文档集合(包括主页展示的所有内容)和博文所在的<code>source</code>文件夹是相对独立的,这会导致<code>markdown</code>的图片引用路径时在本地用一些markdown预览器可以看到图片,但实际呢,在部署的博客上却看不到了!</p><a id="more"></a><p>根据hexo官方文档<a href="https://hexo.io/zh-cn/docs/asset-folders.html" target="_blank" rel="noopener">https://hexo.io/zh-cn/docs/asset-folders.html</a>的描述,我们可以<strong>将图片扔到<code>source/images</code>文件夹下,然后通过类似于 ![](/images/image.jpg) 的方法访问它们</strong>。值得注意的是,这种方法使用的既不是图片的绝对路径也不是图片的相对路径,所以它会出现了一个略微尴尬的情况,在本地不能预览,在部署好的博客上却能看到!上一篇博文写作时正准备回家,没时间整这个幺蛾子,就是这样发的文章。</p><p>当然官网提到可以使用一些非<code>markdown</code>标记符来引用图片,这种我自认为不可取,我看重的就是<code>markdown</code>的简约、文章易迁移特性,使用这种方式会让我的文章<strong>不够自由</strong>。</p><p>仔细阅读文档后,发现可取的办法是:</p><blockquote><p>对于那些想要更有规律地提供图片和其他资源以及想要将他们的资源分布在各个文章上的人来说,Hexo也提供了更组织化的方式来管理资源。这个稍微有些复杂但是管理资源非常方便的功能可以通过将<code>config.yml</code>文件中的<code>post_asset_folder</code>选项设为<code>true</code>来打开。<br> _config.yml<br> post_asset_folder: true<br>当资源文件管理功能打开后,Hexo将会在你每一次通过<code>hexo new [layout] <title></code>命令创建新文章时自动创建一个文件夹。这个资源文件夹将会有与这个 markdown 文件一样的名字。将所有与你的文章有关的资源放在这个关联文件夹中之后,你可以通过相对路径来引用它们,这样你就得到了一个更简单而且方便得多的工作流。</p></blockquote><p>我使用这种方法新建了一个文档,顺便用<code>git</code>监控哪些文档发生了改变,发现这个效果开启其实就是在<code>source/_post</code>目录下新建一个跟新建的<code>markdown</code>博文(去掉后缀)同名的文件夹。那么问题的解决就比较简单了,我只需要设定好<code>knit</code>将<code>rmd</code>文档转换为<code>md</code>文档时,产生图片的输出路径即可,也就是设定<code>opts_chunk$set(fig.path="../_posts/2018-02-12-how-to-create-an-independent-figpath/")</code>选项。这又可以通过两种方式实现,一是在写作使用的<code>rmarkdown</code>模板文件中直接修改此句,每次以手动的方式设定路径;二是将写好的<code>rmarkdown</code>文章读入R,利用正则表达式抓取该部分的值,然后修改选项为每篇文章对应的文件夹名。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">new_md_post <- <span class="keyword">function</span>(template_name=<span class="string">"template.Rmd"</span>,post_name=<span class="literal">NULL</span>,template_path=getwd(),</span><br><span class="line"> post_path=<span class="string">"../_posts"</span>,time_tag=<span class="literal">FALSE</span>, new_fig_path=<span class="literal">TRUE</span>){</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(is.null(post_name)){</span><br><span class="line"> post_name <- gsub(pattern = <span class="string">"^(.*)\\.[Rr]md$"</span>, <span class="string">"\\1"</span>, x = template_name)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> input_file <- paste(template_path,template_name, sep=<span class="string">"/"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(time_tag){</span><br><span class="line"> current_time <- Sys.Date()</span><br><span class="line"> out_file <- paste0(post_path, <span class="string">"/"</span>, current_time, <span class="string">"-"</span>, post_name,<span class="string">".md"</span>)</span><br><span class="line"> out_dir <- paste0(post_path, <span class="string">"/"</span>, current_time, <span class="string">"-"</span>, post_name)</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> out_file <- paste0(post_path, <span class="string">"/"</span>, post_name,<span class="string">".md"</span>)</span><br><span class="line"> out_dir <- paste0(post_path, <span class="string">"/"</span>, post_name)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment"># if new_fig_path is FALSE, use united figure path to sotre figures</span></span><br><span class="line"> <span class="comment"># if this variable is TRUE, create an independent directory for post</span></span><br><span class="line"> <span class="keyword">if</span> (new_fig_path){</span><br><span class="line"> dir.create(out_dir, showWarnings = <span class="literal">TRUE</span>, recursive = <span class="literal">TRUE</span>)</span><br><span class="line"> <span class="comment"># add fig.path option to Rmd file</span></span><br><span class="line"> fl_content <- readLines(input_file)</span><br><span class="line"> new_content <- sub(pattern = <span class="string">"fig.path=\".*\""</span>,</span><br><span class="line"> replacement = paste0(<span class="string">"fig.path=\""</span>, out_dir, <span class="string">"/\""</span>), fl_content)</span><br><span class="line"> writeLines(new_content, input_file)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> knitr::knit(input = input_file, output = out_file)</span><br><span class="line"> print(<span class="string">"New markdown post creat successfully!"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>写作这篇文章其实既用于记录,也用于测试该功能。我们就用最经典的数据集随手画个图好了~</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">plot(mtcars)</span><br></pre></td></tr></table></figure><p><img src="test-fig-path-1.png" alt="测试图片"></p><p>有意思的是,测试后发现<code>hexo</code>多此一举般,如果将图片扔进文章对应的目录下,只需要填入图片名字即可,相对和绝对路径引用还是不能见效。至少比之前方便了,罢了罢了……</p>]]></content>
<summary type="html">
<p>在博文<a href="https://shixiangwang.github.io/2018/02/06/how-to-write-rmd-documents-in-hexo-system/">怎么在hexo博客系统中用Rmarkdown写文章</a>中我介绍了如何使用我创建的函数自动生成<code>Rmarkdown</code>文档并将其转换为<code>markdown</code>博文。文章并没有具体讲生成图片的过程,我在前一篇文章<a href="https://shixiangwang.github.io/2018/02/07/how-to-do-group-survival-analysis/">怎么对连续变量分组并进行生存分析</a>写作时发现用hexo系统存在一些小问题:<code>hexo</code>生成<code>public</code>静态网页文档集合(包括主页展示的所有内容)和博文所在的<code>source</code>文件夹是相对独立的,这会导致<code>markdown</code>的图片引用路径时在本地用一些markdown预览器可以看到图片,但实际呢,在部署的博客上却看不到了!</p>
</summary>
<category term="极客R" scheme="https://shixiangwang.github.io/categories/%E6%9E%81%E5%AE%A2R/"/>
<category term="R" scheme="https://shixiangwang.github.io/tags/R/"/>
<category term="Rmarkdown" scheme="https://shixiangwang.github.io/tags/Rmarkdown/"/>
<category term="markdown" scheme="https://shixiangwang.github.io/tags/markdown/"/>
</entry>
<entry>
<title>简单理解lapply,sapply,vapply</title>
<link href="https://shixiangwang.github.io/2018/02/10/easy-sapply-apply-vapply/"/>
<id>https://shixiangwang.github.io/2018/02/10/easy-sapply-apply-vapply/</id>
<published>2018-02-10T07:04:57.000Z</published>
<updated>2018-02-10T07:33:47.767Z</updated>
<content type="html"><![CDATA[<p>在我之前转载的文章<a href="https://www.jianshu.com/p/9bca3555b06c" target="_blank" rel="noopener">apply,lapply,sapply用法探索</a>中已经对R中<code>apply</code>家族函数进行了比较详细地说明,这篇文章基于我在data campus中对<code>lapply</code>、<code>sapply</code>、<code>vapply</code>几个函数的学习,以更为简单的实例来了解这几个以列表对输入的迭代函数。</p><a id="more"></a><p>使用的是一组温度数据,每天测5次,连续测量一个星期。</p><p>我们先将其输入R:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">temp <- list(c(<span class="number">3</span>, <span class="number">7</span>, <span class="number">9</span>, <span class="number">6</span>, -<span class="number">1</span>), c(<span class="number">6</span>, <span class="number">9</span>, <span class="number">12</span>, <span class="number">13</span>, <span class="number">5</span>), c(<span class="number">4</span>, <span class="number">8</span>, <span class="number">3</span>, -<span class="number">1</span>, -<span class="number">3</span></span><br><span class="line">), c(<span class="number">1</span>, <span class="number">4</span>, <span class="number">7</span>, <span class="number">2</span>, -<span class="number">2</span>), c(<span class="number">5</span>, <span class="number">7</span>, <span class="number">9</span>, <span class="number">4</span>, <span class="number">2</span>), c(-<span class="number">3</span>, <span class="number">5</span>, <span class="number">8</span>, <span class="number">9</span>, <span class="number">4</span>), c(<span class="number">3</span>,</span><br><span class="line"> <span class="number">6</span>, <span class="number">9</span>, <span class="number">4</span>, <span class="number">1</span>))</span><br></pre></td></tr></table></figure><p>我们进行迭代计算的函数是<code>basics</code>,它计算每一天温度的最小、最大值、平均值以及中位数。</p><figure class="highlight livecodeserver"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Definition of the basics() function</span></span><br><span class="line">basics <- <span class="function"><span class="keyword">function</span>(<span class="title">x</span>) {</span></span><br><span class="line"> c(<span class="built_in">min</span> = <span class="built_in">min</span>(x), mean = mean(x), <span class="built_in">median</span> = <span class="built_in">median</span>(x), <span class="built_in">max</span> = <span class="built_in">max</span>(x))</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>lapply</code>最为常见,以列表为输入,以列表为输出。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">> lapply(temp, basics)</span><br><span class="line">[[<span class="number">1</span>]]</span><br><span class="line"> min mean median max</span><br><span class="line"> -<span class="number">1.0</span> <span class="number">4.8</span> <span class="number">6.0</span> <span class="number">9.0</span></span><br><span class="line"></span><br><span class="line">[[<span class="number">2</span>]]</span><br><span class="line"> min mean median max</span><br><span class="line"> <span class="number">5</span> <span class="number">9</span> <span class="number">9</span> <span class="number">13</span></span><br><span class="line"></span><br><span class="line">[[<span class="number">3</span>]]</span><br><span class="line"> min mean median max</span><br><span class="line"> -<span class="number">3.0</span> <span class="number">2.2</span> <span class="number">3.0</span> <span class="number">8.0</span></span><br><span class="line"></span><br><span class="line">[[<span class="number">4</span>]]</span><br><span class="line"> min mean median max</span><br><span class="line"> -<span class="number">2.0</span> <span class="number">2.4</span> <span class="number">2.0</span> <span class="number">7.0</span></span><br><span class="line"></span><br><span class="line">[[<span class="number">5</span>]]</span><br><span class="line"> min mean median max</span><br><span class="line"> <span class="number">2.0</span> <span class="number">5.4</span> <span class="number">5.0</span> <span class="number">9.0</span></span><br><span class="line"></span><br><span class="line">[[<span class="number">6</span>]]</span><br><span class="line"> min mean median max</span><br><span class="line"> -<span class="number">3.0</span> <span class="number">4.6</span> <span class="number">5.0</span> <span class="number">9.0</span></span><br><span class="line"></span><br><span class="line">[[<span class="number">7</span>]]</span><br><span class="line"> min mean median max</span><br><span class="line"> <span class="number">1.0</span> <span class="number">4.6</span> <span class="number">4.0</span> <span class="number">9.0</span></span><br></pre></td></tr></table></figure><p>可以看出,如果迭代次数够大,结果会非常冗长,但我们所需要的结果其实可以以比较紧凑的数组(矩阵)展示出来。因此,我们可以使用<code>sapply</code>函数,<code>s</code>前缀即简化之意。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">> sapply(temp, basics)</span><br><span class="line"> [,<span class="number">1</span>] [,<span class="number">2</span>] [,<span class="number">3</span>] [,<span class="number">4</span>] [,<span class="number">5</span>] [,<span class="number">6</span>] [,<span class="number">7</span>]</span><br><span class="line">min -<span class="number">1.0</span> <span class="number">5</span> -<span class="number">3.0</span> -<span class="number">2.0</span> <span class="number">2.0</span> -<span class="number">3.0</span> <span class="number">1.0</span></span><br><span class="line">mean <span class="number">4.8</span> <span class="number">9</span> <span class="number">2.2</span> <span class="number">2.4</span> <span class="number">5.4</span> <span class="number">4.6</span> <span class="number">4.6</span></span><br><span class="line">median <span class="number">6.0</span> <span class="number">9</span> <span class="number">3.0</span> <span class="number">2.0</span> <span class="number">5.0</span> <span class="number">5.0</span> <span class="number">4.0</span></span><br><span class="line">max <span class="number">9.0</span> <span class="number">13</span> <span class="number">8.0</span> <span class="number">7.0</span> <span class="number">9.0</span> <span class="number">9.0</span> <span class="number">9.0</span></span><br></pre></td></tr></table></figure><p>是否已经足够紧凑?</p><p>最后想要介绍的函数<code>vapply</code>其实是为<code>sapply</code>加了一层验证选项:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">> vapply(temp, basics, numeric(<span class="number">4</span>))</span><br><span class="line"> [,<span class="number">1</span>] [,<span class="number">2</span>] [,<span class="number">3</span>] [,<span class="number">4</span>] [,<span class="number">5</span>] [,<span class="number">6</span>] [,<span class="number">7</span>]</span><br><span class="line">min -<span class="number">1.0</span> <span class="number">5</span> -<span class="number">3.0</span> -<span class="number">2.0</span> <span class="number">2.0</span> -<span class="number">3.0</span> <span class="number">1.0</span></span><br><span class="line">mean <span class="number">4.8</span> <span class="number">9</span> <span class="number">2.2</span> <span class="number">2.4</span> <span class="number">5.4</span> <span class="number">4.6</span> <span class="number">4.6</span></span><br><span class="line">median <span class="number">6.0</span> <span class="number">9</span> <span class="number">3.0</span> <span class="number">2.0</span> <span class="number">5.0</span> <span class="number">5.0</span> <span class="number">4.0</span></span><br><span class="line">max <span class="number">9.0</span> <span class="number">13</span> <span class="number">8.0</span> <span class="number">7.0</span> <span class="number">9.0</span> <span class="number">9.0</span> <span class="number">9.0</span></span><br><span class="line">> vapply(temp, basics, numeric(<span class="number">3</span>))</span><br><span class="line">Error <span class="keyword">in</span> vapply(temp, basics, numeric(<span class="number">3</span>)) : 值的长度必需为<span class="number">3</span>,</span><br><span class="line"> 但FUN(X[[<span class="number">1</span>]])结果的长度却是<span class="number">4</span></span><br></pre></td></tr></table></figure><p> 读者可以发现,当第三个参数其实就是验证选项,命名为<code>FUN.VALUE</code>。</p> <figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"> > args(vapply)</span><br><span class="line"><span class="keyword">function</span> (X, FUN, FUN.VALUE, <span class="keyword">...</span>, USE.NAMES = <span class="literal">TRUE</span>)</span><br><span class="line"><span class="literal">NULL</span></span><br></pre></td></tr></table></figure><p>我们知道每次迭代计算应该返回4个数值型结果,所以当我们设置为<code>numeric(3)</code>时它会报错。这个函数及其选项的设定在我们编写比较大型的迭代计算和整合函数代码时非常有用,可以帮助我们快速检验结果的有效性,尽量避免调试bug带来的苦恼。</p>]]></content>
<summary type="html">
<p>在我之前转载的文章<a href="https://www.jianshu.com/p/9bca3555b06c" target="_blank" rel="noopener">apply,lapply,sapply用法探索</a>中已经对R中<code>apply</code>家族函数进行了比较详细地说明,这篇文章基于我在data campus中对<code>lapply</code>、<code>sapply</code>、<code>vapply</code>几个函数的学习,以更为简单的实例来了解这几个以列表对输入的迭代函数。</p>
</summary>
<category term="极客R" scheme="https://shixiangwang.github.io/categories/%E6%9E%81%E5%AE%A2R/"/>
<category term="R" scheme="https://shixiangwang.github.io/tags/R/"/>
<category term="lapply" scheme="https://shixiangwang.github.io/tags/lapply/"/>
<category term="sapply" scheme="https://shixiangwang.github.io/tags/sapply/"/>
<category term="vapply" scheme="https://shixiangwang.github.io/tags/vapply/"/>
<category term="迭代计算" scheme="https://shixiangwang.github.io/tags/%E8%BF%AD%E4%BB%A3%E8%AE%A1%E7%AE%97/"/>
</entry>
<entry>
<title>怎么对连续变量分组并进行生存分析</title>
<link href="https://shixiangwang.github.io/2018/02/07/how-to-do-group-survival-analysis/"/>
<id>https://shixiangwang.github.io/2018/02/07/how-to-do-group-survival-analysis/</id>
<published>2018-02-07T15:49:11.000Z</published>
<updated>2018-02-07T15:50:29.487Z</updated>
<content type="html"><![CDATA[<p>在探究基因表达、基因拷贝数等连续变量对癌症病人的预后情况的影响时,我不得不面对和处理的主要问题是如何对这种连续型的变量进行分组,然后进行相应的生存分析。</p><a id="more"></a><p>做科研分析的朋友可能都比较了解,针对变量数值分组,一般是采用中位数、四分位数或者均值这些基本描述统计量。如果更细致地,可以按百分比,例如Top/Bottom 5%啊,10%啊之类的进行划分。</p><p>我们先来看怎么实现,然后再谈谈我自己的理解和评价。</p><p>生存分析<strong>最最关键</strong>的两个变量是生存事件和存活时间,前者是指一位病患是死了还是不知道是死是活了,前者一般用1表示,后者用0,其中后者常被称为截尾事件,要么就是研究周期到了,病人还没死;要么是找不到人了。我这里不是在侃概念,述说得也并不一定精准,详细了解就找谷歌度娘,我不再赘述。</p><p>科研分析的目的大抵都可以归根到找差异,你搞出来的跟别人搞出来的不一样,你就有话语权了,可以发文章。所以生存分析第三个必不可少的变量是<strong>组别</strong>变量,用来对比和探寻差异。</p><p>有的时候组别不明自显,比如我们要分析某个癌症组织和正常组织的差异,那么划分组别的方式自然就很明显了,而且在实验或分析设计之时就能确定。这种数据用来进行生存分析是最简单的,标准的代码一套,看结果就可以了。</p><p>如果你是想进行这样的分析,百度一下相信有不少博文可以解决你的这个问题。用R来做,不外乎以下几步:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 载入分析和画图包</span></span><br><span class="line"><span class="keyword">library</span>(survival)</span><br><span class="line"><span class="keyword">library</span>(survminer)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 读入数据</span></span><br><span class="line">df <- read.table(<span class="keyword">...</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 构建生存模型</span></span><br><span class="line">sfit <- survfit(Surv(time, event) ~ group, data=df)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 绘图</span></span><br><span class="line">ggsurvplot(</span><br><span class="line"> sfit, <span class="comment"># survfit object with calculated statistics.</span></span><br><span class="line"> data = df, <span class="comment"># data used to fit survival curves.</span></span><br><span class="line"> risk.table = <span class="literal">FALSE</span>, <span class="comment"># show risk table.</span></span><br><span class="line"> pval = <span class="literal">TRUE</span>, <span class="comment"># show p-value of log-rank test.</span></span><br><span class="line"> conf.int = <span class="literal">FALSE</span>, <span class="comment"># show confidence intervals for</span></span><br><span class="line"> <span class="comment"># point estimates of survival curves.</span></span><br><span class="line"> palette = c(<span class="string">"red"</span>, <span class="string">"blue"</span>),</span><br><span class="line"> <span class="keyword">...</span>)</span><br></pre></td></tr></table></figure><p>这里画图函数涉及一些参数的设定,可以参考<a href="https://www.jianshu.com/p/2da8eb255596" target="_blank" rel="noopener">怎么画出好看的生存曲线</a>这篇文章。</p><p>如果我们想要将连续型变量进行生存对比分析,显然我们要在构建生存模型之前将组别划分好。</p><p>这样的问题是最让人讨厌却又难以避而不见的,像基因表达对预后的影响就以这样的问题呈现出来,做过几次之后我对这种频繁改动组别设定的操作感到厌烦。</p><p>为了提升操作的效率,我花时间将分组和画图两个过程都写成了函数的形式,放在<a href="https://gist.github.com/ShixiangWang/75ae36de5d1b42c3d4de79986d03e16b" target="_blank" rel="noopener">Gist</a>上,有需要的可以下载使用。</p><p>第一个分组函数尽量不要改动,第二个画图函数涉及比较多的参数设定,使用时自由度更高,可以根据自己的需要进行修改。</p><p>我们先查看我载入的样例数据:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">head(df)</span><br></pre></td></tr></table></figure><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">## samples expression OS OS_IND</span><br><span class="line">## <span class="number">1</span> TCGA<span class="number">-05</span><span class="number">-4249</span><span class="number">-01</span> <span class="number">9.53</span> <span class="number">1523</span> <span class="number">0</span></span><br><span class="line">## <span class="number">2</span> TCGA<span class="number">-05</span><span class="number">-4250</span><span class="number">-01</span> <span class="number">9.46</span> <span class="number">121</span> <span class="number">1</span></span><br><span class="line">## <span class="number">3</span> TCGA<span class="number">-05</span><span class="number">-4382</span><span class="number">-01</span> <span class="number">9.56</span> <span class="number">607</span> <span class="number">0</span></span><br><span class="line">## <span class="number">4</span> TCGA<span class="number">-05</span><span class="number">-4384</span><span class="number">-01</span> <span class="number">11.04</span> <span class="number">426</span> <span class="number">0</span></span><br><span class="line">## <span class="number">5</span> TCGA<span class="number">-05</span><span class="number">-4389</span><span class="number">-01</span> <span class="number">10.45</span> <span class="number">1369</span> <span class="number">0</span></span><br><span class="line">## <span class="number">6</span> TCGA<span class="number">-05</span><span class="number">-4390</span><span class="number">-01</span> <span class="number">10.90</span> <span class="number">1126</span> <span class="number">0</span></span><br></pre></td></tr></table></figure><p>载入写好的函数脚本:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">source</span>(<span class="string">"~/Desktop/groupSurvival.R"</span>)</span><br><span class="line"></span><br><span class="line">args(groupSurvival)</span><br></pre></td></tr></table></figure><figure class="highlight clean"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">## function (df, event = <span class="string">"OS_IND"</span>, time = <span class="string">"OS"</span>, var = NULL, time.limit = NULL,</span><br><span class="line">## interval = c(<span class="string">"open"</span>, <span class="string">"close"</span>), method = c(<span class="string">"quartile"</span>, <span class="string">"mean"</span>,</span><br><span class="line">## <span class="string">"median"</span>, <span class="string">"percent"</span>, <span class="string">"custom"</span>), percent = NULL, step = <span class="number">20</span>,</span><br><span class="line">## custom_fun = NULL, group1 = <span class="string">"High"</span>, group2 = <span class="string">"Low"</span>)</span><br><span class="line">## NULL</span><br></pre></td></tr></table></figure><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">args(plot_surv)</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># function (os_mat, cutoff = NULL, pval = TRUE, ...)</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># NULL</span></span></span><br></pre></td></tr></table></figure><p>最重要的<code>groupSurvival</code>函数,一系列的参数都有含义,包括指定最重要的三个变量,设定分组的方法,组名,甚至我还在内部写了一个函数去根据步长计算对应的p值(最小p值和对应的时间会返回为结果列表的一部分)。</p><p>使用函数对基因表达进行分组,分组方式是<code>median</code>中位数。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">res <- groupSurvival(df=df, event=<span class="string">"OS_IND"</span>, time=<span class="string">"OS"</span>, var=<span class="string">"expression"</span>,method=<span class="string">"median"</span>)</span><br></pre></td></tr></table></figure><p>画图看看:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">plot_surv(res$data)</span><br></pre></td></tr></table></figure><p><img src="/images/plot_surv-1.png" alt="plot of chunk plot_surv"></p><p>设置一个时间阈值:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">plot_surv(res$data, cutoff = <span class="number">3000</span>)</span><br></pre></td></tr></table></figure><p><img src="/images/plot_surv2-1.png" alt="plot of chunk plot_surv2"></p><p>使用百分比(上下百分之多少),并确定使用的比例(1表示100%)分组并进行绘图。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">res <- groupSurvival(df=df, event=<span class="string">"OS_IND"</span>, time=<span class="string">"OS"</span>, var=<span class="string">"expression"</span>,method=<span class="string">"percent"</span>, percent = <span class="number">0.1</span>)</span><br><span class="line"></span><br><span class="line">plot_surv(res$data, cutoff = <span class="number">3000</span>)</span><br></pre></td></tr></table></figure><p><img src="/images/grouping_and_plot-1.png" alt="plot of chunk grouping_and_plot"></p><p>如果你有一些R的编程基础,完全可以基于这两个函数将所有的方法算一遍,然后再去查看结果,确定合适的分组方式。</p><hr><p>最后,我们到底应该根据结果选择方法、还是选择方法之后就认定了结果,这是悬在这类分析中的一把利剑。所谓的<strong>差异</strong>到底是什么?我们在进行分析时需要有自己的道德和专业两重标准。</p><p>无论大家是否有共识,做好自己足矣。</p>]]></content>
<summary type="html">
<p>在探究基因表达、基因拷贝数等连续变量对癌症病人的预后情况的影响时,我不得不面对和处理的主要问题是如何对这种连续型的变量进行分组,然后进行相应的生存分析。</p>
</summary>
<category term="极客R" scheme="https://shixiangwang.github.io/categories/%E6%9E%81%E5%AE%A2R/"/>
<category term="R" scheme="https://shixiangwang.github.io/tags/R/"/>
<category term="生存分析" scheme="https://shixiangwang.github.io/tags/%E7%94%9F%E5%AD%98%E5%88%86%E6%9E%90/"/>
<category term="survival" scheme="https://shixiangwang.github.io/tags/survival/"/>
<category term="survminer" scheme="https://shixiangwang.github.io/tags/survminer/"/>
</entry>
<entry>
<title>怎么在hexo博客系统中用Rmarkdown写文章</title>
<link href="https://shixiangwang.github.io/2018/02/06/how-to-write-rmd-documents-in-hexo-system/"/>
<id>https://shixiangwang.github.io/2018/02/06/how-to-write-rmd-documents-in-hexo-system/</id>
<published>2018-02-06T04:36:48.000Z</published>
<updated>2018-02-07T13:20:18.792Z</updated>
<content type="html"><![CDATA[<p>作为一个搞数据分析的,动笔总少不了图和代码,<code>markdown</code>对代码本身就有不错的区分和高亮支持,但你永远不可能用<code>markdown</code>写出图来啊!这类东东人们称之为静态的。为了解决这类问题,现在有两个非常流行的动态文档工具,一是<code>Python</code>中的<a href="http://jupyter.org/" target="_blank" rel="noopener"><code>Jupyter Notebook</code></a>,另外就是<code>Rstudio</code>公司开发的 <a href="http://rmarkdown.rstudio.com/rmarkdown_websites.html#overview" target="_blank" rel="noopener"><code>Rmarkdown</code></a>了。</p><a id="more"></a><p>就流行度来说,<code>Jupyter Notebook</code>是胜<code>Rmarkdown</code>良多的,前者不仅支持<code>Python</code>本身,而且支持<code>R</code>,不仅如此,它还扩展到其他一些语言中去了(当然<code>Rmarkdown</code>现在也能调用<code>Python</code>和<code>Shell</code>等语言代码)。但我不知道什么原因,<code>Jupyter Notebook</code>导出的<code>markdown</code>文档实在丑的很,虽然<code>Github</code>很神奇的能够全部进行识别并显示出应有的效果。另外,Notebook用来调用写博客并不方便。<code>Rmarkdown</code>直接用来写博客则方便得多,语法跟<code>markdown</code>差不多,又能利用谢益辉大大的<code>knitr</code>包将其转换为<code>markdown</code>文档。</p><p>总之,作为一个R爱好者,当然用R搞事情。</p><p>谢益辉已经开发了一个叫<code>blogdown</code>的包,专门用<code>Rmarkdown</code>部署生成博客,支持<code>hugo</code>所有的主题。如果读者还没有自己的博客,又使用R,推荐阅读<a href="https://bookdown.org/yihui/blogdown/" target="_blank" rel="noopener">https://bookdown.org/yihui/blogdown/</a>创建自己的博客并用<code>Rmarkdown</code>发布文章。如果你喜欢使用<code>hexo</code>博客系统,并想要使用<code>Rmarkdown</code>写文章,下面就是你需要阅读的干货了,当然我这种方法不仅限于用在<code>hexo</code>博客系统。</p><p>我实际做的事情就是写了两个<code>R</code>的函数,可以通过调用的方式创建<code>Rmarkdown</code>文档,并利用<code>knitr</code>包的<code>knit</code>函数将其转换为<code>markdown</code>文档。</p><p>需要文档代已经提交到Gist上面,可以<a href="https://gist.github.com/ShixiangWang/197cbe60c6fa096888b701af72511740" target="_blank" rel="noopener">点击查看和下载</a>。</p><h2 id="第一步"><a href="#第一步" class="headerlink" title="第一步"></a>第一步</h2><p>创建一个<code>Rmarkdown</code>文档模板,这样我们可以非常方便地在每次写新文章时生成YAML头信息。</p><p>其内容如下,简单设定标题、作者、日期、目录、标签,你可以根据自己情进行更改:</p><script src="//gist.github.com/197cbe60c6fa096888b701af72511740.js?file=template.Rmd"></script><p>上面Gist文档日期使用了<code>R</code>命令,后续我们使用<code>knit</code>可以将其自动转换为<code>markdown</code>文档生成时的日期。</p><p>你可以自己设置,只要符合<a href="https://hexo.io/zh-cn/docs/front-matter.html" target="_blank" rel="noopener">头信息规范</a>即可:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">title:</span> <span class="string">"Put your title here"</span></span><br><span class="line"><span class="attr">author:</span> <span class="string">王诗翔</span></span><br><span class="line"><span class="attr">date:</span> <span class="string">"2018-02-06 12:36:48"</span></span><br><span class="line"><span class="attr">top:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">categories:</span> <span class="string">Linux杂烩</span></span><br><span class="line"><span class="attr">tags:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">Linux</span></span><br><span class="line"><span class="meta">---</span></span><br></pre></td></tr></table></figure><p>我们将它保存为<code>template.Rmd</code>文件(<code>Rmarkdown</code>文件一般以<code>.Rmd</code>或者<code>.rmd</code>结尾)。</p><h2 id="第二步"><a href="#第二步" class="headerlink" title="第二步"></a>第二步</h2><p>将下面两个函数保存到一个R文件(以<code>.R</code>结尾)中:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">################</span></span><br><span class="line"><span class="comment">## 用rmd写博客 ##</span></span><br><span class="line"><span class="comment">################</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 作者:王诗翔</span></span><br><span class="line"><span class="comment"># 更新日期:2018-02-05</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">#>>>>>> new_rmd_post 函数 <<<<<<<<<<</span></span><br><span class="line"><span class="comment"># 写好模板文档后,你可以用这个函数来创建Rmarkdown文档</span></span><br><span class="line"><span class="comment"># 参数说明:</span></span><br><span class="line"><span class="comment"># post_name: 博文名(最好英文,显示不会乱码),比如我写这篇博文用的是</span></span><br><span class="line"><span class="comment"># how-to-write-rmd-documents-in-hexo-system</span></span><br><span class="line"><span class="comment"># 意思不一定要对,只要能跟其他博文名字有区分就行了</span></span><br><span class="line"><span class="comment"># template_name: 模板名,起template.Rmd最好,因为每次写文章都会用到,</span></span><br><span class="line"><span class="comment"># 这样你创建的时候不用每次都指定模板的名字</span></span><br><span class="line"><span class="comment"># template_path: 模板文档的路径,默认当前工作路径</span></span><br><span class="line"><span class="comment"># post_path: 你想把生成的文档放在哪个路径,默认当前工作路径</span></span><br><span class="line">new_rmd_post <- <span class="keyword">function</span>(post_name=<span class="literal">NULL</span>,template_name=<span class="string">"template.Rmd"</span>,</span><br><span class="line"> template_path=getwd(), post_path=getwd()){</span><br><span class="line"> <span class="keyword">if</span>(is.null(post_name)){</span><br><span class="line"> <span class="keyword">stop</span>(<span class="string">"A post name must be given!"</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> input_file <- paste(template_path,template_name, sep=<span class="string">"/"</span>)</span><br><span class="line"> current_time <- Sys.Date()</span><br><span class="line"> out_file <- paste0(current_time, <span class="string">"-"</span>,post_name,<span class="string">".Rmd"</span>)</span><br><span class="line"> fl_content <- readLines(input_file)</span><br><span class="line"> writeLines(fl_content, out_file)</span><br><span class="line"> print(<span class="string">"New Rmarkdown post creat successfully!"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">#>>>>> new_md_post 函数 <<<<<<<<<<</span></span><br><span class="line"><span class="comment"># 你可以用这个函数来将Rmd文档转换为markdown文档</span></span><br><span class="line"><span class="comment"># 需要安装knitr包,命令为 install.packages("knitr")</span></span><br><span class="line"><span class="comment"># 参数说明:</span></span><br><span class="line"><span class="comment"># post_name: 文章文档名,推荐使用 年-月-日-英文名 的方式</span></span><br><span class="line"><span class="comment"># template_name: 模板名,你需要转换的Rmd文档</span></span><br><span class="line"><span class="comment"># template_path: 模板文档的路径,默认当前工作路径</span></span><br><span class="line"><span class="comment"># post_path: 你想把生成的文档放在哪个路径,默认当前工作路径</span></span><br><span class="line"><span class="comment"># time_tag: 时间标签,如果你转换的文档没有年-月-日这种标记,</span></span><br><span class="line"><span class="comment"># 将time_tag设定为TRUE会自动在名字前加上</span></span><br><span class="line">new_md_post <- <span class="keyword">function</span>(post_name=<span class="literal">NULL</span>,template_name=<span class="string">"template.Rmd"</span>,template_path=getwd(),</span><br><span class="line"> post_path=getwd(),time_tag=<span class="literal">FALSE</span>){</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(is.null(post_name)){</span><br><span class="line"> post_name <- gsub(pattern = <span class="string">"^(.*)\\.[Rr]md$"</span>, <span class="string">"\\1"</span>, x = template_name)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> input_file <- paste(template_path,template_name, sep=<span class="string">"/"</span>)</span><br><span class="line"> <span class="comment"># retrieve system date</span></span><br><span class="line"> <span class="keyword">if</span>(time_tag){</span><br><span class="line"> current_time <- Sys.Date()</span><br><span class="line"> out_file <- paste0(post_path, <span class="string">"/"</span>, current_time, <span class="string">"-"</span>,post_name,<span class="string">".md"</span>)</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> out_file <- paste0(post_path, <span class="string">"/"</span>, post_name,<span class="string">".md"</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> knitr::knit(input = input_file, output = out_file)</span><br><span class="line"> print(<span class="string">"New markdown post creat successfully!"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我把它保存为<code>new_post.R</code>,上述我进行了比较详细的注释,请在使用之前仔细阅读一下。</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>我以现在以<code>Rmarkdown</code>写的这篇文章为例,简单讲一下使用。</p><p>我推荐在与你<code>markdown</code>博文目录同级创建一个<code>_rmd</code>目录,你可以将该目录设为一个项目目录,专门用来写<code>rmarkdown</code>文档。</p><p>或者你每次用<code>setwd()</code>函数设定工作目录。</p><p>将前两步创建的两个文件扔到该目录。运行R文件:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">source</span>(<span class="string">"./new_post.R"</span>)</span><br></pre></td></tr></table></figure><p>这样就能在R控制台调用里面的两个函数了。</p><p>创建一个<code>Rmarkdown</code>文档:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">> new_rmd_post(<span class="string">"how-to-write-rmd-documents-in-hexo-system"</span>)</span><br><span class="line">[<span class="number">1</span>] <span class="string">"New Rmarkdown post creat successfully!"</span></span><br></pre></td></tr></table></figure><p>创建的文档名会自动添加年-月-日和后缀。</p><p>然后你就可以开始写博客了,写好后将<code>Rmarkdown</code>转换为<code>markdown</code>文档:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">> new_md_post(template_name = <span class="string">"2018-02-05-how-to-write-rmd-documents-in-hexo-system.Rmd"</span>, post_path = <span class="string">"../_posts"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">processing file: /home/wsx/blog/<span class="keyword">source</span>/_rmd/<span class="number">2018</span>-<span class="number">02</span>-<span class="number">05</span>-how-to-write-rmd-documents-<span class="keyword">in</span>-hexo-system.Rmd</span><br><span class="line"> |.................................................................| <span class="number">100</span>%</span><br><span class="line"> inline R code fragments</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">output file: ../_posts/<span class="number">2018</span>-<span class="number">02</span>-<span class="number">05</span>-how-to-write-rmd-documents-<span class="keyword">in</span>-hexo-system.md</span><br><span class="line"></span><br><span class="line">[<span class="number">1</span>] <span class="string">"New markdown post creat successfully!"</span></span><br></pre></td></tr></table></figure><p>细心的娃娃可以感觉到我单独创建<code>_rmd</code>目录并将其设为工作目录的好处:需要键入函数的参数非常少。特别是你固定你自己的写法之后,你将两个函数中的目录路径默认参数全部对应上,再使用<code>R</code>的<code>TAB</code>键补全,运行命令简直秒秒种,专心写文章就好啦。</p><p>本文没有涉及到画图,从理论上讲是毫无问题的,因为我只是创建了一个快速的文档创建和转换接口。后续我少不了会用到绘图,到时候再讲。</p>]]></content>
<summary type="html">
<p>作为一个搞数据分析的,动笔总少不了图和代码,<code>markdown</code>对代码本身就有不错的区分和高亮支持,但你永远不可能用<code>markdown</code>写出图来啊!这类东东人们称之为静态的。为了解决这类问题,现在有两个非常流行的动态文档工具,一是<code>Python</code>中的<a href="http://jupyter.org/" target="_blank" rel="noopener"><code>Jupyter Notebook</code></a>,另外就是<code>Rstudio</code>公司开发的 <a href="http://rmarkdown.rstudio.com/rmarkdown_websites.html#overview" target="_blank" rel="noopener"><code>Rmarkdown</code></a>了。</p>
</summary>
<category term="极客R" scheme="https://shixiangwang.github.io/categories/%E6%9E%81%E5%AE%A2R/"/>
<category term="R" scheme="https://shixiangwang.github.io/tags/R/"/>
<category term="写作" scheme="https://shixiangwang.github.io/tags/%E5%86%99%E4%BD%9C/"/>
<category term="Rmd" scheme="https://shixiangwang.github.io/tags/Rmd/"/>
<category term="md" scheme="https://shixiangwang.github.io/tags/md/"/>
<category term="Rstudio" scheme="https://shixiangwang.github.io/tags/Rstudio/"/>
<category term="Gist" scheme="https://shixiangwang.github.io/tags/Gist/"/>
</entry>
<entry>
<title>sed如何在执行命令前过滤特定文本行</title>
<link href="https://shixiangwang.github.io/2018/01/31/sed-how-to-filter-rows-before-using-command/"/>
<id>https://shixiangwang.github.io/2018/01/31/sed-how-to-filter-rows-before-using-command/</id>
<published>2018-01-31T13:19:40.000Z</published>
<updated>2018-01-31T13:23:44.285Z</updated>
<content type="html"><![CDATA[<p>有人在微信群里问到这样一个问题:</p><blockquote><p>请问我想把参考基因组中所有的A和C替换成C和A,小写也按照这样的规则替换,该怎么实现呢,tr可以做到,但是我想保证>后面的染色体名字不会被替换?<br><img src="http://upload-images.jianshu.io/upload_images/3884693-da7fbb654d3f7bc1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p></blockquote><a id="more"></a><p><a href="http://man.linuxde.net/tr" target="_blank" rel="noopener"><code>tr</code></a>命令确实可以完成文本字符替换一对一的映射,但很显然,这样的功能想要解决这个问题是不够的,它把不想要改变的文本也改变了。</p><p>解决问题的思路在于如何实现带>标志的文本直接输出,而DNA字符行被执行转换命令。这个问题其实使用sed命令就可以快速解决,不少朋友对于<code>sed</code>的使用可能记忆为我在<a href="https://shixiangwang.github.io/2017/09/03/Linux-data-analysis-tools/#用Sed进行流编辑">笔记</a>中写的:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">command/pattern/replacement/flag</span><br></pre></td></tr></table></figure><p><code>command</code>是一些命令,比如<code>s</code>,<code>d</code>等,<code>flag</code>是一些标记,<code>i</code>,<code>g</code>等等。</p><p>这样认识sed命令其实不是很全面,sed允许指定文本模式来过滤出命令要作用的行,格式如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/pattern/command</span><br></pre></td></tr></table></figure><p>所以整理起来,sed格式其实(除了一些选项)为</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/pattern/command/pattern/replacement/flag</span><br></pre></td></tr></table></figure><p>第一个<code>pattern</code>用来过滤出要处理的文本行,第二个<code>pattern</code>是sed命令要作用的模式。</p><p>那么利用这个特定,一开始的问题可以利用正则表达式非常简单地解决了:</p><p>先用<code>/^[^>]/</code>找到不以<code>></code>开始的行,然后执行<code>sed</code>字符转换命令。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 随便写一个测试文本</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> cat <span class="built_in">test</span></span></span><br><span class="line"><span class="meta">></span><span class="bash"> CAACCA</span></span><br><span class="line">acgtACGTACTGagct</span><br><span class="line">tacgactNNNNNNNNNNNNNNNN</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> cat <span class="built_in">test</span> | sed <span class="string">'/^[^>]/y/ACac/CAca/'</span></span></span><br><span class="line"><span class="meta">></span><span class="bash"> CAACCA</span></span><br><span class="line">cagtCAGTCATGcgat</span><br><span class="line">tcagcatNNNNNNNNNNNNNNNN</span><br></pre></td></tr></table></figure><hr><p>有兴趣的可以看看<a href="https://shixiangwang.github.io/2017/12/25/sed-and-gawk/">初识sed与awk</a>这篇笔记。</p>]]></content>
<summary type="html">
<p>有人在微信群里问到这样一个问题:</p>
<blockquote>
<p>请问我想把参考基因组中所有的A和C替换成C和A,小写也按照这样的规则替换,该怎么实现呢,tr可以做到,但是我想保证&gt;后面的染色体名字不会被替换?<br><img src="http://upload-images.jianshu.io/upload_images/3884693-da7fbb654d3f7bc1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p>
</blockquote>
</summary>
<category term="Linux杂烩" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/"/>
<category term="sed" scheme="https://shixiangwang.github.io/tags/sed/"/>
<category term="生物信息学" scheme="https://shixiangwang.github.io/tags/%E7%94%9F%E7%89%A9%E4%BF%A1%E6%81%AF%E5%AD%A6/"/>
<category term="文本处理" scheme="https://shixiangwang.github.io/tags/%E6%96%87%E6%9C%AC%E5%A4%84%E7%90%86/"/>
<category term="fasta" scheme="https://shixiangwang.github.io/tags/fasta/"/>
</entry>
<entry>
<title>Sync deploy 命令工具</title>
<link href="https://shixiangwang.github.io/2018/01/31/sync-deploy-tools/"/>
<id>https://shixiangwang.github.io/2018/01/31/sync-deploy-tools/</id>
<published>2018-01-31T13:05:55.000Z</published>
<updated>2018-02-07T15:36:54.917Z</updated>
<content type="html"><![CDATA[<p>该命令集可以非常方便地向远程主机/服务器上传文件、运行远程脚本、下载文件等。</p><div class="github-widget" data-repo="ShixiangWang/sync-deploy"></div><a id="more"></a><p><strong>目录</strong>:</p><ul><li><a href="https://github.com/ShixiangWang/sync-deploy#目的" target="_blank" rel="noopener">目的</a></li><li><a href="https://github.com/ShixiangWang/sync-deploy#下载与使用" target="_blank" rel="noopener">下载与使用</a></li><li><a href="https://github.com/ShixiangWang/sync-deploy#准备与配置" target="_blank" rel="noopener">准备与配置</a></li><li><a href="https://github.com/ShixiangWang/sync-deploy#命令说明" target="_blank" rel="noopener">命令说明</a><ul><li><a href="https://github.com/ShixiangWang/sync-deploy#sync-command" target="_blank" rel="noopener">sync-command</a></li><li><a href="https://github.com/ShixiangWang/sync-deploy#sync-upload" target="_blank" rel="noopener">sync-upload</a></li><li><a href="https://github.com/ShixiangWang/sync-deploy#sync-download" target="_blank" rel="noopener">sync-download</a></li><li><a href="https://github.com/ShixiangWang/sync-deploy#sync-run" target="_blank" rel="noopener">sync-run</a></li><li><a href="https://github.com/ShixiangWang/sync-deploy#sync-deploy" target="_blank" rel="noopener">sync-deploy</a></li><li><a href="https://github.com/ShixiangWang/sync-deploy#sync-check" target="_blank" rel="noopener">sync-check</a></li></ul></li><li><a href="https://github.com/ShixiangWang/sync-deploy#计算操作实例" target="_blank" rel="noopener">计算操作实例</a></li></ul><h2 id="目的"><a href="#目的" class="headerlink" title="目的"></a>目的</h2><p>交互式地输入ssh、scp命令进行远端主机命令/脚本的执行、文件的上传与下载并不是很方便,有时候频繁地键入<code>hostname@ip</code>也是一件非常痛苦的事情。另外一方面,如果是向计算平台提交任务脚本,在远端文本命令窗口内修改作业参数以及调试运行脚本也是蛮不方便。所以仓库里脚本是为了能够比较方便地执行这一些任务。</p><p>命令集内置<code>ssh</code>、<code>scp</code>、<code>qsub</code>、<code>qstat</code>命令,分别用于运行远程脚本、命令、上传/下载文件、提交作业和查看作业状态。</p><h2 id="下载与使用"><a href="#下载与使用" class="headerlink" title="下载与使用"></a>下载与使用</h2><p><a href="https://github.com/ShixiangWang/sync-deploy/releases" target="_blank" rel="noopener">点击下载</a></p><p>或克隆:</p><figure class="highlight crmsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="keyword">clone</span> <span class="title">https</span>://github.com/ShixiangWang/sync-deploy.git</span><br></pre></td></tr></table></figure><p>下载后执行<code>add_path.sh</code>脚本将命令添加到环境路径中,这样无论你处于什么目录都能使用。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd sync-deploy/src</span><br><span class="line">./add_path.sh</span><br></pre></td></tr></table></figure><p>除了<code>sync-command</code>命令没有选项,其他命令基本都有选项需要指定。</p><p><strong>对应地,除了<code>sync-command</code>其他命令都有<code>-h</code>选项,你可以获取帮助</strong>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sync-upload -h</span><br><span class="line">sync-download -h</span><br><span class="line">sync-run -h</span><br><span class="line">sync-deploy -h</span><br><span class="line">sync-check -h</span><br></pre></td></tr></table></figure><h2 id="准备与配置"><a href="#准备与配置" class="headerlink" title="准备与配置"></a>准备与配置</h2><p>首先在服务器端配置本地机器的公钥,以便于实现无密码文件传输。</p><p>参考文章<a href="https://www.liaohuqiu.net/cn/posts/ssh-keygen-abc/" target="_blank" rel="noopener">ssh-keygen基本用法</a>或其他资料生成公钥和私钥(搜索引擎可以找到一大堆这样的博文,我就不啰嗦了)。</p><p>将公钥<code>id_sra.pub</code>(本地机器.ssh子目录下)中文本内容拷贝到服务器.ssh子目录中的<code>authorized_keys</code>中,放在已有文本后面。如果该文件不存在则创建。</p><p>进行测试,如果不需要密码登录则成功。</p><p><strong>然后点击打开当前目录(src/)的<code>sync-setting</code>文件,将远程主机的host名与ip地址改为你自己的</strong>。</p><hr><p><strong>如果你想要在计算平台部署任务</strong>,请点击打开当前目录下的<code>qsub_header</code>文件填入PBS参数,设置可以参考<a href="https://github.com/ShixiangWang/mytoolkit/blob/master/hpc_info.md" target="_blank" rel="noopener">我整理的</a>或者百度上的其他资源,例如<a href="https://wenku.baidu.com/view/5ab820293169a4517723a3ec.html" target="_blank" rel="noopener">1</a>,<a href="https://wenku.baidu.com/view/14ef7c230722192e4536f6f8.html" target="_blank" rel="noopener">2</a>等。</p><p><strong>接着在当前目录的<code>commands</code>文件夹填入你要运行的命令。如果你想要运行其他脚本,请在该文件中调用执行</strong>。</p><h2 id="命令说明"><a href="#命令说明" class="headerlink" title="命令说明"></a>命令说明</h2><h3 id="sync-command"><a href="#sync-command" class="headerlink" title="sync-command"></a>sync-command</h3><p>这个命令最简单粗暴,直接在<code>sync-command</code>后接你想要在远端执行的命令。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> sync-command ls -l <span class="string">'~/test'</span></span></span><br><span class="line">总用量 0</span><br><span class="line">-rw-rw-r-- 1 liuxs liuxs 12 1月 30 19:20 job_id</span><br><span class="line">-rw-rw-r-- 1 liuxs liuxs 34 1月 30 19:20 result.txt</span><br><span class="line">-rw-rw-r-- 1 liuxs liuxs 110 1月 29 11:40 test.R</span><br><span class="line">-rwxrw-r-- 1 liuxs liuxs 240 1月 30 19:20 work.sh</span><br></pre></td></tr></table></figure><p><strong>需要注意的是如果是想使用类似<code>~</code>这种映射到某个路径的符号,需要添加引号,不然它会被解析为本地地址,那当然会出问题的</strong></p><h3 id="sync-upload"><a href="#sync-upload" class="headerlink" title="sync-upload"></a>sync-upload</h3><p>上传文件到远程主机。</p><p>用法:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Usage: sync-upload -n local_files -d 'destdir'</span><br></pre></td></tr></table></figure><p><code>-n</code>选项后接你要上传的(本地机器)文件/目录路径,<code>-d</code>选项接远程主机上的目录路径。</p><p>用法示例:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">==> examples:</span><br><span class="line"> sync-upload -n work.sh -d /public/home/liuxs/test</span><br><span class="line"> or</span><br><span class="line"> sync-upload -n work.sh -d '~/test'</span><br></pre></td></tr></table></figure><p>同样注意使用<code>~</code>时需要加引号。</p><p><strong>重点注意不支持-n与-d倒过来写,也就是选项是有顺序的</strong>,为什么如此的原因是为了使<code>-n</code>选项后能够接大于1个的路径参数,命令脚本内部利用了<code>-n</code>和<code>-d</code>的位置特点运用正则表达式抓取所有路径名,你可以利用该命令同时上传不止一个文件/目录(也算是有得有失吧)。</p><h3 id="sync-download"><a href="#sync-download" class="headerlink" title="sync-download"></a>sync-download</h3><p>从远程主机下载文件到本地机器。</p><p>用法:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Usage: sync-download -n 'remote_files' -d localdir</span><br></pre></td></tr></table></figure><p>这个命令的使用基本和<code>sync-upload</code>一致。</p><p>用法示例:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">==> examples:</span><br><span class="line"> sync-download -n '~/test/*' -d ./test</span><br><span class="line"> or</span><br><span class="line"> sync-download -n /public/home/liuxs/test/* -d ./test</span><br></pre></td></tr></table></figure><p><strong>同样地,不支持<code>-n</code>与<code>-d</code>选项顺序反着写。</strong></p><h3 id="sync-run"><a href="#sync-run" class="headerlink" title="sync-run"></a>sync-run</h3><p>提交远程主机的作业,内置<code>qsub</code>命令向计算平台提交任务脚本。如果只是想要运行远程脚本或命令,请查看<code>sync-command</code>命令。</p><p>用法:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sync-run -f work_script</span><br></pre></td></tr></table></figure><p><code>-f</code>选项后接你要运行的<strong>一个</strong>脚本(需要指定脚本的路径哈)。</p><p>用法示例:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sync-run -f /home/wsx/work.sh</span><br></pre></td></tr></table></figure><h2 id="sync-deploy"><a href="#sync-deploy" class="headerlink" title="sync-deploy"></a>sync-deploy</h2><p>上传文件、提交作业一气呵成。</p><p>该命令内置调用<code>sync-upload</code>和<code>sync-run</code>这两个命令,以及其他几个脚本。在进行相关配置后,它可以根据<code>qsub_header</code>和<code>commands</code>两个文本自动生成作业脚本<code>work.sh</code>,上传指定文档(<code>work.sh</code>不指定也会上传),然后提交到任务节点进行运算。</p><p>用法:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Usage: sync-deploy -n local_files -d 'destdir'</span><br></pre></td></tr></table></figure><p>同样注意<code>~</code>的使用问题,另外,如果你只部署运行<code>work.sh</code>文档,那么请在<code>-n</code>选项后加<code>work.sh</code>,(因为<code>-n</code>选项后不加内容会报错)虽然该文本会被上传两次,但不会影响使用。</p><p>一个实例如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> sync-deploy -n work.sh -d <span class="string">'~/test'</span></span></span><br><span class="line">==> command used: scp -pr -P 22 work.sh /home/wsx/working/sync-deploy/src/work.sh [email protected]:~/test</span><br><span class="line">==></span><br><span class="line">work.sh 100% 240 0.2KB/s 00:00</span><br><span class="line">work.sh 100% 240 0.2KB/s 00:00</span><br><span class="line">==> Files upload successfully.</span><br><span class="line"></span><br><span class="line">==> run as batch mode.......</span><br><span class="line">job_id file locate at ~/test/job_id , id is</span><br><span class="line">87728.node1</span><br><span class="line">==></span><br><span class="line">==> The work deploy successfully.</span><br></pre></td></tr></table></figure><h3 id="sync-check"><a href="#sync-check" class="headerlink" title="sync-check"></a>sync-check</h3><p>用来查看作业状态。</p><p>用法:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Usage: sync-deploy -n id</span><br></pre></td></tr></table></figure><p>如果指定<code>-n</code>选项加上作业号,会查询指定的作业状态,如果不指定,会查看所有的作业状态。</p><p>任务部署后会返回作业号,刚提交了两个作业,我们来查一查。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> sync-check -n 87730</span></span><br><span class="line">Job ID Name User Time Use S Queue</span><br><span class="line">------------------------- ---------------- --------------- -------- - -----</span><br><span class="line">87730.node1 work.sh liuxs 00:00:00 C normal_3</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> sync-check -n 87730.node1</span></span><br><span class="line">Job ID Name User Time Use S Queue</span><br><span class="line">------------------------- ---------------- --------------- -------- - -----</span><br><span class="line">87730.node1 work.sh liuxs 00:00:00 C normal_3</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> sync-check</span></span><br><span class="line">Job ID Name User Time Use S Queue</span><br><span class="line">------------------------- ---------------- --------------- -------- - -----</span><br><span class="line">87729.node1 work.sh liuxs 00:00:00 C normal_3</span><br><span class="line">87730.node1 work.sh liuxs 00:00:00 C normal_3</span><br></pre></td></tr></table></figure><h2 id="计算操作实例"><a href="#计算操作实例" class="headerlink" title="计算操作实例"></a>计算操作实例</h2><p>我们来通过一个完整的实例来了解这些命令。</p><p><strong>我们的任务是</strong>利用远程的计算平台运行一些shell命令,执行一个R脚本。</p><p>该R脚本位于<code>src/</code>的<code>test</code>目录下,这个脚本我们可以看做我们日常工作运行的主脚本。</p><p>我们需要准备什么呢?</p><p>只需要正确填写<code>qsub_header</code>与<code>commands</code>文档即可。</p><p>我们先看看<code>qsub_header</code>的内容:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash">PBS -l nodes=1:ppn=10</span></span><br><span class="line"><span class="meta">#</span><span class="bash">PBS -S /bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash">PBS -j oe</span></span><br><span class="line"><span class="meta">#</span><span class="bash">PBS -q normal_3</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> Please <span class="built_in">set</span> PBS arguments above</span></span><br></pre></td></tr></table></figure><p>上述就是一些PBS选项和参数,按你自己的需求和正确写法填写即可。这里测试我就简单地设定了节点与队列。具体参数你可以百度或者参考说明文档前面提供的信息。</p><p>再瞧瞧<code>commands</code>文档:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> This job<span class="string">'s working directory</span></span></span><br><span class="line">cd ~/test</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> Following are commands</span></span><br><span class="line">sleep 20</span><br><span class="line">echo "Thi mission is run successfully!!" > ~/test/result.txt</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> call Rscripts</span></span><br><span class="line">Rscript ~/test/test.R > ~/test/result2.txt</span><br></pre></td></tr></table></figure><p>这个文档可能是我们工作主要需要修改的地方,这里我们用<code>cd</code>命令设定(作业的)工作目录,为避免任务结束太快,调用<code>sleep</code>命令让机器睡几秒,然后调用<code>echo</code>将一些文字结果传入一个结果文件,最后调用一个R脚本,并将结果传入另一个文件。</p><p>R脚本的内容也非常简单,就是输入几行文本:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">print("==>")</span><br><span class="line">print("==> Hello world!!!!!!!!")</span><br><span class="line">print("==> ")</span><br></pre></td></tr></table></figure><p>为避免程序找不到或者找错文件,我们最好指定文件所在的全部路径。</p><p><strong>让我们开始跑命令吧~</strong></p><p>任务方案很简单,我们将<code>test.R</code>上传到远程主机的工作目录下,注意,<code>work.sh</code>也会自动生成并上传,它的内容就是<code>qsub_header</code>与<code>commands</code>的结合体。然后执行<code>work.sh</code>文本,然后将输出结果传回来。</p><p>上传与运行可以利用<code>sync-deploy</code>命令一步搞定:<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 利用add_path.sh将命令加入环境路径后,我们可以利用tab补全查找命令</span></span><br><span class="line">wsx@Desktop-berry:~$ sync-</span><br><span class="line">sync-check sync-command sync-deploy sync-download sync-run sync-upload</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 利用sync-command查看目标路径情况</span></span><br><span class="line">wsx@Desktop-berry:~$ sync-command "ls -al ~/test"</span><br><span class="line">总用量 8</span><br><span class="line">drwxrwxr-x 2 liuxs liuxs 4096 1月 30 23:52 .</span><br><span class="line">drwx------ 10 liuxs liuxs 4096 1月 30 22:51 ..</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 部署任务到远程</span></span><br><span class="line"></span><br><span class="line">wsx@Desktop-berry:~$ sync-deploy -n ~/working/sync-deploy/src/test/test.R -d '~/test/'</span><br><span class="line">==> command used: scp -pr -P 22 /home/wsx/working/sync-deploy/src/test/test.R /home/wsx/working/sync-deploy/src/work.sh [email protected]:~/test/</span><br><span class="line">==></span><br><span class="line">test.R 100% 60 0.1KB/s 00:00</span><br><span class="line">work.sh 100% 300 0.3KB/s 00:00</span><br><span class="line">==> Files upload successfully.</span><br><span class="line"></span><br><span class="line">==> run as batch mode.......</span><br><span class="line">job_id file locate at ~/test/job_id , id is</span><br><span class="line">87732.node1</span><br><span class="line">==></span><br><span class="line">==> The work deploy successfully.</span><br></pre></td></tr></table></figure></p><p>可以看到任务成功部署并返回了<code>job id</code>,利用<code>sync-check</code>命令查询</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wsx@Desktop-berry:~$ sync-check 87732</span><br><span class="line">Job ID Name User Time Use S Queue</span><br><span class="line">------------------------- ---------------- --------------- -------- - -----</span><br><span class="line">87732.node1 work.sh liuxs 00:00:00 C normal_3</span><br></pre></td></tr></table></figure><p>因为任务时间不长,很快就搞定了,已经出现了<code>C</code>标志(完成)。</p><p>我们查看一下远程目录情况:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx@Desktop-berry:~$ sync-command ls '~/test'</span><br><span class="line">job_id</span><br><span class="line">result2.txt</span><br><span class="line">result.txt</span><br><span class="line">test.R</span><br><span class="line">work.sh</span><br></pre></td></tr></table></figure><p><code>job_id</code>文件是用来保存作业号信息的,就是前面输出的<code>87732.node1</code>。其他不用解释了。</p><p>最后一步,将需要的结果下载回本地。</p><p>我们创建一个临时目录单独存储,然后查看文件内容:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">wsx@Desktop-berry:~$ mkdir test</span><br><span class="line">wsx@Desktop-berry:~$ sync-download -n "~/test/*" -d ~/test</span><br><span class="line">==> command used: scp -pr -P 22 [email protected]:~/test/* /home/wsx/test</span><br><span class="line">==></span><br><span class="line">job_id 100% 12 0.0KB/s 00:00</span><br><span class="line">result2.txt 100% 51 0.1KB/s 00:00</span><br><span class="line">result.txt 100% 34 0.0KB/s 00:00</span><br><span class="line">test.R 100% 60 0.1KB/s 00:00</span><br><span class="line">work.sh 100% 300 0.3KB/s 00:00</span><br><span class="line">==> Files download successfully.</span><br><span class="line"></span><br><span class="line">wsx@Desktop-berry:~$ cd test/</span><br><span class="line">wsx@Desktop-berry:~/test$ ls</span><br><span class="line">job_id result2.txt result.txt test.R work.sh</span><br><span class="line">wsx@Desktop-berry:~/test$ cat result.txt</span><br><span class="line">Thi mission is run successfully!!</span><br><span class="line">wsx@Desktop-berry:~/test$ cat result2.txt</span><br><span class="line">[1] "==>"</span><br><span class="line">[1] "==> Hello world!!!!!!!!"</span><br><span class="line">[1] "==> "</span><br></pre></td></tr></table></figure><p><strong>任务完成!</strong></p><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>有问题欢迎<a href="https://github.com/ShixiangWang/sync-deploy/issues" target="_blank" rel="noopener">提交issue</a>进行讨论。</p>]]></content>
<summary type="html">
<p>该命令集可以非常方便地向远程主机/服务器上传文件、运行远程脚本、下载文件等。</p>
<div class="github-widget" data-repo="ShixiangWang/sync-deploy"></div>
</summary>
<category term="开源工具" scheme="https://shixiangwang.github.io/categories/%E5%BC%80%E6%BA%90%E5%B7%A5%E5%85%B7/"/>
</entry>
<entry>
<title>初识sed与awk</title>
<link href="https://shixiangwang.github.io/2017/12/25/sed-and-gawk/"/>
<id>https://shixiangwang.github.io/2017/12/25/sed-and-gawk/</id>
<published>2017-12-24T16:00:00.000Z</published>
<updated>2018-01-27T04:08:59.634Z</updated>
<content type="html"><![CDATA[<p><strong>学习内容</strong>:</p><blockquote><ul><li>学习sed编辑器</li><li>gawk编辑器入门</li><li>sed编辑器基础</li></ul></blockquote><p>shell脚本最常见的一个用途就是处理文本文件,但仅靠shell脚本命令来处理文本文件的内容有点勉为其难。如果我们想在shell脚本中处理任何类型的数据,需要熟悉Linux中的sed和gawk工具。这两个工具可以极大简化我们需要进行的数据处理任务。</p><a id="more"></a><h2 id="文本处理"><a href="#文本处理" class="headerlink" title="文本处理"></a>文本处理</h2><p>当我们需要自动处理文本文件,又不想动用交互式文本编辑器时,sed和gawk是我们最好的选择。</p><h3 id="sed编辑器"><a href="#sed编辑器" class="headerlink" title="sed编辑器"></a>sed编辑器</h3><p>也被称为<strong>流编辑器</strong>(stream editor),会在编辑器处理数据之前<strong>基于预先提供的一组规则</strong>来编辑数据流。</p><p>sed编辑器可以根据命令来处理数据流中的数据,这些命令既可以从终端输入,也可以存储进脚本文件中。</p><p>sed会执行以下的操作:</p><ul><li>一次从输入中读取一行数据</li><li>根据所提供的命令匹配数据</li><li>按照命令修改流中的数据</li><li>将新的数据输出到STDOUT</li></ul><p>这一过程会重复直至处理完流中的所有数据行。</p><p>sed命令的格式如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sed options script file</span><br></pre></td></tr></table></figure><p>选项<code>options</code>可以允许我们修改<code>sed</code>命令的行为</p><table><thead><tr><th>选项</th><th>描述</th></tr></thead><tbody><tr><td>-e script</td><td>在处理输入时,将script中指定的命令添加到已有的命令中</td></tr><tr><td>-f file</td><td>在处理输入时,将file中指定的命令添加到已有的命令中</td></tr><tr><td>-n</td><td>不产生命令输出,使用<code>print</code>命令来完成输出</td></tr></tbody></table><p><code>script</code>参数指定用于流数据上的单个命令,如果需要多个命令,要么使用<code>-e</code>选项在命令行中指定,要么使用<code>-f</code>选项在单独的文件中指定。</p><h4 id="在命令行中定义编辑器命令"><a href="#在命令行中定义编辑器命令" class="headerlink" title="在命令行中定义编辑器命令"></a>在命令行中定义编辑器命令</h4><p>默认sed会将指定命令应用到STDIN输入流上,我们可以配合管道命令使用。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ echo "This is a test" | sed 's/test/big test/'</span><br><span class="line">This is a big test</span><br></pre></td></tr></table></figure><p><code>s</code>命令使用斜线间指定的第二个文本来替换第一个文本字符串模式(注意是替换整个模式,支持正则匹配),比如这个例子用<code>big test</code>替换了<code>test</code>。</p><p>假如有以下文本:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat data1.txt</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br></pre></td></tr></table></figure><p>键入命令,查看输出</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed 's/dog/cat/' data1.txt</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br></pre></td></tr></table></figure><p>可以看到符合模式的字符串都被修改了。</p><p>要记住,sed并不会修改文本文件的数据,<strong>它只会将修改后的数据发送到STDOUT</strong>。</p><h4 id="在命令行上使用多个编辑器命令"><a href="#在命令行上使用多个编辑器命令" class="headerlink" title="在命令行上使用多个编辑器命令"></a>在命令行上使用多个编辑器命令</h4><p>使用<code>-e</code>选项可以执行多个命令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed -e 's/brown/green/; s/dog/cat/' data1.txt</span><br><span class="line">The quick green fox jumps over the lazy cat.</span><br><span class="line">The quick green fox jumps over the lazy cat.</span><br><span class="line">The quick green fox jumps over the lazy cat.</span><br><span class="line">The quick green fox jumps over the lazy cat.</span><br><span class="line">The quick green fox jumps over the lazy cat.</span><br><span class="line">The quick green fox jumps over the lazy cat.</span><br><span class="line">The quick green fox jumps over the lazy cat.</span><br><span class="line">The quick green fox jumps over the lazy cat.</span><br><span class="line">The quick green fox jumps over the lazy cat.</span><br></pre></td></tr></table></figure><p>两个命令都作用到文件中的每一行数据上。命令之间必须用分号隔开,并且<strong>在命令末尾与分号之间不同有空格</strong>。</p><p>如果不想使用分号,可以用bash shell中的次提示符来分隔命令。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed -e '</span><br><span class="line"><span class="meta">></span><span class="bash"> s/brown/green/</span></span><br><span class="line"><span class="meta">></span><span class="bash"> s/fox/elephant/</span></span><br><span class="line"><span class="meta">></span><span class="bash"> s/dog/cat/<span class="string">' data1.txt</span></span></span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br></pre></td></tr></table></figure><h4 id="从文件中读取编辑器命令"><a href="#从文件中读取编辑器命令" class="headerlink" title="从文件中读取编辑器命令"></a>从文件中读取编辑器命令</h4><p>如果有大量要处理的sed命令,将其单独放入一个文本中会更方便,可以用sed命令的<code>-f</code>选项来指定文件。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat script1.sed</span><br><span class="line">s/brown/green/</span><br><span class="line">s/fox/elephant/</span><br><span class="line">s/dog/cat/</span><br><span class="line"></span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed -f script1.sed data1.txt</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br><span class="line">The quick green elephant jumps over the lazy cat.</span><br></pre></td></tr></table></figure><p>这种情况不用在每个命令后面放一个分号,sed知道每行都有一条单独的命令。</p><h3 id="gawk程序"><a href="#gawk程序" class="headerlink" title="gawk程序"></a>gawk程序</h3><p>gawk是一个处理文本的更高级工具,能够提供一个类编程环境来修改和重新组织文件中的数据。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">说明在所有的发行版都没有默认安装gawk程序,请先安装</span><br></pre></td></tr></table></figure><p>gawk程序是Unix中原始awk的GNU版本,它让流编辑器迈上了一个新的台阶,提供了一种编程语言而不只是编辑器命令。</p><p>我们可以利用它做下面的事情:</p><ul><li>定义变量来保存数据</li><li>使用算术和字符串操作符来处理数据</li><li>使用结构化编程概念来为数据处理增加处理逻辑</li><li>通过提取数据文件中的数据元素,将其重新排列或格式化,生成格式化报告</li></ul><p>gawk程序的报告生成能力通常用来从大文本文件中提取数据元素,并将它们格式化成可读的报告,使得重要的数据更易于可读。</p><h4 id="基本命令格式"><a href="#基本命令格式" class="headerlink" title="基本命令格式"></a>基本命令格式</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gawk options program file</span><br></pre></td></tr></table></figure><p>下面显示了gawk程序的可用选项</p><table><thead><tr><th>选项</th><th>描述</th></tr></thead><tbody><tr><td>-F fs</td><td>指定行中划分数据字段的字段分隔符</td></tr><tr><td>-f file</td><td>从指定文件中读取程序</td></tr><tr><td>-v var=value</td><td>定义gawk程序中的一个变量及其默认值</td></tr><tr><td>-mf N</td><td>指定要处理的数据文件中的最大字段数</td></tr><tr><td>-mr N</td><td>指定数据文件中的最大数据行数</td></tr><tr><td>-W keyword</td><td>指定gawk的兼容模式或警告等级</td></tr></tbody></table><p>gawk的<strong>强大之处在于程序脚本</strong>(善于利用工具最强之处),可以写脚本来读取文本行的数据,然后处理并显示数据,创建任何类型的输出报告。</p><h4 id="从命令行读取脚本"><a href="#从命令行读取脚本" class="headerlink" title="从命令行读取脚本"></a>从命令行读取脚本</h4><p>我们必须将脚本命令放入两个花括号中,而由于gawk命令行假定脚本是单个文本字符串,所以我们必须把脚本放到单引号中。</p><p>下面是一个简单的例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ gawk '{print "Hello World!"}'</span><br><span class="line"></span><br><span class="line">Hello World!</span><br><span class="line">This is a test</span><br><span class="line">Hello World!</span><br><span class="line">This is</span><br><span class="line">Hello World!</span><br><span class="line"></span><br><span class="line">Hello World!</span><br></pre></td></tr></table></figure><p><code>print</code>命令将文本打印到STDOUT。如果尝试允许命令,我们可能会有些失望,因为什么都不会发生,原因是没有指定文件名,所以gawk会从STDIN接收数据,如果我们按下回车,gawk会对这行文本允许一遍程序脚本。</p><p>要终止这个程序必须表明数据流已经结束了,bash shell提供组合键来生成EOF(End-of-File)字符。Ctrl+D组合键会在bash中产生一个EOF字符。</p><h4 id="使用数据字段变量"><a href="#使用数据字段变量" class="headerlink" title="使用数据字段变量"></a>使用数据字段变量</h4><p>gawk的主要特性之一是其处理文本文件中数据的能力,它自动给一行的每个数据元素分配一个变量。</p><ul><li>$0代表整个文本行</li><li>$1代表文本行的第一个数据字段</li><li>$2代表文本行的第二个数据字段</li><li>$n代表文本行的第n个数据字段</li></ul><p>gawk在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。默认字段分隔符为任意的空白字符(例如空格或制表符)。</p><p>下面例子gawk读取文本显示第一个数据字段的值。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat data2.txt</span><br><span class="line">One line of test text.</span><br><span class="line">Two lines of test text.</span><br><span class="line">Three lines of test text.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ gawk '{print $1}' data2.txt</span><br><span class="line">One</span><br><span class="line">Two</span><br><span class="line">Three</span><br></pre></td></tr></table></figure><p>我们可以使用<code>-F</code>选项指定其他字段分隔符:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ gawk -F: '{print $1}' /etc/passwd</span><br><span class="line">root</span><br><span class="line">daemon</span><br><span class="line">bin</span><br><span class="line">sys</span><br><span class="line">sync</span><br><span class="line">games</span><br><span class="line">man</span><br><span class="line">lp</span><br><span class="line">mail</span><br><span class="line">news</span><br><span class="line">uucp</span><br><span class="line">proxy</span><br><span class="line">www-data</span><br><span class="line">backup</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>这个简短程序显示了系统中密码文件的第一个数据字段。</p><h4 id="在程序脚本中使用多个命令"><a href="#在程序脚本中使用多个命令" class="headerlink" title="在程序脚本中使用多个命令"></a>在程序脚本中使用多个命令</h4><p>在命令之间放个分号即可。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ echo "My name is Shixiang" | gawk '{$4="Christine"; print $0}'</span><br><span class="line">My name is Christine</span><br></pre></td></tr></table></figure><p>也可以使用次提示符一次一行输入程序脚本命令(类似sed)。</p><h4 id="从文件中读取程序"><a href="#从文件中读取程序" class="headerlink" title="从文件中读取程序"></a>从文件中读取程序</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat script2.gawk</span><br><span class="line">{print $1 " 's home directory is " $6}</span><br><span class="line">wsx@wsx-laptop:~/tmp$ gawk -F: -f script2.gawk /etc/passwd</span><br><span class="line">root 's home directory is /root</span><br><span class="line">daemon 's home directory is /usr/sbin</span><br><span class="line">bin 's home directory is /bin</span><br><span class="line">sys 's home directory is /dev</span><br><span class="line">sync 's home directory is /bin</span><br><span class="line">games 's home directory is /usr/games</span><br><span class="line">man 's home directory is /var/cache/man</span><br><span class="line">lp 's home directory is /var/spool/lpd</span><br><span class="line">mail 's home directory is /var/mail</span><br><span class="line">news 's home directory is /var/spool/news</span><br><span class="line">uucp 's home directory is /var/spool/uucp</span><br><span class="line">proxy 's home directory is /bin</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>可以在程序文件中指定多条命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat script3.gawk</span><br><span class="line">{</span><br><span class="line">text = "'s home directory is "</span><br><span class="line">print $1 text $6</span><br><span class="line">}</span><br><span class="line">wsx@wsx-laptop:~/tmp$ gawk -F: -f script3.gawk /etc/passwd</span><br><span class="line">root's home directory is /root</span><br><span class="line">daemon's home directory is /usr/sbin</span><br><span class="line">bin's home directory is /bin</span><br><span class="line">sys's home directory is /dev</span><br><span class="line">sync's home directory is /bin</span><br><span class="line">games's home directory is /usr/games</span><br><span class="line">man's home directory is /var/cache/man</span><br><span class="line">lp's home directory is /var/spool/lpd</span><br><span class="line">mail's home directory is /var/mail</span><br><span class="line">news's home directory is /var/spool/news</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h4 id="在处理数据前运行脚本"><a href="#在处理数据前运行脚本" class="headerlink" title="在处理数据前运行脚本"></a>在处理数据前运行脚本</h4><p>使用BEGIN关键字可以强制gawk再读取数据前执行BEGIN关键字指定的程序脚本。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat data3.txt</span><br><span class="line">Line 1</span><br><span class="line">Line 2</span><br><span class="line">Line 3</span><br><span class="line">wsx@wsx-laptop:~/tmp$ gawk 'BEGIN {print "The data3 File Contents:"}</span><br><span class="line"><span class="meta">></span><span class="bash"> {<span class="built_in">print</span> <span class="variable">$0</span>}<span class="string">' data3.txt</span></span></span><br><span class="line">The data3 File Contents:</span><br><span class="line">Line 1</span><br><span class="line">Line 2</span><br><span class="line">Line 3</span><br></pre></td></tr></table></figure><p>在gawk执行了BEGIN脚本后,它会用第二段脚本来处理文件数据。</p><h4 id="在处理数据后允许脚本"><a href="#在处理数据后允许脚本" class="headerlink" title="在处理数据后允许脚本"></a>在处理数据后允许脚本</h4><p>与BEGIN关键字类似,END关键字允许我们指定一个脚本,gawk在读完数据后执行。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ gawk 'BEGIN {print "The data3 File Contents:"}</span><br><span class="line"><span class="meta">></span><span class="bash"> {<span class="built_in">print</span> <span class="variable">$0</span>}</span></span><br><span class="line"><span class="meta">></span><span class="bash"> END {<span class="built_in">print</span> <span class="string">"End of File"</span>}<span class="string">' data3.txt</span></span></span><br><span class="line">The data3 File Contents:</span><br><span class="line">Line 1</span><br><span class="line">Line 2</span><br><span class="line">Line 3</span><br><span class="line">End of File</span><br></pre></td></tr></table></figure><p>我们把所有的内容放在一起组成一个漂亮的小程序脚本,用它从简单的数据文件中创建一份完整报告。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat script4.gawk</span><br><span class="line">BEGIN {</span><br><span class="line">print "The latest list of users and shells"</span><br><span class="line">print " UserID \t Shell"</span><br><span class="line">print "-------- \t ------"</span><br><span class="line">FS=":"</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">{</span><br><span class="line">print $1 " \t " $7</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">END {</span><br><span class="line">print "This concludes the listing"</span><br><span class="line">}</span><br><span class="line">wsx@wsx-laptop:~/tmp$ gawk -f script4.gawk /etc/passwd</span><br><span class="line">The latest list of users and shells</span><br><span class="line"> UserID Shell</span><br><span class="line">-------- ------</span><br><span class="line">root /bin/bash</span><br><span class="line">daemon /usr/sbin/nologin</span><br><span class="line">bin /usr/sbin/nologin</span><br><span class="line">sys /usr/sbin/nologin</span><br><span class="line">sync /bin/sync</span><br><span class="line">games /usr/sbin/nologin</span><br><span class="line">man /usr/sbin/nologin</span><br><span class="line">lp /usr/sbin/nologin</span><br><span class="line">mail /usr/sbin/nologin</span><br><span class="line">news /usr/sbin/nologin</span><br><span class="line">uucp /usr/sbin/nologin</span><br><span class="line">proxy /usr/sbin/nologin</span><br><span class="line">www-data /usr/sbin/nologin</span><br><span class="line">backup /usr/sbin/nologin</span><br><span class="line">list /usr/sbin/nologin</span><br><span class="line">irc /usr/sbin/nologin</span><br><span class="line">gnats /usr/sbin/nologin</span><br><span class="line">nobody /usr/sbin/nologin</span><br><span class="line">systemd-timesync /bin/false</span><br><span class="line">systemd-network /bin/false</span><br><span class="line">systemd-resolve /bin/false</span><br><span class="line">systemd-bus-proxy /bin/false</span><br><span class="line">syslog /bin/false</span><br><span class="line">_apt /bin/false</span><br><span class="line">lxd /bin/false</span><br><span class="line">messagebus /bin/false</span><br><span class="line">uuidd /bin/false</span><br><span class="line">dnsmasq /bin/false</span><br><span class="line">sshd /usr/sbin/nologin</span><br><span class="line">pollinate /bin/false</span><br><span class="line">wsx /bin/bash</span><br><span class="line">This concludes the listing</span><br></pre></td></tr></table></figure><p>我们以后会继续学习gawk高级编程。</p><h2 id="sed编辑器基础"><a href="#sed编辑器基础" class="headerlink" title="sed编辑器基础"></a>sed编辑器基础</h2><p>下面介绍一些可以集成到脚本中的基本命令和功能。</p><h3 id="更多的替换选项"><a href="#更多的替换选项" class="headerlink" title="更多的替换选项"></a>更多的替换选项</h3><p>之前我们已经学习了用<code>s</code>命令在行中替换文本,这个命令还有一些其他选项。</p><h4 id="替换标记"><a href="#替换标记" class="headerlink" title="替换标记"></a>替换标记</h4><p>替换命令<code>s</code>默认只替换每行中出现的第一处。要让该命令能替换一行中不同地方出现的文本必须使用<strong>替换标记</strong>。该标记在替换命令字符串之后设置。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">s/pattern/replacement/flags</span><br></pre></td></tr></table></figure><p>替换标记有4种:</p><ul><li>数字,表明替换第几处模式匹配的地方</li><li>g,表明替换所有匹配的文本</li><li>p,表明原先行的内容要打印出来</li><li>w file,将替换的结果写入文件中</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat data4.txt</span><br><span class="line">This is a test of the test script.</span><br><span class="line">This is the second test of the test script.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed 's/test/trial/2' data4.txt</span><br><span class="line">This is a test of the trial script.</span><br><span class="line">This is the second test of the trial script.</span><br></pre></td></tr></table></figure><p>该命令只替换每行中第二次出现的匹配模式。而<code>g</code>标记替换所有的匹配之处。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed 's/test/trial/g' data4.txt</span><br><span class="line">This is a trial of the trial script.</span><br><span class="line">This is the second trial of the trial script.</span><br></pre></td></tr></table></figure><p><code>p</code>替换标记会打印与替换命令中指定的模式匹配的行,通常与sed的<code>-n</code>选项一起使用。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat data5.txt</span><br><span class="line">This is a test line.</span><br><span class="line">This is a different line.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed -n 's/test/trial/p' data5.txt</span><br><span class="line">This is a trial line.</span><br></pre></td></tr></table></figure><p><code>-n</code>选项禁止sed编辑器输出,但<code>p</code>标记会输出修改过的行。两者配合使用就是<strong>只输出被替换命令修改过的行</strong>。</p><p><code>w</code>标记会产生同样的输出,不过会将输出(只输出被替换命令修改过的行)保存到指定文件中。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed 's/test/trial/w test.txt' data5.txt</span><br><span class="line">This is a trial line.</span><br><span class="line">This is a different line.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ cat test.txt</span><br><span class="line">This is a trial line.</span><br></pre></td></tr></table></figure><h4 id="替换字符"><a href="#替换字符" class="headerlink" title="替换字符"></a>替换字符</h4><p>有一些字符不方便在替换模式中使用,常见的例子为正斜线。</p><p>替换文件中的路径名会比较麻烦,比如用C shell替换/etc/passwd文件中的bash shell,必须这样做(通过反斜线转义):</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ head /etc/passwd</span><br><span class="line">root:x:0:0:root:/root:/bin/bash</span><br><span class="line">daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin</span><br><span class="line">...</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed 's/\/bin\/bash/\/bin\/csh/' /etc/passwd</span><br><span class="line">root:x:0:0:root:/root:/bin/csh</span><br><span class="line">daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin</span><br><span class="line">bin:x:2:2:bin:/bin:/usr/sbin/nologin</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>为解决这样的问题,sed编辑器允许选择其他字符来替换命令中的字符串分隔符:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed 's!/bin/bash!/bin/csh!' /etc/passwd</span><br><span class="line">root:x:0:0:root:/root:/bin/csh</span><br><span class="line">daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h3 id="使用地址"><a href="#使用地址" class="headerlink" title="使用地址"></a>使用地址</h3><p>如果只想要命令作用于特定行或某些行,必须使用<strong>行寻址</strong>。</p><p>有两种形式:</p><ul><li>以数字形式表示行区间</li><li>用文本模式来过滤出行</li></ul><p>它们都使用相同地格式来指定地址:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[address]command</span><br></pre></td></tr></table></figure><p>也可以将多个命令分组</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">address {</span><br><span class="line"> command1</span><br><span class="line"> command2</span><br><span class="line"> command3</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="以数字的方式行寻址"><a href="#以数字的方式行寻址" class="headerlink" title="以数字的方式行寻址"></a>以数字的方式行寻址</h4><p>sed编辑器会将文本流中的第一行编号为1,然后继续按顺序给以下行编号。</p><p>指定的地址<strong>可以是单个行号,或者用行号、逗号以及结尾行号指定的一定区间范围的行</strong>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat data1.txt</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed '2s/dog/cat/' data1.txt</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed '2,3s/dog/cat/' data1.txt</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed '2,$s/dog/cat/' data1.txt # 美元符指代最后一行</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy cat.</span><br></pre></td></tr></table></figure><h4 id="使用文本模式过滤器"><a href="#使用文本模式过滤器" class="headerlink" title="使用文本模式过滤器"></a>使用文本模式过滤器</h4><p>sed允许指定文本模式来过滤出命令要作用的行,格式如下:</p><figure class="highlight livecodeserver"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/pattern/<span class="keyword">command</span></span><br></pre></td></tr></table></figure><p>比如我要修改默认的shell,可以使用sed命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ grep wsx /etc/passwd</span><br><span class="line">wsx:x:1000:1000:"",,,:/home/wsx:/bin/bash</span><br><span class="line">wsx@wsx-laptop:~/tmp$ grep '/wsx/s/bash/csh/' /etc/passwd</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed '/wsx/s/bash/csh/' /etc/passwd</span><br><span class="line">root:x:0:0:root:/root:/bin/bash</span><br><span class="line">daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin</span><br><span class="line">bin:x:2:2:bin:/bin:/usr/sbin/nologin</span><br><span class="line">...</span><br><span class="line">wsx:x:1000:1000:"",,,:/home/wsx:/bin/csh</span><br></pre></td></tr></table></figure><p>正则表达式允许创建高级文本模式匹配表达式来匹配各种数据,结合一系列通配符、特殊字符来生成几乎任何形式文本的简练模式。我们后续会学习到。</p><h4 id="命令组合"><a href="#命令组合" class="headerlink" title="命令组合"></a>命令组合</h4><p>使用花括号可以将多条命令组合在一起。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed '2{</span><br><span class="line"><span class="meta">></span><span class="bash"> s/fox/elephant/</span></span><br><span class="line"><span class="meta">></span><span class="bash"> s/dog/cat/</span></span><br><span class="line"><span class="meta">></span><span class="bash"> }<span class="string">' data1.txt</span></span></span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown elephant jumps over the lazy cat.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br></pre></td></tr></table></figure><p>也可以在一组命令前指定一个地址区间。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed '3,${</span><br><span class="line">s/brown/green/</span><br><span class="line">s/lazy/active/</span><br><span class="line">}' data1.txt</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick green fox jumps over the active dog.</span><br><span class="line">The quick green fox jumps over the active dog.</span><br><span class="line">The quick green fox jumps over the active dog.</span><br><span class="line">The quick green fox jumps over the active dog.</span><br><span class="line">The quick green fox jumps over the active dog.</span><br><span class="line">The quick green fox jumps over the active dog.</span><br><span class="line">The quick green fox jumps over the active dog.</span><br></pre></td></tr></table></figure><h3 id="删除行"><a href="#删除行" class="headerlink" title="删除行"></a>删除行</h3><p>如果需要删除文本流中的特定行,使用删除命令<code>d</code>,它会删除匹配指定寻址模式的所有行。<strong>使用时要特别小心</strong>,如果忘记加入寻址模式,会将所有文本行删除。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat data1.txt</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed 'd' data1.txt</span><br></pre></td></tr></table></figure><p>和指定的地址一起使用才能发挥删除命令的最大功用。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat data6.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br><span class="line">This is line number 3.</span><br><span class="line">This is line number 4.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed '3d' data6.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br><span class="line">This is line number 4.</span><br></pre></td></tr></table></figure><p>通过特定行区间指定:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed '2,3d' data6.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 4.</span><br></pre></td></tr></table></figure><p>通过特殊文本结尾字符指定:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed '2,$d' data6.txt</span><br><span class="line">This is line number 1.</span><br></pre></td></tr></table></figure><p>还可以使用模式匹配特性:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed '/number 1/d' data6.txt</span><br><span class="line">This is line number 2.</span><br><span class="line">This is line number 3.</span><br><span class="line">This is line number 4.</span><br></pre></td></tr></table></figure><p>sed会删除包含匹配模式的行。</p><p>记住,sed不会修改原始文件。</p><p>还可以使用两个文本模式来删除某个区间内的行,但做的时候需要特别小心,指定的第一个模式会“打开”行删除功能,第二个模式会“关闭”行删除功能。sed会删除两个指定行之间的所有行(包括指定行)。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat data7.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br><span class="line">This is line number 3.</span><br><span class="line">This is line number 4.</span><br><span class="line">This is line number 1 again.</span><br><span class="line">This is text you want to keep.</span><br><span class="line">This is the last line in the file.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed '/1/,/3/d' data7.txt</span><br><span class="line">This is line number 4.</span><br></pre></td></tr></table></figure><p>第二个出现的数字“1”的行再次触发了删除命令,因为未能找到停止模式“3”,所以将数据流剩余的行全部删掉了。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed '/1/,/5/d' data7.txt</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed '/2/,/4/d' data7.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 1 again.</span><br><span class="line">This is text you want to keep.</span><br><span class="line">This is the last line in the file.</span><br></pre></td></tr></table></figure><h3 id="插入和附加文本"><a href="#插入和附加文本" class="headerlink" title="插入和附加文本"></a>插入和附加文本</h3><p>sed允许向数据流插入和附加文本行:</p><ul><li>插入命令<code>i</code>会在指定行前增加一个新行</li><li>附加命令<code>a</code>会在指定行后增加一个新行</li></ul><p>注意,它们不能在单个命令行上使用,必须要指定是要插入还是要附加到的那一行。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ echo "Test Line 2" | sed 'i\Test Line 1'</span><br><span class="line">Test Line 1</span><br><span class="line">Test Line 2</span><br><span class="line">wsx@wsx-laptop:~/tmp$ echo "Test Line 2" | sed 'a\Test Line 1'</span><br><span class="line">Test Line 2</span><br><span class="line">Test Line 1</span><br></pre></td></tr></table></figure><p>要向数据流行内部插入或附加数据,必须用寻址来告诉sed数据应该出现在什么位置。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed '3i\ This is an inserted line.' data6.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br><span class="line"> This is an inserted line.</span><br><span class="line">This is line number 3.</span><br><span class="line">This is line number 4.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed '3a\ This is an inserted line.' data6.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br><span class="line">This is line number 3.</span><br><span class="line"> This is an inserted line.</span><br><span class="line">This is line number 4.</span><br></pre></td></tr></table></figure><p>如果想要给数据流末尾添加多行数据,通过<code>$</code>指定位置即可。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br><span class="line">This is line number 3.</span><br><span class="line">This is line number 4.</span><br><span class="line"> This is a new line.</span><br></pre></td></tr></table></figure><h3 id="修改行"><a href="#修改行" class="headerlink" title="修改行"></a>修改行</h3><p>修改(change)命令允许修改整个数据流中整行文本内容。它跟插入和附加命令的工作机制一样。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed '3c\This is a changed line.' data6.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br><span class="line">This is a changed line.</span><br><span class="line">This is line number 4.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed '/number 3/c\This is a changed line.' data6.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br><span class="line">This is a changed line.</span><br><span class="line">This is line number 4.</span><br></pre></td></tr></table></figure><h3 id="转换命令"><a href="#转换命令" class="headerlink" title="转换命令"></a>转换命令</h3><p>转换命令(y)是<strong>唯一可以处理单字符的sed命令</strong>。格式如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[address]y/inchars/outchars</span><br></pre></td></tr></table></figure><p>转换命令会对<code>inchars</code>和<code>outchars</code>值进行一对一的映射。如果两者字符长度不同,则sed产生一条错误信息。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed 'y/123/789/' data6.txt</span><br><span class="line">This is line number 7.</span><br><span class="line">This is line number 8.</span><br><span class="line">This is line number 9.</span><br><span class="line">This is line number 4.</span><br></pre></td></tr></table></figure><p>转换命令是一个全局命令,<strong>它会在文本行中找到的所有指定字符自动进行转换,而不会考虑它们出现的位置</strong>。</p><h3 id="回顾命令"><a href="#回顾命令" class="headerlink" title="回顾命令"></a>回顾命令</h3><p>另有3个命令可以用来打印数据流中的信息:</p><ul><li><code>p</code>命令用来打印文本行</li><li>等号<code>=</code>命令用来打印行号</li><li><code>l</code>用来列出行</li></ul><h4 id="打印行"><a href="#打印行" class="headerlink" title="打印行"></a>打印行</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ echo "this is a test" | sed 'p'</span><br><span class="line">this is a test</span><br><span class="line">this is a test</span><br></pre></td></tr></table></figure><p><code>p</code>打印已有的数据文本。最常用的用法是打印符合匹配文本模式的行。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat data6.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br><span class="line">This is line number 3.</span><br><span class="line">This is line number 4.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed -n '/number 3/p' data6.txt</span><br><span class="line">This is line number 3.</span><br></pre></td></tr></table></figure><p>在命令行上使用<code>-n</code>选项,可以禁止输出其他行,只打印包含匹配文本模式的行。</p><p>也可以用来快速打印数据流中的某些行:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed -n '2,3p' data6.txt</span><br><span class="line">This is line number 2.</span><br><span class="line">This is line number 3.</span><br></pre></td></tr></table></figure><h4 id="打印行号"><a href="#打印行号" class="headerlink" title="打印行号"></a>打印行号</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat data1.txt</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed '=' data1.txt</span><br><span class="line">1</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">2</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">3</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">4</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">5</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">6</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">7</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">8</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br><span class="line">9</span><br><span class="line">The quick brown fox jumps over the lazy dog.</span><br></pre></td></tr></table></figure><p>这用来查找特定文本模式的话非常方便:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed -n '/number 4/{</span><br><span class="line"><span class="meta">></span><span class="bash"> =</span></span><br><span class="line"><span class="meta">></span><span class="bash"> p</span></span><br><span class="line"><span class="meta">></span><span class="bash"> }<span class="string">' data6.txt</span></span></span><br><span class="line">4</span><br><span class="line">This is line number 4.</span><br></pre></td></tr></table></figure><h4 id="列出行"><a href="#列出行" class="headerlink" title="列出行"></a>列出行</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat data9.txt</span><br><span class="line">This line contains tabs.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed -n 'l' data9.txt</span><br><span class="line">This\tline\tcontains\ttabs.$</span><br></pre></td></tr></table></figure><h3 id="使用Sed处理文件"><a href="#使用Sed处理文件" class="headerlink" title="使用Sed处理文件"></a>使用Sed处理文件</h3><h4 id="写入文件"><a href="#写入文件" class="headerlink" title="写入文件"></a>写入文件</h4><p><code>w</code>命令用来向文件写入行。该命令格式如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[address]w filename</span><br></pre></td></tr></table></figure><p>将文本的前两行写入其他文件:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed '1,2w test.txt' data6.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br><span class="line">This is line number 3.</span><br><span class="line">This is line number 4.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ cat test.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br></pre></td></tr></table></figure><p>如果不想让行显示到STDOUT(因为sed默认数据文本流),可以使用sed命令的<code>-n</code>选项。</p><h4 id="读取数据"><a href="#读取数据" class="headerlink" title="读取数据"></a>读取数据</h4><p>读取命令为<code>r</code>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat data12.txt</span><br><span class="line">This is an added line.</span><br><span class="line">This is the second added line.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed '3r data12.txt' data6.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br><span class="line">This is line number 3.</span><br><span class="line">This is an added line.</span><br><span class="line">This is the second added line.</span><br><span class="line">This is line number 4.</span><br></pre></td></tr></table></figure><p>这效果有点像插入文本命令<code>i</code>和补充命令<code>a</code>。</p><p> 同样适用于文本模式地址:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed '/number 2/r data12.txt' data6.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br><span class="line">This is an added line.</span><br><span class="line">This is the second added line.</span><br><span class="line">This is line number 3.</span><br><span class="line">This is line number 4.</span><br></pre></td></tr></table></figure><p>文本末尾添加:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed '$r data12.txt' data6.txt</span><br><span class="line">This is line number 1.</span><br><span class="line">This is line number 2.</span><br><span class="line">This is line number 3.</span><br><span class="line">This is line number 4.</span><br><span class="line">This is an added line.</span><br><span class="line">This is the second added line.</span><br></pre></td></tr></table></figure><p><strong>读取命令的一个很酷的用法是和删除命令配合使用:利用另一个文件中的数据来替换文件中的占位文本</strong>。假如你有一份套用信件保存在文本中:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ cat notice.std</span><br><span class="line">Would the following people:</span><br><span class="line">LIST</span><br><span class="line">please report to the ship's captain.</span><br></pre></td></tr></table></figure><p>套用信件将通用占位文本<code>LIST</code>放在人物名单的位置,我们先根据它插入文本字符,然后删除它。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~/tmp$ sed '/LIST/{</span><br><span class="line"><span class="meta">></span><span class="bash"> r data10.txt</span></span><br><span class="line"><span class="meta">></span><span class="bash"> d</span></span><br><span class="line"><span class="meta">></span><span class="bash"> }<span class="string">' notice.std</span></span></span><br><span class="line">Would the following people:</span><br><span class="line">This line contains an escape character.</span><br><span class="line">please report to the ship's captain.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ cat data10.txt</span><br><span class="line">This line contains an escape character.</span><br><span class="line">wsx@wsx-laptop:~/tmp$ cat data11.txt</span><br><span class="line">wangshx zhdan</span><br><span class="line">wsx@wsx-laptop:~/tmp$ sed '/LIST/{</span><br><span class="line">r data11.txt</span><br><span class="line">d</span><br><span class="line">}' notice.std</span><br><span class="line">Would the following people:</span><br><span class="line">wangshx zhdan</span><br><span class="line">please report to the ship's captain.</span><br></pre></td></tr></table></figure><p>可以看到占位符被替换成了数据文件中的文字。</p><p>完。</p>]]></content>
<summary type="html">
<p><strong>学习内容</strong>:</p>
<blockquote>
<ul>
<li>学习sed编辑器</li>
<li>gawk编辑器入门</li>
<li>sed编辑器基础</li>
</ul>
</blockquote>
<p>shell脚本最常见的一个用途就是处理文本文件,但仅靠shell脚本命令来处理文本文件的内容有点勉为其难。如果我们想在shell脚本中处理任何类型的数据,需要熟悉Linux中的sed和gawk工具。这两个工具可以极大简化我们需要进行的数据处理任务。</p>
</summary>
<category term="Linux杂烩" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/"/>
<category term="文本处理" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/%E6%96%87%E6%9C%AC%E5%A4%84%E7%90%86/"/>
<category term="shell笔记" scheme="https://shixiangwang.github.io/tags/shell%E7%AC%94%E8%AE%B0/"/>
<category term="linux" scheme="https://shixiangwang.github.io/tags/linux/"/>
</entry>
<entry>
<title>学习git</title>
<link href="https://shixiangwang.github.io/2017/12/08/Git-basic-operation/"/>
<id>https://shixiangwang.github.io/2017/12/08/Git-basic-operation/</id>
<published>2017-12-07T16:00:00.000Z</published>
<updated>2018-01-27T04:08:54.162Z</updated>
<content type="html"><![CDATA[<p>纯属搬砖操作,资料来源《Github入门与实战》,这本书的重要信息也就这些了,需要的时候找一找。</p><p>书上提到的一个学习网站<a href="https://learngitbranching.js.org/" target="_blank" rel="noopener">https://learngitbranching.js.org/</a>非常棒,线上学习。</p><a id="more"></a><h1 id="Git基本操作"><a href="#Git基本操作" class="headerlink" title="Git基本操作"></a>Git基本操作</h1><h2 id="git-init——初始化仓库"><a href="#git-init——初始化仓库" class="headerlink" title="git init——初始化仓库"></a>git init——初始化仓库</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> mkdir git-tutorial</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> <span class="built_in">cd</span> git-tutorial</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git init</span></span><br><span class="line">Initialized empty Git repository in /Users/hirocaster/github/github-book</span><br><span class="line">/git-tutorial/.git/</span><br></pre></td></tr></table></figure><p>如果初始化成功,执行了 git init命令的目录下就会生成 .git 目录。这个 .git 目录里存储着管理当前目录内容所需的仓库数据。 在 Git 中,我们将这个目录的内容称为“附属于该仓库的工作树”。文件的编辑等操作在工作树中进行,然后记录到仓库中,以此管理文件的历史快照。如果想将文件恢复到原先的状态,可以从仓库中调取之前的快照,在工作树中打开。</p><h2 id="git-status——查看仓库状态"><a href="#git-status——查看仓库状态" class="headerlink" title="git status——查看仓库状态"></a>git status——查看仓库状态</h2><p>git status命令用于显示 Git 仓库的状态。这是一个十分常用的命令,请务必牢记。</p><p>工作树和仓库在被操作的过程中,状态会不断发生变化。在 Git 操作过程中时常用 git status命令查看当前状态,可谓基本中的基本。下面,就让我们来实际查看一下当前状态 :</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git status</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> On branch master</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Initial commit</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">nothing to commit (create/copy files and use "git add" to track)</span><br></pre></td></tr></table></figure><p>结果显示了我们当前正处于 master 分支下。关于分支我们会在不久后讲到,现在不必深究。接着还显示了没有可提交的内容。所谓提交(Commit),是指“记录工作树中所有文件的当前状态”。</p><h2 id="git-add——向暂存区中添加文件"><a href="#git-add——向暂存区中添加文件" class="headerlink" title="git add——向暂存区中添加文件"></a>git add——向暂存区中添加文件</h2><p>要想让文件成为 Git 仓库的管理对象,就需要用 git add命令将其加入暂存区(Stage 或者 Index)中。暂存区是提交之前的一个临时区域。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git add README.md</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git status</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> On branch master</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Initial commit</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Changes to be committed:</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> (use <span class="string">"git rm --cached <file>..."</span> to unstage)</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> new file: README.md</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br></pre></td></tr></table></figure><p>将 README.md 文件加入暂存区后, git status命令的显示结果发生了变化。可以看到, README.md 文件显示在 Changes to be committed 中了。</p><h2 id="git-commit——保存仓库的历史记录"><a href="#git-commit——保存仓库的历史记录" class="headerlink" title="git commit——保存仓库的历史记录"></a>git commit——保存仓库的历史记录</h2><p>git commit命令可以将当前暂存区中的文件实际保存到仓库的历史记录中。通过这些记录,我们就可以在工作树中复原文件。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git commit -m <span class="string">"First commit"</span></span></span><br><span class="line">[master (root-commit) 9f129ba] First commit</span><br><span class="line">1 file changed, 0 insertions(+), 0 deletions(-)</span><br><span class="line">create mode 100644 README.md</span><br></pre></td></tr></table></figure><p>-m 参数后的 “First commit”称作提交信息,是对这个提交的概述。</p><h2 id="git-log——查看提交日志"><a href="#git-log——查看提交日志" class="headerlink" title="git log——查看提交日志"></a>git log——查看提交日志</h2><p>git log命令可以查看以往仓库中提交的日志。包括可以查看什 么人在什么时候进行了提交或合并,以及操作前后有怎样的差别。关于合并我们会在后面解说。</p><p>我们先来看看刚才的 git commit命令是否被记录了。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git <span class="built_in">log</span></span></span><br><span class="line">commit 9f129bae19b2c82fb4e98cde5890e52a6c546922</span><br><span class="line">Author: hirocaster <[email protected]></span><br><span class="line">Date: Sun May 5 16:06:49 2013 +0900</span><br><span class="line">First commit</span><br></pre></td></tr></table></figure><p>如上图所示,屏幕显示了刚刚的提交操作。 commit 栏旁边显示的“9f129b……”是指向这个提交的哈希值。 Git 的其他命令中,在指向提交时会用到这个哈希值。</p><p>Author 栏中显示我们给 Git 设置的用户名和邮箱地址。 Date 栏中显示提交执行的日期和时间。再往下就是该提交的提交信息。</p><h3 id="只显示提交信息的第一行"><a href="#只显示提交信息的第一行" class="headerlink" title="只显示提交信息的第一行"></a>只显示提交信息的第一行</h3><p>如果只想让程序显示第一行简述信息,可以在 git log命令后加上 –pretty=short。这样一来开发人员就能够更轻松地把握多个提交。</p><h3 id="只显示指定目录、文件的日志"><a href="#只显示指定目录、文件的日志" class="headerlink" title="只显示指定目录、文件的日志"></a>只显示指定目录、文件的日志</h3><p>只要在 git log命令后加上目录名,便会只显示该目录下的日志。如果加的是文件名,就会只显示与该文件相关的日志。</p><h3 id="显示文件的改动"><a href="#显示文件的改动" class="headerlink" title="显示文件的改动"></a>显示文件的改动</h3><p>如果想查看提交所带来的改动,可以加上 -p参数,文件的前后差别就会显示在提交信息之后。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git <span class="built_in">log</span> -p</span></span><br></pre></td></tr></table></figure><p>如上所述, git log命令可以利用多种参数帮助开发者把握以往提交的内容。不必勉强自己一次记下全部参数,每当有想查看的日志就积极去查,慢慢就能得心应手了。</p><h2 id="git-diff——查看更改前后的差别"><a href="#git-diff——查看更改前后的差别" class="headerlink" title="git diff——查看更改前后的差别"></a>git diff——查看更改前后的差别</h2><p>git diff命令可以查看工作树、暂存区、最新提交之间的差别。单从字面上可能很难理解,各位不妨跟着笔者的解说亲手试一试。</p><h3 id="查看工作树和暂存区的差别"><a href="#查看工作树和暂存区的差别" class="headerlink" title="查看工作树和暂存区的差别"></a>查看工作树和暂存区的差别</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git diff</span></span><br><span class="line">diff --git a/README.md b/README.md</span><br><span class="line">index e69de29..cb5dc9f 100644</span><br><span class="line">--- a/README.md</span><br><span class="line">+++ b/README.md</span><br><span class="line">@@ -0,0 +1 @@</span><br><span class="line">+# Git教程</span><br></pre></td></tr></table></figure><p>这里解释一下显示的内容。“+”号标出的是新添加的行,被删除的行则用“-”号标出。我们可以看到,这次只添加了一行 。</p><h3 id="查看工作树和最新提交的差别"><a href="#查看工作树和最新提交的差别" class="headerlink" title="查看工作树和最新提交的差别"></a>查看工作树和最新提交的差别</h3><p>要查看与最新提交的差别,请执行以下命令。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git diff HEAD</span></span><br><span class="line">diff --git a/README.md b/README.md</span><br><span class="line">index e69de29..cb5dc9f 100644</span><br><span class="line">--- a/README.md</span><br><span class="line">+++ b/README.md</span><br><span class="line">@@ -0,0 +1 @@</span><br><span class="line">+# Git教程</span><br></pre></td></tr></table></figure><p> <strong>不妨养成这样一个好习惯:在执行 git commit命令之前先执行git diff HEAD命令,查看本次提交与上次提交之间有什么差别,等确认完毕后再进行提交</strong>。这里的 HEAD 是指向当前分支中最新一次提交的指针。</p><h1 id="分支操作"><a href="#分支操作" class="headerlink" title="分支操作"></a>分支操作</h1><p>通过灵活运用分支,可以让多人同时高效地进行并行开发。在这里,我们将带大家学习与分支相关的 Git 操作。</p><h2 id="git-branch——显示分支一览表"><a href="#git-branch——显示分支一览表" class="headerlink" title="git branch——显示分支一览表"></a>git branch——显示分支一览表</h2><p>git branch命令可以将分支名列表显示,同时可以确认当前所在分支。让我们来实际运行 git branch命令。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git branch</span></span><br><span class="line">* master</span><br></pre></td></tr></table></figure><p>可以看到 master 分支左侧标有“*”(星号),表示这是我们当前所在的分支。也就是说,我们正在 master 分支下进行开发。结果中没有显示其他分支名,表示本地仓库中只存在 master 一个分支。</p><h2 id="git-checkout-b——创建、切换分支"><a href="#git-checkout-b——创建、切换分支" class="headerlink" title="git checkout -b——创建、切换分支"></a>git checkout -b——创建、切换分支</h2><p>如果想以当前的 master 分支为基础创建新的分支,我们需要用到git checkout -b命令。</p><h3 id="切换到-feature-A-分支并进行提交"><a href="#切换到-feature-A-分支并进行提交" class="headerlink" title="切换到 feature-A 分支并进行提交"></a>切换到 feature-A 分支并进行提交</h3><p>执行下面的命令,创建名为 feature-A 的分支。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git checkout -b feature-A</span></span><br><span class="line">Switched to a new branch 'feature-A'</span><br></pre></td></tr></table></figure><p>实际上,连续执行下面两条命令也能收到同样效果。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git branch feature-A</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git checkout feature-A</span></span><br></pre></td></tr></table></figure><p>创建 feature-A 分支,并将当前分支切换为 feature-A 分支。这时再来查看分支列表,会显示我们处于 feature-A 分支下。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git branch</span></span><br><span class="line">* feature-A</span><br><span class="line">master</span><br></pre></td></tr></table></figure><p>feature-A 分支左侧标有“*”,表示当前分支为 feature-A。在这个状态下像正常开发那样修改代码、执行 git add命令并进行提交的话,代 码 就 会 提 交 至 feature-A 分 支。 像 这 样 不 断 对 一 个 分 支(例 如feature-A)进行提交的操作,我们称为“培育分支”。</p><h3 id="切换回上一个分支"><a href="#切换回上一个分支" class="headerlink" title="切换回上一个分支"></a>切换回上一个分支</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git checkout -</span></span><br></pre></td></tr></table></figure><p>像上面这样用“-”(连字符)代替分支名,就可以切换至上一个分支。</p><h2 id="特性分支"><a href="#特性分支" class="headerlink" title="特性分支"></a>特性分支</h2><p>Git 与 Subversion(SVN)等集中型版本管理系统不同,创建分支时不需要连接中央仓库,所以能够相对轻松地创建分支。因此,当今大部分工作流程中都用到了特性(Topic)分支。</p><p>特性分支顾名思义,是集中实现单一特性(主题),除此之外不进行任何作业的分支。在日常开发中,往往会创建数个特性分支,同时在此之外再保留一个随时可以发布软件的稳定分支。稳定分支的角色通常由 master 分支担当。</p><p> 基于特定主题的作业在特性分支中进行,主题完成后再与 master 分支合并。只要保持这样一个开发流程,就能保证 master 分支可以随时供人查看。这样一来,其他开发者也可以放心大胆地从 master 分支创建新的特性分支 。</p><h2 id="git-merge——合并分支"><a href="#git-merge——合并分支" class="headerlink" title="git merge——合并分支"></a>git merge——合并分支</h2><p>接下来,我们假设 feature-A 已经实现完毕,想要将它合并到主干分支 master 中。首先切换到 master 分支。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git checkout master</span></span><br><span class="line">Switched to branch 'master'</span><br></pre></td></tr></table></figure><p>然后合并 feature-A 分支。为了在历史记录中明确记录下本次分支合并,我们需要创建合并提交。因此,在合并时加上 –no-ff参数。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git merge --no-ff feature-A</span></span><br></pre></td></tr></table></figure><p>随后编辑器会启动,用于录入合并提交的信息。</p><h2 id="git-log-–graph——以图表形式查看分支"><a href="#git-log-–graph——以图表形式查看分支" class="headerlink" title="git log –graph——以图表形式查看分支"></a>git log –graph——以图表形式查看分支</h2><p>用 git log –graph命令进行查看的话,能很清楚地看到特性分支(feature-A)提交的内容已被合并。除此以外,特性分支的创建以及合并也都清楚明了。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git <span class="built_in">log</span> --graph</span></span><br><span class="line">* commit 83b0b94268675cb715ac6c8a5bc1965938c15f62</span><br><span class="line">|\ Merge: fd0cbf0 8a6c8b9</span><br><span class="line">| | Author: hirocaster <[email protected]></span><br><span class="line">| | Date: Sun May 5 16:37:57 2013 +0900</span><br><span class="line">| |</span><br><span class="line">| | Merge branch 'feature-A'</span><br><span class="line">| |</span><br><span class="line">| * commit 8a6c8b97c8962cd44afb69c65f26d6e1a6c088d8</span><br><span class="line">|/ Author: hirocaster <[email protected]></span><br><span class="line">| Date: Sun May 5 16:22:02 2013 +0900</span><br><span class="line">|</span><br><span class="line">| Add feature-A</span><br><span class="line">|</span><br><span class="line">* commit fd0cbf0d4a25f747230694d95cac1be72d33441d</span><br><span class="line">| Author: hirocaster <[email protected]></span><br><span class="line">| Date: Sun May 5 16:10:15 2013 +0900</span><br><span class="line">|</span><br><span class="line">| Add index</span><br><span class="line">|</span><br><span class="line">* commit 9f129bae19b2c82fb4e98cde5890e52a6c546922</span><br><span class="line">Author: hirocaster <[email protected]></span><br><span class="line">Date: Sun May 5 16:06:49 2013 +0900</span><br><span class="line">First commit</span><br></pre></td></tr></table></figure><p>git log –graph命令可以用图表形式输出提交日志,非常直观,请大家务必记住。</p><h1 id="更改提交的操作"><a href="#更改提交的操作" class="headerlink" title="更改提交的操作"></a>更改提交的操作</h1><h2 id="git-reset——回溯历史版本"><a href="#git-reset——回溯历史版本" class="headerlink" title="git reset——回溯历史版本"></a>git reset——回溯历史版本</h2><p>Git 的另一特征便是可以灵活操作历史版本。借助分散仓库的优势,可以在不影响其他仓库的前提下对历史版本进行操作。</p><p>要让仓库的 HEAD、暂存区、当前工作树回溯到指定状态,需要用到 git rest –hard命令。只要提供目标时间点的哈希值 ,就可以 完全恢复至该时间点的状态。事不宜迟,让我们执行下面的命令。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git reset --hard fd0cbf0d4a25f747230694d95cac1be72d33441d (使用时这里需要个人更改哈希值)</span></span><br><span class="line">HEAD is now at fd0cbf0 Add index</span><br></pre></td></tr></table></figure><p><strong>git log命令只能查看以当前状态为终点的历史日志。所以这里要使用 git reflog命令,查看当前仓库的操作日志。在日志中找出回溯历史之前的哈希值,通过 git reset –hard命令恢复到回溯历史前的状态 。</strong></p><h2 id="消除冲突"><a href="#消除冲突" class="headerlink" title="消除冲突"></a>消除冲突</h2><h3 id="查看冲突部分并将其解决"><a href="#查看冲突部分并将其解决" class="headerlink" title="查看冲突部分并将其解决"></a>查看冲突部分并将其解决</h3><p>用编辑器打开 README.md (如果你发生了冲突,查看相应的冲突文件)文件,就会发现其内容变成了下面这个样子。 (这是书上的例子)</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> Git教程</span></span><br><span class="line"><<<<<<< HEAD</span><br><span class="line">- feature-A</span><br><span class="line">=======</span><br><span class="line">- fix-B</span><br><span class="line"><span class="meta">></span><span class="bash">>>>>>> fix-B</span></span><br></pre></td></tr></table></figure><p><code>=======</code>以上的部分是当前 HEAD 的内容,以下的部分是要合并的 fix-B 分支中的内容。我们在编辑器中将其改成想要的样子。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> Git教程</span></span><br><span class="line">- feature-A</span><br><span class="line">- fix-B</span><br></pre></td></tr></table></figure><p>如上所示,本次修正让 feature-A 与 fix-B 的内容并存于文件之中。但是在实际的软件开发中,往往需要删除其中之一,所以各位在处理冲突时,务必要仔细分析冲突部分的内容后再行修改。</p><h3 id="提交解决后的结果"><a href="#提交解决后的结果" class="headerlink" title="提交解决后的结果"></a>提交解决后的结果</h3><p>冲突解决后,执行 git add命令与 git commit命令。</p><h2 id="git-commit-–amend——修改提交信息"><a href="#git-commit-–amend——修改提交信息" class="headerlink" title="git commit –amend——修改提交信息"></a>git commit –amend——修改提交信息</h2><h2 id="git-rebase-i——压缩历史"><a href="#git-rebase-i——压缩历史" class="headerlink" title="git rebase -i——压缩历史"></a>git rebase -i——压缩历史</h2><p>在合并特性分支之前,如果发现已提交的内容中有些许拼写错误等,不妨提交一个修改,然后将这个修改包含到前一个提交之中,压缩成一个历史记录。这是个会经常用到的技巧,让我们来实际操作体会一下。</p><p>首先,新建一个 feature-C 特性分支。</p><p>作为 feature-C 的功能实现,我们在 README.md 文件中添加一行文字,并且故意留下拼写错误,以便之后修正。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git checkout -b feature-C</span></span><br><span class="line">Switched to a new branch 'feature-C'</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> Git教程</span></span><br><span class="line">- feature-A</span><br><span class="line">- fix-B</span><br><span class="line">- faeture-C</span><br></pre></td></tr></table></figure><p>提交这部分内容。这个小小的变更就没必要先执行 git add命令再执行 git commit命令了,我们<strong>用 git commit -am命令来一次完成这两步操作</strong>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git commit -am <span class="string">"Add feature-C"</span></span></span><br><span class="line">[feature-C 7a34294] Add feature-C</span><br><span class="line">1 file changed, 1 insertion(+)</span><br></pre></td></tr></table></figure><p>现在来修正刚才预留的拼写错误。 然后进行提交。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git commit -am <span class="string">"Fix typo"</span></span></span><br><span class="line">[feature-C 6fba227] Fix typo</span><br><span class="line">1 file changed, 1 insertion(+), 1 deletion(-)</span><br></pre></td></tr></table></figure><p>错字漏字等失误称作 typo,所以我们将提交信息记为 “Fix typo”。 实际上,我们不希望在历史记录中看到这类提交,因为健全的历史记录并不需要它们。如果能在最初提交之前就发现并修正这些错误,也就不会出现这类提交了。</p><p>我们来更改历史。将 “ Fix typo”修正的内容与之前一次的提交合并,在历史记录中合并为一次完美的提交。为此,我们要用到git rebase命令。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git rebase -i HEAD~2</span></span><br></pre></td></tr></table></figure><p>用上述方式执行 git rebase命令,可以选定当前分支中包含HEAD(最新提交)在内的两个最新历史记录为对象,并在编辑器中打开。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">pick 7a34294 Add feature-C</span><br><span class="line">pick 6fba227 Fix typo</span><br><span class="line"><span class="meta">#</span><span class="bash"> Rebase 2e7db6f..6fba227 onto 2e7db6f</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Commands:</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> p, pick = use commit</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> r, reword = use commit, but edit the commit message</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> e, edit = use commit, but stop <span class="keyword">for</span> amending</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> s, squash = use commit, but meld into previous commit</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> f, fixup = like <span class="string">"squash"</span>, but discard this commit<span class="string">'s log message</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> x, <span class="built_in">exec</span> = run <span class="built_in">command</span> (the rest of the line) using shell</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> These lines can be re-ordered; they are executed from top to bottom.</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> If you remove a line here THAT COMMIT WILL BE LOST.</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> However, <span class="keyword">if</span> you remove everything, the rebase will be aborted.</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Note that empty commits are commented out</span></span><br></pre></td></tr></table></figure><p>我们将 6fba227 的 Fix typo 的历史记录压缩到 7a34294 的 Add feature-C里。按照下图所示,将 6fba227 左侧的 pick 部分删除,改写为 fixup。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">pick 7a34294 Add feature-C</span><br><span class="line">fixup 6fba227 Fix typo</span><br><span class="line">[detached HEAD 51440c5] Add feature-C</span><br><span class="line">1 file changed, 1 insertion(+)</span><br><span class="line">Successfully rebased and updated refs/heads/feature-C.</span><br></pre></td></tr></table></figure><p>这样一来, Fix typo 就从历史中被抹去,也就相当于 Add feature-C中从来没有出现过拼写错误。这算是一种<strong>良性的历史改写</strong>。</p><h1 id="推送至远程仓库"><a href="#推送至远程仓库" class="headerlink" title="推送至远程仓库"></a>推送至远程仓库</h1><h2 id="git-remote-add——添加远程仓库"><a href="#git-remote-add——添加远程仓库" class="headerlink" title="git remote add——添加远程仓库"></a>git remote add——添加远程仓库</h2><p>在 GitHub 上创建的仓库路径为“[email protected]:用户名 /git-tutorial.git”。现在我们用 git remote add命令将它设置成本地仓库的远程仓库。</p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git remote add origin [email protected]:github-book/git-tutorial.git</span></span><br></pre></td></tr></table></figure><p>按照上述格式执行 git remote add命令之后, Git 会自动[email protected]:github-book/git-tutorial.git远程仓库的名称设置为 origin(标识符)。</p><h2 id="git-push——推送至远程仓库"><a href="#git-push——推送至远程仓库" class="headerlink" title="git push——推送至远程仓库"></a>git push——推送至远程仓库</h2><h3 id="推送至-master-分支"><a href="#推送至-master-分支" class="headerlink" title="推送至 master 分支"></a>推送至 master 分支</h3><p>如果想将当前分支下本地仓库中的内容推送给远程仓库,需要用到git push命令。现在假定我们在 master 分支下进行操作。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git push -u origin master</span></span><br><span class="line">Counting objects: 20, done.</span><br><span class="line">Delta compression using up to 8 threads.</span><br><span class="line">Compressing objects: 100% (10/10), done.</span><br><span class="line">Writing objects: 100% (20/20), 1.60 KiB, done.</span><br><span class="line">Total 20 (delta 3), reused 0 (delta 0)</span><br><span class="line">To [email protected]:github-book/git-tutorial.git</span><br><span class="line">* [new branch] master -> master</span><br><span class="line">Branch master set up to track remote branch master from origin.</span><br></pre></td></tr></table></figure><p>像这样执行 git push命令,当前分支的内容就会被推送给远程仓库origin 的 master 分支。 -u参数可以在推送的同时,将 origin 仓库的 master 分支设置为本地仓库当前分支的 upstream(上游)。添加了这个参数,将来运行 git pull命令从远程仓库获取内容时,本地仓库的这个分支就可以直接从 origin 的 master 分支获取内容,省去了另外添加参数的麻烦。执行该操作后,当前本地仓库 master 分支的内容将会被推送到GitHub 的远程仓库中。在 GitHub 上也可以确认远程 master 分支的内容 和本地 master 分支相同。</p><h3 id="推送至-master-以外的分支"><a href="#推送至-master-以外的分支" class="headerlink" title="推送至 master 以外的分支"></a>推送至 master 以外的分支</h3><p>除了 master 分支之外,远程仓库也可以创建其他分支。举个例子,我们在本地仓库中创建 feature-D 分支,并将它以同名形式 push 至远程仓库。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git checkout -b feature-D</span></span><br><span class="line">Switched to a new branch 'feature-D'</span><br></pre></td></tr></table></figure><p>我们在本地仓库中创建了 feature-D 分支,现在将它 push 给远程仓库并保持分支名称不变。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git push -u origin feature-D</span></span><br><span class="line">Total 0 (delta 0), reused 0 (delta 0)</span><br><span class="line">To [email protected]:github-book/git-tutorial.git</span><br><span class="line">* [new branch] feature-D -> feature-D</span><br><span class="line">Branch feature-D set up to track remote branch feature-D from origin.</span><br></pre></td></tr></table></figure><h1 id="从远程仓库获取"><a href="#从远程仓库获取" class="headerlink" title="从远程仓库获取"></a>从远程仓库获取</h1><h2 id="git-clone——获取远程仓库"><a href="#git-clone——获取远程仓库" class="headerlink" title="git clone——获取远程仓库"></a>git clone——获取远程仓库</h2><h3 id="获取远程仓库"><a href="#获取远程仓库" class="headerlink" title="获取远程仓库"></a>获取远程仓库</h3><p>首先我们换到其他目录下,将 GitHub 上的仓库 clone 到本地。注意 不要与之前操作的仓库在同一目录下。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git <span class="built_in">clone</span> [email protected]:github-book/git-tutorial.git</span></span><br><span class="line">Cloning into 'git-tutorial'...</span><br><span class="line">remote: Counting objects: 20, done.</span><br><span class="line">remote: Compressing objects: 100% (7/7), done.</span><br><span class="line">remote: Total 20 (delta 3), reused 20 (delta 3)</span><br><span class="line">Receiving objects: 100% (20/20), done.</span><br><span class="line">Resolving deltas: 100% (3/3), done.</span><br><span class="line"><span class="meta">$</span><span class="bash"> <span class="built_in">cd</span> git-tutorial</span></span><br></pre></td></tr></table></figure><p>执行 git clone命令后我们会默认处于 master 分支下,同时系统会自动将 origin 设置成该远程仓库的标识符。也就是说,当前本地仓库的 master 分支与 GitHub 端远程仓库(origin)的 master 分支在内容上是完全相同的。</p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git branch -a</span></span><br><span class="line">* master</span><br><span class="line">remotes/origin/HEAD -> origin/master</span><br><span class="line">remotes/origin/feature-D</span><br><span class="line">remotes/origin/master</span><br></pre></td></tr></table></figure><p>我们用 git branch -a命令查看当前分支的相关信息。添加 -a参数可以同时显示本地仓库和远程仓库的分支信息。<br>结果中显示了 remotes/origin/feature-D,证明我们的远程仓库中已经有了 feature-D 分支 。</p><h3 id="获取远程的-feature-D-分支"><a href="#获取远程的-feature-D-分支" class="headerlink" title="获取远程的 feature-D 分支"></a>获取远程的 feature-D 分支</h3><p>我们试着将 feature-D 分支获取至本地仓库。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git checkout -b feature-D origin/feature-D</span></span><br><span class="line">Branch feature-D set up to track remote branch feature-D from origin.</span><br><span class="line">Switched to a new branch 'feature-D'</span><br></pre></td></tr></table></figure><p>-b 参数的后面是本地仓库中新建分支的名称。为了便于理解,我们仍将其命名为 feature-D,让它与远程仓库的对应分支保持同名。新建分支名称后面是获取来源的分支名称。例子中指定了 origin/feature-D,就是说以名为 origin 的仓库(这里指 GitHub 端的仓库)的 feature-D 分支为来源,在本地仓库中创建 feature-D 分支。</p><h2 id="git-pull——获取最新的远程仓库分支"><a href="#git-pull——获取最新的远程仓库分支" class="headerlink" title="git pull——获取最新的远程仓库分支"></a>git pull——获取最新的远程仓库分支</h2><p>远程仓库的 feature-D 分支中已经有了我们刚刚推送的提交。这时我们就可以使用 git pull 命令,将本地的 feature-D 分支更新到最新状态。当前分支为 feature-D 分支。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> git pull origin feature-D</span></span><br><span class="line">remote: Counting objects: 5, done.</span><br><span class="line">remote: Compressing objects: 100% (1/1), done.</span><br><span class="line">remote: Total 3 (delta 1), reused 3 (delta 1)</span><br><span class="line">Unpacking objects: 100% (3/3), done.</span><br><span class="line">From github.com:github-book/git-tutorial</span><br><span class="line">* branch feature-D -> FETCH_HEAD</span><br><span class="line">First, rewinding head to replay your work on top of it...</span><br><span class="line">Fast-forwarded feature-D to ed9721e686f8c588e55ec6b8071b669f411486b8.</span><br></pre></td></tr></table></figure><hr><h1 id="如何用Github的gh-pages分支展示自己的项目"><a href="#如何用Github的gh-pages分支展示自己的项目" class="headerlink" title="如何用Github的gh-pages分支展示自己的项目"></a>如何用Github的gh-pages分支展示自己的项目</h1><figure class="highlight maxima"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git subtree <span class="built_in">push</span> --<span class="built_in">prefix</span>=dist <span class="built_in">origin</span> gh-pages</span><br></pre></td></tr></table></figure><p>意思就是把指定的dist文件提交到gh-pages分支上</p>]]></content>
<summary type="html">
<p>纯属搬砖操作,资料来源《Github入门与实战》,这本书的重要信息也就这些了,需要的时候找一找。</p>
<p>书上提到的一个学习网站<a href="https://learngitbranching.js.org/" target="_blank" rel="noopener">https://learngitbranching.js.org/</a>非常棒,线上学习。</p>
</summary>
<category term="Linux杂烩" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/"/>
<category term="Git" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/Git/"/>
<category term="linux" scheme="https://shixiangwang.github.io/tags/linux/"/>
<category term="git" scheme="https://shixiangwang.github.io/tags/git/"/>
<category term="github" scheme="https://shixiangwang.github.io/tags/github/"/>
</entry>
<entry>
<title>使用shell创建文本菜单和窗口部件</title>
<link href="https://shixiangwang.github.io/2017/11/29/shell-create-text-menu-and-window/"/>
<id>https://shixiangwang.github.io/2017/11/29/shell-create-text-menu-and-window/</id>
<published>2017-11-28T16:00:00.000Z</published>
<updated>2018-01-27T04:08:47.822Z</updated>
<content type="html"><![CDATA[<p><em>来源: Linux命令行与shell脚本编程大全</em></p><p>内容:</p><blockquote><ul><li>创建文本菜单</li><li>创建文本窗口部件</li><li>添加X Window图形</li></ul></blockquote><h2 id="创建文本菜单"><a href="#创建文本菜单" class="headerlink" title="创建文本菜单"></a>创建文本菜单</h2><p>创建交互式shell脚本最常用的方法是使用菜单,它提供了各种选项帮助脚本用户了解脚本能做到的和不能做的。</p><p>shell脚本菜单的核心是<code>case</code>命令,该命令会根据用户在菜单上的选择来执行特定命令。</p><p>下面我们逐步了解和创建基于菜单的shell脚本的步骤。</p><a id="more"></a><h3 id="创建菜单布局"><a href="#创建菜单布局" class="headerlink" title="创建菜单布局"></a>创建菜单布局</h3><p><strong>第一步</strong>是决定在菜单上显示哪些元素以及想要显示的布局方式。</p><p><strong>在创建菜单前,通常先清空显示器上已有的内容。这样能在干净的,没有干扰的环境中显示菜单了。</strong></p><p><code>clear</code>命令使用当前终端的<code>terminfo</code>数据来清理出现在屏幕上的文字。运行<code>clear</code>命令后可以使用<code>echo</code>命令显示菜单元素。</p><p><strong>默认,echo命令只显示可打印的文本字符。</strong>而在创建菜单时一些非文本字符也非常有用,比如制表符和换行符。我们需要添加<code>-e</code>选项使得<code>echo</code>命令能解析包含在其中的非文本字符。</p><p>例如,</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ echo -e "1.\tDisplay disk space"</span><br><span class="line">1.Display disk space</span><br></pre></td></tr></table></figure><p>这对于格式化菜单项布局非常方便,只需要几个<code>echo</code>命令就可以创建一个还不错的菜单。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">clear</span><br><span class="line">echo</span><br><span class="line">echo -e "\t\t\tSys Admin Menu\n"</span><br><span class="line">echo -e "\t1. Display disk space"</span><br><span class="line">echo -e "\t2. Display logged on users"</span><br><span class="line">echo -e "\t3. Display memory usage"</span><br><span class="line">echo -e "\t0. Exit menu\n\n"</span><br><span class="line">echo -en "\t\tEnter an option: "</span><br></pre></td></tr></table></figure><p>最后一行<code>-en</code>选项去掉末尾换行符使得菜单更专业点,光标会在行尾等待用户输入。</p><p><strong>创建菜单的最后一步是获取用户输入。</strong>这一步用<code>read</code>命令。因为我们只期望用户使用单字符输入,在命令加<code>-n</code>选项进行限定。这样用户只需要输入一个数字,不用摁回车键。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">read -n 1 option</span><br></pre></td></tr></table></figure><h3 id="创建菜单函数"><a href="#创建菜单函数" class="headerlink" title="创建菜单函数"></a>创建菜单函数</h3><p>shell脚本菜单选项作为一组独立的函数实现起来更为容易。要做到这一点,你要为每个菜单项创建独立的shell函数。<strong>第一步</strong>是决定你希望脚本执行那些功能,然后将这些功能以函数的形式放在代码中。</p><p><strong>通常我们会为还没有实现的函数先创建一个<em>桩函数</em>,它是一个控函数,或者只有一个echo语句,说明最终这里需要什么内容。</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">function diskspace {</span><br><span class="line"> clear</span><br><span class="line"> echo "This is where the diskspace commands will do"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这允许菜单在我实现某个函数时仍然能正常操作。不需要我们写出所有函数之后才能让菜单投入使用。函数从<code>clear</code>命令开始,这样我们就能在一个干净的屏幕上执行该函数,不会收到原先菜单的干扰。</p><p><strong>另外,将菜单布局本身作为一个函数来创建有利于菜单制作。</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">function menu {</span><br><span class="line"> clear</span><br><span class="line">trueecho</span><br><span class="line">trueecho -e "\t\t\tSys Admin Menu\n"</span><br><span class="line">trueecho -e "\t1. Display disk space"</span><br><span class="line">trueecho -e "\t2. Display logged on users"</span><br><span class="line">trueecho -e "\t3. Display memory usage"</span><br><span class="line">trueecho -e "\t0. Exit menu\n\n"</span><br><span class="line">trueecho -en "\t\tEnter an option: "</span><br><span class="line">trueread -n 1 option</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样我们能在任何时候调用该函数以此重现菜单。</p><h3 id="添加菜单逻辑"><a href="#添加菜单逻辑" class="headerlink" title="添加菜单逻辑"></a>添加菜单逻辑</h3><p>下一步我们需要创建程序逻辑将菜单布局和函数结合起来。这需要使用<code>case</code>命令。</p><p><code>case</code>命令应该根据菜单中输入的字符来调用相应的函数,用case命令字符星号来处理所有不正确的菜单项。</p><p>下面展示了典型菜单的<code>case</code>用法:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">menu</span><br><span class="line">case $option in</span><br><span class="line">0)</span><br><span class="line">truebreak ;;</span><br><span class="line">1)</span><br><span class="line">truediskspace ;;</span><br><span class="line">2)</span><br><span class="line">truewhoseon ;;</span><br><span class="line">3)</span><br><span class="line">truememusage ;;</span><br><span class="line">*)</span><br><span class="line">trueclear</span><br><span class="line">trueecho "Sorry, wrong selection";;</span><br><span class="line">esac</span><br></pre></td></tr></table></figure><p>这里首先调用<code>menu</code>函数清空屏幕并显示菜单。<code>menu</code>函数中的<code>read</code>命令会一直等待,知道用户在键盘上键入一个字符。然后<code>case</code>命令会接管余下的处理过程,基于字符调用相应的函数。</p><h3 id="整合shell脚本菜单"><a href="#整合shell脚本菜单" class="headerlink" title="整合shell脚本菜单"></a>整合shell脚本菜单</h3><p>现在让我们将前面的步骤全部组合起来,看看它们是如何协作的。</p><p>这是一个完整的菜单脚本例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test14</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> simple script menu</span></span><br><span class="line"></span><br><span class="line">function diskspace {</span><br><span class="line">trueclear</span><br><span class="line">truedf -k</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function whoseon {</span><br><span class="line">trueclear</span><br><span class="line">truewho</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function memusage {</span><br><span class="line">trueclear</span><br><span class="line">truecat /proc/meminfo</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function menu {</span><br><span class="line">trueclear</span><br><span class="line">trueecho</span><br><span class="line">trueecho -e "\t\t\tSys Admin Menu\n"</span><br><span class="line">trueecho -e "\t1. Display disk space"</span><br><span class="line">trueecho -e "\t2. Display logged on users"</span><br><span class="line">trueecho -e "\t3. Display memory usage"</span><br><span class="line">trueecho -e "\t0. Exit menu\n\n"</span><br><span class="line">trueecho -en "\t\tEnter an option: "</span><br><span class="line">trueread -n 1 option</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">while [ 1 ]</span><br><span class="line">do</span><br><span class="line">truemenu</span><br><span class="line">truecase $option in</span><br><span class="line">true0)</span><br><span class="line">truetruebreak ;;</span><br><span class="line">true1)</span><br><span class="line">truetruediskspace ;;</span><br><span class="line">true2)</span><br><span class="line">truetruewhoseon ;;</span><br><span class="line">true3)</span><br><span class="line">truetruememusage ;;</span><br><span class="line">true*)</span><br><span class="line">truetrueclear</span><br><span class="line">truetrueecho "Sorry, wrong selection" ;;</span><br><span class="line">trueesac</span><br><span class="line">trueecho -en "\n\n\t\t\tHit any key to continue"</span><br><span class="line">trueread -n 1 line</span><br><span class="line">done</span><br><span class="line">clear</span><br></pre></td></tr></table></figure><p>使用:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">truetrueSys Admin Menu</span><br><span class="line"></span><br><span class="line">1. Display disk space</span><br><span class="line">2. Display logged on users</span><br><span class="line">3. Display memory usage</span><br><span class="line">0. Exit menu</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">trueEnter an option:</span><br></pre></td></tr></table></figure><p>输入1:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">文件系统 1K-块 已用 可用 已用% 挂载点</span><br><span class="line">udev 4006080 0 4006080 0% /dev</span><br><span class="line">tmpfs 807220 81004 726216 11% /run</span><br><span class="line">/dev/sda4 305650672 14226064 275828680 5% /</span><br><span class="line">tmpfs 4036100 1724 4034376 1% /dev/shm</span><br><span class="line">tmpfs 5120 4 5116 1% /run/lock</span><br><span class="line">tmpfs 4036100 0 4036100 0% /sys/fs/cgroup</span><br><span class="line">/dev/sda3 524272 4684 519588 1% /boot/efi</span><br><span class="line">tmpfs 807220 52 807168 1% /run/user/1000</span><br><span class="line">tmpfs 807220 16 807204 1% /run/user/125</span><br><span class="line">/dev/sda2 421886972 23340376 398546596 6% /media/wsx/存储</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">truetruetrueHit any key to continue</span><br></pre></td></tr></table></figure><p>其他都可以自己测试一下,我就不赘言了。</p><h3 id="使用select命令"><a href="#使用select命令" class="headerlink" title="使用select命令"></a>使用select命令</h3><p><code>select</code>命令只需要一条命令就可以创建出菜单,然后获取输入的答案并自动处理。</p><p>命令格式如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">select variable in list</span><br><span class="line">do</span><br><span class="line">truecommands</span><br><span class="line">done</span><br></pre></td></tr></table></figure><p><strong><code>list</code>参数是由空格分隔的文本选项列表,这些列表构成了整个菜单。</strong><code>select</code>命令会将每个列表项显示成一个带编号的选项,然后为选项显示一个由<code>PS3</code>环境变量定义的特殊提示符。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat smenu1</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> using select <span class="keyword">in</span> the menu</span></span><br><span class="line"></span><br><span class="line">function diskspace {</span><br><span class="line">trueclear</span><br><span class="line">truedf -k</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function whoseon {</span><br><span class="line">trueclear</span><br><span class="line">truewho</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function memusage {</span><br><span class="line">trueclear</span><br><span class="line">truecat /proc/meminfo</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">PS3="Enter an option: "</span><br><span class="line">select option in "Display disk space" "Display logged on users" "Display memory usage" "Exit program"</span><br><span class="line">do</span><br><span class="line">truecase $option in</span><br><span class="line">true"Exit program")</span><br><span class="line">truetruebreak ;;</span><br><span class="line">true"Display disk space")</span><br><span class="line">truetruediskspace ;;</span><br><span class="line">true"Display logged on users")</span><br><span class="line">truetruememusage ;;</span><br><span class="line">true"Display memory usage")</span><br><span class="line">truetruememusage ;;</span><br><span class="line">true*)</span><br><span class="line">truetrueclear</span><br><span class="line">truetrueecho "Sorry, wrong selection";;</span><br><span class="line">trueesac</span><br><span class="line">done</span><br><span class="line">clear</span><br></pre></td></tr></table></figure><p>运行会自动生成如下菜单项:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ ./smenu1</span><br><span class="line">1) Display disk space 3) Display memory usage</span><br><span class="line">2) Display logged on users 4) Exit program</span><br><span class="line">Enter an option:</span><br></pre></td></tr></table></figure><p><strong>在使用<code>select</code>命令时,记住存储在变量中的结果值是整个文本字符串而不是跟菜单项相关联的数字。文本字符串是要在<code>case</code>语句中比较的内容。</strong></p><h2 id="制作窗口"><a href="#制作窗口" class="headerlink" title="制作窗口"></a>制作窗口</h2><p><code>dialog</code>包能够用ANSI转义控制字符在文本环境中创建标准的窗口对话框。我们可以将这些对话框融入自己的shell脚本中,借此与用户进行交互。这部分我们来学习如何使用<code>dialog</code>包。</p><p>安装:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install dialog</span><br></pre></td></tr></table></figure><h3 id="dialog包"><a href="#dialog包" class="headerlink" title="dialog包"></a>dialog包</h3><p><code>dialog</code>包使用命令行参数来决定生成哪种窗口部件(widget)。部件是dialog包中窗口元素的术语。</p><table><thead><tr><th>部件</th><th>描述</th></tr></thead><tbody><tr><td>calendar</td><td>提供选择日期的日历</td></tr><tr><td>checklist</td><td>显示多个选项(其中每个选项都能打开或关闭)</td></tr><tr><td>form</td><td>构建一个带有标签以及文本字段(可以填写内容)的表单</td></tr><tr><td>fselect</td><td>提供一个文件选择窗口来浏览选择文件</td></tr><tr><td>gauge</td><td>显示完成的百分比进度条</td></tr><tr><td>infobox</td><td>显示一条消息,但不用等待回应</td></tr><tr><td>inputbox</td><td>提供一个输入文本用的文本表单</td></tr><tr><td>inputmenu</td><td>提供一个可编辑的菜单</td></tr><tr><td>menu</td><td>显示可选择的一系列选项</td></tr><tr><td>msgbox</td><td>显示一条消息,并要求用户选择OK按钮</td></tr><tr><td>pause</td><td>显示一个进度条来显示暂定期间的状态</td></tr><tr><td>passwordbox</td><td>显示一个文本框,但会隐藏输入的文本</td></tr><tr><td>passwordform</td><td>显示一个带标签的隐藏文本字段的表单</td></tr><tr><td>radiolist</td><td>提供一组菜单选项,但只能选择其中一个</td></tr><tr><td>tailbox</td><td>用tail命令在滚动窗口中显示文件的内容</td></tr><tr><td>tailboxbg</td><td>跟tailbox一样,但是在后台模式中运行</td></tr><tr><td>textbox</td><td>在滚动窗口中显示文件的内容</td></tr><tr><td>timebox</td><td>提供一个选择小时、分钟和秒数的窗口</td></tr><tr><td>yesno</td><td>提供一条带有Yes和No按钮的简单消息</td></tr></tbody></table><p>如上表所见,我们可以选择很多不同的部件。只需要多花点功夫就可以让脚本看起来更专业。</p><p><strong>要在命令行上指定某个特定的部件,需要使用双破折线格式</strong>:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dialog --widget parameters</span><br></pre></td></tr></table></figure><p>其中<code>widget</code>是上表中某个特定的部件,<code>parameters</code>定义了部件窗口的大小以及部件需要的文本。</p><p><strong>每个dialog部件都提供了两种形式的输出:</strong></p><ul><li>使用STDERR</li><li>使用退出状态码</li></ul><p>可以通过<code>dialog</code>命令的退出状态码来确定用户选择的按钮。如果选择了Yes或OK按钮,命令会返回状态码<code>0</code>。如果选择了Cancer或No按钮,命令会返回状态码<code>1</code>。可用标准的<code>$?</code>变量来确定<code>dialog</code>部件具体选择了哪个按钮。</p><p>如果部件返回了数据,<code>dialog</code>命令会将数据发送到STDERR。我们可以用标准的bash shell方法将其重定向到另一个文件或文件描述符中。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dialog --inputbox "Enter your age:" 10 20 2>age.txt</span><br></pre></td></tr></table></figure><p>这条命令将文本框输入的文本重定向到age.txt文本中。</p><h4 id="msgbox部件"><a href="#msgbox部件" class="headerlink" title="msgbox部件"></a>msgbox部件</h4><p><code>msgbox</code>部件是对话框中最常见的类型。它会在窗口中显示一条简单的信息,直到用户点击OK后消失。</p><p>使用格式:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dialog --msgbox text height width</span><br></pre></td></tr></table></figure><p><code>text</code>参数是要在窗口显示的字符串,<code>height</code>与<code>width</code>参数设定自动换行的窗口大小。如果想要在窗口加一个标题,可以使用<code>--title</code>参数,后接作为标题的文本。</p><p>例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dialog --title Testing --msgbox "This is a test" 10 20</span><br></pre></td></tr></table></figure><p>输入命令后,消息框会显示在终端上。如果你的终端仿真器支持鼠标,可以单击OK来关闭对话框,也可以按下回车键。</p><h4 id="yesno部件"><a href="#yesno部件" class="headerlink" title="yesno部件"></a>yesno部件</h4><p>该部件在窗口底部生成两个按钮:一个是Yes,一个是No。用户可以用鼠标、制表符或者键盘方向键来切换按钮。选择按钮则使用空格或者回车键。</p><p>下面是一个例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~$ dialog --title "Please answer" --yesno "Is this thing on?" 10 20</span><br><span class="line"><span class="meta">#</span><span class="bash"> 中间终端有输出</span></span><br><span class="line">wsx@wsx:~$ echo $?</span><br><span class="line">0</span><br></pre></td></tr></table></figure><p><code>dialog</code>命令的退出状态码会根据用户选择的按钮来设置。选择No返回1,选择Yes就是0。</p><h4 id="inputbox部件"><a href="#inputbox部件" class="headerlink" title="inputbox部件"></a>inputbox部件</h4><p>inputbox部件提供了一个简单的文本框区域来输入文本字符串,dialog会将它传到STDERR,我们需要重定向获得输入。inputbox提供了两个按钮:OK和Cancel。如果选择了OK,命令退出状态码为0,否则为1。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ dialog --inputbox "Enter your age:" 10 20 2>age.txt</span><br><span class="line">wsx@wsx:~/tmp$ cat age.txt</span><br><span class="line">24</span><br></pre></td></tr></table></figure><p>如果你自己运行过的话就会注意到该值后面没有换行符,这让我们能够轻松将文本内容重定向到脚本变量中,以获得用户输入的值。</p><h4 id="textbox部件"><a href="#textbox部件" class="headerlink" title="textbox部件"></a>textbox部件</h4><p>textbox部件是在窗口中显示大量信息的极佳办法。它会生成一个滚动窗口来显示由参数所指定的文件中的文本。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ dialog --textbox /etc/passwd 15 45</span><br></pre></td></tr></table></figure><p><code>/etc/passwd</code>文件内容显示在可滚动的文本窗口中,可以用方向键来左右或上下滚动显示文件的内容。窗口底部的行会显示当前查看文本处于文件中的哪个位置(百分比)。文本框只包含一个用来选择退出部件的Exit按钮。</p><h4 id="menu部件"><a href="#menu部件" class="headerlink" title="menu部件"></a>menu部件</h4><p>我们可以用这个部件来创建之前(上一篇笔记)中制作的文本菜单的窗口版本。只要为每个选项提供一个选择标号和文本就行。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ dialog --menu "Sys Admin Menu" 20 30 10 1 "Display disk space" 2 "Display users" 3 "Display memory usage" 4 "Exit" 2>test.txt</span><br></pre></td></tr></table></figure><p><strong>第一个参数定义了菜单的标题,之后的两个参数定义了菜单窗口的高和宽,而第四个参数则定义了在窗口中一次显示的菜单项总数。如果存在更多选择,则有滚动条。</strong></p><p>菜单项必须使用菜单对:第一个元素是用来选择菜单项的标号(必须唯一);第二个元素是菜单中使用的文本。</p><p>dialog命令会将选定(鼠标点击或回车键或选择OK)的菜单项文本发送到STDERR。</p><h4 id="fselect部件"><a href="#fselect部件" class="headerlink" title="fselect部件"></a>fselect部件</h4><p>该部件在处理文件名时非常方便。不用强制用户键入文件名,我们就可以用<code>fselect</code>部件来浏览文件的位置并选择文件。</p><p>使用格式:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ dialog --title "Select a file" --fselect $HOME/ 10 50 2>file.txt</span><br></pre></td></tr></table></figure><p><strong>第一个参数是窗口使用的其实目录位置。<code>fselect</code>部件窗口由左侧的目录列表、右侧的文件列表和含有当前选定的文件或目录的简单文本框组成。可以手动在文本框键入文件名,或者用目录和文件列表来选定(使用空格键选定)</strong>。</p><h3 id="dialog选项"><a href="#dialog选项" class="headerlink" title="dialog选项"></a>dialog选项</h3><p>除了标准部件,dialog还有大量定制的选项。前面我们使用的<code>title</code>就是一个。</p><p>下面显示了命令可用的选项:</p><table><thead><tr><th>选项</th><th>描述</th></tr></thead><tbody><tr><td>–add-widget</td><td>继续下一个对话框直到按下Esc或Cancel选项</td></tr><tr><td>–aspect ratio</td><td>直到窗口宽度和高度的宽高比</td></tr><tr><td>–backtitle title</td><td>直到显示在屏幕顶部背景上的图标</td></tr><tr><td>–begin x y</td><td>指定窗口左上角的起始位置</td></tr><tr><td>–cancel-label label</td><td>指定Cancel按钮的替代标签</td></tr><tr><td>–clear</td><td>用默认的对话背景色来清空屏幕内容</td></tr><tr><td>–colors</td><td>在对话文本中嵌入ANSI色彩编码</td></tr><tr><td>–cr-wrap</td><td>在对话文本中允许使用换行符并强制换行</td></tr><tr><td>–create-rc file</td><td>将示例配置文件的内容复制到指定的file文件中</td></tr><tr><td>–defaultno</td><td>将yes/no对话框的默认答案设为no</td></tr><tr><td>–default-item string</td><td>设定复选列表、表单或菜单对话中的默认项</td></tr><tr><td>–exit-label label</td><td>指定Exit按钮的替代标签</td></tr><tr><td>–extra-button</td><td>在OK按钮和Cancel按钮之间显示一个额外按钮</td></tr><tr><td>–extra-label label</td><td>指定额外按钮的替换标签</td></tr><tr><td>–help</td><td>显示dialog命令的帮助信息</td></tr><tr><td>–help-button</td><td>在OK按钮和Cancel按钮后显示一个Help按钮</td></tr><tr><td>–help-label label</td><td>指定Help按钮的替换标签</td></tr><tr><td>–help-status</td><td>当选定Help按钮后,在帮助信息后写入多选列表、单选列表或表单信息</td></tr><tr><td>–ignore</td><td>忽略dialog不能识别的选项</td></tr><tr><td>–input-fd fd</td><td>指定STDIN之外的另一个文件描述符</td></tr><tr><td>–insecure</td><td>在passwd部件中键入内容时显示星号</td></tr><tr><td>–item-help</td><td>为多选列表、单选列表或菜单中的每个标号在屏幕底部添加一个帮助栏</td></tr><tr><td>–keep-window</td><td>不要清除屏幕上显示过的部件</td></tr><tr><td>–max-input size</td><td>指定输入的最大字符串长度。默认为2048</td></tr><tr><td>–nocancel</td><td>隐藏Cancel按钮</td></tr><tr><td>–no-collapse</td><td>不要将对话文本中的制表符转换为空格</td></tr><tr><td>–no-kill</td><td>将tailboxbg对话放到后台,并禁止该进程的SIGHUP信号</td></tr><tr><td>–no-label label</td><td>为No按钮指定替换标签</td></tr><tr><td>–no-shadow</td><td>不要显示对话窗口的阴影效果</td></tr><tr><td>–ok-label label</td><td>指定OK按钮的替换标签</td></tr><tr><td>–output-fd fd</td><td>指定除STDERR之外的另一个输出文件描述符</td></tr><tr><td>–print-maxsize</td><td>将对话窗口的最大尺寸打印到输出中</td></tr><tr><td>–print-size</td><td>将每个对话窗口的大小打印到输出中</td></tr><tr><td>–print-version</td><td>将dialog的版本号打印到输出中</td></tr><tr><td>–separate-output</td><td>一次一行地输出checklist部件的结果,不使用引号</td></tr><tr><td>–separator string</td><td>指定用于分隔部件输出的字符串</td></tr><tr><td>–separate-widget string</td><td>指定用于分隔部件输出的字符串</td></tr><tr><td>–shadow</td><td>在每个窗口右下角绘制阴影</td></tr><tr><td>–single-quoted</td><td>需要时对多选列表的输出采用单引号</td></tr><tr><td>–sleep sec</td><td>在处理完对话窗口后延迟指定的秒数</td></tr><tr><td>–stderr</td><td>将输出发送到STDERR(默认)</td></tr><tr><td>–stdout</td><td>将输出发送到STDOUT</td></tr><tr><td>–tab-correct</td><td>将制表符转换为空格</td></tr><tr><td>–tab-len n</td><td>指定一个制表符占用的空格数(默认为8)</td></tr><tr><td>–timeout sec</td><td>指定无用户输入时,sec秒后退出并返回错误代码</td></tr><tr><td>–title title</td><td>指定对话窗口的标题</td></tr><tr><td>–trim</td><td>从对话文本中删除前导空格和换行符</td></tr><tr><td>–visit-tiems</td><td>修改对话窗口制表符的停留位置,使其包括选项列表</td></tr><tr><td>–yes-label label</td><td>为Yes按钮指定替换标签</td></tr></tbody></table><p> <strong><code>--backtitle</code>选项是为脚本中的菜单创建公共标题的简便办法。</strong>上表提供的强大特性允许我们创建任何需要的窗口。</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dialog命令支持运行时配置。该命令会根据配置文件模板创建一份配置文件。dialog启动时会先去检查是否设置了DIALOGRC环境变量,该变量会保存配置文件名信息。如果未设置该变量或未找到该文件,它会将<span class="variable">$HOME</span><span class="regexp">/.dialogrc作为配置文件。如果这个文件还不存在的话就尝试查找编译时指定的GLOBALRC文件,也就是/</span>etc<span class="regexp">/dialogrc。如果还不存在就用编译时的默认值。</span></span><br></pre></td></tr></table></figure><h3 id="在脚本中使用dialog命令"><a href="#在脚本中使用dialog命令" class="headerlink" title="在脚本中使用dialog命令"></a>在脚本中使用dialog命令</h3><p><strong>必须记住两件事:</strong></p><ul><li>如果有Cancel或No按钮,检查dialog命令的退出状态码</li><li>重定向STDERR来获得输出值</li></ul><p>接下来是一个简单的实例,使用dialog部件生成我们之前(上一篇笔记)所创建的系统管理菜单。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-laptop:~$ cat menu3</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> using dialog to create a menu</span></span><br><span class="line"></span><br><span class="line">temp=$(mktemp -t test.XXXXXX)</span><br><span class="line">temp2=$(mktemp -t test2.XXXXXX)</span><br><span class="line"></span><br><span class="line">function diskspace {</span><br><span class="line"> df -k > $temp</span><br><span class="line"> dialog --textbox $temp 20 60</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function whoseon {</span><br><span class="line"> who > $temp</span><br><span class="line"> dialog --textbox $temp 20 50</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function menusage {</span><br><span class="line"> cat /proc/meminfo > $temp</span><br><span class="line"> dialog --textbox $temp 20 50</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">while [ 1 ]</span><br><span class="line">do</span><br><span class="line">dialog --menu "Sys Admin Menu" 20 30 10 1 "Display disk space" 2 "Display users" 3 "Display memory usage" 0 "Exit" 2> $temp2</span><br><span class="line">if [ $? -eq 1 ]</span><br><span class="line">then</span><br><span class="line"> break</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">selection=$(cat $temp2)</span><br><span class="line"></span><br><span class="line">case $selection in</span><br><span class="line">1)</span><br><span class="line"> diskspace ;;</span><br><span class="line">2)</span><br><span class="line"> whoseon ;;</span><br><span class="line">3)</span><br><span class="line"> memusage ;;</span><br><span class="line">0)</span><br><span class="line"> break ;;</span><br><span class="line">*)</span><br><span class="line"> dialog --msgbox "Sorry, invalid selection" 10 30</span><br><span class="line">esac</span><br><span class="line">done</span><br><span class="line">rm -f $temp 2> /dev/null</span><br><span class="line">rm -f $temp 2> /dev/null</span><br></pre></td></tr></table></figure><p>使用while循环加一个真值常量创建了一个无限循环来显示菜单对话。当执行完每个函数后,脚本会返回继续显示菜单。</p><p>脚本使用了mktemp命令创建两个临时文件来保存dialog命令的数据。</p><p>后面本来还有图形环境中(KDE、GNOME)使用的一些流行包介绍。以后等我使用ubuntu写笔记时再作学习和介绍吧。</p>]]></content>
<summary type="html">
<p><em>来源: Linux命令行与shell脚本编程大全</em></p>
<p>内容:</p>
<blockquote>
<ul>
<li>创建文本菜单</li>
<li>创建文本窗口部件</li>
<li>添加X Window图形</li>
</ul>
</blockquote>
<h2 id="创建文本菜单"><a href="#创建文本菜单" class="headerlink" title="创建文本菜单"></a>创建文本菜单</h2><p>创建交互式shell脚本最常用的方法是使用菜单,它提供了各种选项帮助脚本用户了解脚本能做到的和不能做的。</p>
<p>shell脚本菜单的核心是<code>case</code>命令,该命令会根据用户在菜单上的选择来执行特定命令。</p>
<p>下面我们逐步了解和创建基于菜单的shell脚本的步骤。</p>
</summary>
<category term="Linux杂烩" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/"/>
<category term="shell编程" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/shell%E7%BC%96%E7%A8%8B/"/>
<category term="shell笔记" scheme="https://shixiangwang.github.io/tags/shell%E7%AC%94%E8%AE%B0/"/>
<category term="linux" scheme="https://shixiangwang.github.io/tags/linux/"/>
</entry>
<entry>
<title>创建和使用shell函数</title>
<link href="https://shixiangwang.github.io/2017/11/26/shell-create-function/"/>
<id>https://shixiangwang.github.io/2017/11/26/shell-create-function/</id>
<published>2017-11-25T16:00:00.000Z</published>
<updated>2018-01-27T04:08:42.946Z</updated>
<content type="html"><![CDATA[<p><em>来源: Linux命令行与shell脚本编程大全</em></p><p><strong>内容</strong></p><blockquote><ul><li>基本的脚本函数</li><li>返回值</li><li>在函数中使用变量</li><li>数组变量和函数</li><li>函数递归</li><li>创建库</li><li>在命令行上使用函数</li></ul></blockquote><p>我们可以将shell脚本代码放进函数中封装起来,这样就能在脚本中的任何地方多次使用它了。</p><p>下面我们来逐步了解如何创建自己的shell脚本函数并在应用中使用它们。</p><a id="more"></a><h2 id="基本的脚本函数"><a href="#基本的脚本函数" class="headerlink" title="基本的脚本函数"></a>基本的脚本函数</h2><p>函数是一个脚本代码块,我们可以为其命名并在代码中任何位置重用。要在脚本中使用该代码块,只要使用所起的函数名就行了。</p><h3 id="创建函数"><a href="#创建函数" class="headerlink" title="创建函数"></a>创建函数</h3><p>有两种格式可以创建函数。第一种格式是使用关键字<code>function</code>,后跟分配给该代码块的函数名。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">funtion name{</span><br><span class="line">truecommands</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>name</code>属性定义了赋予函数的唯一名称,<code>commands</code>是构成函数的一条或多条bash shell命令。</p><p>第二种格式更接近其他编程语言中定义函数的方式:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">name() {</span><br><span class="line"> commands</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="使用函数"><a href="#使用函数" class="headerlink" title="使用函数"></a>使用函数</h3><p>要使用函数,只需要像其他shell命令一样,在行中指定函数名就行了。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test1</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> using a <span class="keyword">function</span> <span class="keyword">in</span> a script</span></span><br><span class="line"></span><br><span class="line">function func1 {</span><br><span class="line"> echo "This is an example of a function"</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">count=1</span><br><span class="line">while [ $count -le 5 ]</span><br><span class="line">do</span><br><span class="line"> func1</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">echo "This is the end of the loop"</span><br><span class="line">func1</span><br><span class="line">echo "Now, this is the end of the script"</span><br><span class="line"></span><br><span class="line">wsx@wsx:~/tmp$ ./test1</span><br><span class="line">This is an example of a function</span><br><span class="line">This is an example of a function</span><br><span class="line">This is an example of a function</span><br><span class="line">This is an example of a function</span><br><span class="line">This is an example of a function</span><br><span class="line">This is the end of the loop</span><br><span class="line">This is an example of a function</span><br><span class="line">Now, this is the end of the script</span><br></pre></td></tr></table></figure><p>注意,定义函数名<code>func1</code>的后面一定要跟<code>{</code>有空格隔开,不然会报错。<strong>函数要先定义再使用,接触过编程的想必不陌生吧</strong>。</p><h2 id="返回值"><a href="#返回值" class="headerlink" title="返回值"></a>返回值</h2><p>bash shell会把函数当做一个小型脚本,运行结束时会返回一个退出状态码,有3种不同的方法来为函数生成退出状态码。</p><h3 id="默认退出状态码"><a href="#默认退出状态码" class="headerlink" title="默认退出状态码"></a>默认退出状态码</h3><p>默认函数的退出状态码是函数中最后一条命令返回的退出状态码。我们可以使用标准变量<code>$?</code>在函数执行结束后确定函数的状态码。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test2</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> testing the <span class="built_in">exit</span> status of a <span class="keyword">function</span></span></span><br><span class="line"></span><br><span class="line">func1() {</span><br><span class="line">trueecho "trying to display a non-existent file"</span><br><span class="line">truels -l badfile</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">echo "testing the function"</span><br><span class="line">func1</span><br><span class="line">echo "The exit status is: $?"</span><br><span class="line">wsx@wsx:~/tmp$ ./test2</span><br><span class="line">testing the function</span><br><span class="line">trying to display a non-existent file</span><br><span class="line">ls: 无法访问'badfile': 没有那个文件或目录</span><br><span class="line">The exit status is: 2</span><br></pre></td></tr></table></figure><p>函数的退出状态码是2,说明函数的最后一条命令没有成功运行。但你无法知道函数中其他命令中是否成功运行,我们来看看下面一个例子。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test3</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> testing the <span class="built_in">exit</span> status of a <span class="keyword">function</span></span></span><br><span class="line"></span><br><span class="line">func1(){</span><br><span class="line">truels -l badfile</span><br><span class="line">trueecho "This was a test of a bad command"</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">echo "testing the function:"</span><br><span class="line">func1</span><br><span class="line">echo "The exit status is: $?"</span><br><span class="line">wsx@wsx:~/tmp$ ./test3</span><br><span class="line">testing the function:</span><br><span class="line">ls: 无法访问'badfile': 没有那个文件或目录</span><br><span class="line">This was a test of a bad command</span><br><span class="line">The exit status is: 0</span><br></pre></td></tr></table></figure><p>这次函数的退出状态码是0,尽管其中有一条命令没有正常运行。可见使用函数的默认退出状态码是很危险的,幸运的是,我们有几种办法解决它。</p><h3 id="使用return命令"><a href="#使用return命令" class="headerlink" title="使用return命令"></a>使用return命令</h3><p><code>return</code>命令允许指定一个<strong>整数值</strong>来定义函数的退出状态码,从而提供了一种简单的途径来编码设定函数退出状态码。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test4</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> using the <span class="built_in">return</span> <span class="built_in">command</span> <span class="keyword">in</span> a <span class="keyword">function</span></span></span><br><span class="line"></span><br><span class="line">function db1 {</span><br><span class="line">trueread -p "Enter a value: " value</span><br><span class="line">trueecho "doubling the value"</span><br><span class="line">truereturn $[ $value * 2 ]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">db1</span><br><span class="line">echo "The new value is $?"</span><br><span class="line">wsx@wsx:~/tmp$ ./test4</span><br><span class="line">Enter a value: 4</span><br><span class="line">doubling the value</span><br><span class="line">The new value is 8</span><br></pre></td></tr></table></figure><p>当使用这种方法时要小心,记住下面两条技巧来避免问题:</p><ul><li>函数一结束就取返回值</li><li>退出状态码必须是0~255</li></ul><p>如果在用<code>$?</code>变量提取函数的返回值之前使用了其他命令,函数的返回值就会丢失。任何大于255的整数值都会产生一个错误值。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ ./test4</span><br><span class="line">Enter a value: 200</span><br><span class="line">doubling the value</span><br><span class="line">The new value is 144</span><br></pre></td></tr></table></figure><h3 id="使用函数输出"><a href="#使用函数输出" class="headerlink" title="使用函数输出"></a>使用函数输出</h3><p>如同可以将命令的输出保存到shell变量一样,我们也可以对函数的输出采用同样的处理办法。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">result=`db1`</span><br></pre></td></tr></table></figure><p>这个命令会将<code>db1</code>函数的输出赋值给<code>$result</code>变量。下面是脚本的一个实例:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test4b</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> using the <span class="built_in">echo</span> to <span class="built_in">return</span> a value</span></span><br><span class="line"></span><br><span class="line">function db1 {</span><br><span class="line">trueread -p "Enter a value: " value</span><br><span class="line">trueecho $[ $value * 2 ]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">result=$(db1)</span><br><span class="line">echo "The new value is $result"</span><br><span class="line">wsx@wsx:~/tmp$ ./test4b</span><br><span class="line">Enter a value: 200</span><br><span class="line">The new value is 400</span><br></pre></td></tr></table></figure><p>函数会用<code>echo</code>语句来显示计算的结果,该脚本会查看<code>db1</code>函数的输出,而不是查看退出状态码。</p><blockquote><p>通过这种技术,我们还可以返回浮点值和字符串值,这使它成为一种获取函数返回值的强大方法。</p></blockquote><h2 id="在函数中使用变量"><a href="#在函数中使用变量" class="headerlink" title="在函数中使用变量"></a>在函数中使用变量</h2><p>在函数中使用变量时,我们需要注意它们的定义方式以及处理方法。这是shell脚本常见错误的根源。</p><h3 id="向函数传递参数"><a href="#向函数传递参数" class="headerlink" title="向函数传递参数"></a>向函数传递参数</h3><p>函数可以使用标准的参数环境变量来表示命令行上传给函数的参数。例如,函数名会在<code>$0</code>变量中定义,函数命令行上的任何参数都会通过<code>$1</code>、<code>$2</code>定义。也可以用特殊变量<code>$#</code>来判断给函数的参数数目。</p><p>指定函数时,必须将参数和函数放在同一行:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">func1 $value1 10</span><br></pre></td></tr></table></figure><p>然后函数可以用参数环境变量来获得参数值。下面是一个例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test5</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> passing parameters to a <span class="keyword">function</span></span></span><br><span class="line"></span><br><span class="line">function addem {</span><br><span class="line">trueif [ $# -eq 0 ] || [ $# -gt 2 ]</span><br><span class="line">truethen</span><br><span class="line">truetrueecho -1</span><br><span class="line">trueelif [ $# -eq 1 ]</span><br><span class="line">truethen</span><br><span class="line">truetrueecho $[ $1 + $1 ]</span><br><span class="line">trueelse</span><br><span class="line">truetrueecho $[ $1 + $2 ]</span><br><span class="line">truefi</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">echo -n "Adding 10 and 15: "</span><br><span class="line">value=$(addem 10 15)</span><br><span class="line">echo $value</span><br><span class="line">echo -n "Let's try adding just one number: "</span><br><span class="line">value=$(addem 10)</span><br><span class="line">echo $value</span><br><span class="line">echo -n "Now trying adding no numbers: "</span><br><span class="line">value=$(addem)</span><br><span class="line">echo $value</span><br><span class="line">echo -n "Finally, try add three numbers: "</span><br><span class="line">value=$(addem 10 15 20)</span><br><span class="line">echo $value</span><br><span class="line">wsx@wsx:~/tmp$ ./test5</span><br><span class="line">Adding 10 and 15: 25</span><br><span class="line">Let's try adding just one number: 20</span><br><span class="line">Now trying adding no numbers: -1</span><br><span class="line">Finally, try add three numbers: -1</span><br></pre></td></tr></table></figure><p><code>addem</code>函数首先会检查脚本传给它的参数数目。如果没有任何参数,或者参数多于两个,<code>addem</code>会返回<code>-1</code>。如果只有一个参数,<code>addem</code>会将参数与自身相加。如果有两个参数,<code>addem</code>会将它们相加。</p><p><strong>由于函数使用特殊参数环境变量作为自己的参数值,因此它无法直接获取脚本在命令行中的参数值。</strong>下面是个失败的例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat badtest1</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> trying to access script parameters inside a <span class="keyword">function</span></span></span><br><span class="line"></span><br><span class="line">function badfunc1 {</span><br><span class="line">trueecho $[ $1 * $2 ]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">if [ $# -eq 2 ]</span><br><span class="line">then</span><br><span class="line">truevalue=$(badfunc1)</span><br><span class="line">trueecho "The result is $value"</span><br><span class="line">else</span><br><span class="line">trueecho "Usage: badtest1 a b"</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">wsx@wsx:~/tmp$ ./badtest1 10 15</span><br><span class="line">./badtest1: 行 5: * : 语法错误: 需要操作数 (错误符号是 "* ")</span><br><span class="line">The result is</span><br></pre></td></tr></table></figure><p>尽管函数也使用了<code>$1</code>与<code>$2</code>变量,但它们与主脚本中的变量不同,要使用它们必须在调用函数时手动传入。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test6</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> trying to access script parameters inside a <span class="keyword">function</span></span></span><br><span class="line"></span><br><span class="line">function func1 {</span><br><span class="line">trueecho $[ $1 * $2 ]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">if [ $# -eq 2 ]</span><br><span class="line">then</span><br><span class="line">truevalue=$(func1 $1 $2)</span><br><span class="line">trueecho "The result is $value"</span><br><span class="line">else</span><br><span class="line">trueecho "Usage: badtest1 a b"</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">wsx@wsx:~/tmp$ ./test6</span><br><span class="line">Usage: badtest1 a b</span><br><span class="line">wsx@wsx:~/tmp$ ./test6 10 15</span><br><span class="line">The result is 150</span><br></pre></td></tr></table></figure><h3 id="在函数中处理变量"><a href="#在函数中处理变量" class="headerlink" title="在函数中处理变量"></a>在函数中处理变量</h3><p><strong>作用域</strong>是变量可见的区域。对脚本的其他部分而言,函数定义的变量是隐藏的。这些概念其实是编程语言中通用的,想必学过一些其他编程的朋友早已有所理解了。</p><p>函数使用两种类型的变量:</p><ul><li>全局变量</li><li>局部变量</li></ul><h4 id="全局变量"><a href="#全局变量" class="headerlink" title="全局变量"></a>全局变量</h4><p><strong>全局变量</strong>是在shell脚本中任何地方都有效的变量,如果你在函数内定义了一个全局变量,也可以在脚本的主体部分读取它的值。</p><p>默认情况下,我们在脚本中定义的任何变量都是全局变量。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test7</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> using a global variable to pass a value</span></span><br><span class="line"></span><br><span class="line">function db1 {</span><br><span class="line">truevalue=$[ $value * 2 ]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">read -p "Enter a value: " value</span><br><span class="line">db1</span><br><span class="line">echo "The new value is: $value"</span><br><span class="line"></span><br><span class="line">wsx@wsx:~/tmp$ ./test7</span><br><span class="line">Enter a value: 10</span><br><span class="line">The new value is: 20</span><br></pre></td></tr></table></figure><p>无论变量在函数内外定义,在脚本中引用该变量都有效。这样其实非常危险,尤其是如果你想在不同的shell脚本中使用函数的话。它要求你清清楚楚地知道函数中具体使用了哪些变量,包括那些用来计算非返回值的变量。下面是一个如何搞砸的例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat badtest2</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> demonstrating a bad use of variable</span></span><br><span class="line"></span><br><span class="line">function func1 {</span><br><span class="line">truetemp=$[ $value + 5 ]</span><br><span class="line">trueresult=$[ $temp * 2 ]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">temp=4</span><br><span class="line">value=6</span><br><span class="line"></span><br><span class="line">func1</span><br><span class="line">echo "The result is $result"</span><br><span class="line">if [ $temp -gt $value ]</span><br><span class="line">then</span><br><span class="line">trueecho "temp is larger"</span><br><span class="line">else</span><br><span class="line">trueecho "temp is smaller"</span><br><span class="line">fi</span><br><span class="line">wsx@wsx:~/tmp$ ./badtest2</span><br><span class="line">The result is 22</span><br><span class="line">temp is larger</span><br></pre></td></tr></table></figure><p>由于函数中用到了<code>$temp</code>变量,它的值在脚本中使用时受到了影响,产生了意想不到的后果。后面我们会学习如何处理这样的问题。</p><h4 id="局部变量"><a href="#局部变量" class="headerlink" title="局部变量"></a>局部变量</h4><p>无需在函数中使用全局变量,函数内部使用的任何变量都可以被声明成局部变量。<strong>我们只需要在变量声明前加上local关键字就可以了</strong>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">local temp</span><br></pre></td></tr></table></figure><p>也可以在变量赋值时使用local关键字:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">local temp=$[ $value + 5 ]</span><br></pre></td></tr></table></figure><p><code>local</code>关键字保证了变量只局限于该函数中。我们再回看刚才的例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test8</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> demonstrating the <span class="built_in">local</span> keyword</span></span><br><span class="line"></span><br><span class="line">function func1 {</span><br><span class="line">truelocal temp=$[ $value + 5 ]</span><br><span class="line">trueresult=$[ $temp * 2 ]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">temp=4</span><br><span class="line">value=6</span><br><span class="line"></span><br><span class="line">func1</span><br><span class="line">echo "The result is $result"</span><br><span class="line">if [ $temp -gt $value ]</span><br><span class="line">then</span><br><span class="line">trueecho "temp is larger"</span><br><span class="line">else</span><br><span class="line">trueecho "temp is smaller"</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">wsx@wsx:~/tmp$ ./test8</span><br><span class="line">The result is 22</span><br><span class="line">temp is smaller</span><br></pre></td></tr></table></figure><h2 id="数组变量和函数"><a href="#数组变量和函数" class="headerlink" title="数组变量和函数"></a>数组变量和函数</h2><p>在函数中使用数组变量值有点麻烦,还需要一些特殊考虑。下面我们使用一种方法来解决问题。</p><h3 id="向函数传数组参数"><a href="#向函数传数组参数" class="headerlink" title="向函数传数组参数"></a>向函数传数组参数</h3><p>这个方法有点不好理解,将数组变量当做单个参数传递的话不起作用,下面我们看一个bad例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat badtest3</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> trying to pass an array variable</span></span><br><span class="line"></span><br><span class="line">function testit {</span><br><span class="line">trueecho "The parameters are: $@"</span><br><span class="line">truethisarray=$1</span><br><span class="line">trueecho "The received array is ${thisarray[*]}"</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">myarray=(1 2 3 4 5)</span><br><span class="line">echo "The original array is: ${myarray[*]}"</span><br><span class="line">testit $myarray</span><br><span class="line">wsx@wsx:~/tmp$ ./badtest3</span><br><span class="line">The original array is: 1 2 3 4 5</span><br><span class="line">The parameters are: 1</span><br><span class="line">The received array is 1</span><br></pre></td></tr></table></figure><p>可以看到,当我们将数组变量当做函数参数传递时,函数只会取数组变量的第一个值。</p><p>针对这个问题,我们的一个解决方案是将数组变量全部拆分为单个值,然后作为参数传入函数,在函数内部又重新对这些值进行组装。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test9</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> array variable to <span class="keyword">function</span> <span class="built_in">test</span></span></span><br><span class="line"></span><br><span class="line">function testit {</span><br><span class="line">truelocal newarray</span><br><span class="line">truenewarray=(`echo "$@"`)</span><br><span class="line">trueecho "The new array value is: ${newarray[*]}"</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">myarray=(1 2 3 4 5)</span><br><span class="line">echo ${myarray[*]}</span><br><span class="line">testit ${myarray[*]}</span><br><span class="line"></span><br><span class="line">wsx@wsx:~/tmp$ ./test9</span><br><span class="line">1 2 3 4 5</span><br><span class="line">The new array value is: 1 2 3 4 5</span><br></pre></td></tr></table></figure><h3 id="从函数中返回数组"><a href="#从函数中返回数组" class="headerlink" title="从函数中返回数组"></a>从函数中返回数组</h3><p>采用与上面类似的方法,函数用<code>echo</code>语句来按正确顺序输出单个数组值,然后脚本再将它们重新放进一个新的数组变量中。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test10</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> returning an array value</span></span><br><span class="line"></span><br><span class="line">function arraydblr {</span><br><span class="line">truelocal origarray</span><br><span class="line">truelocal newarray</span><br><span class="line">truelocal elements</span><br><span class="line">truelocal i</span><br><span class="line">trueorigarray=($(echo "$@"))</span><br><span class="line">truenewarray=($(echo "$@"))</span><br><span class="line">trueelements=$[ $# - 1 ]</span><br><span class="line">truefor (( i = 0; i <= $elements; i++ ))</span><br><span class="line">true{</span><br><span class="line"><span class="meta">newarray[$</span><span class="bash">i]=$[ <span class="variable">${origarray[$i]}</span> * 2]</span></span><br><span class="line">true}</span><br><span class="line">trueecho ${newarray[*]}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">myarray=(1 2 3 4 5)</span><br><span class="line">echo "The orignal array is ${myarray[*]}"</span><br><span class="line">arg1=$(echo ${myarray[*]})</span><br><span class="line">result=($(arraydblr $arg1))</span><br><span class="line">echo "The new array is: ${result[*]}"</span><br><span class="line"></span><br><span class="line">wsx@wsx:~/tmp$ ./test10</span><br><span class="line">The orignal array is 1 2 3 4 5</span><br><span class="line">The new array is: 2 4 6 8 10</span><br></pre></td></tr></table></figure><p>该脚本用<code>$arg1</code>变量将数组值传给<code>arraydblr</code>函数。该函数将数组重组到新的数组变量中,生成输出数组变量的一个副本,然后对数据元素进行遍历,将每个元素值翻倍,并将结果存入函数中该数组变量的副本。</p><h2 id="函数递归"><a href="#函数递归" class="headerlink" title="函数递归"></a>函数递归</h2><p>局部函数变量的一个特征是<strong>自成体系</strong>。这个特性使得函数可以递归地调用,也就是函数可以调用自己来得到结果。<strong>通常递归函数都有一个最终可以迭代到的基准值。</strong></p><p>递归算法的经典例子是计算阶乘:一个数的阶乘是该数之前的所有数乘以该数的值。</p><p>比如5的阶乘:</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">5</span>! = <span class="number">1</span> * <span class="number">2</span> * <span class="number">3</span> * <span class="number">4</span> * <span class="number">5</span></span><br></pre></td></tr></table></figure><p>方程可以简化为通用形式:</p><figure class="highlight llvm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">x</span>! = <span class="keyword">x</span> * (<span class="keyword">x</span><span class="number">-1</span>)!</span><br></pre></td></tr></table></figure><p>这可以用简单的递归脚本表达为:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">function factorial {</span><br><span class="line"> if [ $1 -eq 1 ]</span><br><span class="line"> then</span><br><span class="line"> echo 1</span><br><span class="line"> else</span><br><span class="line"> local temp=$[ $1 - 1 ]</span><br><span class="line"> local result=`factorial $temp`</span><br><span class="line"> echo $[ $result * $1 ]</span><br><span class="line"> fi</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>下面用它来进行计算:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test11</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> using recursion</span></span><br><span class="line"></span><br><span class="line">function factorial {</span><br><span class="line">trueif [ $1 -eq 1 ]</span><br><span class="line">truethen</span><br><span class="line">truetrueecho 1</span><br><span class="line">trueelse</span><br><span class="line">truetruelocal temp=$[ $1 - 1]</span><br><span class="line">truetruelocal result=`factorial $temp`</span><br><span class="line">truetrueecho $[ $result * $1 ]</span><br><span class="line">truefi</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">read -p "Enter value: " value</span><br><span class="line">result=$(factorial $value)</span><br><span class="line">echo "The factorial of $value is: $result"</span><br><span class="line"></span><br><span class="line">wsx@wsx:~/tmp$ ./test11</span><br><span class="line">Enter value: 5</span><br><span class="line">The factorial of 5 is: 120</span><br></pre></td></tr></table></figure><h2 id="创建库"><a href="#创建库" class="headerlink" title="创建库"></a>创建库</h2><p>如果你碰巧要在多个脚本中使用同一段代码呢?显然在每个脚本中都定义同样的函数太麻烦了,一种解决方法就是创建<strong>库文件</strong>,然后在脚本中引用它。</p><p><strong>第一步</strong>是创建一个包含脚本中所需函数的公用库文件。下面是一个叫做myfuncs的库文件,定义了3个简单的函数。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat myfuncs</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> my script <span class="built_in">functions</span></span></span><br><span class="line"></span><br><span class="line">function addem {</span><br><span class="line">trueecho $[ $1 + $2 ]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function multem {</span><br><span class="line">trueecho $[ $1 * $2 ]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function divem {</span><br><span class="line">trueif [ $2 -ne 0 ]</span><br><span class="line">truethen</span><br><span class="line">truetrueecho $[ $1 / $2 ]</span><br><span class="line">trueelse</span><br><span class="line">truetrueecho -1</span><br><span class="line">truefi</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>下一步</strong>是在用到这些函数的脚本文件中包含myfuncs库文件。</p><p>这里有点复杂,主要问题出在shell函数的作用域上。如果我们尝试像普通脚本一样运行库文件,函数不会出现在脚本中。</p><p>使用函数库的<strong>关键</strong>在于<code>source</code>命令。<strong><code>source</code>命令会在当前shell上下文中执行命令,而不是创建一个新的shell。</strong>通过<code>source</code>命令就可以使用库中的函数了。</p><p><code>source</code>命令有一个<strong>快捷别名</strong>,称为<strong>点操作符</strong>。要在shell脚本中运行myfuncs库文件,只需要使用下面这行:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">. ./myfuncs</span><br></pre></td></tr></table></figure><p>注意第一个点是点操作符,而第二个点指向当前目录(相对路径)。</p><p>下面这个例子假定myfuncs库文件与要使用它的脚本位于同一目录,不然需要使用相对应的路径进行访问。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test12</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> using <span class="built_in">functions</span> defined <span class="keyword">in</span> a library file</span></span><br><span class="line"></span><br><span class="line">. ./myfuncs</span><br><span class="line"></span><br><span class="line">value1=10</span><br><span class="line">value2=5</span><br><span class="line">result1=$(addem $value1 $value2)</span><br><span class="line">result2=$(multem $value1 $value2)</span><br><span class="line">result3=$(divem $value1 $value2)</span><br><span class="line">echo "The result of adding them is: $result1"</span><br><span class="line">echo "The result of multiplying them is: $result2"</span><br><span class="line">echo "The result of dividing them is: $result3"</span><br></pre></td></tr></table></figure><p>运行:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ ./test12</span><br><span class="line">The result of adding them is: 15</span><br><span class="line">The result of multiplying them is: 50</span><br><span class="line">The result of dividing them is: 2</span><br></pre></td></tr></table></figure><h2 id="在命令行上使用函数"><a href="#在命令行上使用函数" class="headerlink" title="在命令行上使用函数"></a>在命令行上使用函数</h2><p>有时候有必要在命令行界面的提示符下直接使用这些函数。这是个灰常不错的功能,在shell中定义的函数可以在整个系统中使用它,无需担心脚本是不是在PATH环境变量中。</p><p><strong>重点在于让shell能够识别这些函数</strong>。以下有几种方法可以实现。</p><h3 id="在命令行上创建函数"><a href="#在命令行上创建函数" class="headerlink" title="在命令行上创建函数"></a>在命令行上创建函数</h3><p>shell会解释用户输入的命令,所以可以在命令行上直接定义一个函数。</p><p>有两种方法。</p><p><strong>一种是采用单行方式定义函数。</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ function divem { echo $[ $1 / $2 ]; }</span><br><span class="line">wsx@wsx:~/tmp$ divem 100 5</span><br><span class="line">20</span><br></pre></td></tr></table></figure><p>当在命令行上定义函数时,你<strong>必须</strong>记得在每个命令后面加个分号,这样shell能识别命令的起始。</p><p><strong>另一种是采用多行方式来定义函数。</strong>在定义时bash shell会使用次提示符来提示输入更多命令。这种方法不必在命令末尾加分号,只要按回车键就可。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ function multem {</span><br><span class="line"><span class="meta">></span><span class="bash"> <span class="built_in">echo</span> $[ <span class="variable">$1</span> * <span class="variable">$2</span> ]</span></span><br><span class="line"><span class="meta">></span><span class="bash"> }</span></span><br><span class="line">wsx@wsx:~/tmp$ multem 2 5</span><br><span class="line">10</span><br></pre></td></tr></table></figure><p><strong>注意</strong>:在命令行上创建函数不要跟内建命令重名,函数会覆盖原来的命令。</p><h3 id="在-bashrc文件中定义函数"><a href="#在-bashrc文件中定义函数" class="headerlink" title="在.bashrc文件中定义函数"></a>在.bashrc文件中定义函数</h3><p>在bash shell每次启动时都会在主目录下查找<code>.bashrc</code>文件,不管是交互式shell还是shell中启动的新shell。所以我们可以将函数写入该文件,或者在脚本中写入命令读取函数文件。操作前面都讲过,不再赘述,<strong>只要把该文件当做脚本对待就可以了</strong>。理解这一点这部分就会了。</p><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p><strong>在开源的世界里,共享代码才是关键,而这一点同样适用于脚本函数。</strong>我们可以下载大量各式各样的函数然后用于自己的应用程序。</p><p>这一节介绍<strong>如何下载、安装和使用GNU shtool shell脚本函数库</strong>。shtool库提供了一些简单的shell脚本函数,可以用来完成日常的shell功能。</p><h3 id="下载和安装"><a href="#下载和安装" class="headerlink" title="下载和安装"></a>下载和安装</h3><p>shtool软件包下载地址:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://mirrors.ustc.edu.cn/gnu/shtool/shtool-2.0.8.tar.gz # China</span><br></pre></td></tr></table></figure><p>可以浏览器或者命令行下载:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ wget http://mirrors.ustc.edu.cn/gnu/shtool/shtool-2.0.8.tar.gz</span><br><span class="line">--2017-11-24 00:34:32-- http://mirrors.ustc.edu.cn/gnu/shtool/shtool-2.0.8.tar.gz</span><br><span class="line">正在解析主机 mirrors.ustc.edu.cn (mirrors.ustc.edu.cn)... 202.141.176.110, 218.104.71.170, 2001:da8:d800:95::110</span><br><span class="line">正在连接 mirrors.ustc.edu.cn (mirrors.ustc.edu.cn)|202.141.176.110|:80... 已连接。</span><br><span class="line">已发出 HTTP 请求,正在等待回应... 200 OK</span><br><span class="line">长度: 97033 (95K) [application/gzip]</span><br><span class="line">正在保存至: “shtool-2.0.8.tar.gz”</span><br><span class="line"></span><br><span class="line">shtool-2.0.8.tar.gz 100%[===================>] 94.76K --.-KB/s 用时 0.1s</span><br><span class="line"></span><br><span class="line">2017-11-24 00:34:32 (783 KB/s) - 已保存 “shtool-2.0.8.tar.gz” [97033/97033])</span><br></pre></td></tr></table></figure><p>复制到主目录,然后用<code>tar</code>命令提取文件:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~$ tar -zxvf shtool-2.0.8.tar.gz</span><br><span class="line">shtool-2.0.8/AUTHORS</span><br><span class="line">shtool-2.0.8/COPYING</span><br><span class="line">shtool-2.0.8/ChangeLog</span><br><span class="line">shtool-2.0.8/INSTALL</span><br><span class="line">shtool-2.0.8/Makefile.in</span><br><span class="line">shtool-2.0.8/NEWS</span><br><span class="line">shtool-2.0.8/RATIONAL</span><br><span class="line">shtool-2.0.8/README</span><br><span class="line">shtool-2.0.8/THANKS</span><br><span class="line">shtool-2.0.8/VERSION</span><br><span class="line">shtool-2.0.8/configure</span><br><span class="line">shtool-2.0.8/configure.ac</span><br><span class="line">shtool-2.0.8/sh.arx</span><br><span class="line">shtool-2.0.8/sh.common</span><br><span class="line">shtool-2.0.8/sh.echo</span><br><span class="line">shtool-2.0.8/sh.fixperm</span><br><span class="line">shtool-2.0.8/sh.install</span><br><span class="line">shtool-2.0.8/sh.mdate</span><br><span class="line">shtool-2.0.8/sh.mkdir</span><br><span class="line">shtool-2.0.8/sh.mkln</span><br><span class="line">shtool-2.0.8/sh.mkshadow</span><br><span class="line">shtool-2.0.8/sh.move</span><br><span class="line">shtool-2.0.8/sh.path</span><br><span class="line">shtool-2.0.8/sh.platform</span><br><span class="line">shtool-2.0.8/sh.prop</span><br><span class="line">shtool-2.0.8/sh.rotate</span><br><span class="line">shtool-2.0.8/sh.scpp</span><br><span class="line">shtool-2.0.8/sh.slo</span><br><span class="line">shtool-2.0.8/sh.subst</span><br><span class="line">shtool-2.0.8/sh.table</span><br><span class="line">shtool-2.0.8/sh.tarball</span><br><span class="line">shtool-2.0.8/sh.version</span><br><span class="line">shtool-2.0.8/shtool.m4</span><br><span class="line">shtool-2.0.8/shtool.pod</span><br><span class="line">shtool-2.0.8/shtool.spec</span><br><span class="line">shtool-2.0.8/shtoolize.in</span><br><span class="line">shtool-2.0.8/shtoolize.pod</span><br><span class="line">shtool-2.0.8/test.db</span><br><span class="line">shtool-2.0.8/test.sh</span><br></pre></td></tr></table></figure><p>接下来可以构建shell脚本库文件了。</p><h3 id="构建库"><a href="#构建库" class="headerlink" title="构建库"></a>构建库</h3><p>shtool文件必须针对特定的Linux环境进行配置。<strong>配置工作必须使用标准的configure和make命令</strong>:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~$ cd shtool-2.0.8/</span><br><span class="line">wsx@wsx:~/shtool-2.0.8$ ./configure</span><br><span class="line">Configuring GNU shtool (Portable Shell Tool), version 2.0.8 (18-Jul-2008)</span><br><span class="line">Copyright (c) 1994-2008 Ralf S. Engelschall <[email protected]></span><br><span class="line">checking whether make sets $(MAKE)... yes</span><br><span class="line">checking for perl interpreter... /usr/bin/perl</span><br><span class="line">checking for pod2man conversion tool... /usr/bin/pod2man</span><br><span class="line">configure: creating ./config.status</span><br><span class="line">config.status: creating Makefile</span><br><span class="line">config.status: creating shtoolize</span><br><span class="line">config.status: executing adjustment commands</span><br><span class="line">wsx@wsx:~/shtool-2.0.8$ make</span><br><span class="line">building program shtool</span><br><span class="line">./shtoolize -o shtool all</span><br><span class="line">Use of assignment to $[ is deprecated at ./shtoolize line 60.</span><br><span class="line">Generating shtool...(echo 11808/12742 bytes)...(mdate 3695/4690 bytes)...(table 1818/2753 bytes)...(prop 1109/2038 bytes)...(move 2685/3614 bytes)...(install 4567/5495 bytes)...(mkdir 2904/3821 bytes)...(mkln 4429/5361 bytes)...(mkshadow 3260/4193 bytes)...(fixperm 1471/2403 bytes)...(rotate 13425/14331 bytes)...(tarball 5297/6214 bytes)...(subst 5255/6180 bytes)...(platform 21739/22662 bytes)...(arx 2401/3312 bytes)...(slo 4139/5066 bytes)...(scpp 6295/7206 bytes)...(version 10234/11160 bytes)...(path 4041/4952 bytes)</span><br><span class="line">building manpage shtoolize.1</span><br><span class="line">building manpage shtool.1</span><br><span class="line">building manpage shtool-echo.1</span><br><span class="line">building manpage shtool-mdate.1</span><br><span class="line">shtool-mdate.tmp around line 222: You forgot a '=back' before '=head1'</span><br><span class="line">POD document had syntax errors at /usr/bin/pod2man line 71.</span><br><span class="line">building manpage shtool-table.1</span><br><span class="line">building manpage shtool-prop.1</span><br><span class="line">building manpage shtool-move.1</span><br><span class="line">building manpage shtool-install.1</span><br><span class="line">building manpage shtool-mkdir.1</span><br><span class="line">shtool-mkdir.tmp around line 186: You forgot a '=back' before '=head1'</span><br><span class="line">POD document had syntax errors at /usr/bin/pod2man line 71.</span><br><span class="line">building manpage shtool-mkln.1</span><br><span class="line">building manpage shtool-mkshadow.1</span><br><span class="line">shtool-mkshadow.tmp around line 191: You forgot a '=back' before '=head1'</span><br><span class="line">POD document had syntax errors at /usr/bin/pod2man line 71.</span><br><span class="line">building manpage shtool-fixperm.1</span><br><span class="line">building manpage shtool-rotate.1</span><br><span class="line">building manpage shtool-tarball.1</span><br><span class="line">building manpage shtool-subst.1</span><br><span class="line">building manpage shtool-platform.1</span><br><span class="line">building manpage shtool-arx.1</span><br><span class="line">building manpage shtool-slo.1</span><br><span class="line">building manpage shtool-scpp.1</span><br><span class="line">building manpage shtool-version.1</span><br><span class="line">building manpage shtool-path.1</span><br></pre></td></tr></table></figure><p><code>configure</code>命令会检查构建shtool库文件所必需的软件。一旦发现所需工具,它会使用工具路径修改配置文件。</p><p><code>make</code>命令负责构建shtool库文件。最终的结果(shtool)是一个完整的库软件包。</p><p>我们可以测试下这个库文件:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/shtool-2.0.8$ make test</span><br><span class="line">Running test suite:</span><br><span class="line">echo..........FAILED</span><br><span class="line">+---Test------------------------------</span><br><span class="line">| test ".`../shtool echo foo bar quux`" = ".foo bar quux" || exit 1</span><br><span class="line">| bytes=`../shtool echo -n foo | wc -c | awk '{ printf("%s", $1); }'` || exit 1</span><br><span class="line">| test ".$bytes" = .3 || exit 1</span><br><span class="line">| bytes=`../shtool echo '\1' | wc -c | awk '{ printf("%s", $1); }'` || exit 1</span><br><span class="line">| test ".$bytes" = .3 || exit 1</span><br><span class="line">| exit 0</span><br><span class="line">+---Trace-----------------------------</span><br><span class="line">| + ../shtool echo foo bar quux</span><br><span class="line">| + test .foo bar quux = .foo bar quux</span><br><span class="line">| + ../shtool echo -n foo</span><br><span class="line">| + wc -c</span><br><span class="line">| + awk { printf("%s", $1); }</span><br><span class="line">| + bytes=3</span><br><span class="line">| + test .3 = .3</span><br><span class="line">| + ../shtool echo \1</span><br><span class="line">| + wc -c</span><br><span class="line">| + awk { printf("%s", $1); }</span><br><span class="line">| + bytes=2</span><br><span class="line">| + test .2 = .3</span><br><span class="line">| + exit 1</span><br><span class="line">+-------------------------------------</span><br><span class="line">mdate.........ok</span><br><span class="line">table.........ok</span><br><span class="line">prop..........ok</span><br><span class="line">move..........ok</span><br><span class="line">install.......ok</span><br><span class="line">mkdir.........ok</span><br><span class="line">mkln..........ok</span><br><span class="line">mkshadow......ok</span><br><span class="line">fixperm.......ok</span><br><span class="line">rotate........ok</span><br><span class="line">tarball.......ok</span><br><span class="line">subst.........ok</span><br><span class="line">platform......ok</span><br><span class="line">arx...........ok</span><br><span class="line">slo...........ok</span><br><span class="line">scpp..........ok</span><br><span class="line">version.......ok</span><br><span class="line">path..........ok</span><br><span class="line">FAILED: passed: 18/19, failed: 1/19</span><br></pre></td></tr></table></figure><p>(有一个没通过~)</p><p>如果全部通过测试,就可以将库安装到系统中,这样所有脚本都能使用这个库了。</p><p>要完成安装,需要使用<code>make</code>命令的<code>install</code>选项。需要使用root权限。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/shtool-2.0.8$ make install</span><br><span class="line">./shtool mkdir -f -p -m 755 /usr/local</span><br><span class="line">./shtool mkdir -f -p -m 755 /usr/local/bin</span><br><span class="line">./shtool mkdir -f -p -m 755 /usr/local/share/man/man1</span><br><span class="line">mkdir: cannot create directory '/usr/local/share/man/man1': Permission denied</span><br><span class="line">chmod: cannot access '/usr/local/share/man/man1': No such file or directory</span><br><span class="line">Makefile:94: recipe for target 'install' failed</span><br><span class="line">make: *** [install] Error 1</span><br><span class="line">wsx@wsx:~/shtool-2.0.8$ sudo make install</span><br><span class="line">[sudo] wsx 的密码:</span><br><span class="line">./shtool mkdir -f -p -m 755 /usr/local</span><br><span class="line">./shtool mkdir -f -p -m 755 /usr/local/bin</span><br><span class="line">./shtool mkdir -f -p -m 755 /usr/local/share/man/man1</span><br><span class="line">./shtool mkdir -f -p -m 755 /usr/local/share/aclocal</span><br><span class="line">./shtool mkdir -f -p -m 755 /usr/local/share/shtool</span><br><span class="line">./shtool install -c -m 755 shtool /usr/local/bin/shtool</span><br><span class="line">./shtool install -c -m 755 shtoolize /usr/local/bin/shtoolize</span><br><span class="line">./shtool install -c -m 644 shtoolize.1 /usr/local/share/man/man1/shtoolize.1</span><br><span class="line">./shtool install -c -m 644 shtool.1 /usr/local/share/man/man1/shtool.1</span><br><span class="line">./shtool install -c -m 644 shtool-echo.1 /usr/local/share/man/man1/shtool-echo.1</span><br><span class="line">./shtool install -c -m 644 shtool-mdate.1 /usr/local/share/man/man1/shtool-mdate.1</span><br><span class="line">./shtool install -c -m 644 shtool-table.1 /usr/local/share/man/man1/shtool-table.1</span><br><span class="line">./shtool install -c -m 644 shtool-prop.1 /usr/local/share/man/man1/shtool-prop.1</span><br><span class="line">./shtool install -c -m 644 shtool-move.1 /usr/local/share/man/man1/shtool-move.1</span><br><span class="line">./shtool install -c -m 644 shtool-install.1 /usr/local/share/man/man1/shtool-install.1</span><br><span class="line">./shtool install -c -m 644 shtool-mkdir.1 /usr/local/share/man/man1/shtool-mkdir.1</span><br><span class="line">./shtool install -c -m 644 shtool-mkln.1 /usr/local/share/man/man1/shtool-mkln.1</span><br><span class="line">./shtool install -c -m 644 shtool-mkshadow.1 /usr/local/share/man/man1/shtool-mkshadow.1</span><br><span class="line">./shtool install -c -m 644 shtool-fixperm.1 /usr/local/share/man/man1/shtool-fixperm.1</span><br><span class="line">./shtool install -c -m 644 shtool-rotate.1 /usr/local/share/man/man1/shtool-rotate.1</span><br><span class="line">./shtool install -c -m 644 shtool-tarball.1 /usr/local/share/man/man1/shtool-tarball.1</span><br><span class="line">./shtool install -c -m 644 shtool-subst.1 /usr/local/share/man/man1/shtool-subst.1</span><br><span class="line">./shtool install -c -m 644 shtool-platform.1 /usr/local/share/man/man1/shtool-platform.1</span><br><span class="line">./shtool install -c -m 644 shtool-arx.1 /usr/local/share/man/man1/shtool-arx.1</span><br><span class="line">./shtool install -c -m 644 shtool-slo.1 /usr/local/share/man/man1/shtool-slo.1</span><br><span class="line">./shtool install -c -m 644 shtool-scpp.1 /usr/local/share/man/man1/shtool-scpp.1</span><br><span class="line">./shtool install -c -m 644 shtool-version.1 /usr/local/share/man/man1/shtool-version.1</span><br><span class="line">./shtool install -c -m 644 shtool-path.1 /usr/local/share/man/man1/shtool-path.1</span><br><span class="line">./shtool install -c -m 644 shtool.m4 /usr/local/share/aclocal/shtool.m4</span><br><span class="line">./shtool install -c -m 644 sh.common /usr/local/share/shtool/sh.common</span><br><span class="line">./shtool install -c -m 644 sh.echo /usr/local/share/shtool/sh.echo</span><br><span class="line">./shtool install -c -m 644 sh.mdate /usr/local/share/shtool/sh.mdate</span><br><span class="line">./shtool install -c -m 644 sh.table /usr/local/share/shtool/sh.table</span><br><span class="line">./shtool install -c -m 644 sh.prop /usr/local/share/shtool/sh.prop</span><br><span class="line">./shtool install -c -m 644 sh.move /usr/local/share/shtool/sh.move</span><br><span class="line">./shtool install -c -m 644 sh.install /usr/local/share/shtool/sh.install</span><br><span class="line">./shtool install -c -m 644 sh.mkdir /usr/local/share/shtool/sh.mkdir</span><br><span class="line">./shtool install -c -m 644 sh.mkln /usr/local/share/shtool/sh.mkln</span><br><span class="line">./shtool install -c -m 644 sh.mkshadow /usr/local/share/shtool/sh.mkshadow</span><br><span class="line">./shtool install -c -m 644 sh.fixperm /usr/local/share/shtool/sh.fixperm</span><br><span class="line">./shtool install -c -m 644 sh.rotate /usr/local/share/shtool/sh.rotate</span><br><span class="line">./shtool install -c -m 644 sh.tarball /usr/local/share/shtool/sh.tarball</span><br><span class="line">./shtool install -c -m 644 sh.subst /usr/local/share/shtool/sh.subst</span><br><span class="line">./shtool install -c -m 644 sh.platform /usr/local/share/shtool/sh.platform</span><br><span class="line">./shtool install -c -m 644 sh.arx /usr/local/share/shtool/sh.arx</span><br><span class="line">./shtool install -c -m 644 sh.slo /usr/local/share/shtool/sh.slo</span><br><span class="line">./shtool install -c -m 644 sh.scpp /usr/local/share/shtool/sh.scpp</span><br><span class="line">./shtool install -c -m 644 sh.version /usr/local/share/shtool/sh.version</span><br><span class="line">./shtool install -c -m 644 sh.path /usr/local/share/shtool/sh.path</span><br></pre></td></tr></table></figure><p>现在我们能在自己的shell脚本中使用这些函数咯。</p><h3 id="shtool库函数"><a href="#shtool库函数" class="headerlink" title="shtool库函数"></a>shtool库函数</h3><table><thead><tr><th>函数</th><th>描述</th></tr></thead><tbody><tr><td>Arx</td><td>创建归档文件(包含一些扩展功能)</td></tr><tr><td>Echo</td><td>显示字符串,并提供了一些扩展构件</td></tr><tr><td>fixperm</td><td>改变目录树的文件权限</td></tr><tr><td>install</td><td>安装脚本或文件</td></tr><tr><td>mdate</td><td>显示文件或目录修改时间</td></tr><tr><td>mkdir</td><td>创建一个或更多目录</td></tr><tr><td>Mkln</td><td>使用相对路径创建链接</td></tr><tr><td>mkshadow</td><td>创建一棵阴影树</td></tr><tr><td>move</td><td>带有替换功能的文件移动</td></tr><tr><td>Path</td><td>处理程序路径</td></tr><tr><td>platform</td><td>显示平台标识</td></tr><tr><td>Prop</td><td>显示一个带有动画效果的进度条</td></tr><tr><td>rotate</td><td>转置日志文件</td></tr><tr><td>Scpp</td><td>共享的C预处理器</td></tr><tr><td>Slo</td><td>根据库的类别,分离链接器选项</td></tr><tr><td>Subst</td><td>使用sed的替换操作</td></tr><tr><td>Table</td><td>以表格的形式显示由字段分隔的数据</td></tr><tr><td>tarball</td><td>从文件和目录中创建tar文件</td></tr><tr><td>version</td><td>创建版本信息文件</td></tr></tbody></table><p>每个shtool函数都包含大量的选项和参数。下面是使用格式:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">shtool [option] [function [option] [args]]</span><br></pre></td></tr></table></figure><h3 id="使用库"><a href="#使用库" class="headerlink" title="使用库"></a>使用库</h3><p>我们能直接在命令行或者在自己构建的脚本中使用shtool的函数。</p><p>下面是在脚本中使用的简单例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ cat test13</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line">shtool platform</span><br><span class="line">wsx@wsx:~/tmp$ ./test13</span><br><span class="line">Ubuntu 17.10 (AMD64)</span><br></pre></td></tr></table></figure><p><code>platform</code>函数会返回Linux发行版以及系统使用的CPU硬件相关信息。</p><p><code>prop</code>函数可以使用<code>\</code>,<code>|</code>,<code>/</code>和<code>-</code>字符创建一个旋转的进度条。它可以告诉shell脚本用户目前正在处理一些后台处理工作。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx:~/tmp$ ls -al /usr/bin | shtool prop -p "waiting..."</span><br><span class="line">waiting...</span><br></pre></td></tr></table></figure><p>在脚本学习中涉及到诸多的符号,在运行时我们可能会感觉到顺利,但自己写的时候往往会用不太对,推荐阅读一下常用的一些符号区分,像小括号、中括号、花括号等等。觉的不懂的可以看看<a href="http://blog.csdn.net/yangtalent1206/article/details/12996797" target="_blank" rel="noopener">Linux<em>Bash脚本</em>单引号’双引号“”反引号`小括号()中括号[]大括号{}</a>以及相关的百度资料。</p>]]></content>
<summary type="html">
<p><em>来源: Linux命令行与shell脚本编程大全</em></p>
<p><strong>内容</strong></p>
<blockquote>
<ul>
<li>基本的脚本函数</li>
<li>返回值</li>
<li>在函数中使用变量</li>
<li>数组变量和函数</li>
<li>函数递归</li>
<li>创建库</li>
<li>在命令行上使用函数</li>
</ul>
</blockquote>
<p>我们可以将shell脚本代码放进函数中封装起来,这样就能在脚本中的任何地方多次使用它了。</p>
<p>下面我们来逐步了解如何创建自己的shell脚本函数并在应用中使用它们。</p>
</summary>
<category term="Linux杂烩" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/"/>
<category term="shell编程" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/shell%E7%BC%96%E7%A8%8B/"/>
<category term="shell笔记" scheme="https://shixiangwang.github.io/tags/shell%E7%AC%94%E8%AE%B0/"/>
<category term="linux" scheme="https://shixiangwang.github.io/tags/linux/"/>
</entry>
<entry>
<title>R-面向对象编程</title>
<link href="https://shixiangwang.github.io/2017/09/20/OOPinR/"/>
<id>https://shixiangwang.github.io/2017/09/20/OOPinR/</id>
<published>2017-09-19T16:00:00.000Z</published>
<updated>2018-01-27T04:08:38.410Z</updated>
<content type="html"><![CDATA[<h2 id="一些概念"><a href="#一些概念" class="headerlink" title="一些概念"></a>一些概念</h2><p>这里首先要提及一些概念,然后我们再看具体的实例加以理解。</p><p>每一个单独的<strong>对象</strong>都可以被称为对应<strong>类</strong>的一个<strong>实例</strong>(instance)。操作指定类的函数称为<strong>方法</strong>(method)。</p><p>把程序接口从具体的实现细节中分离开来的过程称为<strong>封装</strong>。</p><p>在OOP(面向对象编程)中,我们可以通过一个类创建出另外一个类,只需要指定新类的不同信息即可,这种方法称为<strong>继承</strong>。由此衍生出,被继承的类称为<strong>父类或超类</strong>(superclass),新创建的类称为<strong>子类</strong>(subclass)。</p><p>在OOP中,允许同一个方法名操纵不同对象并得到不同的结果,称为<strong>多态</strong>(polymorphism)。</p><p>通过一系列的其他类来创建新类的过程称为<strong>组合</strong>(composition)。在一些语言中,一个类可以从多个类中继承方法,称为<strong>多重继承</strong>(multiple inheritance)。</p><a id="more"></a><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p>大部分其他语言(比如java)的OOP概念都已经包含在R中,但R中具体的语法和结构却有所不同。我们需要通过调用函数<code>setClass</code>来定义一个类,并且需要调用<code>setMethod</code>函数来定义方法。</p><p>我们先看一个简单的例子:</p><p>我们要实现一个类用来表示时间序列,想定义一个对象包含如下信息:</p><ul><li>一个数据集合,取自固定周期的时间段</li><li>一个开始时间</li><li>一个结束时间</li><li>时间序列的周期</li></ul><p>对于可以通过某些属性计算出来的属性信息是多余的。我们从定义一个名为“TimeSeries”的新类开始。</p><p><strong>我们将通过一个包含数据、开始时间、结束时间的数值型向量来描述一个时间序列。然后可以通过它们来计算出时间单位、频率和周期。</strong></p><p>作为类的使用者,如何展现这些信息并不重要。但是对于类的实现者来说,则非常重要。</p><p>R语言中对象存储信息的位置称为<strong>槽</strong>(slot)。我们将该对象需要包含的槽命名为<code>data</code>、<code>start</code>、<code>end</code>。使用<code>setClass</code>函数来创建新类:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">> <span class="keyword">setClass</span>(<span class="string">"TimeSeries"</span>,</span><br><span class="line">+ representation(</span><br><span class="line">+ data=<span class="string">"numeric"</span>,</span><br><span class="line">+ start=<span class="string">"POSIXct"</span>,</span><br><span class="line">+ end=<span class="string">"POSIXct"</span></span><br><span class="line">+ )</span><br><span class="line">+ )</span><br></pre></td></tr></table></figure><p><code>representation</code>部分说明了每个槽所包含的R对象的类型。我们使用<code>new</code>函数(针对S4对象的一个泛型<a href="https://baike.baidu.com/item/%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95/10455265?fr=aladdin" target="_blank" rel="noopener">构造方法</a>)来新建一个TimeSeries对象。第一个参数名指定类名,其他参数指定槽的值:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">> my.TimeSeries <- new(<span class="string">"TimeSeries"</span>,</span><br><span class="line">+ data = c(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>),</span><br><span class="line">+ start=as.POSIXct(<span class="string">"07/01/2009 0:00:00"</span>, tz=<span class="string">"GMT"</span>,</span><br><span class="line">+ format=<span class="string">"%m/%d/%Y %H:%M:%S"</span>),</span><br><span class="line">+ end=as.POSIXct(<span class="string">"07/01/2009 0:05:00"</span>, tz=<span class="string">"GMT"</span>,</span><br><span class="line">+ format=<span class="string">"%m/%d/%Y %H:%M:%S"</span>)</span><br><span class="line">+ )</span><br><span class="line"></span><br><span class="line">> my.TimeSeries</span><br><span class="line">An object of class <span class="string">"TimeSeries"</span></span><br><span class="line">Slot <span class="string">"data"</span>:</span><br><span class="line">[<span class="number">1</span>] <span class="number">1</span> <span class="number">2</span> <span class="number">3</span> <span class="number">4</span> <span class="number">5</span> <span class="number">6</span></span><br><span class="line"></span><br><span class="line">Slot <span class="string">"start"</span>:</span><br><span class="line">[<span class="number">1</span>] <span class="string">"2009-07-01 GMT"</span></span><br><span class="line"></span><br><span class="line">Slot <span class="string">"end"</span>:</span><br><span class="line">[<span class="number">1</span>] <span class="string">"2009-07-01 00:05:00 GMT"</span></span><br></pre></td></tr></table></figure><p><strong>对于一个槽来说,并不是所有的可能值都是有效的。</strong>比如,我们想要确保end发生在start之后,并且两者的长度是1。我们需要编写函数来验证该对象的有效性。R允许自定义函数用来验证特定的类。我们可以通过<code>setValidity</code>函数来设定。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">> setValidity(<span class="string">"TimeSeries"</span>,</span><br><span class="line">+ <span class="keyword">function</span>(object){</span><br><span class="line">+ object@start <= object@end &&</span><br><span class="line">+ length(object@start) == <span class="number">1</span> &&</span><br><span class="line">+ length(object@end) == <span class="number">1</span></span><br><span class="line">+ }</span><br><span class="line">+ )</span><br><span class="line">Class <span class="string">"TimeSeries"</span> [<span class="keyword">in</span> <span class="string">".GlobalEnv"</span>]</span><br><span class="line"></span><br><span class="line">Slots:</span><br><span class="line"></span><br><span class="line">Name: data start end</span><br><span class="line">Class: numeric POSIXct POSIXct</span><br></pre></td></tr></table></figure><p>现在我们可以检查对象在<code>validObject</code>函数下是否有效。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">> validObject(my.TimeSeries)</span><br><span class="line">[<span class="number">1</span>] <span class="literal">TRUE</span></span><br></pre></td></tr></table></figure><p>之后我们新建<code>TimeSeries</code>对象时,R将会自动检查新对象的有效性,并通过抛出错误来拒绝错误的对象。</p><p>(也可以在创建类的时候设定验证有效性的方法,详见setClass的完整定义)</p><p>定义了类之后,我们来创建新的方法。时间序列有一个属性是周期。我们可以创建一个方法用来提取时间序列中的周期信息。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">> period.TimeSeries <- <span class="keyword">function</span>(object) {</span><br><span class="line">+ <span class="keyword">if</span> (length(object@data) > <span class="number">1</span>) {</span><br><span class="line">+ (object@end - object@start) / (length(object@data) - <span class="number">1</span>)</span><br><span class="line">+ } <span class="keyword">else</span> {</span><br><span class="line">+ <span class="literal">Inf</span></span><br><span class="line">+ }</span><br><span class="line">+ }</span><br></pre></td></tr></table></figure><p><strong>假如我们想创建一组函数用来从不同的对象中提取数据序列,而不用考虑对象的类型(即多态)。R提供了一种叫作泛型函数的机制可以实现。</strong>当我们对某个对象调用泛型函数时,R会基于该对象的类找到正确的方法去执行。我们创建一个函数来从泛型对象中提取数据序列:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">> series <- <span class="keyword">function</span>(object) { object@data }</span><br><span class="line">> <span class="keyword">setGeneric</span>(<span class="string">"series"</span>)</span><br><span class="line">[<span class="number">1</span>] <span class="string">"series"</span></span><br><span class="line">> series(my.TimeSeries)</span><br><span class="line">[<span class="number">1</span>] <span class="number">1</span> <span class="number">2</span> <span class="number">3</span> <span class="number">4</span> <span class="number">5</span> <span class="number">6</span></span><br></pre></td></tr></table></figure><p>调用<code>setGeneric</code>可以将<code>series</code>重定义为泛型函数,其默认的方法是旧的<code>series</code>函数的函数体:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">> series</span><br><span class="line">standardGeneric <span class="keyword">for</span> <span class="string">"series"</span> defined from package <span class="string">".GlobalEnv"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> (object)</span><br><span class="line">standardGeneric(<span class="string">"series"</span>)</span><br><span class="line"><environment: <span class="number">0x205e930</span>></span><br><span class="line">Methods may be defined <span class="keyword">for</span> arguments: object</span><br><span class="line">Use showMethods(<span class="string">"series"</span>) <span class="keyword">for</span> currently available ones.</span><br><span class="line">> showMethods(<span class="string">"series"</span>)</span><br><span class="line">Function: series (package .GlobalEnv)</span><br><span class="line">object=<span class="string">"ANY"</span></span><br><span class="line">object=<span class="string">"TimeSeries"</span></span><br><span class="line"> (inherited from: object=<span class="string">"ANY"</span>)</span><br></pre></td></tr></table></figure><p>更进一步地,我们创建一个泛型函数来从对象中提取周期信息,并且特别指定它用来处理我们之前的创建的类。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">> period <- <span class="keyword">function</span>(object) { object@period }</span><br><span class="line">> <span class="keyword">setGeneric</span>(<span class="string">"period"</span>)</span><br><span class="line">[<span class="number">1</span>] <span class="string">"period"</span></span><br><span class="line">> <span class="keyword">setMethod</span>(period, signature=c(<span class="string">"TimeSeries"</span>), definition=period.TimeSeries)</span><br><span class="line">[<span class="number">1</span>] <span class="string">"period"</span></span><br><span class="line">attr(,<span class="string">"package"</span>)</span><br><span class="line">[<span class="number">1</span>] <span class="string">".GlobalEnv"</span></span><br><span class="line">> showMethods(<span class="string">"period"</span>)</span><br><span class="line">Function: period (package .GlobalEnv)</span><br><span class="line">object=<span class="string">"ANY"</span></span><br><span class="line">object=<span class="string">"TimeSeries"</span></span><br></pre></td></tr></table></figure><p>调用泛型函数<code>period</code>可以计算<code>TimeSeries</code>对象:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">> period(my.TimeSeries)</span><br><span class="line">Time difference of <span class="number">1</span> mins</span><br></pre></td></tr></table></figure><p>也可以对已存在的泛型函数定义自己的方法,比如为我们创建的类定义一个<code>summary</code>方法:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">> <span class="keyword">setMethod</span>(<span class="string">"summary"</span>,</span><br><span class="line">+ signature=<span class="string">"TimeSeries"</span>,</span><br><span class="line">+ definition=<span class="keyword">function</span>(object) {</span><br><span class="line">+ print(paste(object@start,</span><br><span class="line">+ <span class="string">" to "</span>,</span><br><span class="line">+ object@end,</span><br><span class="line">+ sep=<span class="string">""</span>, collapse=<span class="string">""</span>))</span><br><span class="line">+ print(paste(object@data, sep=<span class="string">""</span>, collapse=<span class="string">","</span>))</span><br><span class="line">+ }</span><br><span class="line">+ )</span><br><span class="line">[<span class="number">1</span>] <span class="string">"summary"</span></span><br><span class="line">> summary(my.TimeSeries)</span><br><span class="line">[<span class="number">1</span>] <span class="string">"2009-07-01 to 2009-07-01 00:05:00"</span></span><br><span class="line">[<span class="number">1</span>] <span class="string">"1,2,3,4,5,6"</span></span><br></pre></td></tr></table></figure><p>甚至可以为一个已经存在的操作符定义新的方法:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">> <span class="keyword">setMethod</span>(<span class="string">"["</span>,</span><br><span class="line">+ signature=c(<span class="string">"TimeSeries"</span>),</span><br><span class="line">+ definition=<span class="keyword">function</span>(x, i, j, <span class="keyword">...</span>, drop) {</span><br><span class="line">+ x@data[i]</span><br><span class="line">+ }</span><br><span class="line">+ )</span><br><span class="line">[<span class="number">1</span>] <span class="string">"["</span></span><br><span class="line">> my.TimeSeries[<span class="number">3</span>]</span><br><span class="line">[<span class="number">1</span>] <span class="number">3</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">> my.TimeSeries <span class="comment"># 查看my.TimeSeries对象</span></span><br><span class="line">An object of class <span class="string">"TimeSeries"</span></span><br><span class="line">Slot <span class="string">"data"</span>:</span><br><span class="line">[<span class="number">1</span>] <span class="number">1</span> <span class="number">2</span> <span class="number">3</span> <span class="number">4</span> <span class="number">5</span> <span class="number">6</span></span><br><span class="line"></span><br><span class="line">Slot <span class="string">"start"</span>:</span><br><span class="line">[<span class="number">1</span>] <span class="string">"2009-07-01 GMT"</span></span><br><span class="line"></span><br><span class="line">Slot <span class="string">"end"</span>:</span><br><span class="line">[<span class="number">1</span>] <span class="string">"2009-07-01 00:05:00 GMT"</span></span><br></pre></td></tr></table></figure><p>下面演示如何基于<code>TimeSeries</code>类实现一个<code>WeightHistory</code>类以记录个人的历史体重信息。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">> <span class="keyword">setClass</span>(<span class="string">"TimeSeries"</span>,</span><br><span class="line">+ representation(</span><br><span class="line">+ data=<span class="string">"numeric"</span>,</span><br><span class="line">+ start=<span class="string">"POSIXct"</span>,</span><br><span class="line">+ end=<span class="string">"POSIXct"</span></span><br><span class="line">+</span><br><span class="line">+ )</span><br><span class="line">+ )</span><br><span class="line">> setValidity(<span class="string">"TimeSeries"</span>,</span><br><span class="line">+ <span class="keyword">function</span>(object) {</span><br><span class="line">+ object@start <= object@end &&</span><br><span class="line">+ length(object@start)==<span class="number">1</span> &&</span><br><span class="line">+ length(object@end)==<span class="number">1</span></span><br><span class="line">+ }</span><br><span class="line">+ )</span><br><span class="line">Class <span class="string">"TimeSeries"</span> [<span class="keyword">in</span> <span class="string">".GlobalEnv"</span>]</span><br><span class="line"></span><br><span class="line">Slots:</span><br><span class="line"></span><br><span class="line">Name: data start end</span><br><span class="line">Class: numeric POSIXct POSIXct</span><br></pre></td></tr></table></figure><p>创建子类:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">> <span class="keyword">setClass</span>(</span><br><span class="line">+ <span class="string">"WeightHistory"</span>,</span><br><span class="line">+ representation(</span><br><span class="line">+ height = <span class="string">"numeric"</span>,</span><br><span class="line">+ name = <span class="string">"character"</span></span><br><span class="line">+ ),</span><br><span class="line">+ contains = <span class="string">"TimeSeries"</span></span><br><span class="line">+ )</span><br></pre></td></tr></table></figure><p>添加实例对象:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">> john.doe <- new(<span class="string">"WeightHistory"</span>,</span><br><span class="line">+ data=c(<span class="number">170</span>,<span class="number">169</span>,<span class="number">171</span>,<span class="number">168</span>,<span class="number">170</span>,<span class="number">169</span>),</span><br><span class="line">+ start=as.POSIXct(<span class="string">"02/14/2019 0:00:00"</span>, tz=<span class="string">"GMT"</span>,</span><br><span class="line">+ format=<span class="string">"%m/%d/%Y %H:%M:%S"</span>),</span><br><span class="line">+ end=as.POSIXct(<span class="string">"03/28/2019 0:00:00"</span>, tz=<span class="string">"GMT"</span>,</span><br><span class="line">+ format=<span class="string">"%m/%d/%Y %H:%M:%S"</span>),</span><br><span class="line">+ height=<span class="number">72</span>,</span><br><span class="line">+ name=<span class="string">"John Doe"</span>)</span><br><span class="line">> john.doe</span><br><span class="line">An object of class <span class="string">"WeightHistory"</span></span><br><span class="line">Slot <span class="string">"height"</span>:</span><br><span class="line">[<span class="number">1</span>] <span class="number">72</span></span><br><span class="line"></span><br><span class="line">Slot <span class="string">"name"</span>:</span><br><span class="line">[<span class="number">1</span>] <span class="string">"John Doe"</span></span><br><span class="line"></span><br><span class="line">Slot <span class="string">"data"</span>:</span><br><span class="line">[<span class="number">1</span>] <span class="number">170</span> <span class="number">169</span> <span class="number">171</span> <span class="number">168</span> <span class="number">170</span> <span class="number">169</span></span><br><span class="line"></span><br><span class="line">Slot <span class="string">"start"</span>:</span><br><span class="line">[<span class="number">1</span>] <span class="string">"2019-02-14 GMT"</span></span><br><span class="line"></span><br><span class="line">Slot <span class="string">"end"</span>:</span><br><span class="line">[<span class="number">1</span>] <span class="string">"2019-03-28 GMT"</span></span><br></pre></td></tr></table></figure><p>我们还可以通过另外一种方式构建一个体重记录。假设我们已经创建好了一个包含人名和体重的<code>Person</code>类。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">> <span class="keyword">setClass</span>(<span class="string">"Person"</span>,</span><br><span class="line">+ representation(</span><br><span class="line">+ height = <span class="string">"numeric"</span>,</span><br><span class="line">+ name = <span class="string">"character"</span>)</span><br><span class="line">+ )</span><br></pre></td></tr></table></figure><p>我们可以创建一个基于<code>TimeSeries</code>类和<code>Person</code>类的体重记录类。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">> <span class="keyword">setClass</span>(</span><br><span class="line">+ <span class="string">"AltWeightHistory"</span>,</span><br><span class="line">+ contains = c(<span class="string">"TimeSeries"</span>, <span class="string">"Person"</span>)</span><br><span class="line">+ )</span><br></pre></td></tr></table></figure><p>可以发现,如果我们已经有了先期的开发经验或者相关类的代码,对新任务进行重构是非常方便的。短短几行代码就搞定了,充分利用了代码的可重复性。这也是OOP在高级语言中如此普遍的一个原因吧。</p><h2 id="S4类"><a href="#S4类" class="headerlink" title="S4类"></a>S4类</h2><p>我们接下来更深入地探讨构造类的函数。</p><h3 id="类的定义"><a href="#类的定义" class="headerlink" title="类的定义"></a>类的定义</h3><p>R中使用<code>setClass</code>函数来创建一个新类,格式如下:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">setClass</span>(Class, representation, prototype, contains=character(), validity, access, where, version, sealed, package, S3methods=<span class="literal">FALSE</span>)</span><br></pre></td></tr></table></figure><p>描述</p><ul><li>Class - 字符串,用来指定新类的名字(这是唯一必需的参数)</li><li>representation - 列表,列表的每一个元素代表不同的槽的类型,元素名为槽名(可以用”ANY”来指定类型为任意)</li><li>prototype - 包含各个槽的默认值的对象</li><li>contains - 字符向量,包含该类继承的父类名</li><li>validity - 验证该类的对象有效性的函数(默认没有检查),可以后续使用<code>setValidity</code>函数来设置</li><li>access - 无作用,为了和S-PLUS兼容</li><li>where - 存储该对象定义的环境</li><li>version - 无作用,为了和S-PLUS兼容</li><li>sealed - 逻辑值,表示该类是否还能被setClass按照原来的类名重新定义</li><li>package - 字符串,指定该类所在的R包名</li><li>S3methods - 逻辑值,表示是否使用了S3类写这个类</li></ul><p>为了简化类的创建,<code>methods</code>包提供了<code>representation</code>以及<code>protype</code>函数。它们在将其他类继承为数据部分、拥有多个父类、或者组合继承类和槽的时候非常有用。</p><p>值得注意的是,有些名字是属性的保留字因而不能作为槽名使用,包括”class”,”comment”,”dim”,”dimnames”,”names”,”rownames”和”tsp”。</p><p>可以使用<code>setIs</code>函数来显式地定义继承关系。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">setIs(class1, class2, test=<span class="literal">NULL</span>, coerce=<span class="literal">NULL</span>, replace=<span class="literal">NULL</span>,</span><br><span class="line"> by=character(), where=topenv(parent.frame()), classDef=, extensionObject=<span class="literal">NULL</span>, doComplete=<span class="literal">TRUE</span>)</span><br></pre></td></tr></table></figure><p>可以使用<code>setValidity</code>函数来显式地设置类的验证函数:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">setValidity(Class, method, where=topenv(parent.frame()))</span><br></pre></td></tr></table></figure><p>R可以定义一个虚类作为多个其他类的父类。如果一个虚类本身不包含任何数据,但是如果你想要创建一批函数用于一批类中,这种方式非常有用。可以通过<code>setClassUnion</code>函数实现:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">setClassUnion(name, members, where)</span><br></pre></td></tr></table></figure><ul><li>name - 新的父类的名字</li><li>members - 字符向量,指定所有子类的名字</li><li>where - 新类所在的环境</li></ul><h3 id="对象的新建"><a href="#对象的新建" class="headerlink" title="对象的新建"></a>对象的新建</h3><p>我们可以通过调用类的<code>new</code>方法新建一个对象。专业术语中称为构造函数。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">new(c, <span class="keyword">...</span>)</span><br></pre></td></tr></table></figure><p>在调用<code>new</code>的时候,我们可以通过指定参数将数据填充到槽中。如果<code>c</code>中存在名为<code>initialize</code>的方法,那么当新的对象被创建后,会立刻调用<code>initialize</code>函数进行初始化。</p><h3 id="槽的存取"><a href="#槽的存取" class="headerlink" title="槽的存取"></a>槽的存取</h3><p>我们可以使用<code>slot</code>函数或者简化符号<code>@</code>来访问存储对象某个槽中的值,当然也可以用它来赋值。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">> john.doe@name</span><br><span class="line">[<span class="number">1</span>] <span class="string">"John Doe"</span></span><br><span class="line">> slot(john.doe, <span class="string">"name"</span>)</span><br><span class="line">[<span class="number">1</span>] <span class="string">"John Doe"</span></span><br></pre></td></tr></table></figure><h3 id="对象的操作"><a href="#对象的操作" class="headerlink" title="对象的操作"></a>对象的操作</h3><p>使用<code>is(o, c)</code>函数测试对象<code>o</code>是否是类<code>c</code>的成员。使用函数<code>extend(c1, c2)</code>测试类<code>c1</code>是否继承于类<code>c2</code>。</p><p>如果要得到对象<code>o</code>包含的所有槽的名称,使用<code>slotNames(o)</code>,如果要得到槽的类型,使用<code>getSlots(o)</code>。这两个函数也可以对类使用。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">> getSlots(<span class="string">"WeightHistory"</span>)</span><br><span class="line"> height name data start end</span><br><span class="line"> <span class="string">"numeric"</span> <span class="string">"character"</span> <span class="string">"numeric"</span> <span class="string">"POSIXct"</span> <span class="string">"POSIXct"</span></span><br><span class="line"></span><br><span class="line">> slotNames(<span class="string">"WeightHistory"</span>)</span><br><span class="line">[<span class="number">1</span>] <span class="string">"height"</span> <span class="string">"name"</span> <span class="string">"data"</span> <span class="string">"start"</span> <span class="string">"end"</span></span><br><span class="line">> slotNames(<span class="string">"john.doe"</span>)</span><br><span class="line">character(<span class="number">0</span>)</span><br><span class="line">> slotNames(john.doe)</span><br><span class="line">[<span class="number">1</span>] <span class="string">"height"</span> <span class="string">"name"</span> <span class="string">"data"</span> <span class="string">"start"</span> <span class="string">"end"</span></span><br></pre></td></tr></table></figure><p>注意一些差别,有引号和没引号结果是不同的。</p><h3 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h3><p>泛型函数允许使用同一个函数名来代表很多不同的函数,针对不同的类,调用不同的参数。</p><p>设定方法的第一步是创建一个合适的泛型函数,如果该函数还不存在,可以使用<code>setGeneric</code>函数来创建这个泛型方法:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">setGeneric</span>(name, def=, group=list(), valueClass=character(),</span><br><span class="line"> where=, package=, signature=, useAsDefault=,</span><br><span class="line"> genericFUnction=, simpleInheritanceOnly=)</span><br></pre></td></tr></table></figure><p>要把一个方法关联到某个类(具体而言就是指定泛型函数的signature参数),可以使用<code>setMethod</code>函数:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">setMethod</span>(f, signature=character(), definition,</span><br><span class="line"> where = topenv(parent.frame()),</span><br><span class="line"> valueClass=<span class="literal">NULL</span>, sealed=<span class="literal">FALSE</span>)</span><br></pre></td></tr></table></figure><h4 id="方法的管理"><a href="#方法的管理" class="headerlink" title="方法的管理"></a>方法的管理</h4><p><code>methods</code>包包含了很多管理泛型方法的函数。</p><table><thead><tr><th>函数</th><th>描述</th></tr></thead><tbody><tr><td>isGeneric</td><td>检查指定的泛型函数是否存在</td></tr><tr><td>isGroup</td><td>检查指定的分组泛型函数是否存在</td></tr><tr><td>removeGeneric</td><td>删除某个泛型函数关联的所有方法以及该泛型函数本身</td></tr><tr><td>dumpMethod</td><td>转存储某个方法到文件</td></tr><tr><td>findFunction</td><td>根据函数名查找函数对象,返回搜寻列表中的位置或当前顶层环境</td></tr><tr><td>dumpMethods</td><td>转存储一个泛型函数关联的所有方法</td></tr><tr><td>signature</td><td>返回在某个指定路径下定义了方法的泛型函数的名称</td></tr><tr><td>removeMethods</td><td>删除某个泛型函数关联的所有方法</td></tr><tr><td>setGeneric</td><td>根据指定的函数名创建新的泛型函数</td></tr></tbody></table><p><code>methods</code>包同样包含了很多管理方法的函数。</p><table><thead><tr><th>函数</th><th>描述</th></tr></thead><tbody><tr><td>getMethod, selectMethod</td><td>返回某个特定泛型函数和类型标记的方法</td></tr><tr><td>existsMethod, hasMethod</td><td>检查某个方法(指定了泛型函数名和类型标记)是否存在</td></tr><tr><td>findMethod</td><td>返回包含了某个方法的包</td></tr><tr><td>showMethods</td><td>显示关联了某个S4泛型的所有方法</td></tr></tbody></table><p>更多的帮助通过<code>library(help="methods")</code>命令获取。</p><h2 id="守旧派OOP-S3"><a href="#守旧派OOP-S3" class="headerlink" title="守旧派OOP: S3"></a>守旧派OOP: S3</h2><p>如果我们想要用R实现复杂的工程,应该使用S4的类和对象。不幸的是,我们在R中是很难避免S3对象的。比如统计包中的大部分建模工具都是用S3对象实现的。为了能够对这些软件包进行更好地理解、修改和扩展。我们必须了解S3类是如何实现的。</p><h3 id="S3的类"><a href="#S3的类" class="headerlink" title="S3的类"></a>S3的类</h3><p>S3对象只是原始的R对象加上一些额外的属性(包括一个类名)而已。它没有正式的定义,我们可以手工修改属性甚至类。</p><p>之前我们使用了时间序列作为S4的例子,其实在R中已经存在了表示它的S3类,称为<code>ts</code>对象。我们这里创建简单的时间序列对象,查看它的属性以及一些底层对象。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">> my.ts <- ts(data=c(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>), start=c(<span class="number">2009</span>,<span class="number">2</span>), frequency=<span class="number">12</span>)</span><br><span class="line">> my.ts</span><br><span class="line"> Feb Mar Apr May Jun</span><br><span class="line"><span class="number">2009</span> <span class="number">1</span> <span class="number">2</span> <span class="number">3</span> <span class="number">4</span> <span class="number">5</span></span><br><span class="line">> attributes(my.ts)</span><br><span class="line">$tsp</span><br><span class="line">[<span class="number">1</span>] <span class="number">2009.083</span> <span class="number">2009.417</span> <span class="number">12.000</span></span><br><span class="line"></span><br><span class="line">$class</span><br><span class="line">[<span class="number">1</span>] <span class="string">"ts"</span></span><br><span class="line"></span><br><span class="line">> typeof(my.ts)</span><br><span class="line">[<span class="number">1</span>] <span class="string">"double"</span></span><br><span class="line">> unclass(my.ts)</span><br><span class="line">[<span class="number">1</span>] <span class="number">1</span> <span class="number">2</span> <span class="number">3</span> <span class="number">4</span> <span class="number">5</span></span><br><span class="line">attr(,<span class="string">"tsp"</span>)</span><br><span class="line">[<span class="number">1</span>] <span class="number">2009.083</span> <span class="number">2009.417</span> <span class="number">12.000</span></span><br><span class="line">> attributes(my.ts)</span><br><span class="line">$tsp</span><br><span class="line">[<span class="number">1</span>] <span class="number">2009.083</span> <span class="number">2009.417</span> <span class="number">12.000</span></span><br><span class="line"></span><br><span class="line">$class</span><br><span class="line">[<span class="number">1</span>] <span class="string">"ts"</span></span><br></pre></td></tr></table></figure><p>可以发现<code>ts</code>对象只不过是一个数值向量加上<code>class</code>和<code>tsp</code>这两个属性。<code>class</code>属性起始只是<code>ts</code>对象的类名。我们无法像S4对象中操作槽来提取S3对象的属性。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">> my.ts@tsp</span><br><span class="line">错误: 非S4类别的对象(类别为<span class="string">"ts"</span>)没有<span class="string">"tsp"</span>这样的槽</span><br></pre></td></tr></table></figure><h3 id="S3方法"><a href="#S3方法" class="headerlink" title="S3方法"></a>S3方法</h3><p>S3的泛型函数是通过命名约定来实现的。以下是步骤:</p><ol><li>为泛型函数挑选一个名字,这里我们命名为<code>gname</code>。</li><li>新建一个名为<code>gname</code>的函数,在<code>gname</code>的函数体中,调用<code>UseMethod("gname")</code></li><li>为每一个想要使用<code>gname</code>的类创建一个名为<code>gname.classname</code>的函数,该函数的第一个参数必须是该对象的类名<code>classname</code>。</li></ol><p>一个现成的例子是<code>plot</code>函数:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">> plot</span><br><span class="line"><span class="keyword">function</span> (x, y, <span class="keyword">...</span>)</span><br><span class="line">UseMethod(<span class="string">"plot"</span>)</span><br><span class="line"><bytecode: <span class="number">0x1851c30</span>></span><br><span class="line"><environment: namespace:graphics></span><br></pre></td></tr></table></figure><p>在调用<code>plot</code>的时候,<code>plot</code>将会调用<code>UseMethod("plot")</code>。<code>UseMethod</code>会查看<code>x</code>对象的类,然后查找名为<code>plot.class</code>的函数,然后调用该函数。</p><p>比如给我们之前定义的<code>TimeSeries</code>类添加一个<code>plot</code>方法。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">> plot.TimeSeries <- <span class="keyword">function</span>(object, <span class="keyword">...</span>) {</span><br><span class="line">+ plot(object@data, <span class="keyword">...</span>)</span><br><span class="line">+ }</span><br></pre></td></tr></table></figure><h3 id="在S4的类中使用S3的类"><a href="#在S4的类中使用S3的类" class="headerlink" title="在S4的类中使用S3的类"></a>在S4的类中使用S3的类</h3><p>我们不能直接指定S3的类到S4的槽。如果想要做到,我们需要基于S3的类创建一个S4的类。一个简单的方式是使用<code>setOldClass</code>函数:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">setOldClass(Classes, prototype, where, test=<span class="literal">FALSE</span>, S4Class)</span><br></pre></td></tr></table></figure><h3 id="查找隐藏的S3方法"><a href="#查找隐藏的S3方法" class="headerlink" title="查找隐藏的S3方法"></a>查找隐藏的S3方法</h3><p>有时候我们会发现一些包的作者会选择隐藏单个方法,而把方法的实现封装在包中。这样可以鼓励用户去使用泛型函数。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">> <span class="keyword">library</span>(lattice)</span><br><span class="line">> methods(histogram)</span><br><span class="line">[<span class="number">1</span>] histogram.factor* histogram.formula* histogram.numeric*</span><br><span class="line">see <span class="string">'?methods'</span> <span class="keyword">for</span> accessing help and <span class="keyword">source</span> code</span><br></pre></td></tr></table></figure><p>有时候我们可能需要找回这些隐藏的方法(想要查看源代码),这时候可以使用<code>getS3method</code>函数。例如,想要取到<code>histgram.formula</code>中的代码,可以使用以下命令:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">> getS3method(f=<span class="string">"histogram"</span>, class=<span class="string">"formula"</span>)</span><br></pre></td></tr></table></figure><p>或者使用<code>getAnywhere</code>函数:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">> getAnywhere(histogram.formula)</span><br></pre></td></tr></table></figure><hr><p>学习整理自《R核心技术手册》</p>]]></content>
<summary type="html">
<h2 id="一些概念"><a href="#一些概念" class="headerlink" title="一些概念"></a>一些概念</h2><p>这里首先要提及一些概念,然后我们再看具体的实例加以理解。</p>
<p>每一个单独的<strong>对象</strong>都可以被称为对应<strong>类</strong>的一个<strong>实例</strong>(instance)。操作指定类的函数称为<strong>方法</strong>(method)。</p>
<p>把程序接口从具体的实现细节中分离开来的过程称为<strong>封装</strong>。</p>
<p>在OOP(面向对象编程)中,我们可以通过一个类创建出另外一个类,只需要指定新类的不同信息即可,这种方法称为<strong>继承</strong>。由此衍生出,被继承的类称为<strong>父类或超类</strong>(superclass),新创建的类称为<strong>子类</strong>(subclass)。</p>
<p>在OOP中,允许同一个方法名操纵不同对象并得到不同的结果,称为<strong>多态</strong>(polymorphism)。</p>
<p>通过一系列的其他类来创建新类的过程称为<strong>组合</strong>(composition)。在一些语言中,一个类可以从多个类中继承方法,称为<strong>多重继承</strong>(multiple inheritance)。</p>
</summary>
<category term="极客R" scheme="https://shixiangwang.github.io/categories/%E6%9E%81%E5%AE%A2R/"/>
<category term="基本理论" scheme="https://shixiangwang.github.io/categories/%E6%9E%81%E5%AE%A2R/%E5%9F%BA%E6%9C%AC%E7%90%86%E8%AE%BA/"/>
<category term="R" scheme="https://shixiangwang.github.io/tags/R/"/>
<category term="OOP" scheme="https://shixiangwang.github.io/tags/OOP/"/>
</entry>
<entry>
<title>R中的属性与类</title>
<link href="https://shixiangwang.github.io/2017/09/15/Class-in-R/"/>
<id>https://shixiangwang.github.io/2017/09/15/Class-in-R/</id>
<published>2017-09-14T16:00:00.000Z</published>
<updated>2018-01-27T04:08:33.334Z</updated>
<content type="html"><![CDATA[<h2 id="属性"><a href="#属性" class="headerlink" title="属性"></a>属性</h2><p>属性描述了对象所代表的内容以及R解释该对象的方式。<strong>很多时候两个对象之间的唯一差别就在于它们的属性不同</strong>。下表展示了一些重要的属性。很多常见的属性都是针对常见的数值型数据对象而言的:像数组、矩阵和数据框。</p><a id="more"></a><table><thead><tr><th>属性</th><th>描述</th></tr></thead><tbody><tr><td>class</td><td>对象的类</td></tr><tr><td>comment</td><td>对象的注解;一般用于描述对象的含义</td></tr><tr><td>dim</td><td>对象的维度</td></tr><tr><td>dimnames</td><td>与对象的每个维度相关的名字</td></tr><tr><td>names</td><td>返回对象的名字属性。返回结果取决于对象的类型,对于数据框对象会返回数据框的列名,对于数组会返回数组中被命名元素的名字</td></tr><tr><td>row.names</td><td>对象的行名</td></tr><tr><td>tsp</td><td>对象的起始点。对时间序列对象很有用</td></tr><tr><td>levels</td><td>因子型变量的水平</td></tr></tbody></table><p><strong>标准使用方法</strong>:对于对象x和属性a,一般用a(x)来查询x的a属性。</p><p>这个操作也可以改变对象的属性。</p><p>我们可以通过<code>attributes</code>函数获得一个包含对象所有属性的列表。</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">> m <- matrix(data=<span class="number">1</span>:<span class="number">12</span>, nrow=<span class="number">4</span>, ncol=<span class="number">3</span>,</span><br><span class="line">+ dimnames=list(c(<span class="string">"r1"</span>,<span class="string">"r2"</span>,<span class="string">"r3"</span>,<span class="string">"r4"</span>),c(<span class="string">"c1"</span>,<span class="string">"c2"</span>,<span class="string">"c3"</span>)))</span><br><span class="line">> m</span><br><span class="line"> c1 c2 c3</span><br><span class="line">r1 <span class="number">1</span> <span class="number">5</span> <span class="number">9</span></span><br><span class="line">r2 <span class="number">2</span> <span class="number">6</span> <span class="number">10</span></span><br><span class="line">r3 <span class="number">3</span> <span class="number">7</span> <span class="number">11</span></span><br><span class="line">r4 <span class="number">4</span> <span class="number">8</span> <span class="number">12</span></span><br><span class="line">> attributes(m)</span><br><span class="line">$dim</span><br><span class="line">[<span class="number">1</span>] <span class="number">4</span> <span class="number">3</span></span><br><span class="line"></span><br><span class="line">$dimnames</span><br><span class="line">$dimnames<span class="string">[[1]]</span></span><br><span class="line">[<span class="number">1</span>] <span class="string">"r1"</span> <span class="string">"r2"</span> <span class="string">"r3"</span> <span class="string">"r4"</span></span><br><span class="line"></span><br><span class="line">$dimnames<span class="string">[[2]]</span></span><br><span class="line">[<span class="number">1</span>] <span class="string">"c1"</span> <span class="string">"c2"</span> <span class="string">"c3"</span></span><br></pre></td></tr></table></figure><p>用<code>dim</code>和<code>dimnames</code>函数可以直接获取对应属性的信息。</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">> dim(m)</span><br><span class="line">[<span class="number">1</span>] <span class="number">4</span> <span class="number">3</span></span><br><span class="line">> dimnames(m)</span><br><span class="line"><span class="string">[[1]]</span></span><br><span class="line">[<span class="number">1</span>] <span class="string">"r1"</span> <span class="string">"r2"</span> <span class="string">"r3"</span> <span class="string">"r4"</span></span><br><span class="line"></span><br><span class="line"><span class="string">[[2]]</span></span><br><span class="line">[<span class="number">1</span>] <span class="string">"c1"</span> <span class="string">"c2"</span> <span class="string">"c3"</span></span><br></pre></td></tr></table></figure><p>存在简便的函数获取行名和列名:</p><figure class="highlight accesslog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">> colnames(m)</span><br><span class="line"><span class="string">[1]</span> <span class="string">"c1"</span> <span class="string">"c2"</span> <span class="string">"c3"</span></span><br><span class="line">> rownames(m)</span><br><span class="line"><span class="string">[1]</span> <span class="string">"r1"</span> <span class="string">"r2"</span> <span class="string">"r3"</span> <span class="string">"r4"</span></span><br></pre></td></tr></table></figure><p>有意思的是,我们可以通过简单地改变属性将矩阵转化为其他类的对象。例如我们通过移除对象的维度属性,达到改变类型和类的目的。</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">> dim(m) <- NULL</span><br><span class="line">> m</span><br><span class="line"> [<span class="number">1</span>] <span class="number">1</span> <span class="number">2</span> <span class="number">3</span> <span class="number">4</span> <span class="number">5</span> <span class="number">6</span> <span class="number">7</span> <span class="number">8</span> <span class="number">9</span> <span class="number">10</span> <span class="number">11</span> <span class="number">12</span></span><br><span class="line">> class(m)</span><br><span class="line">[<span class="number">1</span>] <span class="string">"integer"</span></span><br><span class="line">> typeof(m)</span><br><span class="line">[<span class="number">1</span>] <span class="string">"integer"</span></span><br></pre></td></tr></table></figure><p>下面我们来比较一下普通一维数组和二维数组的异同。</p><p>先创建一个数组a:</p><figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">> a <- array(1:12, dim=c(3,4))</span><br><span class="line">> a</span><br><span class="line"> [,1] [,2] [,3] [,4]</span><br><span class="line">[1,] <span class="number"> 1 </span> <span class="number"> 4 </span> <span class="number"> 7 </span> 10</span><br><span class="line">[2,] <span class="number"> 2 </span> <span class="number"> 5 </span> <span class="number"> 8 </span> 11</span><br><span class="line">[3,] <span class="number"> 3 </span> <span class="number"> 6 </span> <span class="number"> 9 </span> 12</span><br></pre></td></tr></table></figure><p>然后,定义一个包含相同对象的向量:</p><figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">> b <- 1:12</span><br><span class="line">> b</span><br><span class="line"> [1] <span class="number"> 1 </span><span class="number"> 2 </span><span class="number"> 3 </span><span class="number"> 4 </span><span class="number"> 5 </span><span class="number"> 6 </span><span class="number"> 7 </span><span class="number"> 8 </span><span class="number"> 9 </span>10<span class="number"> 11 </span>12</span><br></pre></td></tr></table></figure><p>我们可以用方括号来操作a中的元素,但对b不能用同样的方式(因为对象b没有任何维度)。</p><figure class="highlight subunit"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">> a[2,2]</span><br><span class="line">[1] 5</span><br><span class="line">> b[2,2]</span><br><span class="line"><span class="keyword">Error </span>in b[2, 2] : 量度数目不对</span><br></pre></td></tr></table></figure><p>咦,这连个对象在R里面是不是相同的啊?下面用<code>==</code>看下:</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">> a == b</span><br><span class="line"> [,<span class="number">1</span>] [,<span class="number">2</span>] [,<span class="number">3</span>] [,<span class="number">4</span>]</span><br><span class="line">[<span class="number">1</span>,] <span class="literal">TRUE</span> <span class="literal">TRUE</span> <span class="literal">TRUE</span> <span class="literal">TRUE</span></span><br><span class="line">[<span class="number">2</span>,] <span class="literal">TRUE</span> <span class="literal">TRUE</span> <span class="literal">TRUE</span> <span class="literal">TRUE</span></span><br><span class="line">[<span class="number">3</span>,] <span class="literal">TRUE</span> <span class="literal">TRUE</span> <span class="literal">TRUE</span> <span class="literal">TRUE</span></span><br></pre></td></tr></table></figure><p>结果表明对象的所有元素都是相同的。但这不意味着这两者完全一样的,我们自己也能很明显感觉它们的差异。</p><p>R中有一个<code>all.equal</code>函数可以用来比较两个对象的数据和维度以甄别两个对象是否近乎相同,若不同则会返回其原因。</p><figure class="highlight accesslog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">> all.equal(a,b)</span><br><span class="line"><span class="string">[1]</span> <span class="string">"Attributes: < Modes: list, NULL >"</span></span><br><span class="line"><span class="string">[2]</span> <span class="string">"Attributes: < Lengths: 1, 0 >"</span></span><br><span class="line"><span class="string">[3]</span> <span class="string">"Attributes: < names for target but not for current >"</span></span><br><span class="line"><span class="string">[4]</span> <span class="string">"Attributes: < current is not list-like >"</span></span><br><span class="line"><span class="string">[5]</span> <span class="string">"target is matrix, current is numeric"</span></span><br></pre></td></tr></table></figure><p>如果我们只care两个对象是不是完全一致,而不关心不一致的原因,可以使用<code>identical</code>函数。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">></span><span class="bash"> identical(a, b)</span></span><br><span class="line">[1] FALSE</span><br></pre></td></tr></table></figure><p>通过赋予对象b一个维度属性,可以将b转化为一个与a相同的数组。</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">> dim(b) <- c(<span class="number">3</span>, <span class="number">4</span>)</span><br><span class="line">> b[<span class="number">2</span>,<span class="number">2</span>]</span><br><span class="line">[<span class="number">1</span>] <span class="number">5</span></span><br><span class="line">> all.equal(a,b)</span><br><span class="line">[<span class="number">1</span>] <span class="literal">TRUE</span></span><br><span class="line">> identical(a,b)</span><br><span class="line">[<span class="number">1</span>] <span class="literal">TRUE</span></span><br></pre></td></tr></table></figure><h2 id="类"><a href="#类" class="headerlink" title="类"></a>类</h2><p>对象的类是对象的属性之一。对于简单的对象而言,其类和类型是有紧密联系的。然而,对于复合型对象,两者则可能不同(最常见的是数据框,你创建一个然后用<code>class</code>与<code>typeof</code>函数看看就知道了)。</p><p>下面是一个简单数值型向量的类型和类:</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">> x <- c(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>)</span><br><span class="line">> typeof(x)</span><br><span class="line">[<span class="number">1</span>] <span class="string">"double"</span></span><br><span class="line">> class(x)</span><br><span class="line">[<span class="number">1</span>] <span class="string">"numeric"</span></span><br></pre></td></tr></table></figure><p>与改变其他属性的操作一样,我们可以改变R对象所属的类。<strong>例如,在计算机内部,因子是通过整型数据以及整型数据到因子水平的映射来实现的</strong>(整型数据占的存储空间较少且固定,因此比字符向量更高效)。</p><p>在调用<code>class</code>函数或者<code>typeof</code>函数时,对于有些对象,我们需要对其进行引用以防止其在调用时被执行。比如,我们想查询符号x而不是x所指向的对象的类型时,用如下操作:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">></span><span class="bash"> x = 1</span></span><br><span class="line"><span class="meta">></span><span class="bash"> class(quote(x))</span></span><br><span class="line">[1] "name"</span><br><span class="line"><span class="meta">></span><span class="bash"> typeof(quote(x))</span></span><br><span class="line">[1] "symbol"</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 观察区别</span></span><br><span class="line"><span class="meta">></span><span class="bash"> class(x)</span></span><br><span class="line">[1] "numeric"</span><br><span class="line"><span class="meta">></span><span class="bash"> typeof(x)</span></span><br><span class="line">[1] "double"</span><br></pre></td></tr></table></figure><hr><p>学习整理自《R核心技术手册》</p>]]></content>
<summary type="html">
<h2 id="属性"><a href="#属性" class="headerlink" title="属性"></a>属性</h2><p>属性描述了对象所代表的内容以及R解释该对象的方式。<strong>很多时候两个对象之间的唯一差别就在于它们的属性不同</strong>。下表展示了一些重要的属性。很多常见的属性都是针对常见的数值型数据对象而言的:像数组、矩阵和数据框。</p>
</summary>
<category term="极客R" scheme="https://shixiangwang.github.io/categories/%E6%9E%81%E5%AE%A2R/"/>
<category term="基本理论" scheme="https://shixiangwang.github.io/categories/%E6%9E%81%E5%AE%A2R/%E5%9F%BA%E6%9C%AC%E7%90%86%E8%AE%BA/"/>
<category term="R" scheme="https://shixiangwang.github.io/tags/R/"/>
<category term="类" scheme="https://shixiangwang.github.io/tags/%E7%B1%BB/"/>
</entry>
<entry>
<title>控制脚本</title>
<link href="https://shixiangwang.github.io/2017/09/04/control_shell/"/>
<id>https://shixiangwang.github.io/2017/09/04/control_shell/</id>
<published>2017-09-03T16:00:00.000Z</published>
<updated>2018-01-27T04:08:26.410Z</updated>
<content type="html"><![CDATA[<blockquote><p><strong>内容</strong></p><ul><li>处理信号</li><li>以后台模式运行脚本</li><li>禁止挂起</li><li>作业控制</li><li>修改脚本优先级</li><li>脚本执行自动化</li></ul></blockquote><a id="more"></a><p>除了在命令行界面世界运行脚本,还存在一些方法:<strong>向脚本发送信号、修改脚本的优先级以及在脚本运行时切换到运行模式</strong>。</p><p>下面逐一讲述。</p><h2 id="处理信号"><a href="#处理信号" class="headerlink" title="处理信号"></a>处理信号</h2><p>Linux利用信号与运行在系统中的进程进行通信。我们可以通过对脚本编程,使其在收到特定信号时执行某些命令,从而实现对脚本运行的控制。</p><h3 id="Linux信号"><a href="#Linux信号" class="headerlink" title="Linux信号"></a>Linux信号</h3><p>Linux和应用程序可以生成超过30个信号。下面列出最常见的系统信号。</p><table><thead><tr><th style="text-align:center">信号</th><th style="text-align:center">值</th><th style="text-align:center">描述</th></tr></thead><tbody><tr><td style="text-align:center">1</td><td style="text-align:center">SIGHUP</td><td style="text-align:center">挂起进程</td></tr><tr><td style="text-align:center">2</td><td style="text-align:center">SIGINT</td><td style="text-align:center">终止进程</td></tr><tr><td style="text-align:center">3</td><td style="text-align:center">SIGQUIT</td><td style="text-align:center">停止进程</td></tr><tr><td style="text-align:center">9</td><td style="text-align:center">SIGKILL</td><td style="text-align:center">无条件终止进程</td></tr><tr><td style="text-align:center">15</td><td style="text-align:center">SIGTERM</td><td style="text-align:center">尽可能终止进程</td></tr><tr><td style="text-align:center">17</td><td style="text-align:center">SIGSTOP</td><td style="text-align:center">无条件停止进程,但不是终止进程</td></tr><tr><td style="text-align:center">18</td><td style="text-align:center">SIGTSTP</td><td style="text-align:center">停止或暂停进程,但不是终止进程</td></tr><tr><td style="text-align:center">19</td><td style="text-align:center">SIGCONT</td><td style="text-align:center">继续运行停止的进程</td></tr></tbody></table><p>默认情况下,bash shell会忽略收到的任何<code>SIGQUIT</code>和<code>SIGTERM</code>信号(所以交互式shell不会被终止)。但是bash shell会处理收到的<code>SIGHUP</code>和<code>SIGINT</code>信号。</p><p>Shell会将这些信号传给shell脚本程序来处理。而shell脚本默认是忽略这些信号的,为了避免它,我们可以在脚本中加入识别信号的代码,并执行命令来处理信号。</p><h3 id="生成信号"><a href="#生成信号" class="headerlink" title="生成信号"></a>生成信号</h3><p>键盘上的组合可以生成两种基本的Linux信号。它在停止或暂停失控程序时非常有用。</p><ol><li><p>中断程序: 使用<code>Ctrl</code>+<code>C</code>,它会发送<code>SIGINT</code>信号。</p><p><del>(测试没起作用,尴尬了~)</del></p></li><li><p>暂停进程:使用<code>Ctrl</code>+<code>Z</code>,它会发送<code>SIGTSTP</code>信号。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ sleep 1000</span><br><span class="line">^Z</span><br><span class="line">[2]+ 已停止 sleep 1000</span><br></pre></td></tr></table></figure></li></ol><p><strong>注意</strong>:停止进程会让程序继续保留在内存中,并能从上次停止的位置继续运行。</p><p>方括号中的数字是shell自动为程序分配的<em>作业号</em>。shell将shell中运行的每个进程成为<em>作业</em>,并为其分配唯一的作业号。</p><p>退出shell时发现有停止的进程,用<code>ps</code>命令查看</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ exit</span><br><span class="line">exit</span><br><span class="line">有停止的任务。</span><br><span class="line">wsx@wsx-ubuntu:~$ ps -l</span><br><span class="line">F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD</span><br><span class="line">0 S 1000 5438 5433 0 80 0 - 6153 wait pts/4 00:00:00 bash</span><br><span class="line">0 T 1000 5452 5438 0 80 0 - 2258 signal pts/4 00:00:00 sleep</span><br><span class="line">0 T 1000 5456 5438 0 80 0 - 2258 signal pts/4 00:00:00 sleep</span><br><span class="line">4 R 1000 5525 5438 0 80 0 - 7665 - pts/4 00:00:00 ps</span><br></pre></td></tr></table></figure><p>在表示进程状态的S列中,<code>ps</code>命令将已经停止作业的状态显示为<code>T</code>。这说明命令要么被跟踪,要么被停止了。</p><p>如果你仍想退出shell,只需要再输入一遍<code>exit</code>。也可以用<code>kill</code>生成<code>SIGKILL</code>信号标识上<code>PID</code>杀死进程。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ kill 5456</span><br><span class="line">wsx@wsx-ubuntu:~$ kill -9 5456</span><br><span class="line">wsx@wsx-ubuntu:~$ kill -9 5452</span><br><span class="line">[1]- 已杀死 sleep 1000</span><br><span class="line">[2]+ 已杀死 sleep 1000</span><br></pre></td></tr></table></figure><h3 id="捕获信号"><a href="#捕获信号" class="headerlink" title="捕获信号"></a>捕获信号</h3><p><code>trap</code>命令允许我们来指定shell脚本要监看并从shell中拦截的Linux信号。</p><p>格式为:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">trap commands signals</span><br></pre></td></tr></table></figure><p>下面展示一个简单的例子,看如何使用<code>trap</code>命令忽略<code>SIGINT</code>信号,并控制脚本的行为。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test1.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Testing signal trapping</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">trap "echo ' Sorry! I have trapped Ctrl-C'" SIGINT</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo This is a test script</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">count=1</span><br><span class="line">while [ $count -le 10 ]</span><br><span class="line">do</span><br><span class="line"> echo "Loop #$count"</span><br><span class="line"> sleep 1</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo "This is the end of the test script"</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br></pre></td></tr></table></figure><p>来运行测试一下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test1.sh</span><br><span class="line">This is a test script</span><br><span class="line">Loop #1</span><br><span class="line">Loop #2</span><br><span class="line">Loop #3</span><br><span class="line">Loop #4</span><br><span class="line">Loop #5</span><br><span class="line">Loop #6</span><br><span class="line">^C Sorry! I have trapped Ctrl-C</span><br><span class="line">Loop #7</span><br><span class="line">Loop #8</span><br><span class="line">Loop #9</span><br><span class="line">Loop #10</span><br><span class="line">This is the end of the test script</span><br></pre></td></tr></table></figure><h3 id="捕获信号-1"><a href="#捕获信号-1" class="headerlink" title="捕获信号"></a>捕获信号</h3><p>我们也可以在shell脚本退出时进行捕获。这是<strong>在shell完成任务时执行命令的一种简便方法</strong>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test2.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Trapping the script <span class="built_in">exit</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">trap "echo Goodbye..." EXIT</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">count=1</span><br><span class="line">while [ $count -le 5 ]</span><br><span class="line">do</span><br><span class="line"> echo "Loop #$count"</span><br><span class="line"> sleep 1</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test2.sh</span><br><span class="line">Loop #1</span><br><span class="line">Loop #2</span><br><span class="line">Loop #3</span><br><span class="line">Loop #4</span><br><span class="line">Loop #5</span><br><span class="line">Goodbye...</span><br></pre></td></tr></table></figure><p>当该脚本运行到退出位置,捕获就触发了,shell会执行在<code>trap</code>命令行指定的命令。就算提取退出,也能够成功捕获。</p><h3 id="修改或移除捕获"><a href="#修改或移除捕获" class="headerlink" title="修改或移除捕获"></a>修改或移除捕获</h3><p>想在不同的位置进行不同的捕获处理,只需要重新使用带新选项的<code>trap</code>命令。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test3.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Modifying a <span class="built_in">set</span> <span class="built_in">trap</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">trap "echo ' Sorry... Ctrc-C is trapped.'" SIGINT # SIGINT是退出信号</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">count=1</span><br><span class="line">while [ $count -le 5 ] # 当count<5的时候</span><br><span class="line">do</span><br><span class="line"> echo "Loop #$count"</span><br><span class="line"> sleep 1 # 睡1秒</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">trap "echo ' I modified the trap!'" SIGINT</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">count=1</span><br><span class="line">while [ $count -le 5 ] # 当count<5的时候</span><br><span class="line">do</span><br><span class="line"> echo "Loop #$count"</span><br><span class="line"> sleep 1 # 睡1秒</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test3.sh</span><br><span class="line">Loop #1</span><br><span class="line">Loop #2</span><br><span class="line">Loop #3</span><br><span class="line">^C Sorry... Ctrc-C is trapped.</span><br><span class="line">Loop #4</span><br><span class="line">Loop #5</span><br><span class="line">Loop #1</span><br><span class="line">Loop #2</span><br><span class="line">Loop #3</span><br><span class="line">^C I modified the trap!</span><br><span class="line">Loop #4</span><br><span class="line">Loop #5</span><br></pre></td></tr></table></figure><p>相当于两次不同的捕获。</p><p>我们也可以删除已经设置好的捕获。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test3.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Modifying a <span class="built_in">set</span> <span class="built_in">trap</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">trap "echo ' Sorry... Ctrc-C is trapped.'" SIGINT # SIGINT是退出信号 在这里设置捕获</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">count=1</span><br><span class="line">while [ $count -le 5 ] # 当count<5的时候</span><br><span class="line">do</span><br><span class="line"> echo "Loop #$count"</span><br><span class="line"> sleep 1 # 睡1秒</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">trap -- SIGINT # 在这里删除捕获</span><br><span class="line">echo "I modified the trap!"</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">count=1</span><br><span class="line">while [ $count -le 5 ] # 当count<5的时候</span><br><span class="line">do</span><br><span class="line"> echo "Loop #$count"</span><br><span class="line"> sleep 1 # 睡1秒</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test3.sh</span><br><span class="line">Loop #1</span><br><span class="line">Loop #2</span><br><span class="line">Loop #3</span><br><span class="line">Loop #4</span><br><span class="line">Loop #5</span><br><span class="line">I modified the trap!</span><br><span class="line">Loop #1</span><br><span class="line">Loop #2</span><br><span class="line">Loop #3</span><br><span class="line">^C</span><br></pre></td></tr></table></figure><p>信号捕获被移除之后,脚本会按照原来的方式处理<code>SIGINT</code>信号。所以使用<code>Ctrl+C</code>键时,脚本运行会退出。当然,如果是在这个信号捕获移除前接受到<code>SIGINT</code>信号,那么脚本还是会捕获。(因为shell脚本运行是按步的,前面没有接收到信号捕获的移除,自然不会实现信号捕获的移除)</p><h2 id="以后台模式运行脚本"><a href="#以后台模式运行脚本" class="headerlink" title="以后台模式运行脚本"></a>以后台模式运行脚本</h2><h3 id="后台运行脚本"><a href="#后台运行脚本" class="headerlink" title="后台运行脚本"></a>后台运行脚本</h3><p>以后台模式运行shell脚本非常简单,只要再命令后加<code>&</code>符就可以了。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ cat test4.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Testing running <span class="keyword">in</span> the background</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">count=1</span><br><span class="line">while [ $count -le 10 ]</span><br><span class="line">do</span><br><span class="line"> sleep 1</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line">wangsx@SC-201708020022:~$ ./test4.sh &</span><br><span class="line">[1] 69</span><br></pre></td></tr></table></figure><p>当添加<code>&</code>符号后,命令和bash shell会分离而作为一个独立的后台进行运行。并返回作业号(方括号内)和进程ID(PID),Linux系统上运行的每一个进程都必须有一个唯一的PID。</p><p>当后台进程结束后,它会在终端显示出消息:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[1] 已完成 ./test4.sh</span><br></pre></td></tr></table></figure><p>需要注意的是,当后台程序运行时,它仍然会使用终端显示器显示<code>STDOUT</code>和<code>STDERR</code>消息。最好是将<code>STDOUT</code>和<code>STDERR</code>进行重定向。</p><h3 id="运行多个后台作业"><a href="#运行多个后台作业" class="headerlink" title="运行多个后台作业"></a>运行多个后台作业</h3><p>我们可以在命令提示符中同时启动多个后台作用,然后用<code>ps</code>命令查看。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ ./test4.sh &</span><br><span class="line">[1] 117</span><br><span class="line">wangsx@SC-201708020022:~$ ./test5.sh &</span><br><span class="line">[2] 122</span><br><span class="line">wangsx@SC-201708020022:~$ ./test6.sh &</span><br><span class="line">[3] 128</span><br><span class="line">wangsx@SC-201708020022:~$ ps</span><br><span class="line"> PID TTY TIME CMD</span><br><span class="line"> 2 tty1 00:00:00 bash</span><br><span class="line"> 117 tty1 00:00:00 test4.sh</span><br><span class="line"> 122 tty1 00:00:00 test5.sh</span><br><span class="line"> 128 tty1 00:00:00 test6.sh</span><br><span class="line"> 135 tty1 00:00:00 sleep</span><br><span class="line"> 136 tty1 00:00:00 sleep</span><br><span class="line"> 137 tty1 00:00:00 ps</span><br><span class="line"> 138 tty1 00:00:00 sleep</span><br></pre></td></tr></table></figure><p>我们特别需要注意,如果终端退出,后台程序也会随之退出。</p><h2 id="在非控制台下运行脚本"><a href="#在非控制台下运行脚本" class="headerlink" title="在非控制台下运行脚本"></a>在非控制台下运行脚本</h2><p>如果我们不想出现终端退出后台程序退出的情况,可以使用<code>nohup</code>命令来实现。</p><p><strong><code>nohup</code>命令运行了另外一个命令来阻断所有发送给该进程的<code>SIGHUP</code>信号。这会在退出终端会话时阻止进程退出。</strong></p><p>其格式如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ nohup ./test4.sh &</span><br><span class="line">[1] 156</span><br><span class="line">wangsx@SC-201708020022:~$ nohup: 忽略输入并把输出追加到'nohup.out'</span><br></pre></td></tr></table></figure><p>由于<code>nohup</code>命令会解除终端与进程的关联,进程也就不再同<code>STDOUT</code>和<code>STDERR</code>联系在一起。它会自动将这两者重定向到名为<code>nohup.out</code>的文件中。</p><h2 id="作业控制"><a href="#作业控制" class="headerlink" title="作业控制"></a>作业控制</h2><p>启动、停止终止以及恢复作业统称为<strong>作业控制</strong>。我们可以通过这几种方式完全控制shell环境中所有的进程的运行方式。</p><h3 id="查看作业"><a href="#查看作业" class="headerlink" title="查看作业"></a>查看作业</h3><p>作业控制的<strong>关键命令</strong>是<code>jobs</code>命令。它允许查看shell当前正在处理的作业。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ cat test10.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Test job control</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo "Script Process ID: $$"</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">count=1</span><br><span class="line">while [ $count -le 10 ]</span><br><span class="line">do</span><br><span class="line"> echo "Loop #$count"</span><br><span class="line"> sleep 10</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo "End of script..."</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">wangsx@SC-201708020022:~$ ./test10.sh</span><br><span class="line">Script Process ID: 27</span><br><span class="line">Loop #1</span><br><span class="line">Loop #2</span><br><span class="line">^Z^C # 我的ctrl+z好像不起作用</span><br></pre></td></tr></table></figure><p>脚本用<code>$$</code>变量来显示系统分配给脚本的PID。使用Ctrl+Z组合键来停止脚本(我的在这不起作用~之前好像也是)。</p><p>我们使用同样的脚本,利用<code>&</code>将另外一个作业作为后台进程启动。我们通过<code>jobs -l</code>命令查看作业的PID。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test10.sh > test10.out &</span><br><span class="line">[1] 121</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ jobs -l</span><br><span class="line">[1]+ 121 运行中 ./test10.sh > test10.out &</span><br></pre></td></tr></table></figure><p>下面看<code>jobs</code>命令的一些参数:</p><table><thead><tr><th>参数</th><th>语法</th></tr></thead><tbody><tr><td>-l</td><td>列出进程的PID以及作业号</td></tr><tr><td>-n</td><td>只列出上次shell发出的通知后改变了状态的作业</td></tr><tr><td>-p</td><td>只列出作业的PID</td></tr><tr><td>-r</td><td>只列出运行中的作业</td></tr><tr><td>-s</td><td>只列出已停止的作业</td></tr></tbody></table><p>如果仔细注意的话,我们发现作业号后面有<code>+</code>号。带加号的作业会被当成默认作业。当前的默认作业完成处理后,带减号的作业成为下一个默认作业。任何时候只有一个带加号的作业和一个带减号的作业。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ jobs -l</span><br><span class="line">[1] 132 运行中 ./test10.sh > test10.out &</span><br><span class="line">[2]- 134 运行中 ./test10.sh > test10.out &</span><br><span class="line">[3]+ 136 运行中 ./test10.sh > test10.out &</span><br></pre></td></tr></table></figure><p>可以发现最好运行的脚本输出排在最前面。</p><p>我们调用了<code>kill</code>命令向默认进程发送了一个<code>SIGHUP</code>信号,终止了该作业。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test10.sh > test10.out &</span><br><span class="line">[1] 165</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test10.sh > test10.out &</span><br><span class="line">[2] 167</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test10.sh > test10.out &</span><br><span class="line">[3] 169</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ jobs -l</span><br><span class="line">[1] 165 运行中 ./test10.sh > test10.out &</span><br><span class="line">[2]- 167 运行中 ./test10.sh > test10.out &</span><br><span class="line">[3]+ 169 运行中 ./test10.sh > test10.out &</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ kill 169</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ jobs -l</span><br><span class="line">[1] 165 运行中 ./test10.sh > test10.out &</span><br><span class="line">[2]- 167 运行中 ./test10.sh > test10.out &</span><br><span class="line">[3]+ 169 已终止 ./test10.sh > test10.out</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ jobs -l</span><br><span class="line">[1]- 165 运行中 ./test10.sh > test10.out &</span><br><span class="line">[2]+ 167 运行中 ./test10.sh > test10.out &</span><br></pre></td></tr></table></figure><h3 id="重启停止的作业"><a href="#重启停止的作业" class="headerlink" title="重启停止的作业"></a>重启停止的作业</h3><p>我们可以将已经停止的作业作为后台进程或者前台进程重启。前台进程会接管当前工作的终端,所以使用时需要注意。</p><p>要以后台模式重启一个作业,可以用<code>bg</code>命令加上作业号(我的Window10子系统好像确实不能使用Ctrl+Z的功能,有兴趣可以自己测试一下)。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test10.sh</span><br><span class="line">Script Process ID: 13</span><br><span class="line">Loop #1</span><br><span class="line">^ZLoop #2</span><br><span class="line">^C</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ bg</span><br><span class="line">bash: bg: 当前: 无此任务</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ jobs</span><br></pre></td></tr></table></figure><p>如果是默认作业,只需要使用<code>bg</code>命令。如果有多个作业,你得在<code>bg</code>命令后加上作业号。</p><h2 id="调整谦让度"><a href="#调整谦让度" class="headerlink" title="调整谦让度"></a>调整谦让度</h2><p>在Linux中,内核负责将CPU时间分配给系统上运行的每一个进程。<strong>调度优先级</strong>是内核分配给进程的CPU时间。在Linux系统中,由shell启动的所有进程的调度优先级默认都是相同的。</p><p>调度优先级是一个整数值,从-20(最高)到+19(最低)。默认bash shell以优先级0来启动所有进程。</p><p>我们可以使用<code>nice</code>命令来改变shell脚本的优先级。</p><h3 id="nice命令"><a href="#nice命令" class="headerlink" title="nice命令"></a>nice命令</h3><p>要让命令以更低的优先级运行,只要用<code>nice</code>的<code>-n</code>命令行来指定新的优先级级别。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wsx@ubuntu:~/tmp$ nice -n 10 ./test10.sh > test10.out &</span><br><span class="line">[5] 18953</span><br><span class="line">wsx@ubuntu:~/tmp$ ps -p 18953 -o pid,ppid,ni,cmd</span><br><span class="line"> PID PPID NI CMD</span><br><span class="line"> 18953 18782 10 /bin/bash ./test10.sh</span><br></pre></td></tr></table></figure><p>如果想要提高优先级,需要使用超级用户权限。<code>nice</code>命令的<code>-n</code>选项不是必须的,只需要在破折号后面跟上优先级就行了。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx@ubuntu:~/tmp$ nice -10 ./test10.sh > test10.out &</span><br><span class="line">[6] 18999</span><br><span class="line">[5] Done nice -n 10 ./test10.sh > test10.out</span><br><span class="line">wsx@ubuntu:~/tmp$ ps -p 18999 -o pid,ppid,ni,cmd</span><br><span class="line"> PID PPID NI CMD</span><br><span class="line"> 18999 18782 10 /bin/bash ./test10.sh</span><br></pre></td></tr></table></figure><h3 id="renice命令"><a href="#renice命令" class="headerlink" title="renice命令"></a>renice命令</h3><p>有时候我们想要改变系统上已经运行命令的优先级,这是<code>renice</code>命令可以做到的。它允许我们指定PID来改变它的优先级。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">wsx@ubuntu:~/tmp$ ./test10.sh &</span><br><span class="line">[3] 19086</span><br><span class="line">wsx@ubuntu:~$ ps -p 19086 -o pid,ppid,ni,cmd</span><br><span class="line"> PID PPID NI CMD</span><br><span class="line"> 19086 19070 0 /bin/bash ./test10.sh</span><br><span class="line">wsx@ubuntu:~$ renice -n 10 -p 19086</span><br><span class="line">19086 (process ID) old priority 0, new priority 10</span><br><span class="line">wsx@ubuntu:~$ ps -p 19086 -o pid,ppid,ni,cmd</span><br><span class="line"> PID PPID NI CMD</span><br><span class="line"> 19086 19070 10 /bin/bash ./test10.sh</span><br></pre></td></tr></table></figure><h2 id="定时运行脚本"><a href="#定时运行脚本" class="headerlink" title="定时运行脚本"></a>定时运行脚本</h2><p>Linux系统提供了多个在预定时间运行脚本的方法:<code>at</code>命令和<code>cron</code>表。</p><h3 id="用at命令来计划执行任务"><a href="#用at命令来计划执行任务" class="headerlink" title="用at命令来计划执行任务"></a>用at命令来计划执行任务</h3><p><code>at</code>命令允许指定Linux系统何时运行脚本。<code>at</code>命令会将作业提交到队列中,指定shell何时运行该作业。<code>at</code>的守护进程<code>atd</code>会以后台模式运行,检查作业队列来运行作业。</p><p><code>atd</code>守护进程会检查系统上的一个特殊目录(通常位于<code>/var/spool/at</code>)来获取用<code>at</code>命令提交的作业。</p><h4 id="at命令的格式"><a href="#at命令的格式" class="headerlink" title="at命令的格式"></a>at命令的格式</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">at [-f filename] time</span><br></pre></td></tr></table></figure><p>默认,<code>at</code>命令会将<code>STDIN</code>的输入放入队列中。我们可以用<code>-f</code>参数来指定用于读取命令的文件名。<code>time</code>参数指定了Linux系统何时运行该脚本。</p><p><code>at</code>命令能识别多种不同的时间格式。</p><ul><li>标准的小时和分钟格式,比如10:15</li><li>AM/PM指示符,比如10:15 PM</li><li>特定可命令的时间,比如now, noon, midnight或teatime (4 PM)</li></ul><p>除了指定运行时间,还可以指定运行的日期。</p><ul><li>标准日期格式,比如MMDDYY, MM/DD/YY或DD.MM.YY</li><li>文本日期,比如Jul 4或Dec 25,加不加年份都可以</li><li>还可以指定时间增量<ul><li>当前时间+25min</li><li>明天10:15PM</li><li>10:15+7天</li></ul></li></ul><p>针对不同的优先级,存在26种不同的作业队列。作业队列通常用小写字母a-z和大写字母A-Z来指代。</p><p>作业队列的字母排序越高,作业运行的优先级就越低(更高的nice值)。可以用<code>-q</code>参数指定不同的队列字母。</p><h4 id="获取作业的输出"><a href="#获取作业的输出" class="headerlink" title="获取作业的输出"></a>获取作业的输出</h4><p>Linux系统会将提交作业的用户的电子邮件地址作为STDOUT和STDERR。任何发到STDOUT或STDERR的输出都会通过邮件系统发送给用户。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 解决atd没启动的问题</span></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ sudo /etc/init.d/atd start</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test13.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Test using at <span class="built_in">command</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo "This script ran at $(date +%B%d,%T)"</span><br><span class="line">echo</span><br><span class="line">sleep 5</span><br><span class="line">echo "This is the script's end..."</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ at -f test13.sh now</span><br><span class="line">warning: commands will be executed using /bin/sh</span><br><span class="line">job 4 at Tue Sep 26 12:12:00 2017</span><br></pre></td></tr></table></figure><p><code>at</code>命令会显示分配给作业的作业号以及为作业安排的运行时间。<code>at</code>命令利用<code>sendmail</code>应用程序来发送邮件。如果没有安装这个工具就无法获得输出,因此在使用<code>at</code>命令时,最好在脚本中对STDOUT和STDERR进行重定向。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test13b.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Test using at <span class="built_in">command</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo "This script ran at $(date +%B%d,%T)" > test13b.out</span><br><span class="line">echo >> test13b.out</span><br><span class="line">sleep 5</span><br><span class="line">echo "This is the script's end..." >> test13b.out</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ at -M -f test13b.sh now</span><br><span class="line">warning: commands will be executed using /bin/sh</span><br><span class="line">job 7 at Tue Sep 26 12:16:00 2017</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ cat test13b.out</span><br><span class="line">This script ran at 九月26,12:16:24</span><br><span class="line"></span><br><span class="line">This is the script's end...</span><br></pre></td></tr></table></figure><p>这里使用了<code>-M</code>选项来屏蔽作业产生的输出信息。</p><h4 id="列出等待的作业"><a href="#列出等待的作业" class="headerlink" title="列出等待的作业"></a>列出等待的作业</h4><p><code>atq</code>命令可以查看系统中有哪些作业再等待。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ at -M -f test13b.sh teatime</span><br><span class="line">warning: commands will be executed using /bin/sh</span><br><span class="line">job 11 at Wed Sep 27 16:00:00 2017</span><br><span class="line">Can't open /var/run/atd.pid to signal atd. No atd running?</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ sudo /etc/init.d/atd start</span><br><span class="line">[sudo] wangsx 的密码:</span><br><span class="line"> * Starting deferred execution scheduler atd [ OK ]</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ at -M -f test13b.sh teatime</span><br><span class="line">warning: commands will be executed using /bin/sh</span><br><span class="line">job 12 at Wed Sep 27 16:00:00 2017</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ at -M -f test13b.sh tomorrow</span><br><span class="line">warning: commands will be executed using /bin/sh</span><br><span class="line">job 13 at Wed Sep 27 21:44:00 2017</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ at -M -f test13b.sh 13:30</span><br><span class="line">warning: commands will be executed using /bin/sh</span><br><span class="line">job 14 at Wed Sep 27 13:30:00 2017</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ atq</span><br><span class="line">11 Wed Sep 27 16:00:00 2017 a wangsx</span><br><span class="line">12 Wed Sep 27 16:00:00 2017 a wangsx</span><br><span class="line">13 Wed Sep 27 21:44:00 2017 a wangsx</span><br><span class="line">14 Wed Sep 27 13:30:00 2017 a wangsx</span><br></pre></td></tr></table></figure><p>作业列表中显示了作业号、系统运行该作业的日期和时间以及它所在的队列位置。</p><h4 id="删除作业"><a href="#删除作业" class="headerlink" title="删除作业"></a>删除作业</h4><p>使用<code>atrm</code>命令删除等待中的作业。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ atq</span><br><span class="line">11 Wed Sep 27 16:00:00 2017 a wangsx</span><br><span class="line">12 Wed Sep 27 16:00:00 2017 a wangsx</span><br><span class="line">13 Wed Sep 27 21:44:00 2017 a wangsx</span><br><span class="line">14 Wed Sep 27 13:30:00 2017 a wangsx</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ atrm 11</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ atq</span><br><span class="line">12 Wed Sep 27 16:00:00 2017 a wangsx</span><br><span class="line">13 Wed Sep 27 21:44:00 2017 a wangsx</span><br><span class="line">14 Wed Sep 27 13:30:00 2017 a wangsx</span><br></pre></td></tr></table></figure><p>只能删除自己提交的作业,不能删除其他人的。</p><h3 id="安排需要定期执行的脚本"><a href="#安排需要定期执行的脚本" class="headerlink" title="安排需要定期执行的脚本"></a>安排需要定期执行的脚本</h3><p>如果是需要定期执行的脚本,我们不需要使用<code>at</code>不断提交作业,而是可以利用Linux系统的另一个功能。</p><p><strong>Linux系统使用<code>cron</code>程序来安排要定期执行的作业。它会在后台运行并检查一个特殊的表(成为cron时间表),以获得已安排执行的作业。</strong></p><h4 id="cron时间表"><a href="#cron时间表" class="headerlink" title="cron时间表"></a>cron时间表</h4><p><code>cron</code>时间表的格式如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">min hour dayofmonth month dayofweek command</span><br></pre></td></tr></table></figure><p><code>cron</code>时间表允许我们用特定值、取值范围(比如1-5)或者通配符(星号)来指定条目。</p><p>例如,我们想在每天的10:15运行一个命令,可以使用:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">15 10 * * * command</span><br></pre></td></tr></table></figure><p>在其中三个字段使用了通配符,表明<code>cron</code>会在每个月的每天的10:15执行该命令。</p><p>要指定在每周一4:15PM运行命令,可以使用:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">15 16 * * 1 command</span><br></pre></td></tr></table></figure><p>可以用三字符的文本值mon,tue,wed,thu,fri,sat,sum或数值(0为周日,6为周六)来指定dayofweek的表项。</p><p><code>dayofmonth</code>可以用1-31表示。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 怎么在每个月的最后一天执行命令?</span></span><br><span class="line">00 12 * * * if [`date +%d -d tomorrow` = 01 ]; then ; command</span><br></pre></td></tr></table></figure><p>命令列表必须指定要运行的命令或脚本的全路径名。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 例如</span></span><br><span class="line">15 10 * * * /home/rich/test4.sh > test4out</span><br></pre></td></tr></table></figure><p>注意:<code>corn</code>会用提交作业的用户账户运行脚本,所以我们在操作指定文件时必须有相应权限。</p><h4 id="构建cron时间表"><a href="#构建cron时间表" class="headerlink" title="构建cron时间表"></a>构建cron时间表</h4><p>Linux提供了<code>crontab</code>命令来处理<code>cron</code>时间表。我们可以使用<code>-l</code>选项列出时间表。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ crontab -l</span><br><span class="line">no crontab for wangsx</span><br></pre></td></tr></table></figure><p>要添加条目,使用<code>-e</code>选项。在添加条目时,<code>crontab</code>命令会启动一个文本编辑器,使用已有的<code>cron</code>时间表作为文件内容。</p><h4 id="浏览cron目录"><a href="#浏览cron目录" class="headerlink" title="浏览cron目录"></a>浏览cron目录</h4><p>如果对时间精确性要求不高,用预配置的<code>cron</code>脚本目录会更方便。有4个基本目录:hourly, daily, monthly和weekly。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ls /etc/cron.*ly</span><br><span class="line">/etc/cron.daily:</span><br><span class="line">apport bsdmainutils man-db passwd upstart</span><br><span class="line">apt-compat dpkg mdadm popularity-contest</span><br><span class="line">aptitude logrotate mlocate update-notifier-common</span><br><span class="line"></span><br><span class="line">/etc/cron.hourly:</span><br><span class="line"></span><br><span class="line">/etc/cron.monthly:</span><br><span class="line"></span><br><span class="line">/etc/cron.weekly:</span><br><span class="line">fstrim man-db update-notifier-common</span><br></pre></td></tr></table></figure><p>如果脚本需要每天运行一次,只要将脚本复制到daily目录,cron每天会执行它。</p><h4 id="anacron程序"><a href="#anacron程序" class="headerlink" title="anacron程序"></a>anacron程序</h4><p>如果提交的作业需要运行时系统处于关机状态,<code>cron</code>不会运行那些错过的脚本。为了解决这个问题,<code>anacron</code>程序诞生了。</p><p>如果<code>anacron</code>知道某个作业错过了执行时间,它会尽快运行该作业。这个功能常用于进行常规日志维护的脚本。</p><p><code>anacron</code>程序只会处理位于<code>cron</code>目录下的程序,比如<code>/etc/cron.monthly</code>。它使用时间戳来决定作业是否在正确的计划间隔内运行了,每个<code>cron</code>目录都有个时间戳文件,该文件位于<code>/var/spool/anacron</code>。</p><p><code>anacron</code>程序使用自己的时间表来检查作业目录。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">wsx@ubuntu:~$ sudo cat /var/spool/anacron/cron.monthly</span><br><span class="line">[sudo] password for wsx:</span><br><span class="line">20170926</span><br><span class="line">wsx@ubuntu:~$ sudo cat /etc/anacrontab</span><br><span class="line"><span class="meta">#</span><span class="bash"> /etc/anacrontab: configuration file <span class="keyword">for</span> anacron</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> See anacron(8) and anacrontab(5) <span class="keyword">for</span> details.</span></span><br><span class="line"></span><br><span class="line">SHELL=/bin/sh</span><br><span class="line">PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin</span><br><span class="line">HOME=/root</span><br><span class="line">LOGNAME=root</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> These replace cron<span class="string">'s entries</span></span></span><br><span class="line">1 5 cron.daily run-parts --report /etc/cron.daily</span><br><span class="line">7 10 cron.weekly run-parts --report /etc/cron.weekly</span><br><span class="line">@monthly 15 cron.monthly run-parts --report /etc/cron.monthly</span><br></pre></td></tr></table></figure><p><code>anacron</code>的时间表的基本格式和<code>cron</code>时间表略有不同:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">period delay identifier command</span><br></pre></td></tr></table></figure><p><code>period</code>定义了作业多久运行一次,以天为单位。<code>anacron</code>用此条目来检查作业的时间戳文件。<code>delay</code>条目会指定系统启动后<code>anacron</code>程序需要等待多少分钟再开始运行错过的脚本。<code>command</code>条目包含了<code>run-parts</code>程序和一个<code>cron</code>脚本目录名。<code>run-parts</code>程序负责运行目录中传给它的任何脚本。</p><p>注意了,<code>anacron</code>不会处理执行时间需求小于一天的脚本,所以它是不会运行<code>/etc/cron.hourly</code>的脚本。</p><p><code>identifier</code>条目是一种特别的非空字符串,如<code>cron-weekly</code>。它用于唯一标识日志消息和错误邮件中的作业。</p>]]></content>
<summary type="html">
<blockquote>
<p><strong>内容</strong></p>
<ul>
<li>处理信号</li>
<li>以后台模式运行脚本</li>
<li>禁止挂起</li>
<li>作业控制</li>
<li>修改脚本优先级</li>
<li>脚本执行自动化</li>
</ul>
</blockquote>
</summary>
<category term="Linux杂烩" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/"/>
<category term="shell编程" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/shell%E7%BC%96%E7%A8%8B/"/>
<category term="shell笔记" scheme="https://shixiangwang.github.io/tags/shell%E7%AC%94%E8%AE%B0/"/>
<category term="linux" scheme="https://shixiangwang.github.io/tags/linux/"/>
</entry>
<entry>
<title>Linux数据处理命令工具</title>
<link href="https://shixiangwang.github.io/2017/09/03/Linux-data-analysis-tools/"/>
<id>https://shixiangwang.github.io/2017/09/03/Linux-data-analysis-tools/</id>
<published>2017-09-02T16:00:00.000Z</published>
<updated>2018-01-27T04:08:22.086Z</updated>
<content type="html"><![CDATA[<p>参考学习《Bioinformatics. Data. Skills》,这里简要地整理下Linux用来处理数据文本的工具。具体命令详情请在<a href="http://man.linuxde.net/" target="_blank" rel="noopener">Linux命令大全</a>中搜索或者查阅其他相关资料。</p><a id="more"></a><p><code>head</code>,<code>tail</code>查看文档头尾。<code>-n</code>选项可以指定行数。</p><p><code>less</code>用来查阅文档,<code>q</code>退出,<code>space bar</code>翻页,<code>g</code>第一行,<code>G</code>最后一行,<code>j</code>下,<code>k</code>上,<code>/<pattern></code>往下搜索模式,<code>?<pattern></code>往上搜索模式,<code>n</code>前一个匹配字符,<code>N</code>后一个匹配字符。</p><p><code>less</code>可以用于debug,查看中间输出结果。比如<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">step1 input.txt | step2 | step3 > output.txt</span><br><span class="line"><span class="meta">#</span><span class="bash"> step1,2,3为程序或命令名</span></span><br></pre></td></tr></table></figure></p><p>可以写为<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">step1 input.txt | less</span><br><span class="line">step1 input.txt | step2 | less</span><br><span class="line">step1 input.txt | step2 | step3 | less</span><br></pre></td></tr></table></figure></p><h2 id="纯文本信息汇总"><a href="#纯文本信息汇总" class="headerlink" title="纯文本信息汇总"></a>纯文本信息汇总</h2><p><code>wc</code>命令默认依次输出单词数、行数、总字符数。查看行数使用<code>wc -l</code>。<br>如果存在空行,空行会被计数。可以使用<code>grep</code>命令实现非空行计数<br><code>grep -c "[^ \\n\\t]" some_data.bed</code></p><p><code>ls -lh</code>以易读形式查看文件大小。</p><p>输出文件列数:<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> -F指定分隔符,此处假定是table键分隔,默认空格键</span></span><br><span class="line">awk -F "\t" '{print NF; exit}' some_data.bed</span><br></pre></td></tr></table></figure></p><h3 id="怎么去除注释的元数据行呢?怎么计数非注释行行数呢?"><a href="#怎么去除注释的元数据行呢?怎么计数非注释行行数呢?" class="headerlink" title="怎么去除注释的元数据行呢?怎么计数非注释行行数呢?"></a>怎么去除注释的元数据行呢?怎么计数非注释行行数呢?</h3><p>可以使用<code>tail</code>结合<code>awk</code>,试试gtf(基因组注释文件)<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/Work/research/Promoter_Research$ head -n 6 Homo_sapiens.GRCh37.75.gtf</span><br><span class="line"><span class="meta">#</span><span class="bash">!genome-build GRCh37.p13</span></span><br><span class="line"><span class="meta">#</span><span class="bash">!genome-version GRCh37</span></span><br><span class="line"><span class="meta">#</span><span class="bash">!genome-date 2009-02</span></span><br><span class="line"><span class="meta">#</span><span class="bash">!genome-build-accession NCBI:GCA_000001405.14</span></span><br><span class="line"><span class="meta">#</span><span class="bash">!genebuild-last-updated 2013-09</span></span><br><span class="line">1pseudogenegene1186914412.+.gene_id "ENSG00000223972"; gene_name "DDX11L1"; gene_source "ensembl_havana"; gene_biotype "pseudogene";</span><br></pre></td></tr></table></figure></p><p>可以看到注释行是5行,我们利用<code>tail</code>试试去掉它<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 注意此处 -n后接的<span class="string">"+"</span>号</span></span><br><span class="line">wsx@wsx-ubuntu:~/Work/research/Promoter_Research$ tail -n +5 Homo_sapiens.GRCh37.75.gtf | head -n 1</span><br><span class="line"><span class="meta">#</span><span class="bash">!genebuild-last-updated 2013-09</span></span><br></pre></td></tr></table></figure></p><p>发现还有一行没去掉<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/Work/research/Promoter_Research$ tail -n +6 Homo_sapiens.GRCh37.75.gtf | head -n 1</span><br><span class="line">1pseudogenegene1186914412.+.gene_id "ENSG00000223972"; gene_name "DDX11L1"; gene_source "ensembl_havana"; gene_biotype "pseudogene";</span><br></pre></td></tr></table></figure></p><p>成功搞定,然后结合前面提到的<code>awk</code>命令即可计算行数。</p><p>上面方法鲁棒性不够(人为地确定行数),一种更为通用的方法是<code>grep</code>结合<code>awk</code>命令<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/Work/research/Promoter_Research$ grep -v "^#" Homo_sapiens.GRCh37.75.gtf | head -n 1</span><br><span class="line">1pseudogenegene1186914412.+.gene_id "ENSG00000223972"; gene_name "DDX11L1"; gene_source "ensembl_havana"; gene_biotype "pseudogene";</span><br></pre></td></tr></table></figure></p><p>推荐使用这种。</p><h2 id="Cut"><a href="#Cut" class="headerlink" title="Cut"></a>Cut</h2><p><code>cut</code>可以处理列数据,<code>-f</code>选项指定列,可以是一个范围(比如2-8),注意不能用它给列排序。<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/Work/research/Promoter_Research$ grep -v "^#" Homo_sapiens.GRCh37.75.gtf | head -n 10 | cut -f 3</span><br><span class="line">gene</span><br><span class="line">transcript</span><br><span class="line">exon</span><br><span class="line">exon</span><br><span class="line">exon</span><br><span class="line">transcript</span><br><span class="line">exon</span><br><span class="line">exon</span><br><span class="line">exon</span><br><span class="line">transcript</span><br><span class="line">wsx@wsx-ubuntu:~/Work/research/Promoter_Research$ grep -v "^#" Homo_sapiens.GRCh37.75.gtf | head -n 10 | cut -f 3-5</span><br><span class="line">gene1186914412</span><br><span class="line">transcript1186914409</span><br><span class="line">exon1186912227</span><br><span class="line">exon1261312721</span><br><span class="line">exon1322114409</span><br><span class="line">transcript1187214412</span><br><span class="line">exon1187212227</span><br><span class="line">exon1261312721</span><br><span class="line">exon1322514412</span><br><span class="line">transcript1187414409</span><br></pre></td></tr></table></figure></p><p><code>-d</code>选项可以指定分隔符,比如<code>-d,</code>指定<code>,</code>为分隔符。</p><p>使用<code>column</code>命令来格式化输出,上次的命令结果输出明显没对齐,我们把它对齐看看:<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/Work/research/Promoter_Research$ grep -v "^#" Homo_sapiens.GRCh37.75.gtf | head -n 10 | cut -f 3-5 | column -t</span><br><span class="line">gene 11869 14412</span><br><span class="line">transcript 11869 14409</span><br><span class="line">exon 11869 12227</span><br><span class="line">exon 12613 12721</span><br><span class="line">exon 13221 14409</span><br><span class="line">transcript 11872 14412</span><br><span class="line">exon 11872 12227</span><br><span class="line">exon 12613 12721</span><br><span class="line">exon 13225 14412</span><br><span class="line">transcript 11874 14409</span><br></pre></td></tr></table></figure></p><p>注意,使用这个命令是为了好观察,不要把用它处理然后把结果传入文本(会导致程序处理文件效率降低,因为文本解析速度会下降)。</p><p><code>cut</code>和<code>column</code>默认以<code>\t</code>为分隔符,这里也能够用<code>-s</code>选项指定。</p><p>先把之前的tab分隔文件弄成逗号分隔文件,然后使用<code>-s</code>选项看看:<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/Work/research/Promoter_Research$ grep -v "^#" Homo_sapiens.GRCh37.75.gtf | head -n 10 | cut -f 3-5 | awk '{FS="\t";OFS=",";}{print $1,$2,$3}'</span><br><span class="line">gene,11869,14412</span><br><span class="line">transcript,11869,14409</span><br><span class="line">exon,11869,12227</span><br><span class="line">exon,12613,12721</span><br><span class="line">exon,13221,14409</span><br><span class="line">transcript,11872,14412</span><br><span class="line">exon,11872,12227</span><br><span class="line">exon,12613,12721</span><br><span class="line">exon,13225,14412</span><br><span class="line">transcript,11874,14409</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/Work/research/Promoter_Research$ grep -v "^#" Homo_sapiens.GRCh37.75.gtf | head -n 10 | cut -f 3-5 | awk '{FS="\t";OFS=",";}{print $1,$2,$3}'| column -s "," -t</span><br><span class="line">gene 11869 14412</span><br><span class="line">transcript 11869 14409</span><br><span class="line">exon 11869 12227</span><br><span class="line">exon 12613 12721</span><br><span class="line">exon 13221 14409</span><br><span class="line">transcript 11872 14412</span><br><span class="line">exon 11872 12227</span><br><span class="line">exon 12613 12721</span><br><span class="line">exon 13225 14412</span><br><span class="line">transcript 11874 14409</span><br></pre></td></tr></table></figure></p><h2 id="grep"><a href="#grep" class="headerlink" title="grep"></a>grep</h2><p><code>grep</code>处理速度非常之快,能用它尽量用它。<code>--color=auto</code>可以激活颜色(标记匹配文字),更方便查看。</p><p><code>-v</code>选项排除匹配到的,<code>-w</code>进行完全匹配。这样可以防止,你想排除<code>abc</code>结果把<code>abc1</code>,<code>abcd</code>也排除掉了。</p><p><code>-B</code>指定输出包括匹配到的前多少行,比如<code>-B1</code>就是前一行;<code>-A</code>指定输出包括匹配到的后多少行,比如<code>-A2</code>就是包括了后两行。<code>-C</code>指定输出包括匹配到的前后多少行。<br><code>grep</code>支持基本正则表达式,<code>-E</code>指定支持扩展表达式,或者用<code>egrep</code>命令。<br><code>-c</code>选项对匹配的行计数;<code>-o</code>选项只抽离输出匹配的部分<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/Work/research/Promoter_Research$ grep -E -o 'gene_id "\w+"' Homo_sapiens.GRCh37.75.gtf | head -n 5</span><br><span class="line">gene_id "ENSG00000223972"</span><br><span class="line">gene_id "ENSG00000223972"</span><br><span class="line">gene_id "ENSG00000223972"</span><br><span class="line">gene_id "ENSG00000223972"</span><br><span class="line">gene_id "ENSG00000223972"</span><br></pre></td></tr></table></figure></p><p>发现冗余项非常多,如果我们只要唯一的呢,怎么办?<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/Work/research/Promoter_Research$ grep -E -o 'gene_id "(\w+)"' Homo_sapiens.GRCh37.75.gtf | cut -f2 -d" "| sed 's/"//g' | sort | uniq | head -n 10</span><br><span class="line">ENSG00000000003</span><br><span class="line">ENSG00000000005</span><br><span class="line">ENSG00000000419</span><br><span class="line">ENSG00000000457</span><br><span class="line">ENSG00000000460</span><br><span class="line">ENSG00000000938</span><br><span class="line">ENSG00000000971</span><br><span class="line">ENSG00000001036</span><br><span class="line">ENSG00000001084</span><br><span class="line">ENSG00000001167</span><br></pre></td></tr></table></figure></p><p>虽然我的笔记本呼啦啦作响,但是还是非常快就跑完了。</p><h2 id="file查看文件编码"><a href="#file查看文件编码" class="headerlink" title="file查看文件编码"></a>file查看文件编码</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ file regular_express.txt</span><br><span class="line">regular_express.txt: ASCII text, with CRLF, LF line terminators</span><br></pre></td></tr></table></figure><p>常用的大型数据文件一般存为ASCII码形式(像几大基因bank的数据文件),而我们自己认为创建的常为UTF-8,所以有时候认为处理文件需要会碰到把UTF-8编码的字符插入到ASCII码文件里去了。遇到这种问题,我们可以用<code>hexdump -c</code>命令查看出错的地方(手边没有这样的文件,就不举例了)。</p><h2 id="用sort对文本排序"><a href="#用sort对文本排序" class="headerlink" title="用sort对文本排序"></a>用sort对文本排序</h2><p>我们先创建一个bed格式文件来试试这个命令:<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ cat test.bed</span><br><span class="line">chr12639</span><br><span class="line">chr33247</span><br><span class="line">chr14050</span><br><span class="line">chr1928</span><br><span class="line">chr23554</span><br><span class="line">chr11019</span><br><span class="line">wsx@wsx-ubuntu:~$ sort test.bed</span><br><span class="line">chr11019</span><br><span class="line">chr12639</span><br><span class="line">chr14050</span><br><span class="line">chr1928</span><br><span class="line">chr23554</span><br><span class="line">chr33247</span><br></pre></td></tr></table></figure></p><p>可以明显看到文本按照第一列进行了排序。<br>默认,<code>sort</code>用空格或tab键作为域(列)分隔符。如果我们用其他形式的分隔符,需要用<code>-t</code>选项指定。</p><p>下面是对<code>bed</code>文件最通用的排序命令:<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ sort -k1,1 -k2,2n test.bed</span><br><span class="line">chr1928</span><br><span class="line">chr11019</span><br><span class="line">chr12639</span><br><span class="line">chr14050</span><br><span class="line">chr23554</span><br><span class="line">chr33247</span><br></pre></td></tr></table></figure></p><p>基本操作<code>bedtools</code>软件都会先用这个命令对<code>bedtools</code>文件排序。<br>现在略加解释一下,<code>sort</code>用<code>-k</code>选项指定某列的排序方式。而每次使用<code>-k</code>选项都要带上指定列的范围(start, end)。如果只指定一列,就为(start,start)了,像上面命令的<code>-k1,1</code>就是。也许你会觉得<code>-k2,2n</code>很奇怪,这里的<code>n</code>指定程序把第二列当做数值对待。如果不做设定,都是当做字符对待(shell都是这么对待数值数据的)。所以总结其他这一行命令就是对第一列按照字符排序,第二列按照数值排序。</p><p>我们可以用<code>-c</code>选项检查一个文件是不是已经按照过某种方式排过序了。<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ sort -k1,1 -k2,2n test.bed | sort -k1,1 -k2,2 -c</span><br><span class="line">sort:-:2:无序: chr11019</span><br><span class="line">wsx@wsx-ubuntu:~$ echo $?</span><br><span class="line">1</span><br><span class="line">wsx@wsx-ubuntu:~$ sort -k1,1 -k2,2n test.bed | sort -k1,1 -k2,2n -c</span><br><span class="line">wsx@wsx-ubuntu:~$ echo $?</span><br><span class="line">0</span><br></pre></td></tr></table></figure></p><p>上面可以清楚地看到<code>sort</code>是怎么对待文件的(一般shell返回0表示成功执行)。<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ tsfds</span><br><span class="line">tsfds:未找到命令</span><br><span class="line">wsx@wsx-ubuntu:~$ echo $?</span><br><span class="line">127</span><br><span class="line">wsx@wsx-ubuntu:~$ echo test</span><br><span class="line">test</span><br><span class="line">wsx@wsx-ubuntu:~$ echo $?</span><br><span class="line">0</span><br></pre></td></tr></table></figure></p><p>shell的命令退出状态码表示了该命令执行的完成的某种情况。不同的状态码有不同的含义,具体可以百度查阅(我之前整理的shell笔记应该讲过,可以看看)。</p><p>反向排序用<code>-r</code>选项。如果你只想反转一列,可以把它加在<code>-k</code>选项后。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ sort -k1,1 -k2,2nr test.bed</span><br><span class="line">chr14050</span><br><span class="line">chr12639</span><br><span class="line">chr11019</span><br><span class="line">chr1928</span><br><span class="line">chr23554</span><br><span class="line">chr33247</span><br></pre></td></tr></table></figure><p>现在我给<code>test.bed</code>加一行:<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ cat test.bed</span><br><span class="line">chr12639</span><br><span class="line">chr33247</span><br><span class="line">chr14050</span><br><span class="line">chr1928</span><br><span class="line">chr23554</span><br><span class="line">chr11019</span><br><span class="line">chr112256</span><br></pre></td></tr></table></figure></p><p>你会发现有点奇怪<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ sort -k1,1 -k2,2n test.bed</span><br><span class="line">chr1928</span><br><span class="line">chr11019</span><br><span class="line">chr12639</span><br><span class="line">chr14050</span><br><span class="line">chr112256</span><br><span class="line">chr23554</span><br><span class="line">chr33247</span><br></pre></td></tr></table></figure></p><p>怎么<code>chr11</code>在<code>chr2</code>前面?其实<code>sort</code>排序的方式有点像查字典。例子中,命令先比较<code>c</code>,然后比较<code>h</code>,然后比较<code>r</code>,接着比较<code>1</code>,自然<code>11</code>会在<code>2</code>前面了。这里可以添加<code>V</code>选项修改。<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ sort -k1,1V -k2,2n test.bed</span><br><span class="line">chr1928</span><br><span class="line">chr11019</span><br><span class="line">chr12639</span><br><span class="line">chr14050</span><br><span class="line">chr23554</span><br><span class="line">chr33247</span><br><span class="line">chr112256</span><br></pre></td></tr></table></figure></p><p>是不是觉得这样更可观一些?不过通常在处理数据时不做此处理,符合 规范的数据可以让后续处理程序效率更高。</p><p>基本掌握<code>sort</code>这些也够用了,它主要为后续处理服务。如果想知道其他的用法,查查吧,同时欢迎发文来交流。</p><h2 id="用uniq寻找唯一值"><a href="#用uniq寻找唯一值" class="headerlink" title="用uniq寻找唯一值"></a>用uniq寻找唯一值</h2><p>首先创建样例文本<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ cat test.letter</span><br><span class="line">A</span><br><span class="line">A</span><br><span class="line">B</span><br><span class="line">C</span><br><span class="line">B</span><br><span class="line">C</span><br><span class="line">C</span><br><span class="line">C</span><br><span class="line">D</span><br><span class="line">F</span><br><span class="line">D</span><br></pre></td></tr></table></figure></p><p>使用<code>uniq</code>看看<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ uniq test.letter</span><br><span class="line">A</span><br><span class="line">B</span><br><span class="line">C</span><br><span class="line">B</span><br><span class="line">C</span><br><span class="line">D</span><br><span class="line">F</span><br><span class="line">D</span><br></pre></td></tr></table></figure></p><p>尼玛,怎么不对。它好像只去掉了连续的同一字符。怎么办?想想我们刚学了什么命令?<code>sort</code>不是刚好可以把同样的字符弄到一起去吗,然后再使用<code>uniq</code>,嘿嘿:<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ sort test.letter | uniq</span><br><span class="line">A</span><br><span class="line">B</span><br><span class="line">C</span><br><span class="line">D</span><br><span class="line">F</span><br></pre></td></tr></table></figure></p><p>哟呵,got you。</p><p>加<code>-c</code>选项计数:<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ sort test.letter | uniq -c</span><br><span class="line"> 2 A</span><br><span class="line"> 2 B</span><br><span class="line"> 4 C</span><br><span class="line"> 2 D</span><br><span class="line"> 1 F</span><br></pre></td></tr></table></figure></p><p>把结果再排序<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ sort test.letter | uniq -c | sort -rn</span><br><span class="line"> 4 C</span><br><span class="line"> 2 D</span><br><span class="line"> 2 B</span><br><span class="line"> 2 A</span><br><span class="line"> 1 F</span><br></pre></td></tr></table></figure></p><p><code>-d</code>选项只输出重复行<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ cat test.letter</span><br><span class="line">A</span><br><span class="line">A</span><br><span class="line">B</span><br><span class="line">C</span><br><span class="line">B</span><br><span class="line">C</span><br><span class="line">C</span><br><span class="line">C</span><br><span class="line">D</span><br><span class="line">F</span><br><span class="line">D</span><br><span class="line">wsx@wsx-ubuntu:~$ uniq -d test.letter</span><br><span class="line">A</span><br><span class="line">C</span><br><span class="line">wsx@wsx-ubuntu:~$ sort test.letter | uniq -d</span><br><span class="line">A</span><br><span class="line">B</span><br><span class="line">C</span><br><span class="line">D</span><br></pre></td></tr></table></figure></p><p>使用时需要注意处理不同导致的结果差异。</p><h2 id="Join-命令"><a href="#Join-命令" class="headerlink" title="Join 命令"></a>Join 命令</h2><p>用来连接文件。<br>假设现在我们有两个文件:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:/tmp$ cat example.bed</span><br><span class="line">chr12639</span><br><span class="line">chr13247</span><br><span class="line">chr31128</span><br><span class="line">chr14049</span><br><span class="line">chr31627</span><br><span class="line">chr1928</span><br><span class="line">chr23553</span><br><span class="line">wsx@wsx-ubuntu:/tmp$ cat example_length.txt</span><br><span class="line">chr153453</span><br><span class="line">chr234356</span><br><span class="line">chr324356</span><br></pre></td></tr></table></figure><p>我想把第二个文件说明染色体长度添加到第一个文件对应染色体的第三列。<br>我们首先要给文件排序(使用<code>join</code>前必须做),然后使用<code>join</code>命令。<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:/tmp$ sort -k1,1 example.bed > example_sorted.bed</span><br><span class="line">wsx@wsx-ubuntu:/tmp$ sort -c -k1,1 example_length.txt</span><br><span class="line">wsx@wsx-ubuntu:/tmp$ cat example_sorted.bed</span><br><span class="line">chr12639</span><br><span class="line">chr13247</span><br><span class="line">chr14049</span><br><span class="line">chr1928</span><br><span class="line">chr23553</span><br><span class="line">chr31128</span><br><span class="line">chr31627</span><br><span class="line">wsx@wsx-ubuntu:/tmp$ join -1 1 -2 1 example_sorted.bed example_length.txt > example_with_length.txt</span><br><span class="line">wsx@wsx-ubuntu:/tmp$ cat example_with_length.txt</span><br><span class="line">chr1 26 39 53453</span><br><span class="line">chr1 32 47 53453</span><br><span class="line">chr1 40 49 53453</span><br><span class="line">chr1 9 28 53453</span><br><span class="line">chr2 35 53 34356</span><br><span class="line">chr3 11 28 24356</span><br><span class="line">chr3 16 27 24356</span><br></pre></td></tr></table></figure></p><p>命令基本语法是<br><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">join</span> -<span class="number">1</span> <span class="symbol"><file_1_field></span> -<span class="number">2</span> <span class="symbol"><file_2_field></span> <span class="symbol"><file_1></span> <span class="symbol"><file_2></span></span><br></pre></td></tr></table></figure></p><p>既然名字叫<code>join</code>,就是两者必须有共同之处,通过共同的支点将两者连为一体。<br><code>-1</code>和<code>-2</code>选项后接参数分别指定了这个支点,也就是连接的域(列)。比如例子中,都是两个文件的第一列。</p><p>两个文件中,第一列都共有<code>chr1(2)(3)</code>。 如果不一致会出现什么情况呢?</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:/tmp$ join -1 1 -2 1 example_sorted.bed example_length_alt.txt chr1 26 39 53453</span><br><span class="line">chr1 32 47 53453</span><br><span class="line">chr1 40 49 53453</span><br><span class="line">chr1 9 28 53453</span><br><span class="line">chr2 35 53 34356</span><br></pre></td></tr></table></figure><p>如果第二个文件没有<code>chr3</code>,<code>join</code>之后也没了!!</p><p>我们可以通过<code>-a</code>选项指定哪一个文件可以不遵循配对<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:/tmp$ join -1 1 -2 1 -a 1 example_sorted.bed example_length_alt.txt</span><br><span class="line">chr1 26 39 53453</span><br><span class="line">chr1 32 47 53453</span><br><span class="line">chr1 40 49 53453</span><br><span class="line">chr1 9 28 53453</span><br><span class="line">chr2 35 53 34356</span><br><span class="line">chr3 11 28</span><br><span class="line">chr3 16 27</span><br></pre></td></tr></table></figure></p><h2 id="awk"><a href="#awk" class="headerlink" title="awk"></a>awk</h2><p><code>awk</code>是文本处理的一把好手,虽然它不能像<code>python</code>,<code>R</code>干一些高级复杂的主题工作,但是它具备完整的命令操作和编程体系。</p><p><code>awk</code>是一门语言,我不可能在学习的时候能够逻辑清晰详细地介绍给大家。主要是还通过实例来了解用法,加深认识。手册可以参考<a href="http://man.linuxde.net/awk。" target="_blank" rel="noopener">http://man.linuxde.net/awk。</a></p><p>首先要明白的是,<code>awk</code>按行处理数据。在shell知识里,如果把一个文档看做一张表。那么一行就是一个<strong>记录</strong>,一列就是一个<strong>域</strong>。可以看出,<code>awk</code>就是按记录处理文本的。</p><p>其次是<code>awk</code>的程序结构是<br><figure class="highlight coq"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">pattern</span> {action}</span><br></pre></td></tr></table></figure></p><p>pattern可以是表达式或者正则表达式。pattern有点像<code>if</code>语句,当它满足时就会执行相应的动作。</p><p>另一个<code>awk</code>核心是它用<code>$0</code>表示所有列,<code>$1</code>,<code>$2</code>…等等表示对应的列。我们可以很方便地用它进行操作。<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:/tmp$ awk '{print $0}' example.bed</span><br><span class="line">chr12639</span><br><span class="line">chr13247</span><br><span class="line">chr31128</span><br><span class="line">chr14049</span><br><span class="line">chr31627</span><br><span class="line">chr1928</span><br><span class="line">chr23553</span><br><span class="line">wsx@wsx-ubuntu:/tmp$ awk '{print $1}' example.bed</span><br><span class="line">chr1</span><br><span class="line">chr1</span><br><span class="line">chr3</span><br><span class="line">chr1</span><br><span class="line">chr3</span><br><span class="line">chr1</span><br><span class="line">chr2</span><br><span class="line">wsx@wsx-ubuntu:/tmp$ awk '{print $2}' example.bed</span><br><span class="line">26</span><br><span class="line">32</span><br><span class="line">11</span><br><span class="line">40</span><br><span class="line">16</span><br><span class="line">9</span><br></pre></td></tr></table></figure></p><p><code>print</code>语句就像动作一样输出你操作的结果。<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:/tmp$ awk '{ print $2 "\t" $3}' example.bed</span><br><span class="line">2639</span><br><span class="line">3247</span><br><span class="line">1128</span><br><span class="line">4049</span><br><span class="line">1627</span><br><span class="line">928</span><br><span class="line">3553</span><br><span class="line">wsx@wsx-ubuntu:/tmp$ awk '{ print $2 $3}' example.bed</span><br><span class="line">2639</span><br><span class="line">3247</span><br><span class="line">1128</span><br><span class="line">4049</span><br><span class="line">1627</span><br><span class="line">928</span><br><span class="line">3553</span><br><span class="line">wsx@wsx-ubuntu:/tmp$ awk '{ print $2 , $3}' example.bed</span><br><span class="line">26 39</span><br><span class="line">32 47</span><br><span class="line">11 28</span><br><span class="line">40 49</span><br><span class="line">16 27</span><br><span class="line">9 28</span><br><span class="line">35 53</span><br></pre></td></tr></table></figure></p><p>了解上述几个语句的不同。</p><p>表示染色体名一般用带<code>chr</code>或者不带<code>chr</code>标志两种方式。当我们要用到这两种时,肯定要让它们能够对应起来,也就是转换。<code>awk</code>命令可以非常方便地添加<code>chr</code>标记。</p><p>下面我先把例子文件的<code>chr</code>去掉,然后加上试试。<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:/tmp$ awk '{ print $1}' example.bed</span><br><span class="line">chr1</span><br><span class="line">chr1</span><br><span class="line">chr3</span><br><span class="line">chr1</span><br><span class="line">chr3</span><br><span class="line">chr1</span><br><span class="line">chr2</span><br><span class="line">wsx@wsx-ubuntu:/tmp$ awk '{ print $1}' example.bed | cut -c4</span><br><span class="line">1</span><br><span class="line">1</span><br><span class="line">3</span><br><span class="line">1</span><br><span class="line">3</span><br><span class="line">1</span><br><span class="line">2</span><br><span class="line">wsx@wsx-ubuntu:/tmp$ awk '{ print $1}' example.bed | cut -c4 | awk '{print "chr"$1}'</span><br><span class="line">chr1</span><br><span class="line">chr1</span><br><span class="line">chr3</span><br><span class="line">chr1</span><br><span class="line">chr3</span><br><span class="line">chr1</span><br><span class="line">chr2</span><br></pre></td></tr></table></figure></p><p><code>awk</code>作为一门编程语言,它支持各种操作符(运算,逻辑,判断)喔。<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:/tmp$ awk '$3 - $2 >18' example.bed</span><br><span class="line">chr1928</span><br><span class="line">wsx@wsx-ubuntu:/tmp$ awk '$1 ~/chr1/ && $3 - $2 > 10' example.bed</span><br><span class="line">chr12639</span><br><span class="line">chr13247</span><br><span class="line">chr1928</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 这里 ~ 符号用来匹配正则表达式</span></span><br></pre></td></tr></table></figure></p><p>还有<code>awk</code>存在一些变量,像<code>NR</code>表示行号,<code>OFS</code>表示输出分隔符等。<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:/tmp$ awk 'NR >= 3 && NR <= 5' example.bed</span><br><span class="line">chr31128</span><br><span class="line">chr14049</span><br><span class="line">chr31627</span><br></pre></td></tr></table></figure></p><p>如果我们想把<code>gtf</code>文件转换成为<code>bed</code>格式,可以使用<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/Work/research/Promoter_Research$ head -n1000 Homo_sapiens.GRCh37.75.gtf | awk '!/^#/{ print $1 "\t" $4-1 "\t" $5} ' | head -n 3</span><br><span class="line">11186814412</span><br><span class="line">11186814409</span><br><span class="line">11186812227</span><br></pre></td></tr></table></figure></p><p>因为篇幅有限,我不可能输出所有结果,所以只取部分数据做了运算。</p><h2 id="用Sed进行流编辑"><a href="#用Sed进行流编辑" class="headerlink" title="用Sed进行流编辑"></a>用Sed进行流编辑</h2><p><code>sed</code>命令从文本或者标准输入中每次读入一行数据。</p><p>我们先从简单的实例出发,看下该命令怎么将一列中的<code>chrm12</code>,<code>chrom2</code>等转换成<code>chr12</code>,<code>chr2</code>的格式。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat chrms.txt</span><br><span class="line">chrom1 3214482 3216968</span><br><span class="line">chrom1 3214234 3216968</span><br><span class="line">chrom1 3213425 3210653</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ sed 's/chrom/chr/' chrms.txt</span><br><span class="line">chr1 3214482 3216968</span><br><span class="line">chr1 3214234 3216968</span><br><span class="line">chr1 3213425 3210653</span><br></pre></td></tr></table></figure><p>虽然示例文件处理仅仅只有三行,但我们可以将这种处理方式运用到上G甚至更大的数据文件中,而不用打开整个文件进行处理。并且,可以借助重导向实现对数据处理结果的输出。</p><p><code>sed</code>替换命令采用的格式是</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">s/pattern/replacement/</span><br></pre></td></tr></table></figure><p><code>sed</code>会自动搜索符合<code>pattern</code>的字符串,然后修改为<code>replacement</code>(我们想要修改后的样子)。一般默认<code>sed</code>只替换第一个匹配的<code>pattern</code>,我们可以通过添加全局标识<code>g</code>将其应用到数据的所有行中。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">s/pattern/replacement/g</span><br></pre></td></tr></table></figure><p>如果我们想要忽略匹配的大小写,使用<code>i</code>标识</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">s/pattern/replacement/i</span><br></pre></td></tr></table></figure><p>默认<code>sed</code>命令支持基本的POSIX正则表达式(BRE),可以通过<code>-E</code>选项进行拓展(ERE)。很多的Linux命令都这种方式,像常用的<code>grep</code>命令。</p><p>再看一个实例,如果我们想把<code>chr1:28647389-28659480</code>这样格式的文字转换为三列,可以使用:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ echo "chr1:28647389-28659480" | \</span><br><span class="line"><span class="meta">></span><span class="bash"> sed -E <span class="string">'s/^(chr[^:]+):([0-9]+)-([0-9]+)/\1\t\2\t\3/'</span></span></span><br><span class="line">chr1 28647389 28659480</span><br></pre></td></tr></table></figure><p>我们聚焦在第二个命令<code>sed</code>上。初看杂乱无章,但是从最大的结构看依旧是</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">s/pattern/replacement/</span><br></pre></td></tr></table></figure><p>先看<code>pattern</code>部分,这是由几个简单正则表达式组成的复合体,几个<code>()</code>括起来的字符串可以单独看。第一个匹配<code>chr</code>加上一个非冒号的字符,第二个和第三个都是匹配多个数字。最开始的<code>^</code>表示以<code>chr</code>起始(前面没有字符),各个括号中间的是对应的字符。整体的<code>pattern</code>的目的就是为了找到文本中符合这种模式的字符串,如果只是想把这个模式找出来的话,几个括号可以不用加。显然这几个括号的作用就是将它们划分成多个域,帮助<code>sed</code>进行处理。可以看到<code>replacement</code>部分存在<code>\1</code>,<code>\2</code>,<code>\3</code>,它恰好对应<code>()</code>的顺序。这样我们在中间插入<code>\t</code>制表符,就可以完成我们想要的功能:将原字符串转换为三列。</p><p>我本身对字符串并不是非常熟悉,懂一些元字符,可能讲解的不是很到位。不熟悉正则表达式的朋友,可以学习和参考下<a href="http://www.jianshu.com/p/7c50954651fa" target="_blank" rel="noopener">学习正则表达式</a>,是我从Github上Copy到的非常好的学习资料,有兴趣也可以Fork学习。</p><p>上山的路总是有很多条,我们下面看下其他实现该功能的办法:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ echo "chr1:28647389-28659480" | sed 's/[:-]/\t/g'</span><br><span class="line">chr1 28647389 28659480</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ echo "chr1:28647389-28659480" | sed 's/:/\t/' | sed 's/-/\t/'</span><br><span class="line">chr1 28647389 28659480</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ echo "chr1:28647389-28659480" | tr ':-' '\t'</span><br><span class="line">chr1 28647389 28659480</span><br></pre></td></tr></table></figure><p>这三种方式看起来都非常简单有效。它处理字符串的思路不是从匹配pattern然后替换入手,不对,应该说是不是从匹配所有pattern然后替换入手。处理的关键是只处理字符串中看似无用的连字符<code>:</code>与<code>-</code>,将其替换成制表符从而轻松完成分割。</p><p><code>sed 's/:/\t/' | sed 's/-/\t/'</code>可以通过<code>-e</code>选项写为<code>sed -e 's/:/\t/' -e 's/-/\t/'</code>,效果等价。</p><p>默认<code>sed</code>命令支持基本的POSIX正则表达式(BRE),可以通过<code>-E</code>选项进行拓展(ERE)。很多的Linux命令都这种方式,像常用的<code>grep</code>命令。</p><p>再看一个实例,如果我们想把<code>chr1:28647389-28659480</code>这样格式的文字转换为三列,可以使用:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ echo "chr1:28647389-28659480" | \</span><br><span class="line"><span class="meta">></span><span class="bash"> sed -E <span class="string">'s/^(chr[^:]+):([0-9]+)-([0-9]+)/\1\t\2\t\3/'</span></span></span><br><span class="line">chr1 28647389 28659480</span><br></pre></td></tr></table></figure><p>我们聚焦在第二个命令<code>sed</code>上。初看杂乱无章,但是从最大的结构看依旧是</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">s/pattern/replacement/</span><br></pre></td></tr></table></figure><p>先看<code>pattern</code>部分,这是由几个简单正则表达式组成的复合体,几个<code>()</code>括起来的字符串可以单独看。第一个匹配<code>chr</code>加上一个非冒号的字符,第二个和第三个都是匹配多个数字。最开始的<code>^</code>表示以<code>chr</code>起始(前面没有字符),各个括号中间的是对应的字符。整体的<code>pattern</code>的目的就是为了找到文本中符合这种模式的字符串,如果只是想把这个模式找出来的话,几个括号可以不用加。显然这几个括号的作用就是将它们划分成多个域,帮助<code>sed</code>进行处理。可以看到<code>replacement</code>部分存在<code>\1</code>,<code>\2</code>,<code>\3</code>,它恰好对应<code>()</code>的顺序。这样我们在中间插入<code>\t</code>制表符,就可以完成我们想要的功能:将原字符串转换为三列。</p><p>我本身对字符串并不是非常熟悉,懂一些元字符,可能讲解的不是很到位。不熟悉正则表达式的朋友,可以学习和参考下<a href="http://www.jianshu.com/p/7c50954651fa" target="_blank" rel="noopener">学习正则表达式</a>,是我从Github上Copy到的非常好的学习资料,有兴趣也可以Fork学习。</p><p>上山的路总是有很多条,我们下面看下其他实现该功能的办法:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ echo "chr1:28647389-28659480" | sed 's/[:-]/\t/g'</span><br><span class="line">chr1 28647389 28659480</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ echo "chr1:28647389-28659480" | sed 's/:/\t/' | sed 's/-/\t/'</span><br><span class="line">chr1 28647389 28659480</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ echo "chr1:28647389-28659480" | tr ':-' '\t'</span><br><span class="line">chr1 28647389 28659480</span><br></pre></td></tr></table></figure><p>这三种方式看起来都非常简单有效。它处理字符串的思路不是从匹配pattern然后替换入手,不对,应该说是不是从匹配所有pattern然后替换入手。处理的关键是只处理字符串中看似无用的连字符<code>:</code>与<code>-</code>,将其替换成制表符从而轻松完成分割。</p><p><code>sed 's/:/\t/' | sed 's/-/\t/'</code>可以通过<code>-e</code>选项写为<code>sed -e 's/:/\t/' -e 's/-/\t/'</code>,效果等价。</p><p>默认,<code>sed</code>会输出每一行的结果,用<code>replacement</code>替换<code>pattern</code>,但实际中我们可能会因此得到不想要的结果。比如下面的这个例子。</p><p>如果我们想要抓出<code>gtf</code>文件第九列的转录名,可能会使用以下命令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/database$ zgrep -v "^#" gencode.v27lift37.annotation.gtf.gz | head -n 3 | \</span><br><span class="line"><span class="meta">></span><span class="bash"> sed -E <span class="string">'s/.*transcript_id "([^"]+)".*/\1/'</span></span></span><br><span class="line">chr1 HAVANA gene 11869 14409 . + . gene_id "ENSG00000223972.5_2"; gene_type "transcribed_unprocessed_pseudogene"; gene_name "DDX11L1"; level 2; havana_gene "OTTHUMG00000000961.2_2"; remap_status "full_contig"; remap_num_mappings 1; remap_target_status "overlap";</span><br><span class="line">ENST00000456328.2_1</span><br><span class="line">ENST00000456328.2_1</span><br></pre></td></tr></table></figure><p>我们可以发现一些没有转录名行的结果是输出整行,这可不是我们想要的。一种解决办法是在使用<code>sed</code>之前先抓出有<code>transcript_id</code>的行。其实<code>sed</code>命令本身也可以通过选项和参数设定解决这个问题,这里我们可以用<code>-n</code>选项关闭<code>sed</code>输出所有行,在最末的<code>/</code>后加<code>p</code>只输出匹配项。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/database$ zgrep -v "^#" gencode.v27lift37.annotation.gtf.gz | head -n 3 | sed -E -n 's/.*transc</span><br><span class="line">ript_id "([^"]+)".*/\1/p'</span><br><span class="line">ENST00000456328.2_1</span><br><span class="line">ENST00000456328.2_1</span><br></pre></td></tr></table></figure><p>注意方括号内<code>^</code>是非(取反)的意思。</p><p>解释如下:</p><ol><li>首先,匹配字符串”transcript_id”之前0或多个任意字符(<code>.</code>表示除换行键的任意字符)。</li><li>然后,匹配和捕获一个或多个不是引号的字符,用的是<code>[^"]</code></li></ol><p><code>+</code>号的使用是一种非贪婪的方法。很多新手会用<code>*</code>,这是贪婪操作,往往会得不偿失,需要注意喔。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/database$ sed -E 's/transcript_id "([^"]+)".*/\1/' greedy_example.txt</span><br><span class="line">ENSMUST00000160944</span><br><span class="line">wangsx@SC-201708020022:~/database$ sed -E 's/transcript_id "(.*)".*/\1/' greedy_example.txt</span><br><span class="line">ENSMUST00000160944"; gene_name "Gm16088</span><br></pre></td></tr></table></figure><p>使用<code>*</code>时它会尽量多地去匹配符合要求的模式。</p><p>我们也可以用<code>sed</code>命令来获取特定范围的行,比如说我要取出头10行,可以使用</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sed -n '1,10p' filename</span><br></pre></td></tr></table></figure><p>20到50行</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sed -n '20,50p' filename</span><br></pre></td></tr></table></figure><p>当然<code>sed</code>的功能特性远远不止这些,有待于大家更多地挖掘。不过需要注意的是,尽量让工具干它最擅长的事情。如果是复杂地大规模计算,还是最好写个Python脚本。</p><blockquote><p><strong>KISS原则</strong>:</p><p>Keep Incredible Sed Simple</p></blockquote><h2 id="高级Shell用法"><a href="#高级Shell用法" class="headerlink" title="高级Shell用法"></a>高级Shell用法</h2><h3 id="子shell"><a href="#子shell" class="headerlink" title="子shell"></a>子shell</h3><p>首先需要记住<strong>连续</strong>命令和<strong>管道</strong>命令的区别:前者是简单地一个一个按顺序运行程序(一般用<code>&&</code>或者<code>;</code>);后者前一个程序的输出结果会直接传到下一个命令程序的输入中(这不就是流程化操作么,用<code>|</code>分隔)。</p><p>子shell可以让我们在一个独立的shell进程中执行连续命令。</p><p>首先看个例子</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/database$ echo "this command"; echo "that command" | sed 's/command/step/'</span><br><span class="line">this command</span><br><span class="line">that step</span><br><span class="line">wangsx@SC-201708020022:~/database$ (echo "this command"; echo "that command") | sed 's/command/step/'</span><br><span class="line">this step</span><br><span class="line">that step</span><br></pre></td></tr></table></figure><p>发现仅仅加了个括号,结果就不同了。第二个命令就用了子shell,它把两个<code>echo</code>命令放进单独的空间执行后将结果传给下游。</p><p>子shell在对<code>gtf</code>文件进行操作时有个非常有意思有用的用处。我们如果想对<code>gtf</code>文件排序,但是又想要保留文件头部注释信息,我们就能够用两次<code>grep</code>操作分别抓出注释和非注释信息,然后又把它结合在一起。下面看看效果,用<code>less</code>进行检查:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/database$ (zgrep "^#" gencode.v27lift37.annotation.gtf.gz; \</span><br><span class="line"><span class="meta">></span><span class="bash"> zgrep -v <span class="string">"^#"</span> gencode.v27lift37.annotation.gtf.gz | \</span></span><br><span class="line"><span class="meta">></span><span class="bash"> sort -k1,1 -k4,4n) | less</span></span><br></pre></td></tr></table></figure><p>可以看到,子shell确实能够给我们提供非常有用的操作去组合命令实现想要的功能。</p><h3 id="命令管道及进程替换"><a href="#命令管道及进程替换" class="headerlink" title="命令管道及进程替换"></a>命令管道及进程替换</h3><p>很多生信命令行工具需要提供多个输入和输出参数,这用在管道命令里可能会导致非常低效的情形(管道只接受一个标准输入和输出)。幸好,我们可以使用命令管道来解决此类问题。</p><p><strong>命名管道</strong>,也成为FIFO(先入先出,额,这不是队列么:smile:)。它是一个特殊的排序文件,命名管道有点像文件,它可以永久保留在你的文件系统上(估计本质就是文件吧~)。</p><p>我们用<code>mkfifo</code>来生成它</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ls -l fqin</span><br><span class="line">prw-rw-rw- 1 wangsx wangsx 0 9月 3 20:45 fqin</span><br></pre></td></tr></table></figure><p>可以它看它权限的第一个字符是p,指代是pipe。说明是个特殊文件。</p><p>我们像文件一样对它进行一些操作</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat fqin</span><br><span class="line">hello, named pipes</span><br><span class="line">[1]+ 已完成 echo "hello, named pipes" > fqin</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ rm fqin</span><br></pre></td></tr></table></figure><p>比如当使用一个生信命令行工具</p><figure class="highlight ceylon"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">processing<span class="number">_</span>tool --<span class="keyword">in</span><span class="number">1</span> <span class="keyword">in</span><span class="number">1</span>.fq --<span class="keyword">in</span><span class="number">2</span> <span class="keyword">in</span><span class="number">2</span>.fq --<span class="keyword">out</span><span class="number">1</span> <span class="keyword">out</span><span class="number">1</span>.fq --<span class="keyword">out</span><span class="number">2</span> <span class="keyword">out</span><span class="number">2</span>.fq</span><br></pre></td></tr></table></figure><p><code>in1.fq in2.fq</code>就可以上游输出数据到<code>processing_tool</code>的命名管道;同理<code>out1.fq out2.fq</code>可以是命名管道用来写进输出数据。</p><p>但这样我们每次都得不停地创建和删除这些文件,解决办法是使用匿名管道,也叫进程替换。</p><p>不能光说,看看例子就知道和理解了。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat <(echo "hello, process substitution")</span><br><span class="line">hello, process substitution</span><br></pre></td></tr></table></figure><p><code>echo</code>命令运行后使用了进程替换,产生匿名文件,然后匿名文件被重导向<code>cat</code>命令。</p><p>把它用到工具上,就变成了(假定上游zcat下游执行grep命令)</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">processing_tool --in1 < (zcat file1) --in2 < (zcat file2) --out1 (gzip > outfile1) --out2 (gzip > outfile2)</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>参考学习《Bioinformatics. Data. Skills》,这里简要地整理下Linux用来处理数据文本的工具。具体命令详情请在<a href="http://man.linuxde.net/" target="_blank" rel="noopener">Linux命令大全</a>中搜索或者查阅其他相关资料。</p>
</summary>
<category term="Linux杂烩" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/"/>
<category term="文本处理" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/%E6%96%87%E6%9C%AC%E5%A4%84%E7%90%86/"/>
<category term="Linux shell" scheme="https://shixiangwang.github.io/tags/Linux-shell/"/>
<category term="数据处理" scheme="https://shixiangwang.github.io/tags/%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86/"/>
</entry>
<entry>
<title>Linux脚本编程——呈现数据</title>
<link href="https://shixiangwang.github.io/2017/08/21/shell-show-data/"/>
<id>https://shixiangwang.github.io/2017/08/21/shell-show-data/</id>
<published>2017-08-20T16:00:00.000Z</published>
<updated>2018-01-27T04:08:16.958Z</updated>
<content type="html"><![CDATA[<blockquote><p>本章内容:</p><ul><li>再探重定向</li><li>标准输入和输出</li><li>报告错误</li><li>丢弃数据</li><li>创建日志文件</li></ul></blockquote><a id="more"></a><h2 id="理解输入和输出"><a href="#理解输入和输出" class="headerlink" title="理解输入和输出"></a>理解输入和输出</h2><p>显示输出的方法有:</p><ul><li>在显示器屏幕上输出</li><li>将输出重定向到文件中</li><li>有时将一部分数据显示在显示器上;一部分保存到文件中。</li></ul><p>之前涉及的脚本都是以第一种方式输出。现在我们来具体了解下输入和输出。</p><h3 id="标准文件描述符"><a href="#标准文件描述符" class="headerlink" title="标准文件描述符"></a>标准文件描述符</h3><p><strong>Linux系统将每个对象当作文件处理</strong>。着包括输入和输出进程。而标识文件对象是通过<strong>文件描述符</strong>完成的。文件描述符是一个非负整数,可以唯一标识会话中打开的文件。每个进程一次最多有九个文件描述符,bash shell保留勒前三个(0,1,2),见下表。</p><table><thead><tr><th>文件描述符</th><th>缩写</th><th>描述</th></tr></thead><tbody><tr><td>0</td><td>STDIN</td><td>标准输入</td></tr><tr><td>1</td><td>STDOUT</td><td>标准输出</td></tr><tr><td>2</td><td>STDERR</td><td>标准错误</td></tr></tbody></table><p>shell用他们将shell默认的输入和输出导向到相应的位置。</p><p><strong>STDIN</strong></p><p>在使用输入重定向符号(<)时,Linux会用重定向指定的文件夹来替换标准输入文件描述符。它会读取文件并提取数据,就像它是从键盘上键入的。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat</span><br><span class="line">this is a test</span><br><span class="line">this is a test</span><br><span class="line">this is a second test</span><br><span class="line">this is a second test</span><br></pre></td></tr></table></figure><p>输入<code>cat</code>命令时,它从STDIN接受输入。输入一行,<code>cat</code>命令会显示一行。</p><p>当然也可以通过<code><</code>符号强制<code>cat</code>命令接受来自另一个非STDIN文件的输入。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ cat < testfile</span><br><span class="line">This is the first line.</span><br><span class="line">This is the second line.</span><br><span class="line">This is the third line.</span><br></pre></td></tr></table></figure><p><strong>STDOUT</strong></p><p>标准输出就是终端显示器。我们可以使用<code><</code>输出重定向符号来改变它。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ ls -l > test2</span><br><span class="line">wangsx@SC-201708020022:~$ cat test2</span><br><span class="line">总用量 28</span><br><span class="line">drwxrwxrwx 0 wangsx wangsx 4096 8月 2 11:48 biosoft</span><br><span class="line">-rw-rw-rw- 1 wangsx wangsx 2156 8月 4 00:12 biostar.yml</span><br><span class="line">drwxrwxrwx 0 wangsx wangsx 4096 8月 3 22:24 miniconda2</span><br><span class="line">drwxrwxrwx 0 wangsx wangsx 4096 8月 2 11:50 ncbi</span><br><span class="line">-rw-rw-rw- 1 wangsx wangsx 5230 8月 14 00:14 spf13-vim.sh</span><br><span class="line">drwxrwxrwx 0 wangsx wangsx 4096 8月 13 23:51 src</span><br><span class="line">-rw-rw-rw- 1 wangsx wangsx 0 8月 21 22:43 test2</span><br><span class="line">-rw-rw-rw- 1 wangsx wangsx 73 8月 21 22:39 testfile</span><br><span class="line">drwxrwxrwx 0 wangsx wangsx 4096 8月 19 17:15 tmp</span><br><span class="line">-rw-rw-rw- 1 wangsx wangsx 2156 8月 14 12:20 wsx_biostar.yml</span><br></pre></td></tr></table></figure><p>如果文件存在,<code>></code>符号会将导向的文件全部重写。如果想要以追加的形式,则使用<code>>></code>。</p><p><strong>STDERR</strong></p><p>STDERR文件描述符代表shell的标准错误输出。运行脚本或命令的错误信息都会发送到这个位置。</p><p>默认,STDERR会和STDOUT指向同样的地方(屏幕终端)。使用脚本时,我们常常会希望将错误信息保存到日志文件中。</p><h3 id="重定向错误"><a href="#重定向错误" class="headerlink" title="重定向错误"></a>重定向错误</h3><p>几种实现方法:</p><ol><li><p>只重定向错误</p><p>将文件描述符值(2)放在重定向符号前。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ ls -al badfile 2> test4</span><br><span class="line">wangsx@SC-201708020022:~$ cat test4</span><br><span class="line">ls: 无法访问'badfile': 没有那个文件或目录</span><br></pre></td></tr></table></figure><p>命令生成的任何错误信息都会保存在输出文件中。<strong>这个方法只重定向错误信息。</strong></p></li><li><p>重定向错误和数据</p><p>这必须用两个重定向符号。需要在符号前面放上待重定向数据所对应的文件描述符,然后指向用于保存数据的输出文件。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ ls -al test test2 test3 bad test 2> test6 1> test7</span><br><span class="line">wangsx@SC-201708020022:~$ cat test6</span><br><span class="line">ls: 无法访问'test': 没有那个文件或目录</span><br><span class="line">ls: 无法访问'test3': 没有那个文件或目录</span><br><span class="line">ls: 无法访问'bad': 没有那个文件或目录</span><br><span class="line">ls: 无法访问'test': 没有那个文件或目录</span><br><span class="line">wangsx@SC-201708020022:~$ cat test7</span><br><span class="line">-rw-rw-rw- 1 wangsx wangsx 571 8月 21 22:43 test2</span><br></pre></td></tr></table></figure><p>可以看到正常输出重定向到了test7,错误重定向到了test6。另外,也可以将STDERR和STDOUT的输出重定向到同一个输出文件,bash shell提供了符号<code>&></code>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ ls -al test test2 test3 bad &> test7</span><br><span class="line">wangsx@SC-201708020022:~$ cat test7</span><br><span class="line">ls: 无法访问'test': 没有那个文件或目录</span><br><span class="line">ls: 无法访问'test3': 没有那个文件或目录</span><br><span class="line">ls: 无法访问'bad': 没有那个文件或目录</span><br><span class="line">-rw-rw-rw- 1 wangsx wangsx 571 8月 21 22:43 test2</span><br></pre></td></tr></table></figure><p>使用这个符号的话,bash shell会自动赋予错误消息更高的优先级。这样能够集中浏览错误信息。</p><p></p></li></ol><h2 id="在脚本中重定向输出"><a href="#在脚本中重定向输出" class="headerlink" title="在脚本中重定向输出"></a>在脚本中重定向输出</h2><p>两种方法在脚本中重定向输出:</p><ul><li>临时重定向输出</li><li>永久重定向脚本中的所有命令</li></ul><h3 id="临时重定向"><a href="#临时重定向" class="headerlink" title="临时重定向"></a>临时重定向</h3><p>如果<strong>有意</strong>在脚本中生成错误信息,可以将单独的一行输出重定向到STDERR。</p><p>使用时需要在文件描述符数字前加<code>&</code>:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">echo "This is an error message" >&2</span><br></pre></td></tr></table></figure><p>下面看个例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ cat test8</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> testing STDERR message</span></span><br><span class="line"></span><br><span class="line">echo "This is an error" >&2</span><br><span class="line">echo "This is normal output"</span><br></pre></td></tr></table></figure><p>像平常一样运行的话,看出不会有什么差别。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ sh test8</span><br><span class="line">This is an error</span><br><span class="line">This is normal output</span><br></pre></td></tr></table></figure><p>但是如果重定向STDERR的话,所有导向STDERR的文本都会被重定向</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ sh test8 2> test9</span><br><span class="line">This is normal output</span><br><span class="line">wangsx@SC-201708020022:~$ cat test9</span><br><span class="line">This is an error</span><br></pre></td></tr></table></figure><p><strong>这个方法非常适合在脚本中生成错误信息</strong>。</p><h3 id="永久重定向"><a href="#永久重定向" class="headerlink" title="永久重定向"></a>永久重定向</h3><p>如果脚本中涉及大量重定向,上述的方法就会非常繁琐。我们可以用<code>exec</code>命令告诉shell在脚本执行期间重定向某个特定文件描述符。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ cat test10</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> redirecting all output to a file</span></span><br><span class="line">exec 1>testout</span><br><span class="line"></span><br><span class="line">echo "This is a test of redirecting all output"</span><br><span class="line">echo "from a script to another file"</span><br><span class="line">echo "without having to redirect every individual line"</span><br><span class="line">wangsx@SC-201708020022:~$ sh test10</span><br><span class="line">wangsx@SC-201708020022:~$ cat testout</span><br><span class="line">This is a test of redirecting all output</span><br><span class="line">from a script to another file</span><br><span class="line">without having to redirect every individual line</span><br></pre></td></tr></table></figure><p>再结合STDERR看看</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ cat test11</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> redirecting output to different locations</span></span><br><span class="line"></span><br><span class="line">exec 2>testerror</span><br><span class="line"></span><br><span class="line">echo "This is the start of the script"</span><br><span class="line">echo "now redirecting all output to another location"</span><br><span class="line"></span><br><span class="line">exec 1>testout</span><br><span class="line">echo "This output should go to the testout file"</span><br><span class="line">echo "but this should go to the testerror file" >&2</span><br><span class="line">wangsx@SC-201708020022:~$ sh test11</span><br><span class="line">This is the start of the script</span><br><span class="line">now redirecting all output to another location</span><br><span class="line">wangsx@SC-201708020022:~$ cat testout</span><br><span class="line">This output should go to the testout file</span><br><span class="line">wangsx@SC-201708020022:~$ cat testerror</span><br><span class="line">but this should go to the testerror file</span><br></pre></td></tr></table></figure><p><strong>这个脚本用<code>exec</code>命令将STDERR的输出重定向到文件testerror。接着echo语句向STDOUT显示几行文本。随后使用<code>exec</code>命令将STDOUT重定向到testout文件。</strong>最后,虽然STDOUT被重定向了,但依然可以将echo语句发给STDERR。</p><p>这里存在一个问题,一旦重定向了STDOUT或STDERR,就很难将他们重定向回原来的位置。</p><p>这个问题可以用以下方式解决。</p><p>前面提到shell只用了3个文件描述符,而总共有9个,我们可以利用其他6个来操作。</p><p>这里只需要另外使用一个,就可以重定向文件描述符:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ cat test14</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> storing STDOUT, the coming back to it</span></span><br><span class="line"></span><br><span class="line">exec 3>&1</span><br><span class="line">exec 1>test14out</span><br><span class="line"></span><br><span class="line">echo "This should store in the output file"</span><br><span class="line">echo "along with this line"</span><br><span class="line"></span><br><span class="line">exec 1>&3</span><br><span class="line">echo "Now things should be back to normal"</span><br><span class="line">wangsx@SC-201708020022:~$ sh test14</span><br><span class="line">Now things should be back to normal</span><br><span class="line">wangsx@SC-201708020022:~$ cat test14out</span><br><span class="line">This should store in the output file</span><br><span class="line">along with this line</span><br></pre></td></tr></table></figure><p>这里有意思的是把重定向当程序变量在玩,类似</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">a=b # 把b的内容存到a</span><br><span class="line">b=c # 修改b的内容</span><br><span class="line"><span class="meta">#</span><span class="bash"> 使用完后</span></span><br><span class="line">b=a # 将b原来的内容还原</span><br></pre></td></tr></table></figure><p>输入文件描述符也可以进行类似的操作。</p><h2 id="阻止命令输出"><a href="#阻止命令输出" class="headerlink" title="阻止命令输出"></a>阻止命令输出</h2><p>有时候不想显示脚本的输出就要这么干。</p><p>一种通用的方法是将STDERR重定向到<code>null</code>的特殊文件(里面什么都没有)。shell输出到null文件的任何数据都不会保存,全部被丢掉了。</p><p>null文件在Linux中的标准位置是<code>/dev/null</code>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ ls -al > /dev/null</span><br><span class="line">wangsx@SC-201708020022:~$ cat /dev/null</span><br><span class="line">wangsx@SC-201708020022:~$</span><br></pre></td></tr></table></figure><p>这是避免出现错误消息,也无需保存它们的一个常用方法。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ ls -al badfile test2 2> /dev/null</span><br><span class="line">-rw-rw-rw- 1 wangsx wangsx 571 8月 21 22:43 test2</span><br></pre></td></tr></table></figure><p>由于null文件不含有任何内容,程序员通常用它来快速清除现有文件中的数据,而不用先删除文件再重新创建。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ cat testfile</span><br><span class="line">This is the first line.</span><br><span class="line">This is the second line.</span><br><span class="line">This is the third line.</span><br><span class="line">wangsx@SC-201708020022:~$ cat /dev/null > testfile</span><br><span class="line">wangsx@SC-201708020022:~$ cat testfile</span><br><span class="line">wangsx@SC-201708020022:~$</span><br></pre></td></tr></table></figure><h2 id="创建临时文件"><a href="#创建临时文件" class="headerlink" title="创建临时文件"></a>创建临时文件</h2><p>Linux使用/tmp目录来存放不需要永久保留的文件。大多数Linux发行版配置了在启动时删除/tmp目录的所有文件。</p><h3 id="创建本地临时文件"><a href="#创建本地临时文件" class="headerlink" title="创建本地临时文件"></a>创建本地临时文件</h3><p>默认<code>mktemp</code>会在本地目录创建一个文件。你只需要指定文件名模板,可以是任意文本名,后面加六个<code>X</code>即可。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ mktemp testing.XXXXXX</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ mktemp testing.XXXXXX</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ll</span><br><span class="line">总用量 12</span><br><span class="line">drwxrwxrwx 0 wangsx wangsx 4096 8月 22 21:34 ./</span><br><span class="line">drwxr-xr-x 0 wangsx wangsx 4096 8月 22 21:31 ../</span><br><span class="line">-rw------- 1 wangsx wangsx 0 8月 22 21:33 testing.R6dAku</span><br><span class="line">-rw------- 1 wangsx wangsx 0 8月 22 21:32 testing.V5psXP</span><br></pre></td></tr></table></figure><p><code>mktemp</code>命令会用6个字符码替换这6个<code>X</code>,从而保证文件名在目录中是唯一的。</p><p>在脚本中使用<code>mktemp</code>命令时,可能要将文件名保存到变量中,这样就可以在脚本后面引用了。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test19</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash t nomore<span class="string">" \</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> creating and using a temp file</span></span><br><span class="line">tempfile=$(mktemp test19.XXXXXX)</span><br><span class="line"></span><br><span class="line">exec 3>$tempfile</span><br><span class="line">echo "This script writes to temp file $tempfile</span><br><span class="line">echo "This is the first line." >&3</span><br><span class="line">echo "This is the last line." >&3</span><br><span class="line">exec 3>&-</span><br><span class="line"></span><br><span class="line">echo "Done creating temp file. The contents are:"</span><br><span class="line">cat $tempfile</span><br><span class="line">rm -f $tempfile 2> /dev/null</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ sh test19</span><br><span class="line">This script writes to temp file test19.fVVEwn</span><br><span class="line">Done creating temp file. The contents are:</span><br><span class="line">This is the first line.</span><br><span class="line">This is the last line.</span><br></pre></td></tr></table></figure><p>显示的内容大致如上,我的ubuntu子系统有点怪怪的,不知道为毛。</p><p><code>-t</code>选项回强制<code>mktemp</code>命令在系统的临时目录来创建该文件,它会返回临时文件的全路径,而不是只有文件名。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ mktemp -t test20.XXXX</span><br><span class="line">/tmp/test20.bY3Q</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ mktemp -t test20.XXXXXX</span><br><span class="line">/tmp/test20.WrkAia</span><br></pre></td></tr></table></figure><p><code>-d</code>选项告诉<code>mktemp</code>创建一个临时目录而不是临时文件。</p><h2 id="记录消息"><a href="#记录消息" class="headerlink" title="记录消息"></a>记录消息</h2><p>有时候想将消息同时发送到显示器和日志文件,用<code>tee</code>命令可以搞定。</p><p><code>tee</code>命令的功能就像一个<code>T</code>,它将从STDIN过来的数据同时发往两处。一处是STDOUT,一处是<code>tee</code>命令行所指定的文件名。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ date | tee testfile</span><br><span class="line">2017年 08月 22日 星期二 21:49:07 DST</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ cat testfile</span><br><span class="line">2017年 08月 22日 星期二 21:49:07 DST</span><br></pre></td></tr></table></figure><p>如果要追加文件,请使用<code>-a</code>选项。</p><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p>文件重定向常见于脚本需要读入文件和输出文件时。</p><p>下面是一个具体的实例:shell脚本使用命令行参数指定待读取的.csv文件。.csv格式用于从电子表格中导出数据,所以我们可以把数据库数据放入电子表格,把电子表格保存成.csv格式,读取文件,然后创建INSERT语句将数据插入MySQL数据库。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> <span class="built_in">read</span> file and create INSERT statements <span class="keyword">for</span> MySQL</span></span><br><span class="line"></span><br><span class="line">outfile='members.sql'</span><br><span class="line">IFS=","</span><br><span class="line">while read lname fname address city state zip # read 使用IFS字符解析读入的文本</span><br><span class="line">do</span><br><span class="line"> cat >> $outfile << EOF</span><br><span class="line"> # >> 将cat的输出追加到$outfile指定的文件中</span><br><span class="line"> # cat的输入不再取自于标准输入,而是被重定向到脚本中存储的数据,EOF符号标记了追加到文件中的数据的起止(两个)</span><br><span class="line"> INSERT INTO members (lname,fname,address,city,state,zip) VALUES</span><br><span class="line"> ('$lname', '$fname', '$address', '$city', '$state', '$zip');</span><br><span class="line">EOF</span><br><span class="line"> # 上面是标准的SQL语句</span><br><span class="line">done < $1 # 将命令行第一个参数指明的数据文件导入while</span><br></pre></td></tr></table></figure><p>造一个符合的csv文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat members.csv</span><br><span class="line">Blum,Richard,123 Main St.,Chicago,IL,60601</span><br><span class="line">Blum,Barbara,123 Main St.,Chicago,IL,60601</span><br><span class="line">Bresnahan,Timothy,456, Oak Ave.,Columbus,OH,43201</span><br></pre></td></tr></table></figure><p>运行脚本</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test23 members.csv</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ cat members.sql</span><br><span class="line"> INSERT INTO members (lname,fname,address,city,state,zip) VALUES</span><br><span class="line"> ('Blum', 'Richard', '123 Main St.', 'Chicago', 'IL', '60601');</span><br><span class="line"> INSERT INTO members (lname,fname,address,city,state,zip) VALUES</span><br><span class="line"> ('Blum', 'Barbara', '123 Main St.', 'Chicago', 'IL', '60601');</span><br><span class="line"> INSERT INTO members (lname,fname,address,city,state,zip) VALUES</span><br><span class="line"> ('Bresnahan', 'Timothy', '456', ' Oak Ave.', 'Columbus', 'OH,43201');</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<blockquote>
<p>本章内容:</p>
<ul>
<li>再探重定向</li>
<li>标准输入和输出</li>
<li>报告错误</li>
<li>丢弃数据</li>
<li>创建日志文件</li>
</ul>
</blockquote>
</summary>
<category term="Linux杂烩" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/"/>
<category term="shell编程" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/shell%E7%BC%96%E7%A8%8B/"/>
<category term="bash shell" scheme="https://shixiangwang.github.io/tags/bash-shell/"/>
<category term="shell笔记" scheme="https://shixiangwang.github.io/tags/shell%E7%AC%94%E8%AE%B0/"/>
</entry>
<entry>
<title>Shell脚本之处理用户输入</title>
<link href="https://shixiangwang.github.io/2017/08/19/working-with-user-input/"/>
<id>https://shixiangwang.github.io/2017/08/19/working-with-user-input/</id>
<published>2017-08-18T16:00:00.000Z</published>
<updated>2018-01-27T04:08:11.150Z</updated>
<content type="html"><![CDATA[<p><strong>Shell脚本笔记系列:</strong></p><p><a href="http://www.flypeom.site/linux/2017/08/11/basic-shell/" target="_blank" rel="noopener">构建基本shell脚本</a></p><p><a href="http://www.flypeom.site/linux/2017/08/11/structural-command-of-shell/" target="_blank" rel="noopener">Linux结构化命令</a></p><blockquote><p>内容:</p><ul><li>传递参数</li><li>跟踪参数</li><li>移动变量</li><li>处理选项</li><li>将选项标准化</li><li>获得用户输入</li></ul></blockquote><p>经过前面的介绍,我们已经可以掌握一些流程化的脚本编程了。但有时候,我们需要编写的脚本能够跟使用者进行交互。它可以是静态的,输入相应的参数让它运行到底;也可以是动态的,脚本根据输入参数反馈不同的信息,使用者又能根据信息调整下一步的处理,实时与程序互动。</p><p>bash shell提供了一些不同的方法来从<strong>用户处获得数据,包括命令行参数、命令行选项以及直接从键盘读取输入</strong>的能力。下面将一一介绍实现。</p><a id="more"></a><h2 id="命令行参数"><a href="#命令行参数" class="headerlink" title="命令行参数"></a>命令行参数</h2><p>使用命令行参数是向脚本传递数据的最基本方法,在运行脚本的同时可以在命令行添加数据。</p><p>比如:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./addem 10 30</span><br></pre></td></tr></table></figure><p>运行当前目录下名为<code>addem</code>脚本的同时向内部传递2个参数(10和30)。而脚本会通过特殊的变量来处理命令行参数。</p><p>下面介绍如何使用它们。</p><h3 id="读入参数"><a href="#读入参数" class="headerlink" title="读入参数"></a>读入参数</h3><p>bash shell会将称为<strong>位置参数</strong>的特殊变量分配给输入到命令行的所有参数:<code>$0</code>是程序名,<code>$1</code>是第一个参数,<code>$2</code>是第二个参数,以此类推。书上介绍直到第九个参数<code>$9</code>,但这个应该不是受限的。<a href="http://www.cnblogs.com/ivictor/p/4022382.html" target="_blank" rel="noopener">Shell最多可以输入多少个参数?</a>一文探索了参数的个数限制,有兴趣的朋友不妨看看和试试。</p><p>下面是单个参数的例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test1.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> using one <span class="built_in">command</span> line parameter</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">factorial=1</span><br><span class="line">for (( number = 1; number <= $1; number++ ))</span><br><span class="line">do</span><br><span class="line">truefactorial=$[ $factorial * $number ]</span><br><span class="line">done</span><br><span class="line">echo The factorial of $1 is $factorial</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test1.sh 5</span><br><span class="line">The factorial of 5 is 120</span><br></pre></td></tr></table></figure><p>我们可以像使用其他变量一样使用<code>$1</code>变量。shell脚本会自动分配,不需要我们做任何处理。可以看得出来这样非常方便,不过如果输入参数过多,很容易让人混淆。</p><p>如果需要输入更多的参数,只需要用空格分隔即可。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test2.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> using two <span class="built_in">command</span> line parameters</span></span><br><span class="line"></span><br><span class="line">total=$[ $1 * $2 ]</span><br><span class="line">echo The first parameter is $1.</span><br><span class="line">echo The second parameter is $2.</span><br><span class="line">echo The total value is $total.</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test2.sh 2 5</span><br><span class="line">The first parameter is 2.</span><br><span class="line">The second parameter is 5.</span><br><span class="line">The total value is 10.</span><br></pre></td></tr></table></figure><p>字符参数也是一样的。如果我没记错,在脚本中的数字默认都是做字符处理的,进行数学运算时会自动调整。不过当我们想要输入的一个参数是带空格的字符串时,需要在两边加上引号以保证shell能够正确识别。不然会被当做多个参数处理的。</p><p>下面取个例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test3.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line">echo Hello $1, glad to meet you.</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test3.sh shixiang wang</span><br><span class="line">Hello shixiang, glad to meet you.</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test3.sh 'shixiang wang'</span><br><span class="line">Hello shixiang wang, glad to meet you.</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test3.sh "shixiang wang"</span><br><span class="line">Hello shixiang wang, glad to meet you.</span><br></pre></td></tr></table></figure><p>看来使用时要注意正确使用引号喔。</p><blockquote><p>说明:在文本字符串作为参数传递时,引号并非数据的一部分。它们只是表明数据的起止位置。</p></blockquote><p>如果需要输入的命令行参数不止9个,写脚本需要修改一下变量名(第9个之后)。比如<code>${10}</code>表示输入的第十个变量(原来如此啊)。</p><p>这样我们就可以向脚本添加任意多的参数啦~</p><p>前面也说了,<code>$0</code>参数可以获取脚本名,这样在编程的时候很方便。但是这里存在一个潜在的问题:如果使用另一个命令来运行shell脚本,命令会和脚本名混在一起,出现在<code>$0</code>中。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test4.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Testing the <span class="variable">$0</span> parameter</span></span><br><span class="line"></span><br><span class="line">echo The zero parameter is set to: $0</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test4.sh</span><br><span class="line">The zero parameter is set to: ./test4.sh</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ bash test4.sh</span><br><span class="line">The zero parameter is set to: test4.sh</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ bash ~/script_learn/test4.sh</span><br><span class="line">The zero parameter is set to: /home/wsx/script_learn/test4.sh</span><br></pre></td></tr></table></figure><p>而且,你发现没有,当指明脚本路径时,这个路径也会带入其中。</p><p>如果我们要编写一个根据脚本名来执行不同功能的脚本,就得把脚本的运行路径剥离掉。还要删除与脚本名混在一起的命令。幸好有一个方便的小命令<code>basename</code>可以帮助我们返回不包含路径的脚本名。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test5.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Using basename with the <span class="variable">$0</span> parameter</span></span><br><span class="line"></span><br><span class="line">name=$(basename $0)</span><br><span class="line">echo</span><br><span class="line">echo The script name is: $name</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ bash ~/script_learn/test5.sh</span><br><span class="line"></span><br><span class="line">The script name is: test5.sh</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test5.sh</span><br><span class="line"></span><br><span class="line">The script name is: test5.sh</span><br></pre></td></tr></table></figure><p>下面编写基于脚本名执行不同功能的脚本。挺有意思的,我们看看吧:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test6.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Testing a Multi-function script</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">name=$(basename $0)</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">if [ $name = "addem" ]</span><br><span class="line">then</span><br><span class="line">truetotal=$[ $1 + $2 ]</span><br><span class="line">elif [ $name = "multem" ]</span><br><span class="line">then</span><br><span class="line">truetotal=$[ $1 * $2 ]</span><br><span class="line">fi</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo</span><br><span class="line">echo The calculated value is $total.</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ cp test6.sh addem</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x addem</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ln -s test6.sh multem</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ll *em</span><br><span class="line">-rwxrw-r-- 1 wsx wsx 216 8月 18 16:47 addem*</span><br><span class="line">lrwxrwxrwx 1 wsx wsx 8 8月 18 16:47 multem -> test6.sh*</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./addem 2 5</span><br><span class="line"></span><br><span class="line">The calculated value is 7.</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./multem 2 5</span><br><span class="line"></span><br><span class="line">The calculated value is 10.</span><br></pre></td></tr></table></figure><p>这个脚本中创建了两个不同的文件名:一个通过复制创建;另一个通过链接。在两种情况下都会先获得脚本的基本名称,然后根据该值执行相应的功能。</p><h3 id="测试参数"><a href="#测试参数" class="headerlink" title="测试参数"></a>测试参数</h3><p>如果在脚本中使用了参数,但是运行脚本时没用参数,这会导致错误发生,需要小心。因此我们在使用参数前应该测试。</p><p>好比下面这个例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test7.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> testing parameters before use</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">if [ -n "$1" ] # -n 选项检查字符是否非空</span><br><span class="line">then</span><br><span class="line">trueecho Hello $1, glad to meet you.</span><br><span class="line">else</span><br><span class="line">trueecho "Sorry, you did not identify yourself."</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test7.sh shixiang</span><br><span class="line">Hello shixiang, glad to meet you.</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test7.sh</span><br><span class="line">Sorry, you did not identify yourself.</span><br></pre></td></tr></table></figure><h2 id="特殊参数变量"><a href="#特殊参数变量" class="headerlink" title="特殊参数变量"></a>特殊参数变量</h2><p>bash shell中有些特殊变量,它们会记录命令行参数。</p><h3 id="参数统计"><a href="#参数统计" class="headerlink" title="参数统计"></a>参数统计</h3><p>bash shell提供了一个特殊的变量<code>$#</code>来统计命令行中输入了多少个参数。我们可以像使用普通变量一样使用它。</p><p>现在能在使用参数前测试参数的总数了。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test9.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Testing parameters</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">if [ $# -ne 2 ]</span><br><span class="line">then</span><br><span class="line"> echo</span><br><span class="line"> echo Usage: test9.sh a b</span><br><span class="line"> echo</span><br><span class="line">else</span><br><span class="line"> total=$[ $1 + $2 ]</span><br><span class="line"> echo</span><br><span class="line"> echo The total is $total</span><br><span class="line"> echo</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ bash test9.sh</span><br><span class="line"></span><br><span class="line">Usage: test9.sh a b</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ bash test9.sh 10</span><br><span class="line"></span><br><span class="line">Usage: test9.sh a b</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ bash test9.sh 2 5</span><br><span class="line"></span><br><span class="line">The total is 7</span><br></pre></td></tr></table></figure><p>例子中,<code>if-then</code>语句用<code>-ne</code>测试命令行参数数量。如果参数数量不对,会显示一条错误信息告诉脚本的正确用法。</p><p>这个变量还提供了一个简便方法来获取命令行最后一个参数,完全不需要知道实际上有多少个参数。不过要实现它需要花些功夫。</p><p>也许,我们会有这样的想法,既然<code>$#</code>变量含有参数的总数,那么变量<code>${$#}</code>就代表了最后一个命令行参数变量。试试看呗:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test10.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> testing grabbing last parameter</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo The last parameter was ${$#}</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test10.sh 10</span><br><span class="line">The last parameter was 65</span><br></pre></td></tr></table></figure><p>我擦,明显不对。这说明<code>${$#}</code>的用法错误。实际上,花括号能不能使用美元符,必须把它换成感叹号。虽然有点奇怪,但的确有用。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test10.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> testing grabbing last parameter</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo The last parameter was ${!#}</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test10.sh 10</span><br><span class="line">The last parameter was 10</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test10.sh 1 2 3 4 5</span><br><span class="line">The last parameter was 5</span><br></pre></td></tr></table></figure><h3 id="抓取所有的数据"><a href="#抓取所有的数据" class="headerlink" title="抓取所有的数据"></a>抓取所有的数据</h3><p><code>$*</code>与<code>$@</code>变量可以用来轻松访问所有的参数。它们都能够在单个变量中存储所有的命令行参数。</p><p><code>$*</code>变量会将命令行提供的所有参数当作一个单词保存。这个单词包含了命令行中出现的每一个参数值。基本上<code>$*</code>变量会将这些参数视为一个整体,而不是多个个体。</p><p><code>$@</code>则将所有参数当作同一字符串中的多个独立的单词。这样能够遍历所有的参数值,得到每个参数。这通常交给<code>for</code>命令完成。</p><p>弄个例子吧,理解两者的区别。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test11.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> testing $* and <span class="variable">$@</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo</span><br><span class="line">echo "Using then \$* method: $*"</span><br><span class="line">echo</span><br><span class="line">echo "Using the \$@ method: $@"</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test11.sh rich barbara katie jessica</span><br><span class="line"></span><br><span class="line">Using then $* method: rich barbara katie jessica</span><br><span class="line"></span><br><span class="line">Using the $@ method: rich barbara katie jessica</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 下面给出两者差异</span></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ cat test12.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line">echo</span><br><span class="line">count=1</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">for param in "$*"</span><br><span class="line">do</span><br><span class="line"> echo "\$* Parameter #$count = $param"</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo</span><br><span class="line">count=1</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line"></span><br><span class="line">for param in "$@"</span><br><span class="line">do</span><br><span class="line"> echo "\$@ Parameter #$count = $param"</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test12.sh rich barbara katie jessica</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash">* Parameter <span class="comment">#1 = rich barbara katie jessica</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash">@ Parameter <span class="comment">#1 = rich</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash">@ Parameter <span class="comment">#2 = barbara</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash">@ Parameter <span class="comment">#3 = katie</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash">@ Parameter <span class="comment">#4 = jessica</span></span></span><br></pre></td></tr></table></figure><h2 id="移动变量"><a href="#移动变量" class="headerlink" title="移动变量"></a>移动变量</h2><p>bash shell的<code>shift</code>命令能够用来操作命令行参数。默认情况下它会将每个参数变量向左移动一个位置。So, 变量<code>$3</code>的值会移到<code>$2</code>,<code>$2</code>移到<code>$1</code>,而<code>$1</code>的值会被删除(<code>$0</code>的值,也就是脚本名,是不会变的)。</p><p>这是遍历命令行参数的另一个好方法,不需要知道参数的个数,我们只需要操作第一个参数,然后移动参数,继续操作第一个参数。</p><p>下面解释它怎么工作的:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test13.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> demonstrating the <span class="built_in">shift</span> <span class="built_in">command</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo</span><br><span class="line">count=1</span><br><span class="line">while [ -n "$1" ]</span><br><span class="line">do</span><br><span class="line"> echo "Parameter #$count = $1"</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line"> shift</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test13.sh rich barbara katie jessica</span><br><span class="line"></span><br><span class="line">Parameter #1 = rich</span><br><span class="line">Parameter #2 = barbara</span><br><span class="line">Parameter #3 = katie</span><br><span class="line">Parameter #4 = jessica</span><br></pre></td></tr></table></figure><blockquote><p>使用shift命令时需要注意,一旦参数被移除,它的值就被丢弃了 ,无法再恢复。</p></blockquote><p><code>shift n</code>中<code>n</code>可以指定移动多个位置。</p><h2 id="处理选项"><a href="#处理选项" class="headerlink" title="处理选项"></a>处理选项</h2><p>熟悉Linux的朋友必然见过不少同时提供参数和选项的bash命令。选项是跟在但破折线后面的单个字母,它能改变命令的行为。下面介绍3中再脚本中处理选项的方法。</p><h3 id="查找选项"><a href="#查找选项" class="headerlink" title="查找选项"></a>查找选项</h3><p>只要我们愿意,我们可以像处理命令行参数一样处理命令行选项。</p><h4 id="处理简单选项"><a href="#处理简单选项" class="headerlink" title="处理简单选项"></a>处理简单选项</h4><p>前面一节最后我们用<code>shift</code>命令来依次处理脚本携带的命令行参数。我们也可以用同样的方法来处理命令行选项。</p><p>在提取每个单独参数时,用<code>case</code>语句来判断某个参数是否为选项。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test15.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> extracting <span class="built_in">command</span> line option as parameter</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo</span><br><span class="line">while [ -n "$1" ]</span><br><span class="line">do</span><br><span class="line"> case "$1" in</span><br><span class="line"> -a) echo "Found the -a option";;</span><br><span class="line"> -b) echo "Found the -b option";;</span><br><span class="line"> -c) echo "Found the -c option";;</span><br><span class="line"> *) echo "$1 is not an option";;</span><br><span class="line"> esac</span><br><span class="line"> shift</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test15.sh -a -b -c -d</span><br><span class="line"></span><br><span class="line">Found the -a option</span><br><span class="line">Found the -b option</span><br><span class="line">Found the -c option</span><br><span class="line">-d is not an option</span><br></pre></td></tr></table></figure><p><code>case</code>语句在命令行参数中找到一个选项,就处理一个选项。如果命令行上还提供了其他参数,你可以在<code>case</code>语句的通用情况处理部分中处理。</p><h4 id="分离参数与选项"><a href="#分离参数与选项" class="headerlink" title="分离参数与选项"></a>分离参数与选项</h4><p>shell脚本通常使用选项和参数,Linux中处理这个问题的标准方法是用<strong>特殊字符</strong>来将二者分开,该字符会告诉脚本何时选项结束以及普通参数何时开始。</p><p>这个所谓的特殊字符就是<code>--</code>双破折号。</p><p>要检查双破折号,在<code>case</code>语句中加一项就行了。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test16.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> extracting options and paramters</span></span><br><span class="line">echo</span><br><span class="line">while [ -n "$1" ]</span><br><span class="line">do</span><br><span class="line"> case "$1" in</span><br><span class="line"> -a) echo "Found the -a option";;</span><br><span class="line"> -b) echo "Found the -b option";;</span><br><span class="line"> -c) echo "Found the -c option";;</span><br><span class="line"> --) shift</span><br><span class="line"> break ;;</span><br><span class="line"> *) echo "$1 is not an option";;</span><br><span class="line"> esac</span><br><span class="line"> shift</span><br><span class="line">done</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">count=1</span><br><span class="line">for param in $@</span><br><span class="line">do</span><br><span class="line"> echo "Parameter #$count: $param"</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br></pre></td></tr></table></figure><p>当遇到双破折号时,先把它移除掉,然后跳出循环,这样shell就会把后面的参数当参数而不是选项处理了。</p><p>先用一组普通的选项和参数来运行测试脚本:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test16.sh -c -a -b test1 test2 test3</span><br><span class="line"></span><br><span class="line">Found the -c option</span><br><span class="line">Found the -a option</span><br><span class="line">Found the -b option</span><br><span class="line">test1 is not an option</span><br><span class="line">test2 is not an option</span><br><span class="line">test3 is not an option</span><br></pre></td></tr></table></figure><p>现在加上双破折号,进行测试:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test16.sh -c -a -b -- test1 test2 test3</span><br><span class="line"></span><br><span class="line">Found the -c option</span><br><span class="line">Found the -a option</span><br><span class="line">Found the -b option</span><br><span class="line">Parameter #1: test1</span><br><span class="line">Parameter #2: test2</span><br><span class="line">Parameter #3: test3</span><br></pre></td></tr></table></figure><p>可以看到,当脚本遇到双破折号时,它会停止处理选项,并将剩下的参数都当做命令处理。</p><p>这样如果顺序填写选项和参数的话,显然没什么问题。但是如果乱序写呢?很显然选项和参数对应不起来。如何解决?</p><h4 id="处理带值的选项"><a href="#处理带值的选项" class="headerlink" title="处理带值的选项"></a>处理带值的选项</h4><p>有些选项会带上一个额外的参数值,类似下面:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./testing.sh -a test1 -b -c -d test2</span><br></pre></td></tr></table></figure><p>下面看看怎么正确处理。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test17.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> extracting <span class="built_in">command</span> line options and values</span></span><br><span class="line">echo</span><br><span class="line">while [ -n "$1" ]</span><br><span class="line">do</span><br><span class="line"> case "$1" in</span><br><span class="line"> -a) echo "Found the -a option";;</span><br><span class="line"> -b) param="$2"</span><br><span class="line"> echo "Found the -b option, with parameter value $param"</span><br><span class="line"> shift;;</span><br><span class="line"> -c) echo "Found the -c option";;</span><br><span class="line"> --) shift</span><br><span class="line"> break;;</span><br><span class="line"> *) echo "$1 is not an option";;</span><br><span class="line"> esac</span><br><span class="line"> shift</span><br><span class="line">done</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">count=1</span><br><span class="line">for param in "$@"</span><br><span class="line">do</span><br><span class="line"> echo "Paramter #$count: $param"</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test17.sh -a -b test1 -d</span><br><span class="line"></span><br><span class="line">Found the -a option</span><br><span class="line">Found the -b option, with parameter value test1</span><br><span class="line">-d is not an option</span><br></pre></td></tr></table></figure><p>本例中,定义了3个要处理的选项,<code>-b</code>还带一个额外参数。因为处理的选项是<code>$1</code>,所以额外参数位于<code>$2</code>,另外因为加了额外参数,所以找到后应该用<code>shift</code>把它移除(这个选项占了两个位置,需要多移动一个)。</p><p>这样,我们可以根据需求进行类似的设定了。不管什么顺序放置选项都可以正常工作。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test17.sh -b test1 -a -d</span><br><span class="line"></span><br><span class="line">Found the -b option, with parameter value test1</span><br><span class="line">Found the -a option</span><br><span class="line">-d is not an option</span><br></pre></td></tr></table></figure><p>不过,这里还有一些限制。如果我们想把多个选项放在一起,这样就行不通啦~</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test17.sh -ac</span><br><span class="line"></span><br><span class="line">-ac is not an option</span><br></pre></td></tr></table></figure><p>而这种功能是Linux常见的喔。那究竟怎么合并选项呢?幸好还有一种处理方法可以帮忙。</p><h3 id="使用getopt命令"><a href="#使用getopt命令" class="headerlink" title="使用getopt命令"></a>使用getopt命令</h3><p><code>getopt</code>命令是一个处理命令行选项和参数时非常方便的工具。它能够识别命令行参数,从而更方便地进行解析。</p><p><strong>命令的格式</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">getopt optstring parameters</span><br></pre></td></tr></table></figure><p><code>optstring</code>是这个过程的关键所在。它定义了命令行有效的选项字母,还定义了哪些字母需要带参数。</p><p><strong>首先,在<code>optstring</code>中列出你要在脚本中用到的每个命令行选项字母。然后,在每个需要参数值的选项字母后加一个冒号。</strong></p><blockquote><p><code>getopt</code>的高级版本叫<code>getoptions</code>。需要注意区分</p></blockquote><p>下面看看<code>getopt</code>如何工作的:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ getopt ab:cd -a -b test1 -cd test2 test3</span><br><span class="line"> -a -b test1 -c -d -- test2 test3</span><br></pre></td></tr></table></figure><p>运行完后看到结果感觉自己晕乎乎的,让我们一起来看看解释:</p><p><code>optstring</code>定义了四个有效选项字母:a,b,c,d。(嗯,对的,这个没问题)。冒号被放在字母b后面,说明b选项需要一个参数。(这样啊)。当<code>getopt</code>命令运行时,它会检查参数列表(就时getopt命令后面跟的),并基于提供的<code>optstring</code>进行解析。值得注意,它会自动把<code>-cd</code>选项分成两个独立的选项,并插入双破折号来分隔行中的额外参数。</p><p>如果指定的选项不在<code>optstring</code>中,会报错。<code>-q</code>选项可以忽略掉它(注意放在<code>optstring</code>之前,因为是命令本身的选项嘛)。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ getopt ab:cd -a -b test1 -cde test2 test3</span><br><span class="line">getopt:无效选项 -- e</span><br><span class="line"> -a -b test1 -c -d -- test2 test3</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ getopt -q ab:cd -a -b test1 -cde test2 test3</span><br><span class="line"> -a -b 'test1' -c -d -- 'test2' 'test3'</span><br></pre></td></tr></table></figure><p><strong>在脚本中用getopt</strong></p><p>用法稍微有点复杂,方法是用<code>getopt</code>命令生成的格式化后的版本替换已有的命令行选项和参数。用<code>set</code>可以做到。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">set -- $(getopt -q ab:cd "$@)</span><br></pre></td></tr></table></figure><p><code>set</code>命令的选项之一是<code>--</code>,它会将命令行参数替换成<code>set</code>命令的命令行值。</p><p>该方法会将原始脚本的命令行参数传给<code>getopt</code>命令,之后将<code>getopt</code>命令的输出传给<code>set</code>命令,用<code>getopt</code>格式化的命令行参数来替换原始的命令行参数。</p><p>现在写下处理命令行参数的脚本吧:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test18.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Extract <span class="built_in">command</span> line options & values with getopt</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">set -- $(getopt -q ab:cd "$@")</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo</span><br><span class="line">while [ -n "$1" ]</span><br><span class="line">do</span><br><span class="line"> case "$1" in</span><br><span class="line"> -a) echo "Found the -a option";;</span><br><span class="line"> -b) param="$2"</span><br><span class="line"> echo "Found the -b option, with parameter value $param"</span><br><span class="line"> shift ;;</span><br><span class="line"> -c) echo "Found the -c option";;</span><br><span class="line"> --) shift</span><br><span class="line"> break ;;</span><br><span class="line"> *) echo "$1 is not an option"</span><br><span class="line"> esac</span><br><span class="line"> shift</span><br><span class="line">done</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">count=1</span><br><span class="line">for param in "$@"</span><br><span class="line">do</span><br><span class="line"> echo "Parameter #$count: $param"</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br></pre></td></tr></table></figure><p>可以看到它跟<code>test17.sh</code>不同的地方是加入了<code>getopt</code>命令来帮助格式化命令行参数。</p><p>下面测试发现新加的功能实现了,之前的也没问题。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test18.sh -ac</span><br><span class="line"></span><br><span class="line">Found the -a option</span><br><span class="line">Found the -c option</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test18.sh -a -b test1 -cd test2 test3 test4</span><br><span class="line"></span><br><span class="line">Found the -a option</span><br><span class="line">Found the -b option, with parameter value 'test1'</span><br><span class="line">Found the -c option</span><br><span class="line">-d is not an option</span><br><span class="line">Parameter #1: 'test2'</span><br><span class="line">Parameter #2: 'test3'</span><br><span class="line">Parameter #3: 'test4'</span><br></pre></td></tr></table></figure><p>相当不错啦。不过<code>getopt</code>命令隐藏一个问题。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test18.sh -a -b test1 -cd "test2 test3" test4</span><br><span class="line"></span><br><span class="line">Found the -a option</span><br><span class="line">Found the -b option, with parameter value 'test1'</span><br><span class="line">Found the -c option</span><br><span class="line">-d is not an option</span><br><span class="line">Parameter #1: 'test2</span><br><span class="line">Parameter #2: test3'</span><br><span class="line">Parameter #3: 'test4'</span><br></pre></td></tr></table></figure><p><code>getopt</code>命令并不擅长处理带空格和引号的参数值。它会将空格当作参数分隔符,而不是根据双引号将两者当作一个参数。</p><p>幸而还有办法能够解决这个问题。</p><h3 id="使用更高级的getopts"><a href="#使用更高级的getopts" class="headerlink" title="使用更高级的getopts"></a>使用更高级的getopts</h3><p><code>getopts</code>命令内建于bash shell。它比<code>getopt</code>多一些扩展功能。</p><p><code>getopts</code>命令格式如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">getopts optstring variable</span><br></pre></td></tr></table></figure><p><code>optstring</code>值类似于<code>getopt</code>命令中的那个。要去掉错误信息的话,可以在<code>optstring</code>之前加一个冒号。<code>getopts</code>命令将当前参数保存在命令行中定义的<code>variable</code>中。</p><p><strong>该命令会用到两个环境变量。如果选项需要跟一个参数值,<code>OPTARG</code>环境变量就会保存这个值。<code>OPTIND</code>环境变量保存了参数列表中<code>getopts</code>正在处理的参数位置。这样你就能在处理选项之后继续处理其他命令行参数了。</strong></p><p>空说无益,还是来练练。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test19.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Simple demonstration of the <span class="built_in">getopts</span> <span class="built_in">command</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo</span><br><span class="line">while getopts :ab:c opt</span><br><span class="line">do</span><br><span class="line"> case "$opt" in</span><br><span class="line"> a) echo "Found the -a option" ;;</span><br><span class="line"> b) echo "Found the -b option, with value $OPTARG";;</span><br><span class="line"> c) echo "Found the -c option";;</span><br><span class="line"> *) echo "Unknown option: $opt";;</span><br><span class="line"> esac</span><br><span class="line">done</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test19.sh -ab test1 -c</span><br><span class="line"></span><br><span class="line">Found the -a option</span><br><span class="line">Found the -b option, with value test1</span><br><span class="line">Found the -c option</span><br></pre></td></tr></table></figure><p><code>while</code>语句定义了<code>getopts</code>命令,指明了要查找哪些命令行选项,以及每次迭代中存储它们的变量名(<code>opt</code>)。</p><p><code>getopts</code>运行时,它一次只处理命令行上检测到的一个参数。处理完所有参数后,它会退出并返回一个大于0的退出状态码。这让它非常适合用于解析命令行所有的参数的循环中。</p><p>这里我们可以已经注意到了例子中的<code>case</code>用法和之前不同。<code>getopts</code>命令解析命令行选项时会移除开头的单破折号,所以在<code>case</code>定义中不用单破折号了。</p><p>上一小节末尾我们遇到的问题可以很好的解决了:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test19.sh -ab "test1 test2" -c</span><br><span class="line"></span><br><span class="line">Found the -a option</span><br><span class="line">Found the -b option, with value test1 test2</span><br><span class="line">Found the -c option</span><br></pre></td></tr></table></figure><p>另一个好用的功能是能够将选项字母和参数值放在一起,而且不用加空格。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test19.sh -abtest1</span><br><span class="line"></span><br><span class="line">Found the -a option</span><br><span class="line">Found the -b option, with value test1</span><br></pre></td></tr></table></figure><p>除此之外,<code>getopts</code>还能够将命令行上找到的所有未定义的选项统一输出成问号。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ ./test19.sh -d</span><br><span class="line"></span><br><span class="line">Unknown option: ?</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test19.sh -acde</span><br><span class="line"></span><br><span class="line">Found the -a option</span><br><span class="line">Found the -c option</span><br><span class="line">Unknown option: ?</span><br><span class="line">Unknown option: ?</span><br></pre></td></tr></table></figure><p><code>getopts</code>命令知道何时停止处理选项,并将参数留给你处理。在<code>getopts</code>处理每一个选项时,它会将<code>OPTIND</code>环境变量值增一。在<code>getopts</code>完成处理后,你可以使用<code>shift</code>命令和<code>OPTIND</code>值来移动参数。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test20.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Processing options & parameters with <span class="built_in">getopts</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo</span><br><span class="line">while getopts :ab:cd opt</span><br><span class="line">do</span><br><span class="line"> case "$opt" in</span><br><span class="line"> a) echo "Found the -a option" ;;</span><br><span class="line"> b) echo "Found the -a option, with value $OPTARG" ;;</span><br><span class="line"> c) echo "Found the -c option" ;;</span><br><span class="line"> d) echo "Found the -d option" ;;</span><br><span class="line"> *) echo "Unknown option: $opt" ;;</span><br><span class="line"> esac</span><br><span class="line">done</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">shift $[ $OPTIND -1 ]</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">echo</span><br><span class="line">count=1</span><br><span class="line">for param in "$@"</span><br><span class="line">do</span><br><span class="line"> echo "Parameter $count: $param"</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test20.sh -a -b test1 -d test2 test3 test4</span><br><span class="line"></span><br><span class="line">Found the -a option</span><br><span class="line">Found the -a option, with value test1</span><br><span class="line">Found the -d option</span><br><span class="line"></span><br><span class="line">Parameter 1: test2</span><br><span class="line">Parameter 2: test3</span><br><span class="line">Parameter 3: test4</span><br></pre></td></tr></table></figure><p>这里<code>shift $[ $OPTIND -1 ]</code>需要解释以下:前面提到<code>OPTIND</code>在<code>getopts</code>每次处理掉一个参数后会加1。比如<code>./test20.sh -a -b test1 -d test2 test3 test4</code>前面键入了4个参数,选项处理完成后<code>OPTIND</code>的值为5。它会指向<code>$5</code>,即第5个参数,后面为了值剩下命令行参数,所以去掉所有的选项(及带的参数),所以用<code>shift $[ $OPTIND - 1]</code>命令。</p><h2 id="将选项标准化"><a href="#将选项标准化" class="headerlink" title="将选项标准化"></a>将选项标准化</h2><p>有些字母在Linux世界里已经拥有了某种程度的标准含义。如果我们能在shellji奥本中支持这些选项,脚本看起来会更加友好。</p><p>下面表格列出一些命令行选项的常用含义</p><table><thead><tr><th style="text-align:center">选项</th><th style="text-align:center">描述</th></tr></thead><tbody><tr><td style="text-align:center">-a</td><td style="text-align:center">显示所有对象</td></tr><tr><td style="text-align:center">-c</td><td style="text-align:center">生成一个计数</td></tr><tr><td style="text-align:center">-d</td><td style="text-align:center">指定一个目录</td></tr><tr><td style="text-align:center">-e</td><td style="text-align:center">扩展一个对象</td></tr><tr><td style="text-align:center">-f</td><td style="text-align:center">指定读入数据的文件</td></tr><tr><td style="text-align:center">-h</td><td style="text-align:center">显示命令的帮助信息</td></tr><tr><td style="text-align:center">-i</td><td style="text-align:center">忽略文本大小写</td></tr><tr><td style="text-align:center">-l</td><td style="text-align:center">产生输出的长格式版本</td></tr><tr><td style="text-align:center">-n</td><td style="text-align:center">使用非交互模式(批处理)</td></tr><tr><td style="text-align:center">-o</td><td style="text-align:center">将所有输出重定向到指定的输出文件</td></tr><tr><td style="text-align:center">-q</td><td style="text-align:center">以安静模式运行</td></tr><tr><td style="text-align:center">-r</td><td style="text-align:center">递归地处理目录和文件</td></tr><tr><td style="text-align:center">-s</td><td style="text-align:center">以安静模式运行</td></tr><tr><td style="text-align:center">-v</td><td style="text-align:center">生成详细输出</td></tr><tr><td style="text-align:center">-x</td><td style="text-align:center">排除某个对象</td></tr><tr><td style="text-align:center">-y</td><td style="text-align:center">对所有问题答yes</td></tr></tbody></table><h2 id="获得用户输入"><a href="#获得用户输入" class="headerlink" title="获得用户输入"></a>获得用户输入</h2><p>有时脚本地交互性还需要更强一些。比如你想要在脚本运行时问个问题,并等待运行脚本地人来回答。bash shell为此提供了read命令。</p><h3 id="基本的读入"><a href="#基本的读入" class="headerlink" title="基本的读入"></a>基本的读入</h3><p><code>read</code>命令从标准输入(键盘)或另一个文件描述符中接受输入。在收到输入后,<code>read</code>命令会将数据放进一个变量。</p><p>简单用法如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test21.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> testing the <span class="built_in">read</span> -p option</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">read -p "Please enter your age: " age</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">days=$[ $age * 365 ]</span><br><span class="line">echo "That makes you over $days days old!"</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test21.sh</span><br><span class="line">Please enter your age: 23</span><br><span class="line">That makes you over 8395 days old!</span><br></pre></td></tr></table></figure><p><code>read</code>命令会将提示符后输入的所有数据分配给单个变量,要么我们需要指定多个变量。当变量数量不够时,剩下的数据就全部分配给最后一个变量。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test22.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> entering multiple variable</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">read -p "Enter your name: " first last</span><br><span class="line">echo "Checking data for $last, $first..."</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test22.sh</span><br><span class="line">Enter your name: shixiang wang</span><br><span class="line">Checking data for wang, shixiang...</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test22.sh</span><br><span class="line">Enter your name: shixiang wang hhhhh</span><br><span class="line">Checking data for wang hhhhh, shixiang...</span><br></pre></td></tr></table></figure><p>也可以在<code>read</code>命令行中不指定变量。如果这样的话,<code>read</code>命令会将它收到的任何数据都放进特殊环境变量<code>REPLY</code>中。我们直接可以使用它。</p><h3 id="超时"><a href="#超时" class="headerlink" title="超时"></a>超时</h3><p>脚本很可能一直苦苦等待脚本用户的输入。如果不管数据是否输入,脚本都执行的话,我们可以用<code>-t</code>选项设定一个计时器。<code>-t</code>指定<code>read</code>命令等待的秒数,计数完成后,<code>read</code>命令会返回一个非零退出状态码。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test23.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> timing the data entry</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">if read -t 5 -p "Please enter your name: " name</span><br><span class="line">then</span><br><span class="line"> echo "Hello $name, welcome to my script"</span><br><span class="line">else</span><br><span class="line"> echo</span><br><span class="line"> echo "Sorry, too slow!"</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test23.sh</span><br><span class="line">Please enter your name: shixiang wang</span><br><span class="line">Hello shixiang wang, welcome to my script</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test23.sh # 这里输入后等以下 不要输入</span><br><span class="line">Please enter your name:</span><br><span class="line">Sorry, too slow!</span><br></pre></td></tr></table></figure><p>也可以不对输入过程计时,而时让<code>read</code>命令来统计输入的字符数。当输入的字符数达到预设的字符数时,就自动退出,将输入的数据赋值给变量。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test24.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> getting just one character of input</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">read -n1 -p "Do you want to continue [Y/N]? " answer</span><br><span class="line">case $answer in</span><br><span class="line"> Y | y ) echo</span><br><span class="line"> echo "fine, continue on...";;</span><br><span class="line"> N | n ) echo</span><br><span class="line"> echo OK, goodbye</span><br><span class="line"> exit;;</span><br><span class="line">esac</span><br><span class="line">echo "This is the end of the script."</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test24.sh</span><br><span class="line">Do you want to continue [Y/N]? Y</span><br><span class="line">fine, continue on...</span><br><span class="line">This is the end of the script.</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test24.sh</span><br><span class="line">Do you want to continue [Y/N]? n</span><br><span class="line">OK, goodbye</span><br></pre></td></tr></table></figure><h3 id="隐藏方式读取"><a href="#隐藏方式读取" class="headerlink" title="隐藏方式读取"></a>隐藏方式读取</h3><p>这种方式输入密码的时候有用。</p><p><code>-s</code>选项可以避免在<code>read</code>命令输入的数据出现在显示器上(实际上,数据会被显示,只是<code>read</code>命令会将文本颜色设成跟背景色一样)。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test25.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> hiding input data from the monitor</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">read -s -p "Enter your password: " pass</span><br><span class="line">echo</span><br><span class="line">echo "Is your password really $pass?"</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test25.sh</span><br><span class="line">Enter your password:</span><br><span class="line">Is your password really what?</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test25.sh</span><br><span class="line">Enter your password:</span><br><span class="line">Is your password really shixiang?</span><br></pre></td></tr></table></figure><h3 id="从文件中读取"><a href="#从文件中读取" class="headerlink" title="从文件中读取"></a>从文件中读取</h3><p>当然,<code>read</code>也可以从系统文件中读取数据。每次调用<code>read</code>命令,它都会读取一行文本。当读完后,<code>read</code>命令会退出并返回非零退出状态码。</p><p>最常见的方法时对文本使用<code>cat</code>命令,将结果通过管道直接传给含<code>read</code>命令的<code>while</code>命令。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test26.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> reading data from a file</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">count=1</span><br><span class="line">cat test | while read line</span><br><span class="line">do</span><br><span class="line"> echo "Line $count: $line"</span><br><span class="line"> count=$[ $count + 1 ]</span><br><span class="line">done</span><br><span class="line">echo "Finished processing the file"</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ cat test</span><br><span class="line">The quick brown dog jumps over the lazy fox.</span><br><span class="line">This is a test, this is only a test.</span><br><span class="line">O Romeo, Romeo! Wherefore art thou Romeo?</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test26.sh</span><br><span class="line">Line 1: The quick brown dog jumps over the lazy fox.</span><br><span class="line">Line 2: This is a test, this is only a test.</span><br><span class="line">Line 3: O Romeo, Romeo! Wherefore art thou Romeo?</span><br><span class="line">Finished processing the file</span><br></pre></td></tr></table></figure><hr><p>写shell脚本的基本内容大体已经整完了,我自己也是边看边想边码过来的。shell博大精深,更多高级内容有待继续学习整理。码字实属不易,觉得内容还行的点赞支持下吧~</p>]]></content>
<summary type="html">
<p><strong>Shell脚本笔记系列:</strong></p>
<p><a href="http://www.flypeom.site/linux/2017/08/11/basic-shell/" target="_blank" rel="noopener">构建基本shell脚本</a></p>
<p><a href="http://www.flypeom.site/linux/2017/08/11/structural-command-of-shell/" target="_blank" rel="noopener">Linux结构化命令</a></p>
<blockquote>
<p>内容:</p>
<ul>
<li>传递参数</li>
<li>跟踪参数</li>
<li>移动变量</li>
<li>处理选项</li>
<li>将选项标准化</li>
<li>获得用户输入</li>
</ul>
</blockquote>
<p>经过前面的介绍,我们已经可以掌握一些流程化的脚本编程了。但有时候,我们需要编写的脚本能够跟使用者进行交互。它可以是静态的,输入相应的参数让它运行到底;也可以是动态的,脚本根据输入参数反馈不同的信息,使用者又能根据信息调整下一步的处理,实时与程序互动。</p>
<p>bash shell提供了一些不同的方法来从<strong>用户处获得数据,包括命令行参数、命令行选项以及直接从键盘读取输入</strong>的能力。下面将一一介绍实现。</p>
</summary>
<category term="Linux杂烩" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/"/>
<category term="shell编程" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/shell%E7%BC%96%E7%A8%8B/"/>
<category term="bash shell" scheme="https://shixiangwang.github.io/tags/bash-shell/"/>
<category term="shell笔记" scheme="https://shixiangwang.github.io/tags/shell%E7%AC%94%E8%AE%B0/"/>
</entry>
<entry>
<title>Linux结构化命令</title>
<link href="https://shixiangwang.github.io/2017/08/11/structural-command-of-shell/"/>
<id>https://shixiangwang.github.io/2017/08/11/structural-command-of-shell/</id>
<published>2017-08-10T16:00:00.000Z</published>
<updated>2018-01-27T04:08:05.774Z</updated>
<content type="html"><![CDATA[<a id="more"></a><h2 id="条件控制"><a href="#条件控制" class="headerlink" title="条件控制"></a>条件控制</h2><blockquote><p><strong>内容</strong></p><ul><li>使用if-then语句</li><li>嵌套if语句</li><li>test命令</li><li>复合条件测试</li><li>使用双方括号和双括号</li><li>case命令</li></ul></blockquote><p>许多程序要求对shell脚本中的命令施加一些逻辑流程控制。而某些命令会根据条件判断执行相应的命令,这样的命令通常叫做<strong>结构化命令</strong>。从概念上理解,结构化命令是shell脚本的逻辑结构,不像顺序执行shell脚本,而是有组织地执行命令以应对复杂任务需求。</p><h3 id="if-then语句"><a href="#if-then语句" class="headerlink" title="if-then语句"></a>if-then语句</h3><p>最基本的结构化命令是if-then语句,它的格式如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">if command</span><br><span class="line">then</span><br><span class="line">truecommands</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><p><strong>注意</strong>,在其他编程语言中,<code>if</code>语句之后的对象是一个等式,等式的结果为<code>TRUE</code>或者<code>FALSE</code>,但是bash shell中的<code>if</code>语句是运行<code>if</code>后面的命令,如果该命令的退出状态码是0(命令成功执行),则运行<code>then</code>语句后面的命令。<code>fi</code>表示<code>if</code>语句到此结束。</p><p>下面是一个简单的例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test1.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">! /bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> testing the <span class="keyword">if</span> statement</span></span><br><span class="line">if pwd</span><br><span class="line">then</span><br><span class="line"> echo "It worked"</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x test1.sh</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test1.sh</span><br><span class="line">/home/wsx/script_learn</span><br><span class="line">It worked</span><br></pre></td></tr></table></figure><p>这个例子中在判断成功执行<code>pwd</code>命令后,执行输出文本字符串。</p><p>大家可以尝试把<code>pwd</code>命令改成随便乱打的字符试试结果。它会显示报错信息,<code>then</code>后面的语句也不会执行。</p><p>if-then语句的另一种形式:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">if command; then</span><br><span class="line">commands</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><p>在then部分,我们可以使用多个命令(从格式中command结尾有没有s也可以看出)。</p><p>我们再来一个例子:在<code>if</code>语句中用<code>grep</code>命令在<code>/etc/passwd</code>文件中查找某个用户名当前是否在系统上使用。如果有用户使用了哪个登录名,脚本会显示一些文本信息并列出该用户HOME目录的bash文件。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test3.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> testing multiple commands <span class="keyword">in</span> the <span class="keyword">then</span> section</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">testuser=wsx</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">if grep $testuser /etc/passwd</span><br><span class="line">then</span><br><span class="line"> echo "This is my first command"</span><br><span class="line"> echo "This is my second command"</span><br><span class="line"> echo "I can even put in other commands besides echo:"</span><br><span class="line"> ls -a /home/$testuser/.b*</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x test3.sh</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test3.sh</span><br><span class="line">wsx:x:1000:1000:wsx,,,:/home/wsx:/bin/bash</span><br><span class="line">This is my first command</span><br><span class="line">This is my second command</span><br><span class="line">I can even put in other commands besides echo:</span><br><span class="line">/home/wsx/.bash_history /home/wsx/.bashrc</span><br><span class="line">/home/wsx/.bash_logout /home/wsx/.bashrc-anaconda3.bak</span><br></pre></td></tr></table></figure><p>如果设置的用户名不存在,那么就没有输出。那么如果在这里显示的一些消息可以说明用户名在系统中未找到,这样可能就会显得更友好。所以接下来看看<code>if-then-else</code>语句。</p><h3 id="if-then-else语句"><a href="#if-then-else语句" class="headerlink" title="if-then-else语句"></a>if-then-else语句</h3><p>我相信意思非常容易理解,这里较之前我们添加了一个<code>else</code>块来处理<code>if</code>中命令没有成功执行的步骤。格式为:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">if command</span><br><span class="line">then</span><br><span class="line"> commands</span><br><span class="line">else commands</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><h3 id="嵌套if"><a href="#嵌套if" class="headerlink" title="嵌套if"></a>嵌套if</h3><p>有时我们需要检查脚本代码中的多种条件,可以是用嵌套的<code>if-then</code>语句。</p><p>处理一个例子:检查<code>/etc/passwd</code>文件中是否存在某个用户名以及该用户名的目录是否存在。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test5.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> Testing nested ifs</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">testuser=NoSuchUser</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">if grep $testuser /etc/passwd</span><br><span class="line">then</span><br><span class="line"> echo "The user $testuser exits on this system."</span><br><span class="line">else</span><br><span class="line"> echo "The user $testuser does not exit on this system."</span><br><span class="line"> if ls -d /home/$testuser/</span><br><span class="line"> then</span><br><span class="line"> echo "However, $testuser has a directory."</span><br><span class="line"> fi</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x test5.sh</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test5.sh</span><br><span class="line">The user NoSuchUser does not exit on this system.</span><br><span class="line">ls: 无法访问'/home/NoSuchUser/': 没有那个文件或目录</span><br></pre></td></tr></table></figure><p>可以使用<code>else</code>部分的另一种形式:<code>elif</code>。这样我们就不再用书写多个<code>if-then</code>语句了。在其他语言中,有的是用<code>elif</code>的形式,有的使用<code>else if</code>等形式。面对相同内含在不同语言中不同的表示方式,我们需要有意识地区别,以免接触的东西多了可能各种语言代码串写喔。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">if command1</span><br><span class="line">then</span><br><span class="line">truecommands</span><br><span class="line">elif command2</span><br><span class="line">then</span><br><span class="line">truemore commands</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><p>这种表示方式逻辑更为清晰,但是也有点容易让写的人搞混。其实可以看到一个<code>if</code>对应一个<code>fi</code>。这是一个大的嵌套<code>if</code>结构。</p><p><strong>记住</strong>,在<code>elif</code>语句中,紧跟其后的<code>else</code>语句属于<code>elif</code>代码块,而不是属于<code>if-then</code>代码块。</p><h3 id="test命令"><a href="#test命令" class="headerlink" title="test命令"></a>test命令</h3><p>到此为止,我们很清楚<code>if</code>后面跟着的是普通的shell命令,那么我们需要测试其他条件怎么办呢?</p><p><code>test</code>命令提供了在<code>if-then</code>语句中测试不同条件的途径。如果<code>test</code>命令中列出的条件成立,<code>test</code>命令就会退出并返回状态码0。这样<code>if-then</code>语句就与其他编程语言中的<code>if-then</code>语句以类似的方式工作了。</p><p>test命令格式:</p><figure class="highlight subunit"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">test </span>condition</span><br></pre></td></tr></table></figure><p><code>condition</code>是<code>test</code>命令要测试的一系列参数和值。如果不写这个<code>condition</code>,<code>test</code>返回非0,<code>if</code>语句跳转到<code>else</code>进行执行。</p><p>bash shell提供了一种条件测试方法,无需在<code>if-then</code>语句中声明<code>test</code>命令。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">if [ condition ]</span><br><span class="line">then commands</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><p>这跟我们其他的编程习惯非常接近。建议使用这种方式。</p><p>如果使用<code>test</code>命令,需要记住的是各种条件参数。</p><p><strong>数值比较</strong></p><table><thead><tr><th>比较</th><th>描述</th></tr></thead><tbody><tr><td>n1 -eq n2</td><td>(n1)等于(n2)</td></tr><tr><td>n1 -ge n2</td><td>大于或等于</td></tr><tr><td>n1 -gt n2</td><td>大于</td></tr><tr><td>n1 -le n2</td><td>小于或等于</td></tr><tr><td>n1 -lt n2</td><td>小于</td></tr><tr><td>n1 -ne n2</td><td>不等于</td></tr></tbody></table><p><strong>字符串比较</strong></p><table><thead><tr><th>比较</th><th>描述</th></tr></thead><tbody><tr><td>str1 = str2</td><td>(str1与str2比较)相同</td></tr><tr><td>str1 != str2</td><td>不同</td></tr><tr><td>str1 < str2</td><td>小</td></tr><tr><td>str1 > str2</td><td>大</td></tr><tr><td>-n str1</td><td>检查string1的长度非0</td></tr><tr><td>-z str1</td><td>检查string1的长度是否为0</td></tr></tbody></table><p>注意,大于和小于号必须转义;大于和小于顺序和sort命令所采用的不同。</p><p><strong>文件比较</strong></p><table><thead><tr><th>比较</th><th>描述</th></tr></thead><tbody><tr><td>-d file</td><td>检查file是否存在并是一个目录</td></tr><tr><td>-e file</td><td>~是否存在</td></tr><tr><td>-f file</td><td>~是否存在并是一个文件</td></tr><tr><td>-r file</td><td>~是否存在并可读</td></tr><tr><td>-s file</td><td>~是否存在并非空</td></tr><tr><td>-w file</td><td>~是否存在并可写</td></tr><tr><td></td><td></td></tr><tr><td>-x file</td><td>~是否存在并可执行</td></tr><tr><td>-O file</td><td>~是否存在并属当前用户所有</td></tr><tr><td>-G file</td><td>~是否存在并且默认组与当前用户相同</td></tr><tr><td>file1 -nt file2</td><td>检查file1是否比file2新</td></tr><tr><td>file1 -ot file2</td><td>检查file1是否比file2旧</td></tr></tbody></table><h3 id="复合条件测试"><a href="#复合条件测试" class="headerlink" title="复合条件测试"></a>复合条件测试</h3><p><code>if-then</code>语句允许我们使用布尔逻辑来组合测试。可用</p><ul><li>[ condition1] && [ condition2]</li><li><code>[ condition1] || [ condition2]</code></li></ul><h3 id="if-then的高级特性"><a href="#if-then的高级特性" class="headerlink" title="if-then的高级特性"></a>if-then的高级特性</h3><ul><li>用于数学表达式的双括号</li><li>用于高级字符串处理功能的双方括号</li></ul><p><strong>双括号</strong></p><p>命令格式:</p><figure class="highlight lisp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(( <span class="name">expresiion</span> ))</span><br></pre></td></tr></table></figure><p><code>expression</code>可以是任意的数学赋值或比较表达式。除了<code>test</code>命令使用的标准数学运算符,下面列出了一些其他的:</p><table><thead><tr><th>符号</th><th>描述</th></tr></thead><tbody><tr><td>val ++</td><td>后增</td></tr><tr><td>val –</td><td>后减</td></tr><tr><td>++ val</td><td>先增</td></tr><tr><td>– val</td><td>先减</td></tr><tr><td>!</td><td>逻辑取反</td></tr><tr><td>~</td><td>位求反</td></tr><tr><td>**</td><td>幂运算</td></tr><tr><td><<</td><td>左位移</td></tr><tr><td>>></td><td>右位移</td></tr><tr><td>&</td><td>位布尔和</td></tr><tr><td>\</td><td></td><td>位布尔或</td></tr><tr><td>&&</td><td>逻辑和</td></tr><tr><td>\</td><td>\</td><td></td><td>逻辑或</td></tr></tbody></table><p>看一个例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test23.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> using doble parenthesis</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">val1=10</span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">if (( $val1 ** 2 > 90 ))</span><br><span class="line">then</span><br><span class="line"> (( val2 = $val1 ** 2 ))</span><br><span class="line"> echo "The square of $val1 is $val2"</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x test23.sh</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test23.sh</span><br><span class="line">The square of 10 is 100</span><br></pre></td></tr></table></figure><p><strong>双方括号</strong></p><p>双方括号命令提供了针对字符串比较的高级特性。命令格式如下:</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">[[ expression ]]</span></span><br></pre></td></tr></table></figure><p>双方括号里的<code>expression</code>使用了<code>test</code>命令中采用的标准字符串比较。但它提供了<code>test</code>没有提供的一个特性——模式匹配。</p><p>在模式匹配中,可以定义一个正则表达式来匹配字符串值。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test24.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">! /bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> using pattern matching</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">if [[ $USER == r* ]]</span><br><span class="line">then</span><br><span class="line"> echo "Hello $USER"</span><br><span class="line">else</span><br><span class="line"> echo "Sorry, I do not know you"</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x test24.sh</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test24.sh</span><br><span class="line">Sorry, I do not know you</span><br></pre></td></tr></table></figure><p>上面一个脚本中,我们使用了双等号。双等号将右边的字符串视为一个模式,并将其应用模式匹配规则。</p><h3 id="case命令"><a href="#case命令" class="headerlink" title="case命令"></a>case命令</h3><p>有了<code>case</code>命令,就不需要写出所有的<code>elif</code>语句来不停地检查同一个变量的值了。<code>case</code>命令会采用列表格式来检查单个变量的多值。</p><p>下面是两个脚本实现相同功能进行对比:</p><p>if语句:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test25.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> looking <span class="keyword">for</span> a possible value</span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">if [ $USER = "rich" ]</span><br><span class="line">then</span><br><span class="line"> echo "Welcome $USER"</span><br><span class="line"> echo "Please enjoy you visit"</span><br><span class="line">elif [ $USER = "barbara" ]</span><br><span class="line">then</span><br><span class="line"> echo "Welcome $USER"</span><br><span class="line"> echo "Please enjoy you visit"</span><br><span class="line">elif [ $USER = "testing" ]</span><br><span class="line">then</span><br><span class="line"> echo "Special testing account"</span><br><span class="line">elif [ $USER = "jessica" ]</span><br><span class="line">then</span><br><span class="line"> echo "Do not forget to logout when you're done"</span><br></pre></td></tr></table></figure><p>case语句:</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">case variable <span class="keyword">in</span></span><br><span class="line">pattern1 | pattern2) commands1;;</span><br><span class="line">pattern3) commands2;;</span><br><span class="line">*)<span class="built_in"> default </span>commands;;</span><br><span class="line">esac</span><br></pre></td></tr></table></figure><p>上面的实例可以用<code>case</code>语句表示为:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test26.sh</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> using the <span class="keyword">case</span> <span class="built_in">command</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"></span></span><br><span class="line">case $USER in</span><br><span class="line">rich | barbara)</span><br><span class="line"> echo "Welcome, $USER"</span><br><span class="line"> echo "Please enjoy your visits";;</span><br><span class="line">testing)</span><br><span class="line"> echo "Special testing account";;</span><br><span class="line">jessica)</span><br><span class="line"> echo "Do not forget to log off whe you're done";;</span><br><span class="line">*)</span><br><span class="line"> echo "Sorry, you are not allowed here";;</span><br><span class="line">esac</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x test26.sh</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test26.sh</span><br><span class="line">Sorry, you are not allowed here</span><br></pre></td></tr></table></figure><p><code>case</code>命令会将指定的变量与不同模式进行比较。如果变量和模式是匹配的,那么shell会执行为该模式指定的命令。可以通过竖线操作符在一行中分隔出多个模式。星号会捕获所有与已知模式不匹配的值。注意双分号的使用。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><blockquote><p>最基本的命令是<code>if-then</code>语句;</p><p>可以拓展<code>if-then</code>语句为<code>if-then-else</code>语句;</p><p>可以将<code>if-then-else</code>语句通过<code>elif</code>语句连接起来;</p><p>在脚本中,我们需要测试一种条件而不是命令时,比如数值、字符串内容、文件或目录的状态,<code>test</code>命令提供了简单方法;</p><p>方括号是<code>test</code>命令统一的特殊bash命令;</p><p>双括号使用另一种操作符进行高级数学运算双方括号允许高级字符串模式匹配运算;</p><p><code>case</code>命令是执行多个<code>if-then-else</code>命令的简便方式,它会参照一个值列表来检查单个变量的值。</p></blockquote><p>关于结构化命令中循环,将在下次整理的笔记中阐述。</p><h2 id="循环控制"><a href="#循环控制" class="headerlink" title="循环控制"></a>循环控制</h2><blockquote><p><strong>内容</strong></p><ul><li>for循环语句</li><li>until迭代语句使用while语句</li><li>循环</li><li>重定向循环的输出</li></ul></blockquote><p>这一节我们来了解如何重复一些过程和命令,也就是循环执行一组命令直到达到了某个特定条件。</p><h3 id="for命令"><a href="#for命令" class="headerlink" title="for命令"></a>for命令</h3><p>基本格式:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">for var in list</span><br><span class="line">do</span><br><span class="line">truecommands</span><br><span class="line">done</span><br></pre></td></tr></table></figure><p>也可以</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">for var in list; do</span><br></pre></td></tr></table></figure><p>分号只用来分隔命令的,让代码更简约。</p><p>来个简单例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test1</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> basic <span class="keyword">for</span> <span class="built_in">command</span></span></span><br><span class="line"></span><br><span class="line">for test in Alabama Alaska Arizona Arkansas California Colorado</span><br><span class="line">do</span><br><span class="line"> echo The next state is $test</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test1</span><br><span class="line">The next state is Alabama</span><br><span class="line">The next state is Alaska</span><br><span class="line">The next state is Arizona</span><br><span class="line">The next state is Arkansas</span><br><span class="line">The next state is California</span><br><span class="line">The next state is Colorado</span><br></pre></td></tr></table></figure><p>这里操作基本和其他语言一致(格式不同),不多讲啦。</p><p><strong>在读取列表中的复杂值时</strong>,我们可能会遇到问题。比如下面这个例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat badtest1</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> another example of how not to use the <span class="keyword">for</span> <span class="built_in">command</span></span></span><br><span class="line"></span><br><span class="line">for test in I don't know if this'll work</span><br><span class="line">do</span><br><span class="line"> echo "word:$test"</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./badtest1</span><br><span class="line">word:I</span><br><span class="line">word:dont know if thisll</span><br><span class="line">word:work</span><br></pre></td></tr></table></figure><p>我们可以看到shell看到了列表值中的单引号尝试使用它们来定义一个单独的数据值。</p><p>这里有两种解决办法:</p><ul><li>使用转义字符将单引号转义</li><li>使用双引号来定义用到单引号的值</li></ul><p>我们将这两种解决办法同时用到上个例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test2</span><br><span class="line"><span class="meta">#</span><span class="bash">! /bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> another example of how not to use the <span class="keyword">for</span> <span class="built_in">command</span></span></span><br><span class="line"></span><br><span class="line">for test in I don\'t know if "this'll" work; do</span><br><span class="line">echo "word:$test"</span><br><span class="line">done</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test2</span><br><span class="line">word:I</span><br><span class="line">word:don't</span><br><span class="line">word:know</span><br><span class="line">word:if</span><br><span class="line">word:this'll</span><br><span class="line">word:work</span><br></pre></td></tr></table></figure><p>我们可能明白了<code>for</code>循环是假定每个值是用空格分隔的,所以当有包含空格的数据时,我们需要用双引号括起来。</p><p><strong>通常我们会将列表值存储在一个变量中</strong>,然后通过遍历变量的方式遍历了其内容的的列表。</p><p>看看怎么完成这个任务:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test3</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> using a variable to hold the list</span></span><br><span class="line">list="Alabama Alaska Arizona Arkansas Colorado"</span><br><span class="line">list=$list" Connecticut" # 在尾部拼接文本</span><br><span class="line"></span><br><span class="line">for state in $list; do</span><br><span class="line"> echo "Have you ever visited $state?"</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test3</span><br><span class="line">Have you ever visited Alabama?</span><br><span class="line">Have you ever visited Alaska?</span><br><span class="line">Have you ever visited Arizona?</span><br><span class="line">Have you ever visited Arkansas?</span><br><span class="line">Have you ever visited Colorado?</span><br><span class="line">Have you ever visited Connecticut?</span><br></pre></td></tr></table></figure><p>注意,代码中还用了另一个赋值语句向<code>$list</code>变量包含的已有列表中添加了一个值。这是在已有文本字符串尾部添加文本的一种常用方法。</p><p>我们还可以<strong>用命令来输出我们需要的列表内容</strong>:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test4</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> reading values from a file</span></span><br><span class="line"></span><br><span class="line">file="states"</span><br><span class="line"></span><br><span class="line">for state in $(cat $file)</span><br><span class="line">do</span><br><span class="line"> echo "Visit beautiful $state"</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat states</span><br><span class="line">Alabama</span><br><span class="line">Alaska</span><br><span class="line">Arizona</span><br><span class="line">Arkansas</span><br><span class="line">Colorado</span><br><span class="line">Connecticut</span><br><span class="line">Delaware</span><br><span class="line">Florida</span><br><span class="line">Georgia</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test4</span><br><span class="line">Visit beautiful Alabama</span><br><span class="line">Visit beautiful Alaska</span><br><span class="line">Visit beautiful Arizona</span><br><span class="line">Visit beautiful Arkansas</span><br><span class="line">Visit beautiful Colorado</span><br><span class="line">Visit beautiful Connecticut</span><br><span class="line">Visit beautiful Delaware</span><br><span class="line">Visit beautiful Florida</span><br><span class="line">Visit beautiful Georgia</span><br></pre></td></tr></table></figure><p><strong>更改字段分隔符</strong></p><p>环境变量<code>IFS</code>,也叫作字段分隔符。它定义了bash shell用作字段分隔符的一系列字符。默认情况下,bash shell会将<strong>空格、制表符和换行符</strong>当作字段分隔符。</p><p>如果想修改<code>IFS</code>的值,比如使其只能识别换行符,我们可以将下面这行代码加入脚本:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">IFS=$'\n'</span><br></pre></td></tr></table></figure><p>在处理大量脚本时,我们可能只在某一部分使用其他的分隔符,这时候可以先保存原有的<code>IFS</code>值,然后修改,最后恢复:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">IFS.OLD=$IFS</span><br><span class="line">IFS=$'\n'</span><br><span class="line"><在代码中使用新的IFS值></span><br><span class="line">IFS=$IFS.OLD</span><br></pre></td></tr></table></figure><p>假如我们要遍历一个文件中用冒号分隔的值:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">IFS=:</span><br></pre></td></tr></table></figure><p>假如要指定多个<code>IFS</code>字符,只要将它们的赋值行串起来:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">IFS=$'\n':;"</span><br></pre></td></tr></table></figure><p>这个赋值会将换行符、冒号、分号以及双引号作为字段分隔符。</p><p><strong>用通配符读取目录</strong></p><p>我们可以用<code>for</code>命令来自动遍历目录中的文件。进行此操作时,必须在文件名或路径名中使用通配符。它会强制shell使用<strong>文件扩展匹配</strong>。文件扩展匹配是生成匹配指定通配符的文件名或路径名的过程。</p><p>我拿我的一个目录来尝试一下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test5</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> iterate through all the files <span class="keyword">in</span> a directory</span></span><br><span class="line"></span><br><span class="line">for file in /home/wsx/python_learn/*</span><br><span class="line">do</span><br><span class="line"> if [ -d "$file" ]</span><br><span class="line"> then</span><br><span class="line"> echo "$file is a directory"</span><br><span class="line"> elif [ -f "$file" ]</span><br><span class="line"> then</span><br><span class="line"> echo "$file is a file"</span><br><span class="line"> fi</span><br><span class="line">done</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test5</span><br><span class="line">/home/wsx/python_learn/athletelist.py is a file</span><br><span class="line">/home/wsx/python_learn/athletemodel.py is a file</span><br><span class="line">/home/wsx/python_learn/ch2_data_input.py is a file</span><br><span class="line">/home/wsx/python_learn/chapter5_first.py is a file</span><br><span class="line">/home/wsx/python_learn/chapter6_first.py is a file</span><br><span class="line">/home/wsx/python_learn/chapter6_second.py is a file</span><br><span class="line">/home/wsx/python_learn/chapter6_third.py is a file</span><br><span class="line">/home/wsx/python_learn/coinFlips.py is a file</span><br><span class="line">/home/wsx/python_learn/Dive_into_python is a directory</span><br></pre></td></tr></table></figure><p><strong>注意:</strong>第一个方括号之后和第二个方括号之前必须加上一个空格,否则会报错。</p><p>在Linux中,目录名和文件名中包含空格是合法的,所以将<code>$file</code>变量用双引号圈起来。当然,大家尽量不要让文件或目录包含空格,不然很容易出问题(命令会把空格当做文件的分隔符)。</p><h3 id="C语言风格的for命令"><a href="#C语言风格的for命令" class="headerlink" title="C语言风格的for命令"></a>C语言风格的for命令</h3><p>C语言风格的<code>for</code>命令看起来如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">for (( a = 1; a < 10; a++ ))</span><br></pre></td></tr></table></figure><p>值得注意的是,这里有些部分没有遵循bash shell标准的<code>for</code>命令:</p><ul><li>变量赋值可以有空格;</li><li>条件中的变量不以美元符开头;</li><li>迭代过程的算式未用<code>expr</code>命令格式。</li></ul><p>在使用这种格式时要小心,不同的格式不注意就会出错。</p><p>下面举个例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test6</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> testing the C-style <span class="keyword">for</span> loop</span></span><br><span class="line"></span><br><span class="line">for (( i=1; i <= 10; i++ ))</span><br><span class="line">do</span><br><span class="line"> echo "The next number is $i"</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test6</span><br><span class="line">The next number is 1</span><br><span class="line">The next number is 2</span><br><span class="line">The next number is 3</span><br><span class="line">The next number is 4</span><br><span class="line">The next number is 5</span><br><span class="line">The next number is 6</span><br><span class="line">The next number is 7</span><br><span class="line">The next number is 8</span><br><span class="line">The next number is 9</span><br><span class="line">The next number is 10</span><br></pre></td></tr></table></figure><h3 id="while命令"><a href="#while命令" class="headerlink" title="while命令"></a>while命令</h3><p><code>while</code>命令的格式为:<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">while test command</span><br><span class="line">do</span><br><span class="line"> other commands</span><br><span class="line">done</span><br></pre></td></tr></table></figure></p><p><code>while</code>命令某种意义上是<code>if-then</code>语句和<code>for</code>循环的混杂体。注意,这里<code>while</code>后面接的也是命令。<code>while</code>命令允许定义一个要测试的命令,然后循环执行一组命令,只要定义的测试命令返回的是退出状态码是0(类似一般语言中 的TRUE)。直到非0时退出循环。</p><p><code>while</code>命令中定义的<code>test command</code>和<code>if-then</code>语句中的格式一模一样。可以使用任何普通的bash shell命令,或者用<code>test</code>命令进行条件测试,比如测试变量值。</p><p>最常见的用法是用方括号来检查循环命令中用到的<code>shell</code>变量的值。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test</span><br><span class="line"><span class="meta">#</span><span class="bash">/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> <span class="keyword">while</span> <span class="built_in">command</span> <span class="built_in">test</span></span></span><br><span class="line"></span><br><span class="line">var1=10</span><br><span class="line">while [ $var1 -gt 0 ]</span><br><span class="line">do</span><br><span class="line"> echo $var1</span><br><span class="line"> var1=$[ $var1 - 1 ]</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test</span><br><span class="line">10</span><br><span class="line">9</span><br><span class="line">8</span><br><span class="line">7</span><br><span class="line">6</span><br><span class="line">5</span><br><span class="line">4</span><br><span class="line">3</span><br><span class="line">2</span><br><span class="line">1</span><br></pre></td></tr></table></figure><p><strong>使用多个测试命令</strong><br><code>while</code>命令允许我们在<code>while</code>语句行中定义多个测试命令。只有最后一个测试命令的退出状态码会被用来决定什么时候结束循环。<br>比如<code>while echo $var1 [ $var1 -ge 0 ]</code>检测的就是后面方括号命令的退出状态码。</p><h3 id="until命令"><a href="#until命令" class="headerlink" title="until命令"></a>until命令</h3><p><code>until</code>命令和<code>while</code>命令工作的方式完全相反。只有测试命令的退出状态码不为0,bash shell才会执行循环中列出的命令。一旦测试命令返回了退出状态码0,循环就结束了。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">until test command</span><br><span class="line">do</span><br><span class="line"> other commands</span><br><span class="line">done</span><br></pre></td></tr></table></figure><p>一个例子:<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test12</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> using the until <span class="built_in">command</span></span></span><br><span class="line"></span><br><span class="line">var1=100</span><br><span class="line"></span><br><span class="line">until [ $var1 -eq 0 ]</span><br><span class="line">do</span><br><span class="line"> echo $var1</span><br><span class="line"> var1=$[ $var1 - 25 ]</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ ./test12</span><br><span class="line">100</span><br><span class="line">75</span><br><span class="line">50</span><br><span class="line">25</span><br></pre></td></tr></table></figure></p><p>同样地,在<code>until</code>命令中放入多个测试命令时也要注意(类似<code>while</code>)。</p><h3 id="嵌套循环"><a href="#嵌套循环" class="headerlink" title="嵌套循环"></a>嵌套循环</h3><p>在循环语句内使用任意类型的命令,包括其他循环命令,叫做嵌套循环。因为是在迭代中迭代,需要注意变量的使用以及程序的效率问题。</p><p>下面举一个<code>for</code>循环嵌套<code>for</code>循环的例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test14</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> nesting <span class="keyword">for</span> loops</span></span><br><span class="line"></span><br><span class="line">for (( a = 1; a <= 3; a++ ))</span><br><span class="line">do</span><br><span class="line"> echo "Starting loop $a:"</span><br><span class="line"> for (( b = 1; b <= 3; b++ ))</span><br><span class="line"> do</span><br><span class="line"> echo " Inside loop: $b"</span><br><span class="line"> done</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ . test14</span><br><span class="line">Starting loop 1:</span><br><span class="line"> Inside loop: 1</span><br><span class="line"> Inside loop: 2</span><br><span class="line"> Inside loop: 3</span><br><span class="line">Starting loop 2:</span><br><span class="line"> Inside loop: 1</span><br><span class="line"> Inside loop: 2</span><br><span class="line"> Inside loop: 3</span><br><span class="line">Starting loop 3:</span><br><span class="line"> Inside loop: 1</span><br><span class="line"> Inside loop: 2</span><br><span class="line"> Inside loop: 3</span><br></pre></td></tr></table></figure><p>shell能够自动识别匹配的<code>do</code>和<code>done</code>字符。这种模式很常见,比如通常的小括号(<code>(</code>与<code>)</code>)、中括号、花括号匹配等等。它们的本质都是字符匹配。</p><p>在混用循环命令时也一样,比如在<code>while</code>循环中内嵌一个<code>for</code>循环:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test15</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> placing a <span class="keyword">for</span> loop inside a <span class="keyword">while</span> loop</span></span><br><span class="line"></span><br><span class="line">var1=5</span><br><span class="line"></span><br><span class="line">while [ $var1 -ge 0 ]</span><br><span class="line">do</span><br><span class="line"> echo "Outer loop: $var1"</span><br><span class="line"> for (( var2 = 1; $var2 < 3; var2++))</span><br><span class="line"> do</span><br><span class="line"> var3=$[ $var1 * $var2 ]</span><br><span class="line"> echo " Inner loop: $var1 * $var2 = $var3"</span><br><span class="line"> done</span><br><span class="line"> var1=$[ $var1 - 1 ]</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ . test15</span><br><span class="line">Outer loop: 5</span><br><span class="line"> Inner loop: 5 * 1 = 5</span><br><span class="line"> Inner loop: 5 * 2 = 10</span><br><span class="line">Outer loop: 4</span><br><span class="line"> Inner loop: 4 * 1 = 4</span><br><span class="line"> Inner loop: 4 * 2 = 8</span><br><span class="line">Outer loop: 3</span><br><span class="line"> Inner loop: 3 * 1 = 3</span><br><span class="line"> Inner loop: 3 * 2 = 6</span><br><span class="line">Outer loop: 2</span><br><span class="line"> Inner loop: 2 * 1 = 2</span><br><span class="line"> Inner loop: 2 * 2 = 4</span><br><span class="line">Outer loop: 1</span><br><span class="line"> Inner loop: 1 * 1 = 1</span><br><span class="line"> Inner loop: 1 * 2 = 2</span><br><span class="line">Outer loop: 0</span><br><span class="line"> Inner loop: 0 * 1 = 0</span><br><span class="line"> Inner loop: 0 * 2 = 0</span><br></pre></td></tr></table></figure><p>如果想要挑战脑力,可以混用<code>until</code>和<code>while</code>循环。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test16</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> using until and <span class="keyword">while</span> loop</span></span><br><span class="line"></span><br><span class="line">var1=3</span><br><span class="line"></span><br><span class="line">until [ $var1 -eq 0 ]</span><br><span class="line">do</span><br><span class="line"> echo "Outer loop: $var1"</span><br><span class="line"> var2=1</span><br><span class="line"> while [ $var2 -lt 5 ]</span><br><span class="line"> do</span><br><span class="line"> var3=$(echo "scale=4; $var1 / $var2" | bc)</span><br><span class="line"> echo " Inner loop: $var1 / $var2 = $var3"</span><br><span class="line"> var2=$[ $var2 + 1 ]</span><br><span class="line"> done</span><br><span class="line"> var1=$[ $var1 - 1 ]</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">wangsx@SC-201708020022:~/tmp$ . test16</span><br><span class="line">Outer loop: 3</span><br><span class="line"> Inner loop: 3 / 1 = 3.0000</span><br><span class="line"> Inner loop: 3 / 2 = 1.5000</span><br><span class="line"> Inner loop: 3 / 3 = 1.0000</span><br><span class="line"> Inner loop: 3 / 4 = .7500</span><br><span class="line">Outer loop: 2</span><br><span class="line"> Inner loop: 2 / 1 = 2.0000</span><br><span class="line"> Inner loop: 2 / 2 = 1.0000</span><br><span class="line"> Inner loop: 2 / 3 = .6666</span><br><span class="line"> Inner loop: 2 / 4 = .5000</span><br><span class="line">Outer loop: 1</span><br><span class="line"> Inner loop: 1 / 1 = 1.0000</span><br><span class="line"> Inner loop: 1 / 2 = .5000</span><br><span class="line"> Inner loop: 1 / 3 = .3333</span><br><span class="line"> Inner loop: 1 / 4 = .2500</span><br></pre></td></tr></table></figure><p>外部的<code>until</code>循环以值3开始,并继续执行到值等于0。内部<code>while</code>循环以值1开始一直执行,只要值小于5。需要注意循环条件的设置,我跑的几次都没写完整,然后无限循环只好重开终端。</p><h3 id="控制循环"><a href="#控制循环" class="headerlink" title="控制循环"></a>控制循环</h3><p>之前的学的命令已经可以让我们写循环程序了,设定好以后等待命令开始执行和等待循环结束。但是很多情况下,在循环中我们设定的某个(多个)变量达到某种条件时,我们就想要停止循环,然后运行循环下面的命令。这时候我们需要用到<code>break</code>和<code>continue</code>命令来帮我们控制住循环。</p><p>这两个命令在其他语言中基本都时关键字,特别是<code>C</code>,用法差不多。我也就不具体介绍了,只点出它们的功能。</p><p><strong>break</strong></p><blockquote><p>在shell执行break命令时,它会尝试跳出当前正在执行的循环。</p><p>在处理多个循环时,break命令会自动终止你所在的最内层循环。</p><p>break命令接受单个命令行参数值:</p><p> break n</p><p> 其中n制订了要跳出的循环层级(层数)</p></blockquote><p><strong>continue</strong></p><blockquote><p>continue命令可以提前终止某次循环的命令,但并不会完全终止整个循环。可以在循环内部设置shell不执行命令的条件。</p><p>也就是说使用continue命令时,它会自动跳过本次循环中接下来的运行步骤,跳转到下一次循环。但注意不是跳出,跳出时break的功能。</p><p>同样的可以使用continue n n制定要继续执行哪一级循环</p></blockquote><h3 id="处理循环的输出"><a href="#处理循环的输出" class="headerlink" title="处理循环的输出"></a>处理循环的输出</h3><p>在shell脚本中,我们可以对循环的输出使用管道或进行重定向。这是通过在<code>done</code>命令之后添加一个处理命令来实现的。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~/tmp$ cat test</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line">for file in /home/*</span><br><span class="line">do</span><br><span class="line"> if [ -d "$file" ]</span><br><span class="line"> then</span><br><span class="line"> echo "$file is a directory"</span><br><span class="line"> else</span><br><span class="line"> echo "$file is a file"</span><br><span class="line"> fi</span><br><span class="line">done > output.txt</span><br><span class="line">wangsx@SC-201708020022:~/tmp$ cat output.txt</span><br><span class="line">/home/wangsx is a directory</span><br></pre></td></tr></table></figure><p>shell将<code>for</code>命令的结果重定向到文件<code>output.txt</code>中,而不是显示在屏幕上。</p><h3 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h3><p>下面两个例子演示如何用简单循环来处理数据。</p><p><strong>查找可执行文件</strong></p><p>Linux运行程序时通过环境变量<code>$PATH</code>提供的目录搜索可执行文件。如果徒手找的话,比较费时间,我们可以写个脚本来搞定它。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">wangsx@SC-201708020022:~$ cat test25</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> finding files <span class="keyword">in</span> the PATH</span></span><br><span class="line"></span><br><span class="line">IFS=:</span><br><span class="line">for folder in $PATH</span><br><span class="line">do</span><br><span class="line"> echo "$folder:"</span><br><span class="line"> for file in $folder/*</span><br><span class="line"> do</span><br><span class="line"> if [ -x $file ]</span><br><span class="line"> then</span><br><span class="line"> echo " $file"</span><br><span class="line"> fi</span><br><span class="line"> done</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 输出结果太多,我就不拷贝结果了</span></span><br></pre></td></tr></table></figure><p>先设定<code>IFS</code>分隔符以便于能正确分隔目录,然后将目录存放在<code>$folder</code>中,用<code>for</code>循环来迭代特定的目录中所有文件,然后用<code>if-then</code>命令检查文件的可执行权限。</p><p>Linux有一个<code>tree</code>工具,非常方便输出目录结构,推荐使用下。</p><p><strong>创建多个用户账号</strong></p><p>如果你是管理员,需要创建大量账号时。不必每次都有<code>useradd</code>命令添加用户。将用户信息存放在指定文件,然后用脚本进行处理就可以了。</p><p>用户信息的格式如下:</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">userid,<span class="built_in"> user </span>name</span><br></pre></td></tr></table></figure><p>第一个是你为用户选择的id,第二个是用户的全名。这是<code>csv</code>文件格式。</p><p>为了能够读取它,我们使用以下命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">while IFS=',' read -r userid name</span><br></pre></td></tr></table></figure><p><code>read</code>命令会自动获取<code>.csv</code>文本文件的下一行内容,所以不用再写一个循环来处理。当<code>read</code>命令返回<code>FALSE</code>时(也就是读完了),<code>while</code>命令就会退出。</p><p>为了把数据从文件导向<code>while</code>命令,只要再<code>while</code>命令尾部加一个重定向符号。</p><p>处理过程写成脚本如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> process new user accounts</span></span><br><span class="line"></span><br><span class="line">input="users.csv"</span><br><span class="line">while IFS=',', read -r userid name</span><br><span class="line">do</span><br><span class="line">trueecho "adding $userid"</span><br><span class="line">trueuseradd -c "$name" -m $userid</span><br><span class="line">done < "$input"</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<a id="more"></a>
<h2 id="条件控制"><a href="#条件控制" class="headerlink" title="条件控制"></a>条件控制</h2><blockquote>
<p><strong>内容</strong></p>
<ul>
<l
</summary>
<category term="Linux杂烩" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/"/>
<category term="shell编程" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/shell%E7%BC%96%E7%A8%8B/"/>
<category term="bash shell" scheme="https://shixiangwang.github.io/tags/bash-shell/"/>
<category term="shell笔记" scheme="https://shixiangwang.github.io/tags/shell%E7%AC%94%E8%AE%B0/"/>
</entry>
<entry>
<title>构建基本shell脚本</title>
<link href="https://shixiangwang.github.io/2017/08/11/basic-shell/"/>
<id>https://shixiangwang.github.io/2017/08/11/basic-shell/</id>
<published>2017-08-10T16:00:00.000Z</published>
<updated>2018-01-27T04:07:39.126Z</updated>
<content type="html"><![CDATA[<a id="more"></a><blockquote><p><strong>内容</strong></p><ul><li>使用多个命令</li><li>创建脚本文件</li><li>显示消息</li><li>使用变量</li><li>输入输出重定向</li><li>管道</li><li>数学运算</li><li>退出脚本</li></ul></blockquote><h3 id="使用多个命令"><a href="#使用多个命令" class="headerlink" title="使用多个命令"></a>使用多个命令</h3><p>如果多个命令一起使用,可以放在一行并用分号分隔。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~$ date; who</span><br><span class="line">2017年 07月 26日 星期三 09:53:43 CST</span><br><span class="line">wsx tty7 2017-07-26 09:48 (:0)</span><br></pre></td></tr></table></figure><h3 id="创建脚本文件"><a href="#创建脚本文件" class="headerlink" title="创建脚本文件"></a>创建脚本文件</h3><p>在创建脚本文件时,必须在文件的第一行指定要使用的shell,格式为:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br></pre></td></tr></table></figure><p>脚本文件的第一行中<code>#</code>后的惊叹号会告诉shell使用哪个shell来运行脚本(如果是其他编码语言脚本,像python,第一行类似)。</p><p>其他地方的<code>#</code>用作注释行。</p><p>添加名为<code>test1</code>的脚本文件,内容为:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># This script displays the date and who's logged on</span></span><br><span class="line">date</span><br><span class="line">who</span><br></pre></td></tr></table></figure><p>现在运行脚本,结果会是:</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx<span class="meta">@wsx</span>-<span class="string">ubuntu:</span>~/script_learn$ test1</span><br><span class="line">未找到 <span class="string">'test1'</span> 命令,您要输入的是否是:</span><br><span class="line"> 命令 <span class="string">'testr'</span> 来自于包 <span class="string">'python3-testrepository'</span> (main)</span><br><span class="line"> 命令 <span class="string">'testr'</span> 来自于包 <span class="string">'python-testrepository'</span> (universe)</span><br><span class="line"> 命令 <span class="string">'test'</span> 来自于包 <span class="string">'coreutils'</span> (main)</span><br><span class="line">test1:未找到命令</span><br></pre></td></tr></table></figure><p>我们现在需要做的是让bash shell能够找到我们的脚本文件。shell会通过<code>PATH</code>环境变量来查找命令,我们可以看看:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ echo $PATH</span><br><span class="line">/home/wsx/Anaconda/bin:/home/wsx/Anoconda/ENTER/bin:/usr/lib/jvm/default-java/bin:/home/wsx/bin:/home/wsx/Anaconda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin</span><br></pre></td></tr></table></figure><p>很显然,我们的文件没有在这些目录范围内。要让shell找到test1脚本,我们可以采取以下两种做法之一:</p><ul><li>将shell脚本文件所处的目录添加到<code>PATH</code>环境变量中;</li><li>在提示符中用绝对路径或相对路径来引用shell脚本文件。</li></ul><p>第二种方法比较简单,我们在这里试试:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test1</span><br><span class="line">bash: ./test1: 权限不够</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ll test1 # 发现权限不够,查看文件的权限</span><br><span class="line">-rw-rw-r-- 1 wsx wsx 73 7月 26 10:03 test1</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x test1 # 修改权限,添加可执行属性</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test1 # 成功运行脚本</span><br><span class="line">2017年 07月 26日 星期三 10:09:23 CST</span><br><span class="line">wsx tty7 2017-07-26 09:48 (:0)</span><br></pre></td></tr></table></figure><h3 id="显示消息"><a href="#显示消息" class="headerlink" title="显示消息"></a>显示消息</h3><p>在<code>echo</code>命令后面加上一个字符串,就能显示出这个文本字符串。这种方式可以添加自己的文本消息来告诉脚本用户脚本正在做什么。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ echo This is a test</span><br><span class="line">This is a test</span><br></pre></td></tr></table></figure><p>如果文本本身带有字符串,我们需要<strong>用单引号或双引号来划定文本字符串</strong>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ echo "Let's see if this'll work"</span><br><span class="line">Let's see if this'll work</span><br></pre></td></tr></table></figure><p>我们修改下之前的test1文件,增加消息显示:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> This script displays the date and who<span class="string">'s logged on</span></span></span><br><span class="line">echo The time and date are:</span><br><span class="line">date</span><br><span class="line">echo "Let's see who's logged into the system"</span><br><span class="line">who</span><br></pre></td></tr></table></figure><p>运行:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test1</span><br><span class="line">The time and date are:</span><br><span class="line">2017年 07月 26日 星期三 10:17:59 CST</span><br><span class="line">Let's see who's logged into the system</span><br><span class="line">wsx tty7 2017-07-26 09:48 (:0)</span><br></pre></td></tr></table></figure><p><strong>如果想把文本字符串和命令输出显示在同一行中</strong>,可以用<code>echo</code>语句的<code>-n</code>参数。需要在字符串的两侧加上引号,并且保证字符串尾部有一个空格(不然字符串和命令输出就粘连到一起了)。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> This script displays the date and who<span class="string">'s logged on</span></span></span><br><span class="line">echo -n "The time and date are: "</span><br><span class="line">date</span><br><span class="line">echo "Let's see who's logged into the system: "</span><br><span class="line">who</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 运行结果输出</span></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test1</span><br><span class="line">The time and date are: 2017年 07月 26日 星期三 10:24:04 CST</span><br><span class="line">Let's see who's logged into the system:</span><br><span class="line">wsx tty7 2017-07-26 09:48 (:0)</span><br></pre></td></tr></table></figure><h3 id="使用变量"><a href="#使用变量" class="headerlink" title="使用变量"></a>使用变量</h3><p>变量允许我们临时性地将信息存储在shell脚本中,以便和脚本中的其他命令一起使用。</p><p><strong>环境变量</strong></p><p>shell维护着一组环境变量,用来记录特定的系统信息。比如系统的名称、登录到系统上的用户名、用户的系统ID(也称为UID)、用户默认主目录以及shell查找程序的搜索路径。</p><p>使用<code>set</code>命令显示一份完整的当前环境变量列表。<code>env</code>与<code>printenv</code>命令都可以显示全局变量。(这些命令输出结果比较多,不展示了。之前关于环境变量的笔记有比较详细的描述。)</p><p>在环境变量名称之前加上美元符可以使用这些环境变量。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test2</span><br><span class="line"><span class="meta">#</span><span class="bash">! /bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> display user information from the system</span></span><br><span class="line">echo "User info for userid: $USER"</span><br><span class="line">echo UID: $UID</span><br><span class="line">echo HOME: $HOME</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x test2</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test2</span><br><span class="line">User info for userid: wsx</span><br><span class="line">UID: 1000</span><br><span class="line">HOME: /home/wsx</span><br></pre></td></tr></table></figure><p>可以想象的到,如果我们想要使用实际的美元符而不是引用变量,肯定会出问题。这时候我们需要在美元符前面加上<code>\</code>进行转义,以显示美元符本身。</p><p><strong>用户变量</strong></p><p>使用等号将值赋给用户变量。<strong>注意,在变量、等号和值之间不能出现空格!</strong>这个是初学者常见的一个问题,本人也非常不太适应这个。因为在其他语言中不区分等号两边的空格,相信接触过其他脚本的朋友们肯定有习惯打空格使代码美观的,这在bash shell中是万万行不通滴。</p><p>一个使用的例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test3</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> testing variables</span></span><br><span class="line">days=10</span><br><span class="line">guest="Katie"</span><br><span class="line">echo "$guest checked in $days days ago"</span><br><span class="line">days=5</span><br><span class="line">guest="Jessica"</span><br><span class="line">echo "$guest checked in $days days ago"</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x test3</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test3</span><br><span class="line">Katie checked in 10 days ago</span><br><span class="line">Jessica checked in 5 days ago</span><br></pre></td></tr></table></figure><p>变量每次被引用时,都会输出当前赋给它的值。重要的是要记住,引用一个变量值时需要使用美元符,而引用变量来对其进行赋值时则不需要使用美元符。</p><h3 id="命令替换"><a href="#命令替换" class="headerlink" title="命令替换"></a>命令替换</h3><p>shell脚本最有用的特性之一就是可以从命令输出中提取信息,并将其赋给变量。</p><p>有两种方法可以将命令输出赋给变量:</p><ul><li>反引号字符(`)</li><li>$()格式</li></ul><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">要么用一对反引号把整个命令行命令围起来:</span><br><span class="line">testing=`date`</span><br><span class="line">要么使用$()格式</span><br><span class="line">testing=<span class="variable">$(date)</span></span><br></pre></td></tr></table></figure><p>下面是一个例子,在脚本中通过命令替换获得当前日期并用它来生成唯一文件名:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test4</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> copy the /usr/bin directory listing to a <span class="built_in">log</span> file</span></span><br><span class="line">today=$(date +%y%m%d)</span><br><span class="line">ls /usr/bin -al > log.$today</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x test4</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test4</span><br></pre></td></tr></table></figure><h3 id="重定向输入和输出"><a href="#重定向输入和输出" class="headerlink" title="重定向输入和输出"></a>重定向输入和输出</h3><p>通过几个操作符进行重定向,我们可以将命令的结果输出到另外的位置(文件)。当然,重定向可以用于输入。</p><p><strong>输出重定向</strong></p><p>最基本的操作符是<code>></code>。比如我们想要输出命令结果到一个指定文件:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ date > test6</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ls -l test6</span><br><span class="line">-rw-rw-r-- 1 wsx wsx 43 7月 26 16:42 test6</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test6</span><br><span class="line">2017年 07月 26日 星期三 16:42:34 CST</span><br></pre></td></tr></table></figure><p>如果想要将命令的输出追加到已有文件中,需要用双大于号(>>)来追加数据。</p><p><strong>输入重定向</strong></p><p>输入重定向和输出重定向正好相反。输入重定向将文件的内容重定向到命令,而非将命令的输出重定向到文件。</p><p>使用的符号是小于号(<)。</p><blockquote><p>一种简单的记忆方法是:在命令行上,命令总是在左侧,而重定向符号“指向”数据流动的方向。小于号说明数据正在从输入文件流向命令。</p></blockquote><p>比如用wc命令检查文本的行数、词数和字节数。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ wc < test6</span><br><span class="line"> 1 6 43</span><br></pre></td></tr></table></figure><p>另一种输入重定向的方法是<strong>内联输入重定向</strong>。它无需使用文件进行重定向,只需要在命令行中指定用于输入重定向的数据即可。它使用的符号是远小于号(<<),除了这个符号,我们还需要指定一个文本标记用来划分输入数据的开始和结尾。任何字符串都可以作为文本标记,但在数据的开始和结尾文本标记必须一致。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ wc << EOF</span><br><span class="line"><span class="meta">></span><span class="bash"> <span class="built_in">test</span> string1</span></span><br><span class="line"><span class="meta">></span><span class="bash"> <span class="built_in">test</span> string2</span></span><br><span class="line"><span class="meta">></span><span class="bash"> <span class="built_in">test</span> string3</span></span><br><span class="line"><span class="meta">></span><span class="bash"> EOF</span></span><br><span class="line"> 3 6 39</span><br></pre></td></tr></table></figure><p>它的形式为:</p><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">command</span> << marker</span><br><span class="line"><span class="class"><span class="keyword">data</span></span></span><br><span class="line"><span class="title">marker</span></span><br></pre></td></tr></table></figure><h3 id="管道"><a href="#管道" class="headerlink" title="管道"></a>管道</h3><p>有时候需要将一个命令的输出作为另一个命令的输入。通过<code>|</code>符号分隔命令即可实现管道。</p><p>比如我想查看某个文件(test1)的前两行并进行排序,操作如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test1</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> This script displays the date and who<span class="string">'s logged on</span></span></span><br><span class="line">echo -n "The time and date are: "</span><br><span class="line">date</span><br><span class="line">echo "Let's see who's logged into the system: "</span><br><span class="line">who</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test1 | head -2 | sort</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> This script displays the date and who<span class="string">'s logged on</span></span></span><br></pre></td></tr></table></figure><p>管道的强大之处在于可以根据自己的需求灵活地组合和使用各种linux命令工具。这里只是一个简单的例子,要熟练掌握少不了平时多多研究和练习。</p><h3 id="执行数学运算"><a href="#执行数学运算" class="headerlink" title="执行数学运算"></a>执行数学运算</h3><p>对shell脚本来说,执行数学运算非常麻烦。有两种实现方式。</p><p><strong>expr</strong>命令</p><p><code>expr</code>命令允许在命令行上处理数学表达式,但是特别笨拙。(Bourne shell中)</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ exrpr 1 + 5</span><br><span class="line">未找到 'exrpr' 命令,您要输入的是否是:</span><br><span class="line"> 命令 'expr' 来自于包 'coreutils' (main)</span><br><span class="line">exrpr:未找到命令</span><br></pre></td></tr></table></figure><p>看到没有,那算了。它基本涉及的操作跟我们使用的其他语言是一致的。但是有些问题需要处理,像<code>*</code>是通配符,在运算是是做乘号处理的,需要进行转义。</p><p><strong>使用方括号</strong></p><p>bash shell提供了一种更简单的方法来执行数学表达式。在bash中,在将一个数学运算结果赋给某个变量时,可以用美元符和方括号($[operator])将数学表达式围起来。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ var1=$[1+5]</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ echo $var1</span><br><span class="line">6</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ var2=$[$var1+2]</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ echo $var2</span><br><span class="line">8</span><br></pre></td></tr></table></figure><p>这种方式不仅方便,而且因为在方括号内,不会让shell误解乘号或其他符号。</p><p>但bash shell计算有一个主要限制:<strong>它只支持整数运算!</strong></p><p><strong>浮点解决方案</strong></p><p>最常见的方案是用内建的bash计算器。它实际上是一门编程语言,它允许在命令行中输入浮点表达式,然后解释并计算该表达式,最后返回结果。bash计算器能够识别:</p><ul><li>数字(整数和浮点数)</li><li>变量(简单变量和数组)</li><li>注释(/<em> </em>/开始的行)</li><li>表达式</li><li>编程语句</li><li>函数</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ bc</span><br><span class="line">bc 1.06.95</span><br><span class="line">Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.</span><br><span class="line">This is free software with ABSOLUTELY NO WARRANTY.</span><br><span class="line">For details type `warranty'.</span><br><span class="line">12 * 5.4</span><br><span class="line">64.8</span><br><span class="line">3.156 * (3 + 5)</span><br><span class="line">25.248</span><br><span class="line">quit</span><br></pre></td></tr></table></figure><p>在脚本中使用bc</p><p>可以用命令替换运行bc命令,并将输出赋给一个变量。基本格式如下:</p><figure class="highlight gams"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">variable</span>=$(echo <span class="comment">"options; expression"</span> |<span class="comment"> bc)</span></span><br></pre></td></tr></table></figure><p>options设置变量,expression参数定义了通过bc执行的数学表达式。</p><p>看一个简单实例:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test9</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line">var1=$(echo "scale=4; 3.44/5" | bc)</span><br><span class="line">echo The answer is $var1</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x test9</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test9</span><br><span class="line">The answer is .6880</span><br></pre></td></tr></table></figure><p>这个例子将<code>scale</code>变量设置为四位小数,并在<code>expression</code>部分指定了特定的运算。</p><p>这个方法适用于较短的运算,但有时我们会涉及更多的数字。如果需要进行大量运算,在一个命令行中列出多个表达式就会有点麻烦。</p><p>这里有一个解决方法:使用内联输入重定向,将一个文件重定向到bc命令来处理。格式为:</p><figure class="highlight gauss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">variable=$(bc << <span class="built_in">EOF</span></span><br><span class="line">options</span><br><span class="line"><span class="built_in">statements</span></span><br><span class="line">expressions</span><br><span class="line"><span class="built_in">EOF</span>)</span><br></pre></td></tr></table></figure><p><code>EOF</code>文本字符串标识了内联重定向数据的起始。注意,仍然需要命令替换符号将bc命令的输出赋给变量。</p><p>下面是一个例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test10</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line">var1=10.46</span><br><span class="line">var2=43.67</span><br><span class="line">var3=33.2</span><br><span class="line">var4=71</span><br><span class="line"></span><br><span class="line">var5=$(bc <<EOF</span><br><span class="line">scale=4</span><br><span class="line">a1 = ( $var1 * $var2)</span><br><span class="line">b1 = ( $var3 * $var4)</span><br><span class="line">a1 + b1</span><br><span class="line">EOF</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">echo The final answer for this mess is $var5</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x test10</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test10</span><br><span class="line">The final answer for this mess is 2813.9882</span><br></pre></td></tr></table></figure><p>在普通的shell脚本中,数字默认当做字符串处理。这也是为什么我们脚本处理计算麻烦和我们需要特定的工具和方法来进行处理。一定要注意区分。</p><h3 id="退出脚本"><a href="#退出脚本" class="headerlink" title="退出脚本"></a>退出脚本</h3><p>前面运行的脚本都是命令执行完成,脚本自动结束。其实我们可以用更为优雅的方式告诉shell命令运行完成,因为每个命令都使用<strong>退出状态码(exit status)</strong>,它是一个0-255的整数值,我们可以捕获这个值并在脚本中使用。</p><p>Linux提供了一个专门的变量<code>$?</code>来保存上个已执行命令的退出状态码。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ date</span><br><span class="line">2017年 07月 27日 星期四 10:44:18 CST</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ echo $?</span><br><span class="line">0</span><br></pre></td></tr></table></figure><p>按照惯例,一个成功结束的命令的退出状态码是0。如果有错误,则显示一个正数值。</p><p>Linux错误退出状态码没有什么标准,但有一些参考:</p><table><thead><tr><th>状态码</th><th>描述</th></tr></thead><tbody><tr><td>0</td><td>命令成功结束</td></tr><tr><td>1</td><td>一般性未知错误</td></tr><tr><td>2</td><td>不适合的shell命令</td></tr><tr><td>126</td><td>命令不可执行</td></tr><tr><td>127</td><td>没找到命令</td></tr><tr><td>128</td><td>无效的退出参数</td></tr><tr><td>128+x</td><td>与Linux信号x相关的严重错误</td></tr><tr><td>130</td><td>通过Ctrl+C终止的命令</td></tr><tr><td>255</td><td>正常范围之外的退出状态码</td></tr></tbody></table><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ asfg</span><br><span class="line">未找到 'asfg' 命令,您要输入的是否是:</span><br><span class="line"> 命令 'asdfg' 来自于包 'aoeui' (universe)</span><br><span class="line">asfg:未找到命令</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ echo $?</span><br><span class="line">127</span><br></pre></td></tr></table></figure><p><strong>exit命令</strong></p><p>默认,shell脚本会以脚本最后的一个命令的退出状态码退出。</p><p>但是我们可以改变这种默认行为,返回自己的退出状态码。exit命令允许在脚本结束时指定一个状态退出码。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">wsx@wsx-ubuntu:~/script_learn$ cat test13</span><br><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> testing the <span class="built_in">exit</span> status</span></span><br><span class="line">var1=10</span><br><span class="line">var2=30</span><br><span class="line">var3=$[$var1 + $var2]</span><br><span class="line">echo The answer is $var3</span><br><span class="line">exit 5</span><br><span class="line"></span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ chmod u+x test13</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ ./test13</span><br><span class="line">The answer is 40</span><br><span class="line">wsx@wsx-ubuntu:~/script_learn$ echo $?</span><br><span class="line">5</span><br></pre></td></tr></table></figure><p>注意最大255,如果大于它,得到的是求模的结果(余数)。</p>]]></content>
<summary type="html">
<a id="more"></a>
<blockquote>
<p><strong>内容</strong></p>
<ul>
<li>使用多个命令</li>
<li>创建脚本文件</li>
<li>显示消息</li>
<li>使用变量</li>
<li>输入输出重定向</li>
</summary>
<category term="Linux杂烩" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/"/>
<category term="shell编程" scheme="https://shixiangwang.github.io/categories/Linux%E6%9D%82%E7%83%A9/shell%E7%BC%96%E7%A8%8B/"/>
<category term="bash shell" scheme="https://shixiangwang.github.io/tags/bash-shell/"/>
<category term="shell笔记" scheme="https://shixiangwang.github.io/tags/shell%E7%AC%94%E8%AE%B0/"/>
</entry>
</feed>