声明:本文章为《鸟哥的Linux私房菜》读书摘要!
正规表示法
正规表示法(Regular Expression, RE,或称为常规表示法)是透过一些特殊字符的排列,用以『搜寻/取代/删除』一列或多列文字字符串, 简单的说,正规表示法就是用在字符串的处理上面的一项『表示式』。正规表示法就是处理字符串的方法,他是以行为单位来进行字符串的处理行为, 正规表示法透过一些特殊符号的辅助,可以让使用者轻易的达到『搜寻/删除/取代』某特定字符串的处理程序!
正规表示法的字符串表示方式依照不同的严谨度而分为: 基础正规表示法与延伸正规表示法。延伸型正规表示法除了简单的一组字符串处理之外,还可以作群组的字符串处理, 例如进行搜寻VBird或netman或lman的搜寻,注意,是『或(or)』而不是『和(and)』的处理, 此时就需要延伸正规表示法的帮助。
正规表示法与通配符是完全不一样的东西!通配符(wildcard)代表的是bash操作接口的一个功能』,但正规表示法则是一种字符串处理的表示方式。
基础正规表达法
语系对正规表示法的影响
文件其实记录的仅有 0与1,我们看到的字符文字与数字都是透过编码表转换来的。由于不同语系的编码数据并不相同,所以就会造成数据撷取结果的差异了。 举例来说,在英文大小写的编码顺序中,zh_TW.big5及C 这两种语系的输出结果分别如下:
LANG=C 时:0 1 2 3 4 ... A B C D ... Z a b c d ...z
LANG=zh_TW 时:0 1 2 3 4 ... a A b B c C d D ... z Z
所以使用正规表示法时,需要特别留意当时环境的语系为何
由于一般我们在练习正规表示法时,使用的是兼容于POSIX的标准,因此就使用『C』这个语系。
grep 在数据中查寻一个字符串时,是以 " 整行 " 为单位来进行数据的撷取的! 』也就是说,假如一个文件内有 10 行,其中有两行具有你所搜寻的字符串,则将那两行显示在屏幕上,其他的就丢弃了!
基础正规表示法练习
grep 已经使用 alias 设定成为『grep --color=auto』
例题二、利用中括号[]来搜寻集合字符
如果我想要搜寻 test 或taste这两个单字时,可以发现到,其实她们有共通的't?st'存在
其实[]里面不论有几个字符,他都仅代表某『一个』字符, 所以,上面的例子说明了,我需要的字符串是『tast』或『test』两个字符串而已
如果想要搜寻到有 oo 的字符时
但是,如果我不想要oo前面有g的话
再来,假设我oo前面不想要有小写字符,所以,我可以这样写[^abcd....z]oo ,由于小写字符的ASCII上编码的顺序是连续的,也可以这样写 [^a-z]oo
例题三、行首与行尾字符^ $
我们在例题一当中,可以查询到一行字符串里面有the的,那如果我想要让the只在行首列出 ,则需要制表符。
如果我想要开头是小写字符的那一行就列出
那个^符号,在字符集合符号(括号[])之内与之外是不同的! 在[]内代表『反向选择』,在 [] 之外则代表定位在行首的意义
那如果我想要找出来,行尾结束为小数点(.)的那一行
特别注意到,因为小数点具有其他意义(底下会介绍),所以必须要使用跳脱字符(\)来加以解除其特殊意义
不过,你或许会觉得奇怪,但是第 5~9 行最后面也是 . 啊~怎么无法打印出来? 这里就牵涉到Windows平台的软件对于断行字符的判断问题了!我们使用cat -A将第五行拿出来看, 你会发现:
在Linux与Windows上的差异, 在上面的表格中我们可以发现5~9行为Windows的断行字符 (^M$) ,而正常的Linux应该仅有第10 行显示的那样($)。所以那个.自然就不是紧接在$之前喽。
如果我想要找出来,哪一行是『空白行』, 也就是说,该行并没有输入任何数据
不要空白行,不要#开头的注释行
例题四、任意一个字符.与重复字符*
我们知道通配符*可以用来代表任意(0或多个)字符, 但是正规表示法并不是通配符,两者之间是不相同的
. (小数点):代表『一定有一个任意字符』的意思;
* (星星号):代表『重复前一个字符,0到无穷多次』的意思,为组合形态;
假设我需要找出g??d的字符串,亦即共有四个字符, 起头是g而结束是d
* 代表的是『 重复0个或多个前面的RE字符』的意义, 因此,『o*』代表的是:『拥有空字符或一个o以上的字符』
当我们需要『至少两个 o 以上的字符串』时,就需要ooo*
如果我想要字符串开头与结尾都是g,但是两个g之间仅能存在至少一个o ,亦即是 gog, goog, gooog....等等
.*就代表零个或多个任意字符
我想要找出两个到五个 o 的连续字符串,该如何作?这时候就得要使用到限定范围的字符{}了。 但因为{与}的符号在shell是有特殊意义的,因此, 我们必须要使用跳脱字符\来让他失去特殊意义才行。
我们要找出g后面接2到5个o,然后再接一个g 的字符串
如果我想要的是2个
o 以上的 goooo....g呢?除了可以是gooo*g,也可以是go\{2,\}g
基础正规表示法字符汇整 (characters)
sed工具
sed本身也是一个管线命令,可以分析standard input的。sed还可以将数据进行取代、删除、新增、撷取特定行等等的功能
下面来看看具体的应用例程。
如果题型变化一下,举例来说,如果只要删除第2行,可以使用『nl /etc/passwd | sed '2d'』来达成,至于若是要删除第3 到最后一行,则是『nl /etc/passwd | sed '3,$d'』的啦,那个钱字号『$』代表最后一行!
这个范例的重点是『我们可以新增不只一行喔!可以新增好几行』但是每一行之间都必须要以反斜杠『\』来进行新行的增加喔!所以,上面的例子中,我们可以发现在第一行的最后面就有\存在。
使用底下这个取得IP数据的范例,一段一段的来处理给您瞧瞧,让你了解一下什么是咱们所谓的搜寻并取代
假设我只要MAN存在的那几行数据, 但是含有# 在内的批注我不想要,而且空白行我也不要
直接修改文件内容(危险动作)
延伸正规表示法
在上节的例题三的最后一个例子中,我们要去除空白行与行首为#的行列,使用的是
grep -v '^$' regular_express.txt | grep -v '^#'
需要使用到管线命令来搜寻两次!那么如果使用延伸型的正规表示法,我们可以简化为:
egrep -v '^$|^#' regular_express.txt
延伸型正规表示法可以透过群组功能『|』来进行一次搜寻!那个在单引号内的管线意义为『或 or』啦! 是否变的更简单呢?此外,grep预设仅支持基础正规表示法,如果要使用延伸型正规表示法,你可以使用grep -E, 不过更建议直接使用egrep!直接区分指令比较好记忆!其实egrep与 grep -E是类似命令别名的关系
文件的格式化与相关处理
格式化打印: printf
printf.txt文件
我们将每个数据都以[tab]作为分隔,但是由于 Chinese长度太长,导致 English中间多了一个 [tab] 来将资料排列整齐
在 printf 后续的那一段格式中,%s代表一个不固定长度的字符串,而字符串与字符串中间就以\t这个 [tab]分隔符来处理!你要记得的是,由于\t与 %s中间还有空格,因此每个字符串间会有一个[tab]与一个空格键的分隔 。
%10s代表的是一个长度为10个字符的字符串字段, %5i 代表的是长度为 5个字符的数字字段,至于那个%8.2f则代表长度为 8 个字符的具有小数点的字段,其中小数点有两个字符宽度。我们可以使用底下的说明来介绍%8.2f的意义:
字符宽度: 12345678
%8.2f 意义: 00000.00
awk:好用的数据处理工具
awk 也是一个非常棒的数据处理工具!相较于sed 常常作用于一整个行的处理,awk 则比较倾向于一行当中分成数个『字段』来处理
awk可以处理后续接
的文件,也可以读取来自前个指令的 standard output 。 但如前面说的,awk主要是处理『每一行
的字段内的数据』,而默认的『字段的分隔符为 "空格键"或"[tab]键"』!举例来说,我们用last 可
以将登入者的数据取出来,结果如下所示:
若我想要取出账号与登入者的IP,且账号与 IP之间以[tab] 隔开,则会变成这样:
上表是awk最常使用的动作!透过 print 的功能将字段数据列出来!字段的分隔则以空格键或[tab]按键来隔开。
在awk 的括号内, 每一行的每个字段都是有变量名称的,那就是 $1, $2...等变量名称。以上面的例子来说,dmtsai 是 $1,因为他是第一栏嘛!至于 192.168.1.100是第三栏, 所以他就是$3 啦
1. 读入第一行,并将第一行的资料填入$0, $1, $2.... 等变数当中;
2. 依据"条件类型"的限制,判断是否需要进行后面的 "动作";
3. 做完所有的动作与条件类型;
4. 若还有后续的『行』的数据,则重复上面1~3 的步骤,直到所有的数据都读完为止。
经过这样的步骤,你会晓得, awk 是『 以行为一次处理的单位』, 而『以字段为最小的处理单位』。好了,那么awk 怎么知道我到底这个数据有几行?有几栏呢?这就需要awk 的内建变量的帮忙啦~
列出每一行的账号(就是$1);
列出目前处理的行数(就是awk 内的 NR变量)
并且说明,该行有多少字段(就是awk 内的 NF变量)
则可以这样:
awk的逻辑运算字符
在 /etc/passwd 当中是以冒号":" 来作为字段的分隔, 该文件中第一字段为账号,第三字段则是UID。那假设我要查阅,第三栏小于10 以下的数据,并且仅列出账号与第三栏, 那么可以这样做:
有趣吧!不过,怎么第一行没有正确的显示出来呢?这是因为我们读入第一行的时候,那些变数$1,$2... 默认还是以空格键为分隔的,所以虽然我们定义了FS=":" 了, 但是却仅能在第二行后才开始生效。那么怎么办呢?我们可以预先设定awk 的变量啊! 利用 BEGIN 这个关键词
awk的指令间隔:所有 awk 的动作,亦即在 {} 内的动作,如果有需要多个指令辅助时,可利用分号『;』
间隔, 或者直接以 [Enter] 按键来隔开每个指令,例如上面的范例中,鸟哥共按了三次[enter] 喔!
逻辑运算当中,如果是『等于』的情况,则务必使用两个等号『 ==』!
格式化输出时,在 printf 的格式设定当中,务必加上 \n ,才能进行分行!
与 bash shell 的变量不同,在 awk 当中,变量可以直接使用,不需加上$ 符号。
patch这个指令与 diff 可是有密不可分的关系啊!我们前面提到, diff可以用来分辨两个版本之间的差异, 举例来说,刚刚我们所建立的 passwd.old及 passwd.new 之间就是两个不同版本的文件。 那么,如果要『升级』呢?就是『 将旧的文件升级成为新的文件』时,应该要怎么做呢? 其实也不难啦!就是『先比较先旧版本的差异,并将差异档制作成为补丁档,再由补丁档更新旧文件』即可。
一般来说,使用diff 制作出来的比较文件通常使用扩展名为.patch 啰。至于内容就如同上面介绍的样子。 基本上就是以行为单位,看看哪边有一样与不一样的,找到一样的地方,然后将不一样的地方取代掉! 以上面表格为例,新文件看到- 会删除,看到 +会加入!好了,那么如何将旧的文件更新成为新的内容呢? 就是将 passwd.old改成与 passwd.new 相同!可以这样做:
文件打印准备:pr