-
Notifications
You must be signed in to change notification settings - Fork 15
/
11、正则表达式与格式化输出
609 lines (341 loc) · 14.1 KB
/
11、正则表达式与格式化输出
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
正则表达式分为 基础正则表达式(basic RE) 和 扩展正则表达式(extended RE)
对于 RE 的 可视化测试,可以进入 https://www.debuggex.com/ https://regex101.com
语系的设置对正则表达式输出的结果又很大的影响
因为不同语系对于字符的排列方式是不一样的
下面的一些符号将在不同的语系之下对应相同的字符,请注意
符号 含义
[:alnum:] 大小写英文字母 + 阿拉伯数字
[:alpha:] 大小写英文字母
[:blank:] 空格键 和 Tab 键
[:cntrl:] 键盘上的控制键,比如 CR, LF, Tab, Del 等等
[:digit:] 阿拉伯数字
[:graph:] 除了 空格键 和 Tab 键 之外的其他键
[:lower:] 小写英文字母
[:print:] 可以被打印出来的字符
[:punct:] 标点符号,比如 " ' ? ! ; : # $ 等等
[:upper:] 大写英文字母
[:space:] 会产生空白的字符,比如 空格键, Tab键, CR 等等
[:xdigit:] 十六位字符,包含 0-9 a-f A-F
grep 的进阶选项
grep [-AB] [--color=auto] <需匹配的字符串> [文件名]
-A <num> 将匹配所在位置向后(after)的 n 行也列出来
-B <num> 将匹配所在位置向前(before)的 n 行也列出来
-P, --perl-regexp (实验性质的)启用 Perl 形式的正则表达式
basic RE 的语法
[字符]
中括号:待搜索项 至少出现 一次 中括号中的 某个 字符
eg.
grep 't[ae]st'
搜寻 含有 tast 或者 test 的行
[^字符]
中括号里有 ^ :待搜索项里 不包含 括号中的 某个 字符
eg.
grep '[^g]oo'
那么不会标注 'google',但会标注 'gooogle',因为最后两个 o 的前面是 o ,不是 g
[字符1-字符2]
中括号里有 - :待搜索项里包含 字符1 至 字符2 之间的任意一个字符
eg.
grep '[a-z]' 等价于 grep '[abcdefghijklmnopqrstuvwxyz]'
注意这个匹配的最终内容和系统编码相关
上述内容最好写成
gerp '[[:lower:]]'
^ 和 - 可以连用
eg.
grep '[^[:lower:]]'
表示 去除仅有小写字母的行
^字符
在方括号外使用,表示后面的字符出现在行首
eg.
grep '^[^[:upper:]]'
表示去除 以大写英文字母开头的行
字符$
表示以字符作为行结尾
eg.
grep '\.$'
将搜索已英文句号为结尾的行
由于 英文句号 . 在 RE 中有特殊的含义,所以用 反斜线 \ 来进行转义
但要注意,由于 Windows 的换行符为 <CR><FL> 而 <CR> 在 Linux 表示为 ^M
所以用 Linux 处理 Windows 文件时,一定要注意每行是以 ^M 结尾的
.
有且只有一个 任意字符
eg.
grep 'g..d'
将选出 g 在 d 前,且两者之间恰好含有两个字符的行
字符*
该字符重复 0 至 任意多次
eg.
grep 'o*'
看起来这将选择连续出现 o 这个字符的行,实际上会选择所有行
因为 * 可以匹配 0 次重复
正确写法
grep 'oo*'
首先匹配含有一个 o 的字符,紧跟其后的是 0 个或多个 o
RE 中 通配符 的写法
eg.
如果要匹配类似 g*****g 这样的内容
grep 'g*g'
这种写法是错误的,这可以匹配 g gg ggg gggg ggggg ... 这种写法
正确的写法是
grep 'g.*g'
这种写法表示:以g开头,以g结尾,且中间含有 0 个 或 多个 未知字符 的写法
默认为 greedy search,也就是尽量长的匹配可能匹配上的内容,若要改为 lazy search,则连用问号 ? 如下所示
grep -P 'g.*?g'
字符{最少重复次数,最多重复次数}
匹配 n 至 m 次重复的字符
eg.
grep 'o\{2\}' 匹配两个 o,注意 花括号 {} 在 Bash 中有特殊含义,所以要用转义字符转义
grep '[^o]o{2}[^o]' 匹配有且仅有两个 o 连续出现
grep 'o\{2,5\}' 匹配 2 至 5 个 o 连续出现
grep 'o\{2,\}' 匹配 2 至 无穷多个 o 连续出现
RE 中的仅匹配不选中(前后查找/前后断言)
eg.
仅当字母 a 前的字符为 b 的时候才匹配 a
(?<=b)a
仅当字母 a 前的字符不为 b 的时候才匹配 a
(?<!b)a
仅当字母 a 后的字符为 b 的时候才匹配 a
a(?=b)
仅当字母 a 后的字符不为 b 的时候才匹配 a
a(?!b)
sed 数据的查找、替换、删除、添加
sed [-En] '<command>' [文件1] [文件2]...
sed [-En] [[-e '<command1>'] [-e '<command2>']...] [-f command_file] [-i extension] [文件1] [文件2]...
默认情况下,sed 从 StdOut 写出数据,也接受来自 StdIn 的数据
-E 使用 Extended RE,而不是 Basic RE 作为 command 中正则表达式的语法
-n 仅将的确被改动的行输出到 StdOut,默认会将所有行输出到 StdOut
-e 后面可以接多个 -e 参数来添加不同的 command
-f 将 command 写入一个文件中,通过这个参数调用文件来执行命令
-i 就地写入(in_place) 将修改写入原文件,若指定了 extension,则原文件的备份将使用 extension 作为扩展名
command 部分的
整个 command 部分必须用 单引号 包括起来
address1,[address2] function[arguments]
address1 address2 限定操作的行数
function 包含如下命令
a <string> 【add】在当前操作行的下一行插入 <string>
c <string> 【change】用 <string> 取代 address1 address2 之间行的内容
d 【delete】删除当前操作行
i <string> 【insert】在当前操作行的上一行插入 <string>
p 【print】打印当前操作行
s/<oldString>/<newString>/g
【search】在当前操作行中,用 <newString> 替换 <oldString>
若不输入 <newString> 则为删除 <oldString>
eg1.
nl /etc/passwd | sed '3,5d' 在 StdOut 中删除 /etc/passwd 的 第三至第五行
eg2.
ifconfig | grep "192.168." | sed 's/.*inet //g'| sed 's/ net.*//g'
截取当前的 ip 地址
扩展正则表达式
grep -E 或 egrep 来使用扩展正则表达式
eg.
grep -v '^$' regular_expression.txt | grep -v '^#'
可以被简化为
egrep -v '^$|^#' regular_expression.txt
extended RE 的语法
<字符>+
该字符 重复了 一次或以上
eg.
egrep 'go+d'
将匹配 god good goood ...
<字符>?
该字符 没有出现或只出现了一次
eg.
egrep 'go?d'
将匹配 gd god
<RE1>|<RE2>
将匹配符合 RE1 或 RE2 的字符串
eg.
egrep '^$|^#'
将匹配 空行 或 以 # 开头的行
(<字符>)
将括号内的字符串作为群组对待
eg.
egrep 'g(la|oo)d'
将匹配 glad 或 good
群组字符也可以构成形如 ()+ ()? ()|() 的样式
printf 格式化输出
printf '<输出格式>' <待输出内容1> <待输出内容2> ...
输出格式中
可以指定以下转义字符
\a 警告声音输出
\b 退格键(backspace)
\f 清空屏幕(form feed)
\n 输出新的一行
\r 即 回车键
\t 水平制表符
\v 垂垂直制表符
\NNN 八进制数(1至3位)
\xNN 十六进制数(1至2位)
\uHHHH Unicode 字符(4位)
可以指定以下变量
%d 十进制整数
%i 十进制整数
%f 浮点数
%c 单一字符
%s 字符串
变量可以使用使用以下修改器
- 强制左对齐(默认右对齐)
+ 强制显示正负号(默认只显示负号)
0 强制补零(默认使用空格)
非0整数 仅对 %d %i 起效,表示最小显示位数,不够的默认使用空格补位
.非0整数 仅对 %f 起效,表示最大显示小数位数,多余部分将省略
EG.
printf '%+i\n' 1 2 4
#返回
+1
+2
+4
printf '%02i %s' 1 ab 6 6
#返回01 ab06 6
注意1:printf 以 空格 作为 数据的分界
注意2:printf 不会再行尾添加 LF
注意3:printf 会将所有的数据逐次 mapping 至 格式化变量 上,直至所有的 数据都被 map 完成
awk 分栏字段处理
awk '匹配条件1{动作1} 匹配条件2{动作2} ...' [待处理文件]
读取 文件 或者 StdIn 的数据,将文件分为 记录(record)和 分栏(field),并对记录和分栏进行指定的操作
一个文件 中包含数个 记录,一个记录 中包含数个 分栏
默认的 记录分隔符 为 <FL>
默认的 分栏分隔符 为 <Space> 或 <Tab>
常见的变量
$0 当前所在 记录的内容
NR 当前所在 记录的计数
FNR 文件的总 记录的计数
$1 $2 当前行的 分栏内容
NF 当前行的 总分栏数
FS 当前 分栏 的 分隔符
RS 当前 记录 的 分隔符
OFS 输出 分栏 的 分隔符
ORS 输出 记录 的 分隔符
BEGIN 在读取第一笔记录之前执行
eg1.
last -n 5 | awk '{print $1 "\t" $3}'
# {打印 第一个分栏内容 水平制表符 第三个分栏内容}
# 注意,如果有 额外文本(包含额外的转义字符) 出现,一定要使用引号引起来
last -n 5| awk '{print $1 "\t lines: " NR "\t columns: " NF}'
# 注意,除了 分栏内容的 需要使用 $ 以外,其他变量都不用 $ 引出
动作部分,除了可以执行读出还可以执行写入操作
eg2.
awk 'BEGIN {RS=":"} {ORS=";"} {print $0}' /etc/passwd
# 会将 /etc/passwd 中所有的 : 转化为 ;
# 但要注意,由于 RS 补在是 <FL> 了,所以每行的最后一个字段和下一行的第一个字段将成为同一个分栏
# 另外,若出现了 定义分栏符/分记录符 这类操作,一定要使用 BEGIN 这个变量
# 让这行命令在读取第一笔记录之前就执行,否则第一笔记录会按照预设的 分栏符/分记录符 执行
匹配条件部分,可以使用逻辑运算符
>, <, >=, <=, ==,!=
EG.
有如下 pay.txt 文件,记录了三个人三个月的收入,请计算出总收入,并打印至每行行尾
Name 1st 2nd 3th
VBird 23000 24000 25000
DMTsai 21000 20000 23000
Bird2 43000 42000 41000
[eP@ePCt ~]$ awk 'NR==1{printf "%10s %10s %10s %10s %10s\n",$1,$2,$3,$4,"Total"}\
> NR>=2{total = $2 + $3 + $4;\
> printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}' pay.txt
# 返回
Name 1st 2nd 3th Total
VBird 23000 24000 25000 72000.00
DMTsai 21000 20000 23000 64000.00
Bird2 43000 42000 41000 126000.00
# 注意1,在 awk 语句中,除了 命令 和 变量(包含 awk 内建变量 和 自定义变量) 外,其他所有的字符都要用 双引号引起来
# 注意2,若同一个大括号 {} 中,需要执行两个或以上的 独立操作时,需要用分号 ; 分开
上述命令可以改写为
[eP@ePCt ~]$ awk '{if(NR==1) printf "%10s %10s %10s %10s %10s\n",$1,$2,$3,$4,"Total"}\
> NR>=2{total = $2 + $3 + $4;\
> printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}' pay.txt
# 注意3,判断语句可以用 if() 引用,而放置在 大括号 {} 的区域
上述命令可以改写为
[eP@ePCt ~]$ awk '{if(NR==1) printf "%10s %10s %10s %10s %10s\n",$1,$2,$3,$4,"Total"}\
> NR>=2{total = $2 + $3 + $4\
> {printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}}' pay.txt
或
[eP@ePCt ~]$ awk '{if(NR==1) printf "%10s %10s %10s %10s %10s\n",$1,$2,$3,$4,"Total"}\
> NR>=2{total = $2 + $3 + $4}\
> {printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}' pay.txt
# 注意4,大括号 {} 可以嵌套,自定义变量可以在 嵌套 或者 非嵌套 的 大括号 之间传递
若要在 awk 命令中使用正则表达式,使用 双斜线 /<RE 表达式>/
awk '/<RE 表达式>/{<操作内容>}'
文本文件/文件夹差异比较
diff [-y] [-W <width>] [--suppress-common-lines] [file1] [file2]
diff [-cu] [file1] [file2]
-y 并列对比模式(side by side)
-W 定义返回行的总宽度,默认 130 字符,仅在并列对比模式下起效
--suppress-common-lines 不显示完全相同的行,仅在并列对比模式下起效
-c, -C <num>, --context[=num] 上下文模式(context)
-u, -U <num>, --unified[=num] 合并模式(unified)
可接受 StdIn
diff 的排查结果不一定就是最佳人类理解方式,但是是计算机能最快查找到结果的方式
EG.
有如下两个文件
[t.old]
line1
line2
line3
line4
line5
[t.new]
line2
line2
line4
line5
line6
line7
eg0.并列模式
[eP@ePCt ~]$ diff -y -W 20 t.old t.new
# 返回
line1 < # 小于号(<)表示该行只出现在右侧文件
line2 line2 # 空格表示该行等同
line3 | line2 # 管线符(|)表示该行介于完全相同和完全不相同之间
line4 line4
line5 line5
> line6 # 大于号(>)表示该行只出现在左侧文件
> line7
eg1.普通模式
[eP@ePCt ~]$ diff t.old t.new
# 返回
1d0 # 左侧文件的第 1 行在右侧文件中被删除(deleted),若不删除,应该出现在右侧文件的第 0 行(全文最开始)
< line1 # 内容 line1 来自左侧文件
3c2 # 左侧文件的第 3 行被修改(changed),右侧文件的第 2 行被修改(changed)
< line3 # 内容 line3 来自左侧文件
--- # 左侧右侧文件分隔符
> line2 # 内容 line2 来自右侧文件
5a5,6 # 右侧文件增加(added)了第 5 - 6 行,应该增加在左侧文件的第 5 行之后
> line6 # 内容 line6 来自右侧文件
> line7 # 内容 line7 来自右侧文件
eg2.上下文模式
[eP@ePCt ~]$ diff -c t.old t.new
# 返回
*** t.old 2016-08-19 16:24:03.380771314 +0800 # 文件1 的名称与修改时间
--- t.new 2016-08-19 17:40:46.133031752 +0800 # 文件2 的名称与修改时间
***************
*** 1,5 **** # 下方将显示 文件1 的 第 1 - 5 行
- line1 # 该行在另一个文件中被删除
line2 # 该行没有变化
! line3 # 该行在两个文件中介于完全相同和完全不同之间
line4
line5
--- 1,6 ---- # 下方将显示 文件2 的 第 1 - 6 行
line2
! line2
line4
line5
+ line6 # 该行在这个文件中被添加
+ line7
注意:
diff -C <num> 定义了除了不同之处最多还有多少行也一同被显示出来,默认 3 行
eg3.合并模式
[eP@ePCt ~]$ diff -c t.old t.new
# 返回
--- t.old 2016-08-19 16:24:03.380771314 +0800 # 文件1 的名称与修改时间
+++ t.new 2016-08-19 17:40:46.133031752 +0800 # 文件2 的名称与修改时间
@@ -1,5 +1,6 @@ # 下方显示了 文件1 第 1 行的前后共 5 行内容,以及 文件2 第 1 行的前后共 6 行内容
-line1 # 文件2 中删去了该行
line2
-line3
+line2 # 文件2 中添加了该行
line4
line5
+line6
+line7
字节形式的文件差异比较
cmp [-l] file1 file2
-l, --verbose 输出所有不同字节的位置,以及不同字节的值
默认情况下,只输出寻找到的第一个不同字节的行数与字节位置