-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path0330regularex.html
1301 lines (1299 loc) · 112 KB
/
0330regularex.html
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
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="Author" content="VBird, 鸟哥">
<meta name="Description" content="介绍正规表示法的应用与相关技巧">
<title>鸟哥的 Linux 私房菜 -- 第十一章、正规表示法与文档格式化处理</title>
<style type="text/css">
</style>
<link href="./vbird_files/style_2013.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="tablearea">
<div class="mainarea">
<div class="block1">
<!-- 本文的档头部分 -->
<h1>第十一章、正规表示法与文档格式化处理</h1>
<div style="text-align:right">
<span class="text_history">最近更新日期:2015/07/14</span>
</div>
<!-- 本文的档头部分 -->
<div class="abstract">
<p>正规表示法 (Regular Expression, RE, 或称为常规表示法)是透过一些特殊字符的排列,用以『搜索/取代/删除』一列或多列文本字符串,
简单的说,正规表示法就是用在字符串的处理上面的一项『表达式』。正规表示法并不是一个工具程序,
而是一个字符串处理的标准依据,如果您想要以正规表示法的方式处理字符串,就得要使用支持正规表示法的工具程序才行,
这类的工具程序很多,例如 vi, sed, awk 等等。</p>
<p>正规表示法对于系统管理员来说实在是很重要!因为系统会产生很多的消息,这些消息有的重要有的仅是告知,
此时,管理员可以透过正规表示法的功能来将重要消息截取出来,并产生便于查阅的报表来简化管理流程。此外,
很多的套装软件也都支持正规表示法的分析,例如邮件服务器的过滤机制(过滤垃圾信件)就是很重要的一个例子。
所以,您最好要了解正规表示法的相关技能,在未来管理主机时,才能够更精简处理您的日常事务!</p>
<p>注:本章节用户需要多加练习,因为目前很多的套件都是使用正规表示法来达成其『过滤、分析』的目的,
为了未来主机管理的便利性,用户至少要能看的懂正规表示法的意义!</p>
</div>
<!-- 本文的链接区部分 -->
<div class="links">
<ul>
<li><a href="0330regularex.html#before">11.1 开始之前:什么是正规表示法</a></li>
<li><a href="0330regularex.html#basic_regexp">11.2 基础正规表示法</a>
<ul>
<li><a href="0330regularex.html#lang">11.2.1 语系对正规表示法的影响</a></li>
<li><a href="0330regularex.html#grep">11.2.2 grep 的一些高端选项</a></li>
<li><a href="0330regularex.html#basicre">11.2.3 基础正规表示法练习</a></li>
<li> <a href="0330regularex.html#basic_regexp_char">11.2.4 基础正规表示法字符汇整(characters)</a></li>
<li><a href="0330regularex.html#sed">11.2.5 sed 工具</a>:
<a href="0330regularex.html#sed_line_add">行的添加/删除</a>, <a href="0330regularex.html#sed_line_replace">行的取代/显示</a>,
<a href="0330regularex.html#sed_replace">搜索并取代</a>, <a href="0330regularex.html#sed_file">直接改档</a></li>
</ul></li>
<li><a href="0330regularex.html#extend">11.3 延伸正规表示法</a></li>
<li><a href="0330regularex.html#docs">11.4 文档的格式化与相关处理</a>
<ul>
<li><a href="0330regularex.html#printf">11.4.1 printf: 格式化打印</a></li>
<li><a href="0330regularex.html#awk">11.4.2 awk:好用的数据处理工具</a></li>
<li><a href="0330regularex.html#doc_compare">11.4.3 文件比对工具:</a>, <a href="0330regularex.html#diff">diff</a>, <a href="0330regularex.html#cmp">cmp</a>,
<a href="0330regularex.html#patch">patch</a></li>
<li><a href="0330regularex.html#pr">11.4.4 文件打印准备工具: pr</a></li>
</ul></li>
<li><a href="0330regularex.html#hint">11.5 重点回顾</a></li>
<li><a href="0330regularex.html#ex">11.6 本章习题</a></li>
<li><a href="0330regularex.html#reference">11.7 参考数据与延伸阅读</a></li>
</ul>
</div>
</div>
<!-- 本文的正式部分 -->
<a id="before"></a>
<div class="block1"><div class="gototop"><a href="0330regularex.html#top">Top</a></div>
<h2>11.1 开始之前:什么是正规表示法</h2>
<p>约略了解了 Linux 的基本指令 (<a href="0320bash.html">BASH</a>) 并且熟悉了
<a href="0310vi.html">vim</a> 之后,相信你对于敲击键盘的打字与指令下达比较不陌生了吧?
接下来,底下要开始介绍一个很重要的观念,那就是所谓的『<span class="text_import2">正规表示法
(Regular Expression)</span>』啰!</p>
<div class="illus">
<ul><li><span class="text_import1">什么是正规表示法</span></li></ul>
<p>任何一个有经验的系统管理员,都会告诉你:『<span class="text_import2">正规表示法真是挺重要的</span>!』
为什么很重要呢?因为日常生活就使用的到啊!举个例子来说,
在你日常使用 <a href="0310vi.html">vim</a> 作文字处理或程序撰写时使用到的『搜索/取代』等等的功能,
这些举动要作的漂亮,就得要配合正规表示法来处理啰!</p>
<p class="text_import2">简单的说,正规表示法就是处理字符串的方法,他是以行为单位来进行字符串的处理行为,
正规表示法透过一些特殊符号的辅助,可以让用户轻易的达到『搜索/删除/取代』某特定字符串的处理进程!</p>
<p>举例来说,我只想找到 VBird(前面两个大写字符) 或 Vbird(仅有一个大写字符) 这个字样,但是不要其他的字符串 (例如 VBIRD, vbird
等不需要),该如何办理?如果在没有正规表示法的环境中(例如 MS word),你或许就得要使用忽略大小写的办法,
或者是分别以 VBird 及 Vbird 搜索两遍。但是,忽略大小写可能会搜索到 VBIRD/vbird/VbIrD 等等的不需要的字符串而造成困扰。</p>
<p>再举个系统常见的例子好了,假设你发现系统在开机的时候,老是会出现一个关于 mail 程序的错误,
而开机过程的相关进程都是在 /lib/systemd/system/ 底下,也就是说,在该目录底下的某个文件内具有 mail
这个关键字,你想要将该文件捉出来进行查找修改的动作。此时你怎么找出来含有这个关键字的文件?
你当然可以一个文件一个文件的开启,然后去搜索 mail 这个关键字,只是.....该目录底下的文件可能不止 100 个说~
如果了解正规表示法的相关技巧,那么只要一行指令就找出来啦:『grep 'mail' /lib/systemd/system/*』
那个 grep 就是支持正规表示法的工具程序之一!如何~很简单吧!</p>
<p>谈到这里就得要进一步说明了,<span class="text_import2">正规表示法基本上是一种『表示法』,
只要工具程序支持这种表示法,那么该工具程序就可以用来作为正规表示法的字符串处理之用。</span>
例如 vi, grep, awk ,sed 等等工具,因为她们有支持正规表示法,
所以,这些工具就可以使用正规表示法的特殊字符来进行字符串的处理。但例如 cp, ls 等指令并未支持正规表示法,
所以就只能使用 <a href="0320bash.html#settings_wildcard">bash 自己本身的通配符</a>而已。</p>
<ul><li><span class="text_import1">正规表示法对于系统管理员的用途</span></li></ul>
<p>那么为何我需要学习正规表示法呢?对于一般用户来说,由于使用到正规表示法的机会可能不怎么多,
因此感受不到他的魅力,不过,对于身为系统管理员的你来说,<span class="text_import2">正规表示法则是一个『不可不学的好东西!</span>』
怎么说呢?由于系统如果在繁忙的情况之下,每天产生的消息信息会多到你无法想像的地步,
而我们也都知道,系统的『<a href="0570syslog.html">错误消息注册表案 (第十八章)</a>』
的内容记载了系统产生的所有消息,当然,这包含你的系统是否被『入侵』的记录数据。</p>
<p>但是系统的数据量太大了,要身为系统管理员的你每天去看这么多的消息数据,
从千百行的数据里面找出一行有问题的消息,呵呵~光是用肉眼去看,想不疯掉都很难!
这个时候,我们就可以透过『正规表示法』的功能,将这些登录的信息进行处理,
仅取出『有问题』的信息来进行分析,哈哈!如此一来,你的系统管理工作将会
『快乐得不得了』啊!当然,正规表示法的优点还不止于此,等你有一定程度的了解之后,你会爱上他喔!</p>
<ul><li><span class="text_import1">正规表示法的广泛用途</span></li></ul>
<p>正规表示法除了可以让系统管理员管理主机更为便利之外,事实上,由于正规表示法强大的字符串处理能力,
目前一堆软件都支持正规表示法呢!最常见的就是『邮件服务器』啦!</p>
<p>如果你留意互联网上的消息,那么应该不难发现,目前造成网络大塞车的主因之一就是『垃圾/广告信件』了,
而如果我们可以在服务器端,就将这些问题邮件剔除的话,用户端就会减少很多不必要的带宽耗损了。
那么如何剔除广告信件呢?由于广告信件几乎都有一定的标题或者是内容,因此,
只要每次有来信时,都先将来信的标题与内容进行特殊字符串的比对,发现有不良信件就予以剔除!
嘿!这个工作怎么达到啊?就使用正规表示法啊!目前两大邮件服务器软件 sendmail 与 postfix
以及支持邮件服务器的相关分析软件,都支持正规表示法的比对功能!</p>
<p>当然还不止于此啦,很多的服务器软件都支持正规表示法呢!当然,
虽然各家软件都支持他,不过,这些『字符串』的比对还是需要系统管理员来加入比对规则的,
所以啦!身为系统管理员的你,为了自身的工作以及用户端的需求,
正规表示法实在是很需要也很值得学习的一项工具呢!</p>
<ul><li><span class="text_import1">正规表示法与 Shell 在 Linux 当中的角色定位</span></li></ul>
<p>说实在的,我们在学数学的时候,一个很重要、但是粉难的东西是一定要『背』的,
那就是九九乘法表,背成功了之后,未来在数学应用的路途上,真是一帆风顺啊!
这个九九乘法表我们在小学的时候几乎背了一整年才背下来,并不是这么好背的呢!
但他却是基础当中的基础!你现在一定受惠相当的多呢 ^_^!</p>
<p>而我们谈到的这个正规表示法,与前一章的 <a href="0320bash.html">BASH</a>
就有点像是数学的九九乘法表一样,是 Linux 基础当中的基础,虽然也是最难的部分,
不过,如果学成了之后,一定是『大大的有帮助』的!这就好像是金庸小说里面的学武难关:任督二脉!
打通任督二脉之后,武功立刻成倍成长!所以啦,
不论是对于系统的认识与系统的管理部分,他都有很棒的辅助啊!请好好的学习这个基础吧! ^_^</p>
<ul><li><span class="text_import1">延伸的正规表示法</span></li></ul>
<p>唔!正规表示法还有分喔?没错喔!<span class="text_import2">正规表示法的字符串表示方式依照不同的严谨度而分为:
基础正规表示法与延伸正规表示法</span>。延伸型正规表示法除了简单的一组字符串处理之外,还可以作群组的字符串处理,
例如进行搜索 VBird 或 netman 或 lman 的搜索,注意,是『或(or)』而不是『和(and)』的处理,
此时就需要延伸正规表示法的帮助啦!借由特殊的『 ( 』与『 | 』等字符的协助,
就能够达到这样的目的!不过,我们在这里主力仅是介绍最基础的基础正规表示法而已啦!好啦!清清脑门,咱们用功去啰!</p>
</div>
<fieldset class="vbirdface"><legend style="font-family: serif; font-size:12pt; color: darkblue;">Tips</legend><img src="./vbird_files/vbird_face.gif" alt="鸟哥的图标" title="鸟哥的图标" style="float: right;"> 有一点要向大家报告的,那就是:『<b>正规表示法与通配符是完全不一样的东西!</b>』
这很重要喔!因为『通配符 (wildcard) 代表的是 bash 操作接口的一个功能』,但正规表示法则是一种字符串处理的表示方式!
这两者要分的很清楚才行喔!所以,学习本章,请将前一章 bash 的通配符意义先忘掉吧!<br><br>
老实说,鸟哥以前刚接触正规表示法时,老想着要将这两者归纳在一起,结果就是...错误认知一大堆~
所以才会建议您学习本章先忘记通配符再来学习吧!
</fieldset><br></div>
<a id="basic_regexp"></a>
<div class="block1">
<h2>11.2 基础正规表示法</h2>
<p>既然正规表示法是处理字符串的一种表示方式,那么<span class="text_import2">对字符排序有影响的语系数据就会对正规表示法的结果有影响</span>!
此外,正规表示法也需要支持工具程序来辅助才行!所以,我们这里就先介绍一个最简单的字符串截取功能的工具程序,那就是 grep 啰!
前一章已经介绍过 grep 的相关选项与参数,本章着重在较高端的 grep 选项说明啰!
介绍完 grep 的功能之后,就进入正规表示法的特殊字符的处理能力了。</p>
<a id="lang"></a>
<div class="block2"><div class="gototop"><a href="0330regularex.html#top">Top</a></div>
<h2>11.2.1 语系对正规表示法的影响</h2>
<p>为什么语系的数据会影响到正规表示法的输出结果呢?我们在<a href="0105computers.html#data_word">第零章计算机概论的文本编码系统</a>里面谈到,文件其实记录的仅有 0 与
1,我们看到的字符文本与数字都是透过编码表转换来的。由于不同语系的编码数据并不相同,所以就会造成数据截取结果的差异了。
举例来说,在英文大小写的编码顺序中,zh_TW.big5 及 C 这两种语系的输出结果分别如下:</p>
<ul class="text_import2" style="font-family:'细明体'">
<li>LANG=C 时:0 1 2 3 4 ... A B C D ... Z a b c d ...z</li>
<li>LANG=zh_TW 时:0 1 2 3 4 ... a A b B c C d D ... z Z</li>
</ul>
<p>上面的顺序是编码的顺序,我们可以很清楚的发现这两种语系明显就是不一样!如果你想要截取大写字符而使用 [A-Z] 时,
会发现 LANG=C 确实可以仅捉到大写字符 (因为是连续的) ,但是如果 LANG=zh_TW.big5 时,就会发现到,
连同小写的 b-z 也会被截取出来!因为就编码的顺序来看, big5 语系可以截取到『 A b B c C ... z Z 』这一堆字符哩!
所以,<span class="text_import2">使用正规表示法时,需要特别留意当时环境的语系为何,
否则可能会发现与别人不相同的截取结果</span>喔!</p>
<p>由于一般我们在练习正规表示法时,使用的是兼容于 POSIX 的标准,因此就使用『 C 』这个语系(<a href="0330regularex.html#ps1">注1</a>)!
因此,<span class="text_import2">底下的很多练习都是使用『 LANG=C 』这个语系数据来进行</span>的喔!
另外,为了要避免这样编码所造成的英文与数字的截取问题,因此有些特殊的符号我们得要了解一下的!
这些符号主要有底下这些意义:(<a href="0330regularex.html#ps1">注1</a>)</p>
<table class="news" style="width: 95%; font-family: 'Courier New', '细明体'">
<tbody><tr class="theader"><td>特殊符号</td><td>代表意义</td></tr>
<tr><td>[:alnum:]</td><td>代表英文大小写字符及数字,亦即 0-9, A-Z, a-z</td></tr>
<tr><td>[:alpha:]</td><td>代表任何英文大小写字符,亦即 A-Z, a-z</td></tr>
<tr><td>[:blank:]</td><td>代表空白键与 [Tab] 按键两者</td></tr>
<tr><td>[:cntrl:]</td><td>代表键盘上面的控制按键,亦即包括 CR, LF, Tab, Del.. 等等</td></tr>
<tr><td>[:digit:]</td><td>代表数字而已,亦即 0-9</td></tr>
<tr><td>[:graph:]</td><td>除了空白字符 (空白键与 [Tab] 按键) 外的其他所有按键</td></tr>
<tr><td>[:lower:]</td><td>代表小写字符,亦即 a-z</td></tr>
<tr><td>[:print:]</td><td>代表任何可以被打印出来的字符</td></tr>
<tr><td>[:punct:]</td><td>代表标点符号 (punctuation symbol),亦即:" ' ? ! ; : # $...</td></tr>
<tr><td>[:upper:]</td><td>代表大写字符,亦即 A-Z</td></tr>
<tr><td>[:space:]</td><td>任何会产生空白的字符,包括空白键, [Tab], CR 等等</td></tr>
<tr><td>[:xdigit:]</td><td>代表 16 进位的数字类型,因此包括: 0-9, A-F, a-f 的数字与字符</td></tr>
</tbody></table>
<p>尤其上表中的<span class="text_import2">[:alnum:], [:alpha:], [:upper:], [:lower:], [:digit:]</span>
这几个一定要知道代表什么意思,因为他要比 a-z 或 A-Z 的用途要确定的很!好了,底下就让我们开始来玩玩高端版的 grep 吧!</p>
<br></div><br>
<a id="grep"></a>
<div class="block2"><div class="gototop"><a href="0330regularex.html#top">Top</a></div>
<h2>11.2.2 grep 的一些高端选项</h2>
<p>我们在<a href="0320bash.html#grep">第十章 BASH 里面的 grep </a>谈论过一些基础用法,
但其实 grep 还有不少的高端用法喔!底下我们仅列出较高端的 grep 选项与参数给大家参考,
<a href="0320bash.html#grep">基础的 grep 用法</a>请参考前一章的说明啰!</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep [-A] [-B] [--color=auto] '搜索字符串' filename</span>
<span class="term_say">选项与参数:
-A :后面可加数字,为 after 的意思,除了列出该行外,后续的 n 行也列出来;
-B :后面可加数字,为 befer 的意思,除了列出该行外,前面的 n 行也列出来;
--color=auto 可将正确的那个截取数据列出颜色</span>
<span class="term_hd">范例一:用 dmesg 列出核心消息,再以 grep 找出内含 qxl 那行</span>
[dmtsai@study ~]$ <span class="term_command">dmesg | grep 'qxl'</span>
[ 0.522749] [drm] qxl: 16M of VRAM memory size
[ 0.522750] [drm] qxl: 63M of IO pages memory ready (VRAM domain)
[ 0.522750] [drm] qxl: 32M of Surface memory size
[ 0.650714] fbcon: qxldrmfb (fb0) is primary device
[ 0.668487] qxl 0000:00:02.0: fb0: qxldrmfb frame buffer device
<span class="term_say"># dmesg 可列出核心产生的消息!包括硬件侦测的流程也会显示出来。
# 鸟哥使用的显卡是 QXL 这个虚拟卡,透过 grep 来 qxl 的相关信息,可发现如上信息。</span>
<span class="term_hd">范例二:承上题,要将捉到的关键字显色,且加上行号来表示:</span>
[dmtsai@study ~]$ <span class="term_command">dmesg | grep -n --color=auto 'qxl'</span>
515:[ 0.522749] [drm] <span class="term_write">qxl</span>: 16M of VRAM memory size
516:[ 0.522750] [drm] <span class="term_write">qxl</span>: 63M of IO pages memory ready (VRAM domain)
517:[ 0.522750] [drm] <span class="term_write">qxl</span>: 32M of Surface memory size
529:[ 0.650714] fbcon: <span class="term_write">qxl</span>drmfb (fb0) is primary device
539:[ 0.668487] <span class="term_write">qxl</span> 0000:00:02.0: fb0: <span class="term_write">qxl</span>drmfb frame buffer device
<span class="term_say"># 除了 qxl 会有特殊颜色来表示之外,最前面还有行号喔!其实颜色显示已经是缺省在 alias 当中了!</span>
<span class="term_hd">范例三:承上题,在关键字所在行的前两行与后三行也一起捉出来显示</span>
[dmtsai@study ~]$ <span class="term_command">dmesg | grep -n -A3 -B2 --color=auto 'qxl'</span>
<span class="term_say"># 你会发现关键字之前与之后的数行也被显示出来!这样可以让你将关键字前后数据捉出来进行分析啦!</span>
</pre></td></tr></tbody></table>
<p>grep 是一个很常见也很常用的指令,他最重要的功能就是进行字符串数据的比对,然后将符合用户需求的字符串打印出来。
需要说明的是『<span class="text_import2">grep 在数据中查寻一个字符串时,是以 "整行"
为单位来进行数据的截取的!</span>』也就是说,假如一个文件内有 10
行,其中有两行具有你所搜索的字符串,则将那两行显示在屏幕上,其他的就丢弃了!</p>
<p>在 CentOS 7 当中,缺省已经将 --color=auto 加入在 alias 当中了!用户就可以直接使用有关键字显色的 grep 啰!非常方便!</p>
<br></div><br>
<a id="basicre"></a>
<div class="block2"><div class="gototop"><a href="0330regularex.html#top">Top</a></div>
<h2>11.2.3 基础正规表示法练习</h2>
<p>要了解正规表示法最简单的方法就是由实际练习去感受啦!所以在汇整正规表示法特殊符号前,
我们先以底下这个文件的内容来进行正规表示法的理解吧!先说明一下,底下的练习大前提是:</p>
<ul class="text_import2">
<li>语系已经使用『 export LANG=C; export LC_ALL=C 』的设置值;</li>
<li>grep 已经使用 alias 设置成为『 grep --color=auto 』</li>
</ul>
<p>至于本章的练习用文件请由底下的链接来下载。需要特别注意的是,底下这个文件是鸟哥在 MS Windows 系统下编辑的,
并且已经特殊处理过,因此,他虽然是纯文本档,但是内含一些 Windows
系统下的软件常常自行加入的一些特殊字符,例如断行字符 (^M) 就是一例!
所以,你可以直接将底下的文本以 vi 保存成 regular_express.txt 这个文件,
不过,还是比较建议直接点底下的链接:</p>
<blockquote style="font-family:'细明体'">
<a href="0330regularex/regular_express.txt">0330regularex/regular_express.txt</a></blockquote>
<p>如果你的 Linux 可以直接连上 Internet 的话,那么使用如下的指令来捉取即可:</p>
<blockquote>
wget 0330regularex/regular_express.txt
</blockquote>
<p>至于这个文件的内容如下:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">vi regular_express.txt</span>
"Open Source" is a good mechanism to develop programs.
apple is my favorite food.
Football game is not use feet only.
this dress doesn't fit me.
However, this dress is about $ 3183 dollars.^M
GNU is free air not free beer.^M
Her hair is very beauty.^M
I can't finish the test.^M
Oh! The soup taste good.^M
motorcycle is cheap than car.
This window is clear.
the symbol '*' is represented as start.
Oh! My god!
The gd software is a library for drafting programs.^M
You are the best is mean you are the no. 1.
The world <Happy> is the same with "glad".
I like dog.
google is the best tools for search keyword.
goooooogle yes!
go! go! Let's go.
# I am VBird
</pre></td></tr></tbody></table>
<p>这文件共有 22 行,最底下一行为空白行!现在开始我们一个案例一个案例的来介绍吧!</p>
<ul class="toplist"><li>例题一、搜索特定字符串</li></ul>
<p>搜索特定字符串很简单吧?假设我们要从刚刚的文件当中取得 the 这个特定字符串,最简单的方式就是这样:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n 'the' regular_express.txt</span>
8:I can't finish <span class="term_write">the</span> test.
12:<span class="term_write">the</span> symbol '*' is represented as start.
15:You are <span class="term_write">the</span> best is mean you are <span class="term_write">the</span> no. 1.
16:The world <Happy> is <span class="term_write">the</span> same with "glad".
18:google is <span class="term_write">the</span> best tools for search keyword.
</pre></td></tr></tbody></table>
<p>那如果想要『<span class="text_import2">反向选择</span>』呢?也就是说,当该行没有
'the' 这个字符串时才显示在屏幕上,那就直接使用:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -vn 'the' regular_express.txt</span>
</pre></td></tr></tbody></table>
<p>你会发现,屏幕上出现的行列为除了 8,12,15,16,18 五行之外的其他行列!
接下来,如果你想要取得不论大小写的 the 这个字符串,则:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -in 'the' regular_express.txt</span>
8:I can't finish <span class="term_write">the</span> test.
9:Oh! <span class="term_write">The</span> soup taste good.
12:<span class="term_write">the</span> symbol '*' is represented as start.
14:<span class="term_write">The</span> gd software is a library for drafting programs.
15:You are <span class="term_write">the</span> best is mean you are <span class="term_write">the</span> no. 1.
16:<span class="term_write">The</span> world <Happy> is <span class="term_write">the</span> same with "glad".
18:google is <span class="term_write">the</span> best tools for search keyword.
</pre></td></tr></tbody></table>
<p>除了多两行 (9, 14行) 之外,第 16 行也多了一个 The 的关键字被截取到喔!</p>
<ul class="toplist"><li>例题二、利用中括号 [] 来搜索集合字符</li></ul>
<p>如果我想要搜索 test 或 taste 这两个单字时,可以发现到,其实她们有共通的 't?st'
存在~这个时候,我可以这样来搜索:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n 't[ae]st' regular_express.txt</span>
8:I can't finish the <span class="term_write">test</span>.
9:Oh! The soup <span class="term_write">tast</span>e good.
</pre></td></tr></tbody></table>
<p>了解了吧?<span class="text_import2">其实 [] 里面不论有几个字符,他都仅代表某『一个』字符</span>,
所以,上面的例子说明了,我需要的字符串是『tast』或『test』两个字符串而已!
而如果想要搜索到有 oo 的字符时,则使用:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n 'oo' regular_express.txt</span>
1:"Open Source" is a g<span class="term_write">oo</span>d mechanism to develop programs.
2:apple is my favorite f<span class="term_write">oo</span>d.
3:F<span class="term_write">oo</span>tball game is not use feet only.
9:Oh! The soup taste g<span class="term_write">oo</span>d.
18:g<span class="term_write">oo</span>gle is the best t<span class="term_write">oo</span>ls for search keyword.
19:g<span class="term_write">oooooo</span>gle yes!
</pre></td></tr></tbody></table>
<p>但是,如果我不想要 oo 前面有 g 的话呢?此时,可以利用在集合字符的反向选择 [^] 来达成:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n '[^g]oo' regular_express.txt</span>
2:apple is my favorite <span class="term_write">foo</span>d.
3:<span class="term_write">Foo</span>tball game is not use feet only.
18:google is the best <span class="term_write">too</span>ls for search keyword.
19:g<span class="term_write">oooooo</span>gle yes!
</pre></td></tr></tbody></table>
<p>意思就是说,我需要的是 oo ,但是 oo 前面不能是 g 就是了!仔细比较上面两个表格,你会发现,第
1,9 行不见了,因为 oo 前面出现了 g 所致!第 2,3 行没有疑问,因为 foo 与 Foo 均可被接受!但是第 18
行明明有 google 的 goo 啊~别忘记了,因为该行后面出现了 tool 的 too 啊!所以该行也被列出来~
也就是说, 18 行里面虽然出现了我们所不要的项目 (goo) 但是由于有需要的项目 (too) ,
因此,是符合字符串搜索的喔!</p>
<p>至于第 19 行,同样的,因为 goooooogle 里面的 oo 前面可能是 o ,例如:
go(ooo)oogle ,所以,这一行也是符合需求的!</p>
<p>再来,假设我 oo 前面不想要有小写字符,所以,我可以这样写 [^abcd....z]oo ,
但是这样似乎不怎么方便,由于小写字符的 ASCII 上编码的顺序是连续的,
因此,我们可以将之简化为底下这样:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n '[^a-z]oo' regular_express.txt</span>
3:<span class="term_write">Foo</span>tball game is not use feet only.
</pre></td></tr></tbody></table>
<p>也就是说,当我们在一组集合字符中,如果该字符组是连续的,例如大写英文/小写英文/数字等等,
就可以使用[a-z],[A-Z],[0-9]等方式来书写,那么如果我们的要求字符串是数字与英文呢?
呵呵!就将他全部写在一起,变成:[a-zA-Z0-9]。例如,我们要取得有数字的那一行,就这样:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n '[0-9]' regular_express.txt</span>
5:However, this dress is about $ <span class="term_write">3183</span> dollars.
15:You are the best is mean you are the no. <span class="term_write">1</span>.
</pre></td></tr></tbody></table>
<p>但由于考虑到语系对于编码顺序的影响,因此除了连续编码使用减号『 - 』之外,
你也可以使用如下的方法来取得前面两个测试的结果:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n '[^[:lower:]]oo' regular_express.txt</span>
<span class="term_say"># 那个 [:lower:] 代表的就是 a-z 的意思!请参考前两小节的说明表格</span>
[dmtsai@study ~]$ <span class="term_command">grep -n '[[:digit:]]' regular_express.txt</span>
</pre></td></tr></tbody></table>
<p>啥?上头在写啥东西呢?不要害怕!分开来瞧一瞧。我们知道 [:lower:] 就是 a-z 的意思,那么 [<span class="text_import2">a-z</span>] 当然就是
[<span class="text_import2">[:lower:]</span>] 啰!鸟哥第一次接触正规表示法的时候,看到两层中括号差点昏倒~完全看不懂!现在,请注意那个叠代的意义,
自然就能够比较清楚了解啰!</p>
<p>这样对于 [] 以及 [^] 以及 [] 当中的 - ,还有关于前面表格提到的特殊关键字有了解了吗?^_^!</p>
<ul class="toplist"><li>例题三、行首与行尾字符 ^ $</li></ul>
<p>我们在例题一当中,可以查找到一行字符串里面有 the 的,那如果我想要让 the 只在行首列出呢?
这个时候就得要使用定位字符了!我们可以这样做:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n '^the' regular_express.txt</span>
12:<span class="term_write">the</span> symbol '*' is represented as start.
</pre></td></tr></tbody></table>
<p>此时,就只剩下第 12 行,因为只有第 12 行的行首是 the 开头啊~此外,
如果我想要开头是小写字符的那一行就列出呢?可以这样:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n '^[a-z]' regular_express.txt</span>
2:<span class="term_write">a</span>pple is my favorite food.
4:<span class="term_write">t</span>his dress doesn't fit me.
10:<span class="term_write">m</span>otorcycle is cheap than car.
12:<span class="term_write">t</span>he symbol '*' is represented as start.
18:<span class="term_write">g</span>oogle is the best tools for search keyword.
19:<span class="term_write">g</span>oooooogle yes!
20:<span class="term_write">g</span>o! go! Let's go.
</pre></td></tr></tbody></table>
<p>你可以发现我们可以捉到第一个字符都不是大写的!上面的指令也可以用如下的方式来取代的:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n '^[[:lower:]]' regular_express.txt</span>
</pre></td></tr></tbody></table>
<p>好!那如果我不想要开头是英文本母,则可以是这样:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n '^[^a-zA-Z]' regular_express.txt</span>
1:<span class="term_write">"</span>Open Source" is a good mechanism to develop programs.
21:<span class="term_write">#</span> I am VBird
<span class="term_say"># 指令也可以是: grep -n '^[^[:alpha:]]' regular_express.txt</span>
</pre></td></tr></tbody></table>
<p>注意到了吧?<span class="text_import2">那个 ^ 符号,在字符集合符号(括号[])之内与之外是不同的!
在 [] 内代表『反向选择』,在 [] 之外则代表定位在行首的意义</span>!要分清楚喔!
反过来思考,那如果我想要找出来,行尾结束为小数点 (.) 的那一行,该如何处理:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n '\.$' regular_express.txt</span>
1:"Open Source" is a good mechanism to develop programs<span class="term_write">.</span>
2:apple is my favorite food<span class="term_write">.</span>
3:Football game is not use feet only<span class="term_write">.</span>
4:this dress doesn't fit me<span class="term_write">.</span>
10:motorcycle is cheap than car<span class="term_write">.</span>
11:This window is clear<span class="term_write">.</span>
12:the symbol '*' is represented as start<span class="term_write">.</span>
15:You are the best is mean you are the no. 1<span class="term_write">.</span>
16:The world <Happy> is the same with "glad"<span class="term_write">.</span>
17:I like dog<span class="term_write">.</span>
18:google is the best tools for search keyword<span class="term_write">.</span>
20:go! go! Let's go<span class="term_write">.</span>
</pre></td></tr></tbody></table>
<p>特别注意到,因为小数点具有其他意义(底下会介绍),所以必须要使用跳脱字符(\)来加以解除其特殊意义!
不过,你或许会觉得奇怪,但是第 5~9 行最后面也是 . 啊~怎么无法打印出来?
这里就牵涉到 Windows 平台的软件对于断行字符的判断问题了!我们使用 cat -A 将第五行拿出来看,
你会发现:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">cat -An regular_express.txt | head -n 10 | tail -n 6</span>
5 However, this dress is about $ 3183 dollars.<span class="term_write">^M</span>$
6 GNU is free air not free beer.<span class="term_write">^M</span>$
7 Her hair is very beauty.<span class="term_write">^M</span>$
8 I can't finish the test.<span class="term_write">^M</span>$
9 Oh! The soup taste good.<span class="term_write">^M</span>$
10 motorcycle is cheap than car.$
</pre></td></tr></tbody></table>
<p>我们在<a href="0310vi.html#tips_dos">第九章内谈到过断行字符</a>在 Linux 与 Windows 上的差异,
在上面的表格中我们可以发现 5~9 行为 Windows 的断行字符 (^M$) ,而正常的 Linux 应该仅有第 10 行显示的那样 ($)
。所以啰,那个 . 自然就不是紧接在 $ 之前喔!也就捉不到 5~9 行了!这样可以了解 ^ 与 $ 的意义吗?
好了,先不要看底下的解答,自己想一想,那么如果我想要找出来,哪一行是『空白行』,
也就是说,该行并没有输入任何数据,该如何搜索?</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n '^$' regular_express.txt</span>
22:
</pre></td></tr></tbody></table>
<p>因为只有行首跟行尾 (^$),所以,这样就可以找出空白行啦!再来,假设你已经知道在一个程序脚本
(shell script) 或者是设置档当中,空白行与开头为 # 的那一行是注解,因此如果你要将数据列出给别人参考时,
可以将这些数据省略掉以节省保贵的纸张,那么你可以怎么作呢?
我们以 /etc/rsyslog.conf 这个文件来作范例,你可以自行参考一下输出的结果:</p>
<a id="blank_line"></a>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">cat -n /etc/rsyslog.conf</span>
<span class="term_say"># 在 CentOS 7 中,结果可以发现有 91 行的输出,很多空白行与 # 开头的注解行</span>
[dmtsai@study ~]$ <span class="term_command">grep -v '^$' /etc/rsyslog.conf | grep -v '^#'</span>
<span class="term_say"># 结果仅有 14 行,其中第一个『 -v '^$' 』代表『不要空白行』,
# 第二个『 -v '^#' 』代表『不要开头是 # 的那行』喔!</span>
</pre></td></tr></tbody></table>
<p>是否节省很多版面啊?另外,你可能也会问,那为何不要出现 # 的符号的那行就直接舍弃呢?没办法!因为某些注解是与设置写在同一行的后面,
如果你只是抓 # 就予以去除,那就会将某些设置也同时移除了!那错误就大了~</p>
<ul class="toplist"><li>例题四、任意一个字符 . 与重复字符 *</li></ul>
<p>在<a href="0320bash.html">第十章 bash</a> 当中,我们知道<a href="0320bash.html#settings_wildcard">通配符 *</a> 可以用来代表任意(0或多个)字符,
但是<span class="text_import2">正规表示法并不是通配符</span>,两者之间是不相同的!
至于正规表示法当中的『 . 』则代表『绝对有一个任意字符』的意思!这两个符号在正规表示法的意义如下:</p>
<ul class="text_import2" style="font-family:'细明体'">
<li>. (小数点):代表『一定有一个任意字符』的意思;</li>
<li><a id="20100416"></a>* (星星号):代表『重复前一个字符, 0 到无穷多次』的意思,为组合形态</li>
</ul>
<p>这样讲不好懂,我们直接做个练习吧!假设我需要找出 g??d 的字符串,亦即共有四个字符,
起头是 g 而结束是 d ,我可以这样做:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n 'g..d' regular_express.txt</span>
1:"Open Source" is a <span class="term_write">good</span> mechanism to develop programs.
9:Oh! The soup taste <span class="term_write">good</span>.
16:The world <Happy> is the same with "<span class="term_write">glad</span>".
</pre></td></tr></tbody></table>
<p>因为强调 g 与 d 之间一定要存在两个字符,因此,第 13 行的 god 与第 14 行的 gd
就不会被列出来啦!再来,如果我想要列出有 oo, ooo, oooo 等等的数据,
也就是说,至少要有两个(含) o 以上,该如何是好?是 o* 还是 oo* 还是 ooo* 呢?
虽然你可以试看看结果, 不过结果太占版面了 @_@ ,所以,我这里就直接说明。</p>
<p>因为 * 代表的是『<span class="text_import2">重复 0 个或多个前面的 RE 字符</span>』的意义,
因此,<span class="text_import2">『o*』代表的是:『拥有空字符或一个 o 以上的字符』</span>,
特别注意,因为允许空字符(就是有没有字符都可以的意思),因此,『 <span class="text_import2">
grep -n 'o*' regular_express.txt</span> 』将会把所有的数据都打印出来屏幕上!</p>
<p>那如果是『oo*』呢?则第一个 o 肯定必须要存在,第二个 o 则是可有可无的多个 o ,
所以,凡是含有 o, oo, ooo, oooo 等等,都可以被列出来~</p>
<p>同理,当我们需要『至少两个 o 以上的字符串』时,就需要 ooo* ,亦即是:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n 'ooo*' regular_express.txt</span>
1:"Open Source" is a g<span class="term_write">oo</span>d mechanism to develop programs.
2:apple is my favorite f<span class="term_write">oo</span>d.
3:F<span class="term_write">oo</span>tball game is not use feet only.
9:Oh! The soup taste g<span class="term_write">oo</span>d.
18:g<span class="term_write">oo</span>gle is the best t<span class="term_write">oo</span>ls for search keyword.
19:g<span class="term_write">oooooo</span>gle yes!
</pre></td></tr></tbody></table>
<p>这样理解 * 的意义了吗?好了,现在出个练习,如果我想要字符串开头与结尾都是 g,但是两个 g
之间仅能存在至少一个 o ,亦即是 gog, goog, gooog.... 等等,那该如何?</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n 'goo*g' regular_express.txt</span>
18:<span class="term_write">goog</span>le is the best tools for search keyword.
19:<span class="term_write">goooooog</span>le yes!
</pre></td></tr></tbody></table>
<p>如此了解了吗?再来一题,如果我想要找出 g 开头与 g 结尾的字符串,当中的字符可有可无,那该如何是好?是『g*g』吗?</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n 'g*g' regular_express.txt</span>
1:"Open Source" is a <span class="term_write">g</span>ood mechanism to develop pro<span class="term_write">g</span>rams.
3:Football <span class="term_write">g</span>ame is not use feet only.
9:Oh! The soup taste <span class="term_write">g</span>ood.
13:Oh! My <span class="term_write">g</span>od!
14:The <span class="term_write">g</span>d software is a library for draftin<span class="term_write">g</span> pro<span class="term_write">g</span>rams.
16:The world <Happy> is the same with "<span class="term_write">g</span>lad".
17:I like do<span class="term_write">g</span>.
18:<span class="term_write">g</span>oo<span class="term_write">g</span>le is the best tools for search keyword.
19:<span class="term_write">g</span>oooooo<span class="term_write">g</span>le yes!
20:<span class="term_write">g</span>o! <span class="term_write">g</span>o! Let's <span class="term_write">g</span>o.
</pre></td></tr></tbody></table>
<p>但测试的结果竟然出现这么多行?太诡异了吧?其实一点也不诡异,因为 g*g 里面的 g* 代表『空字符或一个以上的 g』
在加上后面的 g ,因此,整个 RE 的内容就是 g, gg, ggg, gggg ,
因此,只要该行当中拥有一个以上的 g 就符合所需了!</p>
<p>那该如何得到我们的 g....g 的需求呢?呵呵!就利用任意一个字符『.』啊!
亦即是:『g.*g』的作法,因为 * 可以是 0 或多个重复前面的字符,而 . 是任意字符,所以:
『<span class="text_import2">.* 就代表零个或多个任意字符</span>』的意思啦!</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n 'g.*g' regular_express.txt</span>
1:"Open Source" is a <span class="term_write">good mechanism to develop prog</span>rams.
14:The <span class="term_write">gd software is a library for drafting prog</span>rams.
18:<span class="term_write">goog</span>le is the best tools for search keyword.
19:<span class="term_write">goooooog</span>le yes!
20:<span class="term_write">go! go! Let's g</span>o.
</pre></td></tr></tbody></table>
<p>因为是代表 g 开头与 g 结尾,中间任意字符均可接受,所以,第 1, 14, 20 行是可接受的喔!
这个 .* 的 RE 表示任意字符是很常见的,希望大家能够理解并且熟悉!
再出一题,如果我想要找出『任意数字』的行列呢?因为仅有数字,所以就成为:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n '[0-9][0-9]*' regular_express.txt</span>
5:However, this dress is about $ <span class="term_write">3183</span> dollars.
15:You are the best is mean you are the no. <span class="term_write">1</span>.
</pre></td></tr></tbody></table>
<p>虽然使用 grep -n '[0-9]' regular_express.txt 也可以得到相同的结果,
但鸟哥希望大家能够理解上面指令当中 RE 表示法的意义才好!</p>
<ul class="toplist"><li>例题五、限定连续 RE 字符范围 {}</li></ul>
<p>在上个例题当中,我们可以利用 . 与 RE 字符及 * 来设置 0 个到无限多个重复字符,
那如果我想要限制一个范围区间内的重复字符数呢?举例来说,我想要找出两个到五个 o
的连续字符串,该如何作?这时候就得要使用到限定范围的字符 {} 了。
但<span class="text_import2">因为 { 与 } 的符号在 shell 是有特殊意义的,因此,
我们必须要使用跳脱字符 \ 来让他失去特殊意义才行。</span>
至于 {} 的语法是这样的,假设我要找到两个 o 的字符串,可以是:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n 'o\{2\}' regular_express.txt</span>
1:"Open Source" is a g<span class="term_write">oo</span>d mechanism to develop programs.
2:apple is my favorite f<span class="term_write">oo</span>d.
3:F<span class="term_write">oo</span>tball game is not use feet only.
9:Oh! The soup taste g<span class="term_write">oo</span>d.
18:g<span class="term_write">oo</span>gle is the best t<span class="term_write">oo</span>ls for search keyword.
19:g<span class="term_write">oooooo</span>gle yes!
</pre></td></tr></tbody></table>
<p>这样看似乎与 ooo* 的字符没有什么差异啊?因为第 19 行有多个 o 依旧也出现了!
好,那么换个搜索的字符串,假设我们要找出 g 后面接 2 到 5 个 o ,然后再接一个 g 的字符串,他会是这样:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n 'go\{2,5\}g' regular_express.txt</span>
18:<span class="term_write">goog</span>le is the best tools for search keyword.
</pre></td></tr></tbody></table>
<p>嗯!很好!第 19 行终于没有被取用了(因为 19 行有 6 个 o 啊!)。
那么,如果我想要的是 2 个 o 以上的 goooo....g 呢?除了可以是 gooo*g ,也可以是:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">grep -n 'go\{2,\}g' regular_express.txt</span>
18:<span class="term_write">goog</span>le is the best tools for search keyword.
19:<span class="term_write">goooooog</span>le yes!
</pre></td></tr></tbody></table>
<p>呵呵!就可以找出来啦~</p>
<br></div><br>
<a id="basic_regexp_char"></a>
<div class="block2"><div class="gototop"><a href="0330regularex.html#top">Top</a></div>
<h2>11.2.4 基础正规表示法字符汇整 (characters)</h2>
<p>经过了上面的几个简单的范例,我们可以将基础的正规表示法特殊字符汇整如下:</p>
<table class="news" style="width: 95%; font-family: 'Courier New', '细明体'">
<tbody><tr class="theader"><td style="width: 15%">RE 字符</td><td>意义与范例</td></tr>
<tr><td class="tcenter">^word</td><td><span style="text-decoration:underline">意义:待搜索的字符串(word)在行首!</span><br>
范例:搜索行首为 # 开始的那一行,并列出行号
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
grep -n '^#' regular_express.txt</blockquote></td></tr>
<tr><td class="tcenter">word$</td><td><span style="text-decoration:underline">意义:待搜索的字符串(word)在行尾!</span><br>
范例:将行尾为 ! 的那一行打印出来,并列出行号
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
grep -n '!$' regular_express.txt</blockquote></td></tr>
<tr><td class="tcenter">.</td><td><span style="text-decoration:underline">意义:代表『一定有一个任意字符』的字符!</span><br>
范例:搜索的字符串可以是 (eve) (eae) (eee) (e e), 但不能仅有 (ee) !亦即 e 与 e 中间『一定』仅有一个字符,而空白字符也是字符!
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
grep -n 'e.e' regular_express.txt</blockquote></td></tr>
<tr><td class="tcenter">\</td><td><span style="text-decoration:underline">意义:跳脱字符,将特殊符号的特殊意义去除!</span><br>
范例:搜索含有单引号 ' 的那一行!
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
grep -n \' regular_express.txt</blockquote></td></tr>
<tr><td class="tcenter">*</td><td><span style="text-decoration:underline">意义:重复零个到无穷多个的前一个 RE 字符</span><br>
范例:找出含有 (es) (ess) (esss) 等等的字符串,注意,因为 * 可以是 0 个,所以 es 也是符合带搜索字符串。另外,因为 *
为重复『前一个 RE 字符』的符号, 因此,在 * 之前必须要紧接着一个 RE 字符喔!例如任意字符则为 『.*』 !
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
grep -n 'ess*' regular_express.txt</blockquote></td></tr>
<tr><td class="tcenter">[list]</td><td><span style="text-decoration:underline">意义:字符集合的 RE 字符,里面列出想要截取的字符!</span><br>
范例:搜索含有 (gl) 或 (gd) 的那一行,需要特别留意的是,在 [] 当中『谨代表一个待搜索的字符』,
例如『 a[afl]y 』代表搜索的字符串可以是 aay, afy, aly 即 [afl] 代表 a 或 f 或 l 的意思!
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
grep -n 'g[ld]' regular_express.txt</blockquote></td></tr>
<tr><td class="tcenter">[n1-n2]</td><td><span style="text-decoration:underline">意义:字符集合的 RE 字符,里面列出想要截取的字符范围!</span><br>
范例:搜索含有任意数字的那一行!需特别留意,在字符集合 [] 中的减号 -
是有特殊意义的,他代表两个字符之间的所有连续字符!但这个连续与否与 ASCII 编码有关,因此,你的编码需要设置正确(在
bash 当中,需要确定 LANG 与 LANGUAGE 的变量是否正确!) 例如所有大写字符则为 [A-Z]
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
grep -n '[A-Z]' regular_express.txt</blockquote></td></tr>
<tr><td class="tcenter">[^list]</td><td><span style="text-decoration:underline">意义:字符集合的 RE 字符,里面列出不要的字符串或范围!</span><br>
范例:搜索的字符串可以是 (oog) (ood) 但不能是 (oot) ,那个 ^ 在 [] 内时,代表的意义是『反向选择』的意思。
例如,我不要大写字符,则为 [^A-Z]。但是,需要特别注意的是,如果以 grep -n [^A-Z] regular_express.txt
来搜索,却发现该文件内的所有行都被列出,为什么?因为这个 [^A-Z] 是『非大写字符』的意思,
因为每一行均有非大写字符,例如第一行的 "Open Source" 就有 p,e,n,o.... 等等的小写字
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
grep -n 'oo[^t]' regular_express.txt</blockquote></td></tr>
<tr><td class="tcenter">\{n,m\}</td><td><span style="text-decoration:underline">意义:连续 n 到 m 个的『前一个 RE 字符』<br>
意义:若为 \{n\} 则是连续 n 个的前一个 RE 字符,<br>
意义:若是 \{n,\} 则是连续 n 个以上的前一个 RE 字符!</span>
范例:在 g 与 g 之间有 2 个到 3 个的 o 存在的字符串,亦即 (goog)(gooog)
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
grep -n 'go\{2,3\}g' regular_express.txt</blockquote></td></tr>
</tbody></table>
<p>再次强调:『<span class="text_import2">正规表示法的特殊字符』与一般在指令列输入指令的『通配符』并不相同</span>,
例如,在通配符当中的 * 代表的是『 0 ~ 无限多个字符』的意思,但是在正规表示法当中,
* 则是『重复 0 到无穷多个的前一个 RE 字符』的意思~使用的意义并不相同,不要搞混了!</p>
<p>举例来说,不支持正规表示法的 ls 这个工具中,若我们使用 『ls -l * 』
代表的是任意文件名的文件,而 『ls -l a* 』代表的是以 a 为开头的任何文件名的文件,
但在正规表示法中,我们要找到含有以 a 为开头的文件,则必须要这样:(需搭配支持正规表示法的工具)</p>
<blockquote style="font-family:'细明体'">ls | grep -n '^a.*' </blockquote>
<table class="exam" style="width: 90%"><tbody><tr><td>
例题:<div>
以 ls -l 配合 grep 找出 /etc/ 底下文件类型为链接档属性的文件名
</div>
答:<div>
由于 ls -l 列出链接档时标头会是『 lrwxrwxrwx 』,因此使用如下的指令即可找出结果:
<blockquote style="font-family:'细明体'">ls -l /etc | grep '^l'</blockquote>
若仅想要列出几个文件,再以『 |wc -l 』 来累加处理即可。
</div>
</td></tr></tbody></table><br>
<br></div><br>
<a id="sed"></a>
<div class="block2"><div class="gototop"><a href="0330regularex.html#top">Top</a></div>
<h2>11.2.5 sed 工具</h2>
<p>在了解了一些正规表示法的基础应用之后,再来呢?呵呵~两个东西可以玩一玩的,那就是 sed 跟底下会介绍的 awk 了!
这两个家伙可是相当的有用的啊!举例来说,鸟哥写的 <a href="0570syslog.html">logfile.sh 分析注册表的小程序</a>
(第十八章会谈到),绝大部分分析关键字的取用、统计等等,就是用这两个宝贝蛋来帮我完成的!那么你说,要不要玩一玩啊?^_^</p>
<p>我们先来谈一谈 sed 好了, sed 本身也是一个管线命令,可以分析 standard input 的啦!
而且 sed 还可以将数据进行取代、删除、添加、截取特定行等等的功能呢!很不错吧~
我们先来了解一下 sed 的用法,再来聊他的用途好了!</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">sed [-nefr] [动作]</span>
<span class="term_say">选项与参数:
-n :使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到屏幕上。
但如果加上 -n 参数后,则只有经过 sed 特殊处理的那一行(或者动作)才会被列出来。
-e :直接在指令列模式上进行 sed 的动作编辑;
-f :直接将 sed 的动作写在一个文件内, -f filename 则可以运行 filename 内的 sed 动作;
-r :sed 的动作支持的是延伸型正规表示法的语法。(缺省是基础正规表示法语法)
-i :直接修改读取的文件内容,而不是由屏幕输出。
动作说明: [n1[,n2]]function
n1, n2 :不见得会存在,一般代表『选择进行动作的行数』,举例来说,如果我的动作
是需要在 10 到 20 行之间进行的,则『 10,20[动作行为] 』
function 有底下这些咚咚:
a :添加, a 的后面可以接字符串,而这些字符串会在新的一行出现(目前的下一行)~
c :取代, c 的后面可以接字符串,这些字符串可以取代 n1,n2 之间的行!
d :删除,因为是删除啊,所以 d 后面通常不接任何咚咚;
i :插入, i 的后面可以接字符串,而这些字符串会在新的一行出现(目前的上一行);
p :打印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运作~
s :取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!
例如 1,20s/old/new/g 就是啦!</span>
</pre></td></tr></tbody></table>
<a id="sed_line_add"></a>
<ul class="toplist"><li>以行为单位的添加/删除功能</li></ul>
<p>sed 光是用看的是看不懂的啦!所以又要来练习了!先来玩玩删除与添加的功能吧!</p>
<table class="term"><tbody><tr><td class="term"><pre><span class="term_hd">范例一:将 /etc/passwd 的内容列出并且打印行号,同时,请将第 2~5 行删除!</span>
[dmtsai@study ~]$ <span class="term_command">nl /etc/passwd | sed '2,5d'</span>
1 root:x:0:0:root:/root:/bin/bash
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
<span class="term_say">.....(后面省略).....</span>
</pre></td></tr></tbody></table>
<p>看到了吧?sed 的动作为 '2,5d' ,那个 d 就是删除!因为 2-5 行给他删除了,所以显示的数据就没有 2-5 行啰~
另外,注意一下,原本应该是要下达 sed -e 才对,没有 -e 也行啦!同时也要注意的是, sed
后面接的动作,请务必以 '' 两个单引号括住喔!</p>
<p>如果题型变化一下,举例来说,如果只要删除第 2 行,可以使用『 nl /etc/passwd | sed '2d' 』来达成,
至于若是要删除第 3 到最后一行,则是『 nl /etc/passwd | sed '3,$d' 』的啦,那个<span class="text_import2">钱字号『 $ 』代表最后一行!</span></p>
<table class="term"><tbody><tr><td class="term"><pre><span class="term_hd">范例二:承上题,在第二行后(亦即是加在第三行)加上『drink tea?』字样!</span>
[dmtsai@study ~]$ <span class="term_command">nl /etc/passwd | sed '2a drink tea'</span>
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
drink tea
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
<span class="term_say">.....(后面省略).....</span>
</pre></td></tr></tbody></table>
<p>嘿嘿!在 a 后面加上的字符串就已将出现在第二行后面啰!那如果是要在第二行前呢?『
nl /etc/passwd | sed '2i drink tea' 』就对啦!就是将『 a 』变成『 i 』即可。
增加一行很简单,那如果是要增将两行以上呢?</p>
<table class="term"><tbody><tr><td class="term"><pre><span class="term_hd">范例三:在第二行后面加入两行字,例如『Drink tea or .....』与『drink beer?』</span>
[dmtsai@study ~]$ <span class="term_command">nl /etc/passwd | sed '2a Drink tea or ......\</span>
> <span class="term_command">drink beer ?'</span>
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
Drink tea or ......
drink beer ?
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
<span class="term_say">.....(后面省略).....</span>
</pre></td></tr></tbody></table>
<p>这个范例的重点是『我们可以添加不只一行喔!可以添加好几行』但是每一行之间都必须要以反斜线『 \
』来进行新行的增加喔!所以,上面的例子中,我们可以发现在第一行的最后面就有 \ 存在啦!在多行添加的情况下, \ 是一定要的喔!</p>
<a id="sed_line_replace"></a>
<ul class="toplist"><li>以行为单位的取代与显示功能</li></ul>
<p>刚刚是介绍如何添加与删除,那么如果要整行取代呢?看看底下的范例吧:</p>
<table class="term"><tbody><tr><td class="term"><pre><span class="term_hd">范例四:我想将第2-5行的内容取代成为『No 2-5 number』呢?</span>
[dmtsai@study ~]$ <span class="term_command">nl /etc/passwd | sed '2,5c No 2-5 number'</span>
1 root:x:0:0:root:/root:/bin/bash
No 2-5 number
6 sync:x:5:0:sync:/sbin:/bin/sync
<span class="term_say">.....(后面省略).....</span>
</pre></td></tr></tbody></table>
<p>透过这个方法我们就能够将数据整行取代了!非常容易吧!sed 还有更好用的东东!我们以前想要列出第 11~20 行,
得要透过『head -n 20 | tail -n 10』之类的方法来处理,很麻烦啦~
sed 则可以简单的直接取出你想要的那几行!是透过行号来捉的喔!看看底下的范例先:</p>
<table class="term"><tbody><tr><td class="term"><pre><span class="term_hd">范例五:仅列出 /etc/passwd 文件内的第 5-7 行</span>
[dmtsai@study ~]$ <span class="term_command">nl /etc/passwd | sed -n '5,7p'</span>
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
</pre></td></tr></tbody></table>
<p>上述的指令中有个重要的选项『 -n 』,按照说明文档,这个 -n 代表的是『安静模式』!
那么为什么要使用安静模式呢?你可以自行下达 sed '5,7p' 就知道了 (5-7 行会重复输出)!
有没有加上 -n 的参数时,输出的数据可是差很多的喔!你可以透过这个 sed 的以行为单位的显示功能,
就能够将某一个文件内的某些行号捉出来查阅!很棒的功能!不是吗?</p>
<a id="sed_replace"></a>
<ul class="toplist"><li>部分数据的搜索并取代的功能</li></ul>
<p>除了整行的处理模式之外, sed 还可以用行为单位进行部分数据的搜索并取代的功能喔!
基本上 sed 的搜索与取代的与 vi 相当的类似!他有点像这样:</p>
<table class="term"><tbody><tr><td class="term"><pre><span class="term_command">sed 's/</span>要被取代的字符串<span class="term_command">/</span>新的字符串<span class="term_command">/g'</span>
</pre></td></tr></tbody></table>
<p>上表中特殊字体的部分为关键字,请记下来!至于三个斜线分成两栏就是新旧字符串的替换啦!
我们使用底下这个取得 IP 数据的范例,一段一段的来处理给您瞧瞧,让你了解一下什么是咱们所谓的搜索并取代吧!</p>
<table class="term"><tbody><tr><td class="term"><pre><span class="term_hd">步骤一:先观察原始消息,利用 /sbin/ifconfig 查找 IP 为何?</span>
[dmtsai@study ~]$ <span class="term_command">/sbin/ifconfig eth0</span>
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
<span class="term_write">inet 192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255</span>
inet6 fe80::5054:ff:fedf:e174 prefixlen 64 scopeid 0x20<link>
ether 52:54:00:df:e1:74 txqueuelen 1000 (Ethernet)
<span class="term_say">.....(以下省略).....
# 因为我们还没有讲到 IP ,这里你先有个概念即可啊!我们的重点在第二行,
# 也就是 192.168.1.100 那一行而已!先利用关键字捉出那一行!</span>
<span class="term_hd">步骤二:利用关键字配合 grep 截取出关键的一行数据</span>
[dmtsai@study ~]$ <span class="term_command">/sbin/ifconfig eth0 | grep 'inet '</span>
<span class="term_write">inet </span>192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255
<span class="term_say"># 当场仅剩下一行!要注意, CentOS 7 与 CentOS 6 以前的 ifconfig 指令输出结果不太相同,
# 鸟哥这个范例主要是针对 CentOS 7 以后的喔!接下来,我们要将开始到 addr: 通通删除,
# 就是像底下这样:
# <span style="text-decoration: line-through">inet </span>192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255
# 上面的删除关键在于『 ^.*inet 』啦!正规表示法出现! ^_^</span>
<span class="term_hd">步骤三:将 IP 前面的部分予以删除</span>
[dmtsai@study ~]$ <span class="term_command">/sbin/ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g'</span>
192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255
<span class="term_say"># 仔细与上个步骤比较一下,前面的部分不见了!接下来则是删除后续的部分,亦即:
192.168.1.100<span style="text-decoration: line-through"> netmask 255.255.255.0 broadcast 192.168.1.255</span>
# 此时所需的正规表示法为:『 ' *netmask.*$ 』就是啦!</span>
<span class="term_hd">步骤四:将 IP 后面的部分予以删除</span>
[dmtsai@study ~]$ <span class="term_command">/sbin/ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g' \</span>
> <span class="term_command"> | sed 's/ *netmask.*$//g'</span>
192.168.1.100
</pre></td></tr></tbody></table>
<p>透过这个范例的练习也建议您依据此一步骤来研究你的指令!就是先观察,然后再一层一层的试做,
如果有做不对的地方,就先予以修改,改完之后测试,成功后再往下继续测试。以鸟哥上面的介绍中,
那一大串指令就做了四个步骤!对吧! ^_^</p>
<p>让我们再来继续研究 sed 与正规表示法的配合练习!假设我只要 MAN 存在的那几行数据,
但是含有 # 在内的注解我不想要,而且空白行我也不要!此时该如何处理呢?可以透过这几个步骤来实作看看:</p>
<table class="term"><tbody><tr><td class="term"><pre><span class="term_hd">步骤一:先使用 grep 将关键字 MAN 所在行取出来</span>
[dmtsai@study ~]$ <span class="term_command">cat /etc/man_db.conf | grep 'MAN'</span>
# MANDATORY_MANPATH manpath_element
# MANPATH_MAP path_element manpath_element
# MANDB_MAP global_manpath [relative_catpath]
# every automatically generated MANPATH includes these fields
<span class="term_say">....(后面省略)....</span>
<span class="term_hd">步骤二:删除掉注解之后的数据!</span>
[dmtsai@study ~]$ <span class="term_command">cat /etc/man_db.conf | grep 'MAN'| sed 's/#.*$//g'</span>
MANDATORY_MANPATH /usr/man
<span class="term_say">....(后面省略)....
# 从上面可以看出来,原本注解的数据都变成空白行啦!所以,接下来要删除掉空白行</span>
[dmtsai@study ~]$ <span class="term_command">cat /etc/man_db.conf | grep 'MAN'| sed 's/#.*$//g' | sed '/^$/d'</span>
MANDATORY_MANPATH /usr/man
MANDATORY_MANPATH /usr/share/man
MANDATORY_MANPATH /usr/local/share/man
<span class="term_say">....(后面省略)....</span>
</pre></td></tr></tbody></table>
<a id="sed_file"></a>
<ul class="toplist"><li>直接修改文件内容(危险动作)</li></ul>
<p>你以为 sed 只有这样的能耐吗?那可不! sed 甚至可以直接修改文件的内容呢!而不必使用管线命令或数据流重导向!
不过,由于这个动作会直接修改到原始的文件,所以请你千万不要随便拿系统设置档来测试喔!
我们还是使用你下载的 regular_express.txt 文件来测试看看吧!</p>
<table class="term"><tbody><tr><td class="term"><pre><span class="term_hd">范例六:利用 sed 将 regular_express.txt 内每一行结尾若为 . 则换成 !</span>
[dmtsai@study ~]$ <span class="term_command">sed -i 's/\.$/\!/g' regular_express.txt</span>
<span class="term_say"># 上头的 -i 选项可以让你的 sed 直接去修改后面接的文件内容而不是由屏幕输出喔!
# 这个范例是用在取代!请您自行 cat 该文件去查阅结果啰!</span>
<span class="term_hd">范例七:利用 sed 直接在 regular_express.txt 最后一行加入『# This is a test』</span>
[dmtsai@study ~]$ <span class="term_command">sed -i '$a # This is a test' regular_express.txt</span>
<span class="term_say"># 由于 $ 代表的是最后一行,而 a 的动作是添加,因此该文件最后添加啰!</span>
</pre></td></tr></tbody></table>
<p>sed 的『 -i 』选项可以直接修改文件内容,这功能非常有帮助!举例来说,如果你有一个 100 万行的文件,你要在第
100 行加某些文本,此时使用 vim 可能会疯掉!因为文件太大了!那怎办?就利用 sed 啊!透过 sed
直接修改/取代的功能,你甚至不需要使用 vim 去修订!很棒吧!</p>
<p>总之,这个 sed 不错用啦!而且很多的 shell script 都会使用到这个指令的功能~
sed 可以帮助系统管理员管理好日常的工作喔!要仔细的学习呢!</p>
<br></div>
</div>
<a id="extend"></a>
<div class="block1"><div class="gototop"><a href="0330regularex.html#top">Top</a></div>
<h2>11.3 延伸正规表示法</h2>
<p>事实上,一般读者只要了解基础型的正规表示法大概就已经相当足够了,不过,某些时刻为了要简化整个指令操作,
了解一下使用范围更广的延伸型正规表示法的表达式会更方便呢!举个简单的例子好了,在上节的<a href="0330regularex.html#blank_line">例题三的最后一个例子</a>中,我们要去除空白行与行首为 # 的行列,使用的是</p>
<blockquote class="fontwidth">grep -v '^$' regular_express.txt | grep -v '^#'</blockquote>
<p>需要使用到管线命令来搜索两次!那么如果使用延伸型的正规表示法,我们可以简化为:</p>
<blockquote class="fontwidth">egrep -v '^$|^#' regular_express.txt</blockquote>
<p>延伸型正规表示法可以透过群组功能『 | 』来进行一次搜索!那个在单引号内的管线意义为『或 or』啦!
是否变的更简单呢?此外,grep 缺省仅支持基础正规表示法,如果要使用延伸型正规表示法,你可以使用 grep -E ,
不过更建议直接使用 egrep !直接区分指令比较好记忆!其实 egrep 与 grep -E 是类似命令别名的关系啦!</p>
<p>熟悉了正规表示法之后,到这个延伸型的正规表示法,你应该也会想到,不就是多几个重要的特殊符号吗? ^_^y
是的~所以,我们就直接来说明一下,延伸型正规表示法有哪几个特殊符号?由于底下的范例还是有使用到 regular_express.txt
,不巧的是刚刚我们可能将该文件修改过了 @_@,所以,请重新下载该文件来练习喔!</p>
<table class="news" style="width: 95%; font-family: 'Courier New', '细明体'">
<tbody><tr class="theader"><td style="width: 10%">RE 字符</td><td>意义与范例</td></tr>
<tr><td class="tcenter">+</td><td><span style="text-decoration:underline">意义:重复『一个或一个以上』的前一个 RE 字符</span><br>
范例:搜索 (god) (good) (goood)... 等等的字符串。 那个 o+ 代表『一个以上的 o 』所以,底下的运行成果会将第 1, 9, 13 行列出来。
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
egrep -n 'go+d' regular_express.txt</blockquote></td></tr>
<tr><td class="tcenter">?</td><td><span style="text-decoration:underline">意义:『零个或一个』的前一个 RE 字符</span><br>
范例:搜索 (gd) (god) 这两个字符串。 那个 o? 代表『空的或 1 个 o 』所以,上面的运行成果会将第 13, 14 行列出来。
有没有发现到,这两个案例( 'go+d' 与 'go?d' )的结果集合与 'go*d' 相同? 想想看,这是为什么喔! ^_^
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
egrep -n 'go?d' regular_express.txt</blockquote></td></tr>
<tr><td class="tcenter">|</td><td><span style="text-decoration:underline">意义:用或( or )的方式找出数个字符串</span><br>
范例:搜索 gd 或 good 这两个字符串,注意,是『或』! 所以,第 1,9,14 这三行都可以被打印出来喔!那如果还想要找出 dog 呢?
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
egrep -n 'gd|good' regular_express.txt<br>
egrep -n 'gd|good|dog' regular_express.txt</blockquote></td></tr>
<tr><td class="tcenter">()</td><td><span style="text-decoration:underline">意义:找出『群组』字符串</span><br>
范例:搜索 (glad) 或 (good) 这两个字符串,因为 g 与 d 是重复的,所以, 我就可以将 la 与 oo 列于 ( ) 当中,并以 | 来分隔开来,就可以啦!
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
egrep -n 'g(la|oo)d' regular_express.txt</blockquote></td></tr>
<tr><td class="tcenter">()+</td><td><span style="text-decoration:underline">意义:多个重复群组的判别</span><br>
范例:将『AxyzxyzxyzxyzC』用 echo 叫出,然后再使用如下的方法搜索一下!
<blockquote style="padding: 0 0 0 25px; margin: 0; color: #000088; font-weight:bolder">
echo 'AxyzxyzxyzxyzC' | egrep 'A(xyz)+C'</blockquote>
上面的例子意思是说,我要找开头是 A 结尾是 C ,中间有一个以上的 "xyz" 字符串的意思~</td></tr>
</tbody></table>
<p>以上这些就是延伸型的正规表示法的特殊字符。另外,要特别强调的是,那个 ! 在正规表示法当中并不是特殊字符,
所以,如果你想要查出来文件中含有 ! 与 > 的字行时,可以这样:</p>
<blockquote class="fontwidth">grep -n '[!>]' regular_express.txt</blockquote>
<p>这样可以了解了吗?常常看到有陷阱的题目写:『反向选择这样对否? '[!a-z]'?』,
呵呵!是错的呦~要 '[^a-z] 才是对的!至于更多关于正规表示法的高端文章,请参考文末的参考数据(<a href="0330regularex.html#ps2">注2</a>)</p>
</div>
<a id="docs"></a>
<div class="block1">
<h2>11.4 文档的格式化与相关处理</h2>
<p>接下来让我们来将文档进行一些简单的编排吧!底下这些动作可以将你的消息进行排版的动作,
不需要重新以 vim 去编辑,透过数据流重导向配合底下介绍的 printf 功能,以及 awk 指令,
就可以让你的消息以你想要的模样来输出了!试看看吧!</p>
<a id="printf"></a>
<div class="block2"><div class="gototop"><a href="0330regularex.html#top">Top</a></div>
<h2>11.4.1 格式化打印: printf</h2>
<p>在很多时候,我们可能需要将自己的数据给他格式化输出的!
举例来说,考试卷分数的输出,姓名与科目及分数之间,总是可以稍微作个比较漂亮的版面配置吧?
例如我想要输出底下的样式:</p>
<table class="term"><tbody><tr><td class="term"><pre>Name Chinese English Math Average
DmTsai 80 60 92 77.33
VBird 75 55 80 70.00
Ken 60 90 70 73.33
</pre></td></tr></tbody></table>
<p>上表的数据主要分成五个字段,各个字段之间可使用 tab 或空白键进行分隔。
请将上表的数据转存成为 printf.txt 文件名,等一下我们会利用这个文件来进行几个小练习的。
因为每个字段的原始数据长度其实并非是如此固定的 (Chinese 长度就是比 Name 要多),
而我就是想要如此表示出这些数据,此时,就得需要打印格式管理员 printf 的帮忙了!
printf 可以帮我们将数据输出的结果格式化,而且而支持一些特殊的字符~底下我们就来看看!</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">printf '打印格式' 实际内容</span>
<span class="term_say">选项与参数:
关于格式方面的几个特殊样式:
\a 警告声音输出
\b 倒退键(backspace)
\f 清除屏幕 (form feed)
\n 输出新的一行
\r 亦即 Enter 按键
\t 水平的 [tab] 按键
\v 垂直的 [tab] 按键
\xNN NN 为两位数的数字,可以转换数字成为字符。
关于 C 编程语言内,常见的变量格式
%ns 那个 n 是数字, s 代表 string ,亦即多少个字符;
%ni 那个 n 是数字, i 代表 integer ,亦即多少整数字数;
%N.nf 那个 n 与 N 都是数字, f 代表 floating (浮点),如果有小数字数,
假设我共要十个位数,但小数点有两位,即为 %10.2f 啰!</span>
</pre></td></tr></tbody></table>
<p>接下来我们来进行几个常见的练习。假设所有的数据都是一般文本 (这也是最常见的状态),因此最常用来分隔数据的符号就是
[Tab] 啦!因为 [Tab] 按键可以将数据作个整齐的排列!那么如何利用 printf 呢?参考底下这个范例:</p>
<table class="term"><tbody><tr><td class="term"><pre><span class="term_hd">范例一:将刚刚上头数据的文件 (printf.txt) 内容仅列出姓名与成绩:(用 [tab] 分隔)</span>
[dmtsai@study ~]$ <span class="term_command">printf '%s\t %s\t %s\t %s\t %s\t \n' $(cat printf.txt)</span>
Name Chinese English Math Average
DmTsai 80 60 92 77.33
VBird 75 55 80 70.00
Ken 60 90 70 73.33
</pre></td></tr></tbody></table>
<p>由于 printf 并不是管线命令,因此我们得要透过类似上面的功能,将文件内容先提出来给 printf 作为后续的数据才行。
如上所示,我们将每个数据都以 [tab] 作为分隔,但是由于 Chinese 长度太长,导致 English 中间多了一个 [tab]
来将数据排列整齐!啊~结果就看到数据对齐结果的差异了!</p>
<p>另外,在 printf 后续的那一段格式中,%s 代表一个不固定长度的字符串,而字符串与字符串中间就以 \t 这个 [tab]
分隔符号来处理!你要记得的是,由于 \t 与 %s 中间还有空格,因此每个字符串间会有一个 [tab] 与一个空白键的分隔喔!</p>
<p>既然每个字段的长度不固定会造成上述的困扰,那我将每个字段固定就好啦!没错没错!这样想非常好!
所以我们就将数据给他进行固定字段长度的设计吧!</p>
<table class="term"><tbody><tr><td class="term"><pre><span class="term_hd">范例二:将上述数据关于第二行以后,分别以字符串、整数、小数点来显示:</span>
[dmtsai@study ~]$ <span class="term_command">printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt | grep -v Name)</span>
DmTsai 80 60 92 77.33
VBird 75 55 80 70.00
Ken 60 90 70 73.33
</pre></td></tr></tbody></table>
<p>上面这一串格式想必您看得很辛苦!没关系!一个一个来解释!上面的格式共分为五个字段, %10s
代表的是一个长度为 10 个字符的字符串字段,%5i 代表的是长度为 5 个字符的数字字段,至于那个 %8.2f
则代表长度为 8 个字符的具有小数点的字段,其中小数点有两个字符宽度。我们可以使用底下的说明来介绍 %8.2f 的意义:</p>
<blockquote class="fontwidth">字符宽度: 12345678<br>%8.2f意义:00000.00</blockquote>
<p>如上所述,全部的宽度仅有 8 个字符,整数部分占有 5 个字符,小数点本身 (.) 占一位,小数点下的位数则有两位。
这种格式经常使用于数值程序的设计中!这样了解乎?自己试看看如果要将小数点位数变成 1 位又该如何处理?</p>
<p>printf 除了可以格式化处理之外,他还可以依据 ASCII 的数字与图形对应来显示数据喔(<a href="0330regularex.html#ps3">注3</a>)!
举例来说 16 进位的 45 可以得到什么 ASCII 的显示图 (其实是字符啦)?</p>
<table class="term"><tbody><tr><td class="term"><pre><span class="term_hd">范例三:列出 16 进位数值 45 代表的字符为何?</span>
[dmtsai@study ~]$ <span class="term_command">printf '\x45\n'</span>
E
<span class="term_say"># 这东西也很好玩~他可以将数值转换成为字符,如果你会写 script 的话,
# 可以自行测试一下,由 20~80 之间的数值代表的字符是啥喔! ^_^</span>
</pre></td></tr></tbody></table>
<p>printf 的使用相当的广泛喔!包括等一下后面会提到的 awk 以及在 C 编程语言当中使用的屏幕输出,
都是利用 printf 呢!鸟哥这里也只是列出一些可能会用到的格式而已,有兴趣的话,可以自行多作一些测试与练习喔! ^_^</p>
<fieldset class="vbirdface"><legend style="font-family: serif; font-size:12pt; color: darkblue;">Tips</legend><img src="./vbird_files/vbird_face.gif" alt="鸟哥的图标" title="鸟哥的图标" style="float: right;"> 打印格式化这个 printf 指令,乍看之下好像也没有什么很重要的~
不过,如果你需要自行撰写一些软件,需要将一些数据在屏幕上头漂漂亮亮的输出的话,
那么 printf 可也是一个很棒的工具喔!
</fieldset><br> </div><br>
<a id="awk"></a>
<div class="block2"><div class="gototop"><a href="0330regularex.html#top">Top</a></div>
<h2>11.4.2 awk:好用的数据处理工具</h2>
<p>awk 也是一个非常棒的数据处理工具!相较于 sed 常常作用于一整个行的处理, awk
则比较倾向于一行当中分成数个『字段』来处理。因此,awk 相当的适合处理小型的数据数据处理呢!awk
通常运作的模式是这样的:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">awk '条件类型1{动作1} 条件类型2{动作2} ...' filename</span>
</pre></td></tr></tbody></table>
<p>awk 后面接两个单引号并加上大括号 {} 来设置想要对数据进行的处理动作。
awk 可以处理后续接的文件,也可以读取来自前个指令的 standard output 。
但如前面说的, <span class="text_import2">awk 主要是处理『每一行的字段内的数据』,而缺省的『字段的分隔符号为
"空白键" 或 "[tab]键" 』</span>!举例来说,我们用 last 可以将登录者的数据取出来,结果如下所示:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">last -n 5</span> <span class="term_note"><==仅取出前五行</span>
dmtsai pts/0 192.168.1.100 Tue Jul 14 17:32 still logged in
dmtsai pts/0 192.168.1.100 Thu Jul 9 23:36 - 02:58 (03:22)
dmtsai pts/0 192.168.1.100 Thu Jul 9 17:23 - 23:36 (06:12)
dmtsai pts/0 192.168.1.100 Thu Jul 9 08:02 - 08:17 (00:14)
dmtsai tty1 Fri May 29 11:55 - 12:11 (00:15)
</pre></td></tr></tbody></table>
<p>若我想要取出帐号与登录者的 IP ,且帐号与 IP 之间以 [tab] 隔开,则会变成这样:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">last -n 5 | awk '{print $1 "\t" $3}'</span>
dmtsai 192.168.1.100
dmtsai 192.168.1.100
dmtsai 192.168.1.100
dmtsai 192.168.1.100
dmtsai Fri
</pre></td></tr></tbody></table>
<p>上表是 awk 最常使用的动作!透过 print 的功能将字段数据列出来!字段的分隔则以空白键或 [tab] 按键来隔开。
因为不论哪一行我都要处理,因此,就不需要有 "条件类型" 的限制!我所想要的是第一栏以及第三栏,
但是,第五行的内容怪怪的~这是因为数据格式的问题啊!所以啰~使用 awk
的时候,请先确认一下你的数据当中,如果是连续性的数据,请不要有空格或 [tab]
在内,否则,就会像这个例子这样,会发生误判喔!</p>
<p>另外,由上面这个例子你也会知道,在 awk 的括号内,<span class="text_import2">每一行的每个字段都是有变量名称的,那就是 $1, $2...
等变量名称</span>。以上面的例子来说, dmtsai 是 $1 ,因为他是第一栏嘛!至于 192.168.1.100 是第三栏,
所以他就是 $3 啦!后面以此类推~呵呵!还有个变量喔!那就是 $0 ,<span class="text_import2">$0
代表『一整列数据』的意思~</span>以上面的例子来说,第一行的 $0 代表的就是『dmtsai .... 』那一行啊!
由此可知,刚刚上面五行当中,整个 awk 的处理流程是:</p>
<ol class="text_import2">
<li>读入第一行,并将第一行的数据填入 $0, $1, $2.... 等变量当中;</li>
<li>依据 "条件类型" 的限制,判断是否需要进行后面的 "动作";</li>
<li>做完所有的动作与条件类型;</li>
<li>若还有后续的『行』的数据,则重复上面 1~3 的步骤,直到所有的数据都读完为止。</li>
</ol>
<p>经过这样的步骤,你会晓得, awk 是『<span class="text_import2">以行为一次处理的单位</span>』,
而『<span class="text_import2">以字段为最小的处理单位</span>』。好了,那么 awk
怎么知道我到底这个数据有几行?有几栏呢?这就需要 awk 的内置变量的帮忙啦~</p>
<table class="news" style="width: 75%">
<tbody><tr class="theader"><td>变量名称</td><td>代表意义</td></tr>
<tr><td class="tcenter">NF</td><td>每一行 ($0) 拥有的字段总数</td></tr>
<tr><td class="tcenter">NR</td><td>目前 awk 所处理的是『第几行』数据</td></tr>
<tr><td class="tcenter">FS</td><td>目前的分隔字符,缺省是空白键</td></tr>
</tbody></table><br>
<p>我们继续以上面 last -n 5 的例子来做说明,如果我想要:</p>
<ul>
<li>列出每一行的帐号(就是 $1);</li>
<li>列出目前处理的行数(就是 awk 内的 NR 变量)</li>
<li>并且说明,该行有多少字段(就是 awk 内的 NF 变量)</li></ul>
<p>则可以这样:</p>
<fieldset class="vbirdface"><legend style="font-family: serif; font-size:12pt; color: darkblue;">Tips</legend><img src="./vbird_files/vbird_face.gif" alt="鸟哥的图标" title="鸟哥的图标" style="float: right;"> 要注意喔,awk 后续的所有动作是以单引号『 ' 』括住的,由於单引号与双引号都必须是成对的,
所以, awk 的格式内容如果想要以 print 打印时,记得非变量的文本部分,包含上一小节
<a href="0330regularex.html#printf">printf</a> 提到的格式中,都需要使用双引号来定义出来喔!因为单引号已经是 awk 的指令固定用法了!
</fieldset><br>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">last -n 5| awk '{print $1 "\t lines: " NR "\t columns: " NF}'</span>
dmtsai lines: 1 columns: 10
dmtsai lines: 2 columns: 10
dmtsai lines: 3 columns: 10
dmtsai lines: 4 columns: 10
dmtsai lines: 5 columns: 9
<span class="term_say"># 注意喔,在 awk 内的 NR, NF 等变量要用大写,且不需要有钱字号 $ 啦!</span>
</pre></td></tr></tbody></table>
<p>这样可以了解 NR 与 NF 的差别了吧?好了,底下来谈一谈所谓的 "条件类型" 了吧!</p>
<ul class="toplist"><li>awk 的逻辑运算字符</li></ul>
<p>既然有需要用到 "条件" 的类别,自然就需要一些逻辑运算啰~例如底下这些:</p>
<table class="news" style="width:75%">
<tbody><tr class="theader"><td>运算单元</td><td>代表意义</td></tr>
<tr class="tcenter"><td style="font-family: 'Courier New', '细明体'">></td><td>大于</td></tr>
<tr class="tcenter"><td style="font-family: 'Courier New', '细明体'"><</td><td>小于</td></tr>
<tr class="tcenter"><td style="font-family: 'Courier New', '细明体'">>=</td><td>大于或等于</td></tr>
<tr class="tcenter"><td style="font-family: 'Courier New', '细明体'"><=</td><td>小于或等于</td></tr>
<tr class="tcenter"><td style="font-family: 'Courier New', '细明体'">==</td><td>等于</td></tr>
<tr class="tcenter"><td style="font-family: 'Courier New', '细明体'">!=</td><td>不等于</td></tr>
</tbody></table>
<p>值得注意的是那个『 <span style="font-family: 'Courier New', '细明体'">==</span> 』的符号,因为:</p>
<ul>
<li>逻辑运算上面亦即所谓的大于、小于、等于等判断式上面,习惯上是以『 <span style="font-family: 'Courier New', '细明体'">==</span> 』来表示;</li>
<li>如果是直接给予一个值,例如变量设置时,就直接使用 = 而已。</li>
</ul>
<p>好了,我们实际来运用一下逻辑判断吧!举例来说,在 /etc/passwd 当中是以冒号 ":" 来作为字段的分隔,
该文件中第一字段为帐号,第三字段则是 UID。那假设我要查阅,第三栏小于 10 以下的数据,并且仅列出帐号与第三栏,
那么可以这样做:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">cat /etc/passwd | awk '{FS=":"} $3 < 10 {print $1 "\t " $3}'</span>
root:x:0:0:root:/root:/bin/bash
bin 1
daemon 2
<span class="term_say">....(以下省略)....</span>
</pre></td></tr></tbody></table>
<p>有趣吧!不过,怎么第一行没有正确的显示出来呢?这是因为我们读入第一行的时候,那些变量 $1,
$2... 缺省还是以空白键为分隔的,所以虽然我们定义了 FS=":" 了,
但是却仅能在第二行后才开始生效。那么怎么办呢?我们可以预先设置 awk 的变量啊!
利用 BEGIN 这个关键字喔!这样做:</p>
<table class="term"><tbody><tr><td class="term"><pre>[dmtsai@study ~]$ <span class="term_command">cat /etc/passwd | awk 'BEGIN {FS=":"} $3 < 10 {print $1 "\t " $3}'</span>
root 0
bin 1
daemon 2
<span class="term_say">......(以下省略)......</span>
</pre></td></tr></tbody></table>
<p>很有趣吧!而除了 BEGIN 之外,我们还有 END 呢!另外,如果要用 awk 来进行『计算功能』呢?以底下的例子来看,
假设我有一个薪资数据表文件名为 pay.txt ,内容是这样的:</p>
<table class="term"><tbody><tr><td class="term"><pre>Name 1st 2nd 3th