Linux(9):正则表达式
目录
正则表达式 (Regular Expression, RE, 或称为常规表达式)是通过一些特殊字符的排列,用以“搜寻/取代/删除”一列或多列文字字串, 简单的说,正则表达式就是用在字串的处理上面的一项“表示式”。正则表达式并不是一个工具程序, 而是一个字串处理的标准依据,如果您想要以正则表达式的方式处理字串,就得要使用支持正则表达式的工具程序才行, 这类的工具程序很多,例如 vi, sed, awk 等等。但例如 cp, ls 等指令并未支持正则表达式, 所以就只能使用 bash 自己本身的万用字符而已。
正则表达式与通配符是完全不一样的东西!通配符(wildcard)代表的是 bash 操作接口的一个功能, 但正则表达式则是一种字串处理的表示方式!
1 基础正则表达式
正则表达式也需要支持工具程序来辅助才行,这里就先介绍一个最简单的字串撷取功能的工具程序,那就是 grep 。
1.1 语系对正则表达式的影响
文件其实记录的仅有 0 与 1,我们看到的字符文字与数字都是通过编码表转换来的。由于不同语系的编码数据并不相同,所以就会造成数据撷取结果的差异了。 举例来说,在英文大小写的编码顺序中,zh_CN.big5 及 C 这两种语系的输出结果分别如下:
- LANG=C 时:0 1 2 3 4 ... A B C D ... Z a b c d ...z
- LANG=zh_CN 时:0 1 2 3 4 ... a A b B c C d D ... z Z
所以,使用正则表达式时,需要特别留意当时环境的语系为何, 否则可能会发现与别人不相同的选取结果。
特殊符号 | 代表意义 |
---|---|
[:alnum:] | 代表英文大小写字符及数字,亦即 0-9, A-Z, a-z |
[:alpha:] | 代表任何英文大小写字符,亦即 A-Z, a-z |
[:blank:] | 代表空白键与 [Tab] 按键两者 |
[:cntrl:] | 代表键盘上面的控制按键,亦即包括 CR, LF, Tab, Del.. 等等 |
[:digit:] | 代表数字而已,亦即 0-9 |
[:graph:] | 除了空白字符 (空白键与 [Tab] 按键) 外的其他所有按键 |
[:lower:] | 代表小写字符,亦即 a-z |
[:print:] | 代表任何可以被打印出来的字符 |
[:punct:] | 代表标点符号 (punctuation symbol),亦即:" ' ? ! ; : # $... |
[:upper:] | 代表大写字符,亦即 A-Z |
[:space:] | 任何会产生空白的字符,包括空白键, [Tab], CR 等等 |
[:xdigit:] | 代表 16 进位的数字类型,因此包括: 0-9, A-F, a-f 的数字与字符 |
尤其上表中的[:alnum:], [:alpha:], [:upper:], [:lower:], [:digit:] 这几个一定要知道代表什么意思,因为他要比 a-z 或 A-Z 的用途更确定。
1.2 grep 的一些高级选项
[[email protected] ~]$ grep [-A] [-B] [--color=auto] '搜寻字串' filename
选项与参数:
- -A :后面可加数字,为 after 的意思,除了列出该行外,后续的 n 行也列出来;
- -B :后面可加数字,为 befer 的意思,除了列出该行外,前面的 n 行也列出来;
- -n 或 --line-number : 在显示符合样式的那一行之前,标示出该行的列数编号。
- --color=auto 可将正确的那个撷取数据列出颜色
例如:用 dmesg 列出核心讯息,再以 grep 找出内含 eth 那行;要将捉到的关键字显色,且加上行号来表示;在关键字所在行的前两行与后三行也一起捉出来显示。
1.3 基础正则表达式练习
之后的练习大前提是:
- 语系已经使用“ export LANG=C; export LC_ALL=C ”的设置值;
- grep 已经使用 alias 设置成为“ grep --color=auto ”
如果 Linux 可以直接连上 Internet 的话,那么使用如下的指令来获取测试文件regular_express.txt即可:
wget http://linux.vbird.org/linux_basic/0330regularex/regular_express.txt
1.3.1 搜寻特定字串
要从刚刚的文件当中取得 the 这个特定字串,最简单的方式就是这样:
“反向选择”呢?也就是说,当该行没有 'the' 这个字串时才显示在屏幕上,那就直接使用:
1.3.2 利用中括号 [ ] 来搜寻集合字符
[ ] 里面不论有几个字符,他都仅代表某“一个”字符。
如果我想要搜寻 test 或 taste 这两个单字时,可以发现到,其实她们有共通的 't?st' 存在~这个时候,我可以这样来搜寻:
上面的例子说明了,我需要的字串是“tast”或“test”两个字串而已! 而如果想要搜寻到 oo 前面不想要有小写字符,则使用:
1.3.3 行首与行尾字符 ^ $
(1)行首
想要让 the 只在行首列出,这个时候就得要使用定位字符^:
想要开头是小写字符的那一行就列出:
^ 符号,在字符集合符号(括号[ ])之内与之外是不同的! 在 [ ] 内代表“反向选择”,在 [ ] 之外则代表定位在行首的意义。
(2)行尾
特别注意到,因为小数点具有其他意义,所以必须要使用转义字符(\)来加以解除其特殊意义。
想要找出来行尾结束为小数点 (.) 的那一行:
如果想要找出来,哪一行是“空白行”, 也就是说,该行并没有输入任何数据:
在一个程序脚本 (shell script) 或者是配置文件当中,空白行与开头为 # 的那一行是批注,因此如果你要将数据列出给别人参考时, 可以将这些数据省略掉以节省保贵的纸张,那么你可以怎么作呢? 我们以 /etc/rsyslog.conf 这个文件来作范例:
1.3.4 任意一个字符 . 与重复字符 *
在bash 当中,我们知道 通配符 * 可以用来代表任意(0或多个)字符, 但是正则表达式并不是通配符,两者之间是不相同的! 至于正则表达式当中的“ . ”则代表“绝对有一个任意字符”的意思!这两个符号在正则表达式的意义如下:
- . (小数点):代表“一定有一个任意字符”的意思;
- *(星星号):代表“重复前一个字符, 0 到无穷多次”的意思,为组合形态。
假设需要找出 g??d 的字串,亦即共有四个字符, 起头是 g 而结束是 d ,可以这样做:
当需要“至少两个 o 以上的字串”时,就需要 ooo* ,亦即是:
想要找出 g 开头与 g 结尾的字串,当中的字符可有可无,那该如何是好?
1.3.5 限定连续 RE 字符范围 {}
在上个例题当中,我们可以利用 . 与 RE 字符及 * 来设置 0 个到无限多个重复字符, 那如果我想要限制一个范围区间内的重复字符数呢?举例来说,我想要找出两个到五个 o 的连续字串,该如何作?这时候就得要使用到限定范围的字符 {} 了。 但因为 { 与 } 的符号在 shell 是有特殊意义的,因此, 我们必须要使用转义字符 \ 来让他失去特殊意义才行。
假设我要找到两个 o 的字串,可以是:
我们要找出 g 后面接 2 到 5 个 o ,然后再接一个 g 的字串:
想要的是 2 个 o 以上的 goooo....g 呢?除了可以是 gooo*g ,也可以是:
1.4 基础正则表达式字符汇整 (characters)
经过了上面的几个简单的范例,我们可以将基础的正则表达式特殊字符汇整如下:
RE 字符 | 意义与范例 |
---|---|
^word | 意义:待搜寻的字串(word)在行首!范例:搜寻行首为 # 开始的那一行,并列出行号 > grep -n '^#' regular_express.txt
|
word$ | 意义:待搜寻的字串(word)在行尾!范例:将行尾为 ! 的那一行打印出来,并列出行号 > grep -n '!$' regular_express.txt
|
. | 意义:代表“一定有一个任意字符”的字符!范例:搜寻的字串可以是 (eve) (eae) (eee) (e e), 但不能仅有 (ee) !亦即 e 与 e 中间“一定”仅有一个字符,而空白字符也是字符! > grep -n 'e.e' regular_express.txt
|
\ | 意义:跳脱字符,将特殊符号的特殊意义去除!范例:搜寻含有单引号 ' 的那一行! > grep -n \' regular_express.txt
|
* | 意义:重复零个到无穷多个的前一个 RE 字符 范例:找出含有 (es) (ess) (esss) 等等的字串,注意,因为 可以是 0 个,所以 es 也是符合带搜寻字串。另外,因为 为重复“前一个 RE 字符”的符号, 因此,在 之前必须要紧接着一个 RE 字符喔!例如任意字符则为 “.” ! > grep -n 'ess*' regular_express.txt
|
[list] | 意义:字符集合的 RE 字符,里面列出想要选取的字符!范例:搜寻含有 (gl) 或 (gd) 的那一行,需要特别留意的是,在 [] 当中“谨代表一个待搜寻的字符”, 例如“ a[afl]y ”代表搜寻的字串可以是 aay, afy, aly 即 [afl] 代表 a 或 f 或 l 的意思! > grep -n 'g[ld]' regular_express.txt
|
[n1-n2] | 意义:字符集合的 RE 字符,里面列出想要选取的字符范围!范例:搜寻含有任意数字的那一行!需特别留意,在字符集合 [] 中的减号 - 是有特殊意义的,他代表两个字符之间的所有连续字符!但这个连续与否与 ASCII 编码有关,因此,你的编码需要设置正确(在 bash 当中,需要确定 LANG 与 LANGUAGE 的变量是否正确!) 例如所有大写字符则为 [A-Z] > grep -n '[A-Z]' regular_express.txt
|
[^list] | 意义:字符集合的 RE 字符,里面列出不要的字串或范围!范例:搜寻的字串可以是 (oog) (ood) 但不能是 (oot) ,那个 ^ 在 [] 内时,代表的意义是“反向选择”的意思。 例如,我不要大写字符,则为 [^A-Z]。但是,需要特别注意的是,如果以 grep -n [^A-Z] regular_express.txt 来搜寻,却发现该文件内的所有行都被列出,为什么?因为这个 [^A-Z] 是“非大写字符”的意思, 因为每一行均有非大写字符,例如第一行的 "Open Source" 就有 p,e,n,o.... 等等的小写字 > grep -n 'oo[^t]' regular_express.txt
|
\{n,m\} | 意义:连续 n 到 m 个的“前一个 RE 字符” 意义:若为 {n} 则是连续 n 个的前一个 RE 字符, 意义:若是 {n,} 则是连续 n 个以上的前一个 RE 字符! 范例:在 g 与 g 之间有 2 个到 3 个的 o 存在的字串,亦即 (goog)(gooog) > grep -n 'go\{2,3\}g' regular_express.txt
|
再次强调:“正则表达式的特殊字符”与一般在命令行输入指令的“通配符”并不相同, 例如,在通配符当中的 * 代表的是“ 0 ~ 无限多个字符”的意思,但是在正则表达式当中, 则是“重复 0 到无穷多个的前一个 RE 字符”的意思~使用的意义并不相同,不要搞混了!
举例来说,不支持正则表达式的 ls 这个工具中,若我们使用 “ls -l ” 代表的是任意文件名的文件,而 “ls -l a”代表的是以 a 为开头的任何文件名的文件, 但在正则表达式中,我们要找到含有以 a 为开头的文件,则必须要这样:(需搭配支持正则表达式的工具)
例题:以 ls -l 配合 grep 找出 /etc/ 下面文件类型为链接文件属性的文件名答:由于 ls -l 列出链接文件时标头会是“ lrwxrwxrwx ”,因此使用如下的指令即可找出结果:
ls -l | grep '^l
若仅想要列出几个文件,再以“ |wc -l ” 来累加处理即可。
注:
wc命令:
Linux wc命令用于计算字数。
利用wc指令我们可以计算文件的Byte数、字数、或是列数,若不指定文件名称、或是所给予的文件名为"-",则wc指令会从标准输入设备读取数据。
(1)语法:
wc [-clw][--help][--version][文件...]
(2)参数:
- -c或--bytes或--chars 只显示Bytes数。
- -l或--lines 只显示行数。
- -w或--words 只显示字数。
- --help 在线帮助。
- --version 显示版本信息。
(3)实例:
在默认的情况下,wc将计算指定文件的行数、字数,以及字节数。使用的命令为:
wc testfile
1.5 sed 工具
sed 本身也是一个管道命令,可以分析 standard input 。而且 sed 还可以将数据进行替换、删除、新增、选取特定行等等的功能呢!
[[email protected] ~]$ sed [-nefr] [动作]
选项与参数:
- -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 。
1.5.1 以行为单位的新增/删除功能
范例一:将 /etc/passwd 的内容列出并且打印行号,同时,请将第 2~5 行删除!
[[email protected] ~]$ nl /etc/passwd | sed '2,5d'
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
.....(后面省略).....
sed 的动作为 '2,5d' ,那个 d 就是删除!因为 2-5 行给他删除了,所以显示的数据就没有 2-5 行。 sed 后面接的动作,请务必以 '' 两个单引号括住!
举例来说,如果只要删除第 2 行,可以使用“ nl /etc/passwd | sed '2d' ”来达成, 至于若是要删除第 3 到最后一行,则是“ nl /etc/passwd | sed '3,$d' ”。
增加两行以上:
可以新增好几行,但是每一行之间都必须要以反斜线“ \ ”来进行新行的增加。
1.5.2 以行为单位的取代与显示功能
如果要整行取代呢?
示例:将第2-5行的内容取代成为“No 2-5 number”呢?
[[email protected] ~]$ nl /etc/passwd | sed '2,5c No 2-5 number'
1 root:x:0:0:root:/root:/bin/bash
No 2-5 number
6 sync:x:5:0:sync:/sbin:/bin/sync
.....(后面省略).....
我们以前想要列出第 11~20 行, 得要通过“head -n 20 | tail -n 10”之类的方法来处理,很麻烦啦。sed 则可以简单的直接取出你想要的那几行!是通过行号来找的!看看下面的范例先:
范例:仅列出 /etc/passwd 文件内的第 5-7 行
[[email protected] ~]$ nl /etc/passwd | sed -n '5,7p'
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
上述的指令中有个重要的选项“ -n ”,按照说明文档,这个 -n 代表的是“安静模式”。没有加上 -n 的参数时,2-5行会重复输出。
1.5.3 部分数据的搜寻并取代的功能
除了整行的处理模式之外, sed 还可以用行为单位进行部分数据的搜寻并取代的功能喔! 基本上 sed 的搜寻与取代的与 vi 相当的类似!
sed 's/要被取代的字串/新的字串/g'
上表中特殊字体的部分为关键字,请记下来!至于三个斜线分成两栏就是新旧字串的替换!
取得 IP 数据的范例,先观察原始讯息,利用 /sbin/ifconfig 查询 IP ,利用关键字配合 grep 撷取出关键的一行数据,将 IP 前面的部分予以删除,将 IP 后面的部分予以删除:
1.5.4 直接修改文件内容(危险动作)
sed 甚至可以直接修改文件的内容!而不必使用管道命令或数据流重导向! 不过,由于这个动作会直接修改到原始的文件,所以请你千万不要随便拿系统配置文件来测试。
示例:利用 sed 将 regular_express.txt 内每一行结尾若为 . 则换成 !
[[email protected] ~]# sed -i 's/\.$/\!/g' regular_express.txt
利用 sed 直接在 regular_express.txt 最后一行加入“# This is a test”:
[[email protected] ~]# sed -i '$a # This is a test' regular_express.txt
2 扩展正则表达式
我们要去除空白行与行首为 # 的行列,使用的是
grep -v '^$' regular_express.txt | grep -v '^#'
需要使用到管线命令来搜寻两次!那么如果使用延伸型的正则表达式,我们可以简化为:
egrep -v '^$|^#' regular_express.txt
grep 默认仅支持基础正则表达式,如果要使用延伸型正则表达式,你可以使用 grep -E , 不过更建议直接使用 egrep !直接区分指令比较好记忆!其实 egrep 与 grep -E 是类似命令别名的关系!
喔!
RE 字符 | 意义与范例 |
---|---|
+ | 意义:重复“一个或一个以上”的前一个 RE 字符 范例:搜寻 (god) (good) (goood)... 等等的字串。 那个 o+ 代表“一个以上的 o ”所以,下面的执行成果会将第 1, 9, 13 行列出来。 > egrep -n 'go+d' regular_express.txt
|
? | 意义:“零个或一个”的前一个 RE 字符 范例:搜寻 (gd) (god) 这两个字串。 那个 o? 代表“空的或 1 个 o ”所以,上面的执行成果会将第 13, 14 行列出来。 有没有发现到,这两个案例( 'go+d' 与 'go?d' )的结果集合与 'go*d' 相同? 想想看,这是为什么喔! ^_^ > egrep -n 'go?d' regular_express.txt
|
| | 意义:用或( or )的方式找出数个字串 范例:搜寻 gd 或 good 这两个字串,注意,是“或”! 所以,第 1,9,14 这三行都可以被打印出来喔!那如果还想要找出 dog 呢? > egrep -n 'gd|good' regular_express.txt > egrep -n 'gd|good|dog' regular_express.txt
|
() | 意义:找出“群组”字串 范例:搜寻 (glad) 或 (good) 这两个字串,因为 g 与 d 是重复的,所以, 我就可以将 la 与 oo 列于 ( ) 当中,并以 | 来分隔开来,就可以啦! > egrep -n 'g(la|oo)d' regular_express.txt
|
()+ | 意义:多个重复群组的判别 范例:将“AxyzxyzxyzxyzC”用 echo 叫出,然后再使用如下的方法搜寻一下! > echo 'AxyzxyzxyzxyzC' | egrep 'A(xyz)+C'
|
上面的例子意思是说,我要找开头是 A 结尾是 C ,中间有一个以上的 "xyz" 字串的意思~
以上这些就是延伸型的正则表达式的特殊字符。另外,要特别强调的是,那个 ! 在正则表达式当中并不是特殊字符, 所以,如果你想要查出来文件中含有 ! 与 > 的字行时,可以这样:
> grep -n '[!>]' regular_express.txt
3 文件的格式化与相关处理
3.1 格式化打印: printf
printf 可以帮我们将数据输出的结果格式化,而且而支持一些特殊的字符。
[[email protected] ~]$ printf '打印格式' 实际内容
选项与参数:
关于格式方面的几个特殊样式:
- \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 !%8.2f意义:00000.00
示例:关于第二行以后,分别以字串、整数、小数点来显示:
[[email protected] ~]$ printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt | grep -v Name)
DmTsai 80 60 92 77.33
VBird 75 55 80 70.00
Ken 60 90 70 73.33
3.2 awk:好用的数据处理工具
awk 也是一个非常棒的数据处理工具!相较于 sed 常常作用于一整个行的处理, awk 则比较倾向于一行当中分成数个“字段”来处理。因此,awk 相当的适合处理小型的数据数据处理呢!awk 通常运行的模式是这样的:
[[email protected] ~]$ awk '条件类型1{动作1} 条件类型2{动作2} ...' filename
awk 后面接两个单引号并加上大括号 {} 来设置想要对数据进行的处理动作。 awk 可以处理后续接的文件,也可以读取来自前个指令的 standard output 。 但如前面说的, awk 主要是处理“每一行的字段内的数据”,而默认的“字段的分隔符号为 "空白键" 或 "[tab]键" ”!
举例来说,想要取出帐号与登陆者的 IP ,且帐号与 IP 之间以 [tab] 隔开,结果如下所示:
[[email protected] ~]$ last -n 5 | awk '{print $1 "\t" $3}'
dmtsai 192.168.1.100
dmtsai 192.168.1.100
dmtsai 192.168.1.100
dmtsai 192.168.1.100
dmtsai Fri
使用 awk 的时候,请先确认一下你的数据当中,如果是连续性的数据,请不要有空格或 [tab] 在内,否则,会发生误判!
在 awk 的括号内,每一行的每个字段都是有变量名称的,那就是 $1, $2... 等变量名称。以上面的例子来说, dmtsai 是 $1 ,因为他是第一栏嘛!至于 192.168.1.100 是第三栏, 所以他就是 $3 啦!后面以此类推~呵呵!还有个变量喔!那就是 $0 ,$0 代表“一整列数据”的意思~以上面的例子来说,第一行的 $0 代表的就是“dmtsai .... ”那一行啊! 由此可知,刚刚上面五行当中,整个 awk 的处理流程是:
- 读入第一行,并将第一行的数据填入 $0, $1, $2.... 等变量当中;
- 依据 "条件类型" 的限制,判断是否需要进行后面的 "动作";
- 做完所有的动作与条件类型;
- 若还有后续的“行”的数据,则重复上面 1~3 的步骤,直到所有的数据都读完为止。
变量名称 | 代表意义 |
---|---|
NF | 每一行 ($0) 拥有的字段总数 |
NR | 目前 awk 所处理的是“第几行”数据 |
FS | 目前的分隔字符,默认是空白键 |
Tips 要注意喔,awk 后续的所有动作是以单引号“ ' ”括住的,由于单引号与双引号都必须是成对的, 所以, awk 的格式内容如果想要以 print 打印时,记得非变量的文字部分,包含上一小节 printf 提到的格式中,都需要使用双引号来定义出来喔!因为单引号已经是 awk 的指令固定用法了!
[[email protected] ~]$ last -n 5 | awk '{print $1 "\t lines: " NR "\t columns: " NF}'
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
# 注意喔,在 awk 内的 NR, NF 等变量要用大写,且不需要 $ 啦!
这样可以了解 NR 与 NF 的差别了吧?好了,下面来谈一谈所谓的 "条件类型" 了吧
- awk 的逻辑运算字符
既然有需要用到 "条件" 的类别,自然就需要一些逻辑运算啰~例如下面这些:
运算单元 | 代表意义 |
---|---|
> | 大于 |
< | 小于 |
>= | 大于或等于 |
<= | 小于或等于 |
== | 等于 |
!= | 不等于 |
举例来说,在 /etc/passwd 当中是以冒号 ":" 来作为字段的分隔, 该文件中第一字段为帐号,第三字段则是 UID。那假设我要查阅,第三栏小于 10 以下的数据,并且仅列出帐号与第三栏。因为我们读入第一行的时候,那些变量 $1, $2... 默认还是以空白键为分隔的,所以虽然我们定义了 FS=":" 了, 但是却仅能在第二行后才开始生效。那么怎么办呢?我们可以预先设置 awk 的变量啊! 利用 BEGIN 这个关键字喔! 那么可以这样做:
而除了 BEGIN 之外,我们还有 END !另外,如果要用 awk 来进行“计算功能”呢?以下面的例子来看, 假设我有一个薪资数据表文件名为 pay.txt ,内容是这样的:
Name 1st 2nd 3th
VBird 23000 24000 25000
DMTsai 21000 20000 23000
Bird2 43000 42000 41000
如何计算每个人的总额呢?而且还想要格式化输出喔!我们可以这样考虑:
- 第一行只是说明,所以第一行不要进行加总 (NR==1 时处理);
- 第二行以后就会有加总的情况出现 (NR>=2 以后处理)
[[email protected] ~]$ cat pay.txt | \
< 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}'
Name 1st 2nd 3th Total
VBird 23000 24000 25000 72000.00
DMTsai 21000 20000 23000 64000.00
Bird2 43000 42000 41000 126000.00
上面的例子有几个重要事项应该要先说明的:
- awk 的指令间隔:所有 awk 的动作,亦即在 {} 内的动作,如果有需要多个指令辅助时,可利用分号“;”间隔, 或者直接以 [Enter] 按键来隔开每个指令,例如上面的范例中,鸟哥共按了三次 [enter] 喔!
- 逻辑运算当中,如果是“等于”的情况,则务必使用两个等号“==”!
- 格式化输出时,在 printf 的格式设置当中,务必加上 \n ,才能进行分行!
- 与 bash shell 的变量不同,在 awk 当中,变量可以直接使用,不需加上 $ 符号。
利用 awk 这个玩意儿,就可以帮我们处理很多日常工作了!此外, awk 的输出格式当中,常常会以 printf 来辅助。另外, awk 的动作内 {} 也是支持 if (条件) 的喔! 举例来说,上面的指令可以修订成为这样:
[[email protected] ~]$ cat pay.txt | \
< 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}'
3.3 文件比对工具
3.3.1 diff
diff 就是用在比对两个文件之间的差异的,并且是以行为单位来比对的!一般是用在 ASCII 纯文本文件的比对上。 由于是以行为比对的单位,因此 diff 通常是用在同一的文件(或软件)的新旧版本差异上!
[[email protected] ~]$ diff [-bBi] from-file to-file
选项与参数:
- from-file :一个文件名,作为原始比对文件的文件名;
- to-file :一个文件名,作为目的比对文件的文件名;
注意,from-file 或 to-file 可以 - 取代,那个 - 代表“Standard input”之意。
- -b :忽略一行当中,仅有多个空白的差异(例如 "about me" 与 "about me" 视为相同
- -B :忽略空白行的差异。
- -i :忽略大小写的不同。
3.3.2 cmp
相对于 diff 的广泛用途, cmp 似乎就用的没有这么多了~ cmp 主要也是在比对两个文件,他主要利用“字节”单位去比对, 因此,当然也可以比对 binary file 啰~(还是要再提醒喔, diff 主要是以“行”为单位比对, cmp 则是以“字节”为单位去比对,这并不相同!)
[[email protected] ~]$ cmp [-l] file1 file2
选项与参数:
-l :将所有的不同点的字节处都列出来。因为 cmp 默认仅会输出第一个发现的不同点。
范例一:用 cmp 比较一下 passwd.old 及 passwd.new
[[email protected] testpw]$ cmp passwd.old passwd.new
passwd.old passwd.new differ: char 106, line 4
3.3.3 patch
diff 可以用来分辨两个版本之间的差异,如果要“升级”呢?就是“将旧的文件升级成为新的文件”时,应该要怎么做呢?就是“先比较先旧版本的差异,并将差异档制作成为补丁文件,再由补丁文件更新旧文件”即可。
一般来说,使用 diff 制作出来的比较文件通常使用扩展名为 .patch 。至于内容就如同上面介绍的样子。 基本上就是以行为单位,看看哪边有一样与不一样的,找到一样的地方,然后将不一样的地方取代掉! 以上面表格为例,新文件看到 - 会删除,看到 + 会加入!好了,那么如何将旧的文件更新成为新的内容呢? 就是将 passwd.old 改成与 passwd.new 相同!可以这样做:
[[email protected] ~]$ patch -pN < patch_file <==更新
[[email protected] ~]$ patch -R -pN < patch_file <==还原
选项与参数:
- -p :后面可以接“取消几层目录”的意思。
- -R :代表还原,将新的文件还原成原来旧的版本。
3.4 文件打印准备: pr
如果想要打印 /etc/man_db.conf :
[[email protected] ~]$ pr /etc/man_db.conf
2014-06-10 05:35 /etc/man_db.conf Page 1
#
#
# This file is used by the man-db package to configure the man and cat paths.
# It is also used to provide a manpath for those without one by examining
# configure script.
.....(以下省略)......
https://wizardforcel.gitbooks.io/vbird-linux-basic-4e/content/96.html