鸟哥的linux私房菜学习笔记6

来源:互联网 发布:手甲钩淘宝 编辑:程序博客网 时间:2024/06/06 20:56

P354

. (小数点):代表『一定有一个任意字节』的意思;

  • * (星星号):代表『重复前一个字节, 0 到无穷多次』的意思,为组合形态

    这样讲不好懂,我们直接做个练习吧!假设我需要找出 g??d 的字串,亦即共有四个字节, 起头是 g 而结束是 d ,我可以这样做:

    [root@www ~]# grep -n 'g..d' regular_express.txt1:"Open Source" is a good mechanism to develop programs.9:Oh! The soup taste good.16:The world <Happy> is the same with "glad".

    因为强调 g 与 d 之间一定要存在两个字节,因此,第 13 行的 god 与第 14 行的 gd 就不会被列出来啦!再来,如果我想要列出有 oo, ooo, oooo 等等的数据, 也就是说,至少要有两个(含) o 以上,该如何是好?是 o* 还是 oo* 还是 ooo* 呢? 虽然你可以试看看结果, 不过结果太占版面了 @_@ ,所以,我这里就直接说明。

    因为 * 代表的是『重复 0 个或多个前面的 RE 字符』的意义, 因此,『o*』代表的是:『拥有空字节或一个 o 以上的字节』


    如果我想要找出『任意数字』的行列呢?因为仅有数字,所以就成为:

    [root@www ~]# grep -n '[0-9][0-9]*' regular_express.txt5:However, this dress is about $ 3183 dollars.15:You are the best is mean you are the no. 1.

    虽然使用 grep -n '[0-9]' regular_express.txt 也可以得到相同的结果, 但鸟哥希望大家能够理解上面命令当中 RE 表示法的意义才好!


    • 例题五、限定连续 RE 字符范围 {}

    在上个例题当中,我们可以利用 . 与 RE 字符及 * 来配置 0 个到无限多个重复字节, 那如果我想要限制一个范围区间内的重复字节数呢?举例来说,我想要找出两个到五个 o 的连续字串,该如何作?这时候就得要使用到限定范围的字符 {} 了。 但因为 { 与 } 的符号在 shell 是有特殊意义的,因此, 我们必须要使用跳脱字符 \ 来让他失去特殊意义才行。 至於 {} 的语法是这样的,假设我要找到两个 o 的字串,可以是:

    [root@www ~]# grep -n 'o\{2\}' regular_express.txt1:"Open Source" is a good mechanism to develop programs.2:apple is my favorite food.3:Football game is not use feet only.9:Oh! The soup taste good.18:google is the best tools for search keyword.19:goooooogle yes!

    这样看似乎与 ooo* 的字符没有什么差异啊?因为第 19 行有多个 o 依旧也出现了! 好,那么换个搜寻的字串,假设我们要找出 g 后面接 2 到 5 个 o ,然后再接一个 g 的字串,他会是这样:

    [root@www ~]# grep -n 'go\{2,5\}g' regular_express.txt18:google is the best tools for search keyword.

    嗯!很好!第 19 行终於没有被取用了(因为 19 行有 6 个 o 啊!)。 那么,如果我想要的是 2 个 o 以上的 goooo....g 呢?除了可以是 gooo*g ,也可以是:

    [root@www ~]# grep -n 'go\{2,\}g' regular_express.txt18:google is the best tools for search keyword.19:goooooogle yes!
    基础正规表示法字符汇整(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 | grep -n '^a.*'
    例题:
    以 ls -l 配合 grep 找出 /etc/ 底下文件类型为连结档属性的档名
    答:
    由於 ls -l 列出连结档时标头会是『 lrwxrwxrwx 』,因此使用如下的命令即可找出结果:
    ls -l /etc | grep '^l'
    若仅想要列出几个文件,再以『 |wc -l 』 来累加处理即可。
    P357 
    sed
    sed 本身也是一个管线命令,可以分析 standard input 的啦!而且 sed 还可以将数据进行取代、删除、新增、撷取特定行等等的功能
    范例一:将 /etc/passwd 的内容列出并且列印行号,同时,请将第 2~5 行删除![root@www ~]# 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.....(后面省略).....

    延伸正规表示法

    事实上,一般读者只要了解基础型的正规表示法大概就已经相当足够了,不过,某些时刻为了要简化整个命令操作, 了解一下使用范围更广的延伸型正规表示法的表示式会更方便呢!举个简单的例子好了,在上节的例题三的最后一个例子中,我们要去除空白行与行首为 # 的行列,使用的是

    grep -v '^$' regular_express.txt | grep -v '^#'

    需要使用到管线命令来搜寻两次!那么如果使用延伸型的正规表示法,我们可以简化为:

    egrep -v '^$|^#' regular_express.txt

    延伸型正规表示法可以透过群组功能『 | 』来进行一次搜寻!那个在单引号内的管线意义为『或 or』啦! 是否变的更简单呢?此外,grep 默认仅支持基础正规表示法,如果要使用延伸型正规表示法,你可以使用 grep -E , 不过更建议直接使用 egrep !直接区分命令比较好记忆!其实 egrep 与 grep -E 是类似命令别名的关系啦!

    熟悉了正规表示法之后,到这个延伸型的正规表示法,你应该也会想到,不就是多几个重要的特殊符号吗? ^_^y 是的~所以,我们就直接来说明一下,延伸型正规表示法有哪几个特殊符号?由於底下的范例还是有使用到 regular_express.txt ,不巧的是刚刚我们可能将该文件修改过了 @_@,所以,请重新下载该文件来练习喔!

    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

    这样可以了解了吗?常常看到有陷阱的题目写:『反向选择这样对否? '[!a-z]'?』, 呵呵!是错的呦~要 '[^a-z] 才是对的

    格式化列印: printf

    awk 也是一个非常棒的数据处理工具!相较於 sed 常常作用於一整个行的处理, awk 则比较倾向於一行当中分成数个『栏位』来处理。因此,awk 相当的适合处理小型的数据数据处理呢!awk 通常运行的模式是这样的:

    [root@www ~]# awk '条件类型1{动作1} 条件类型2{动作2} ...' filename

    awk 后面接两个单引号并加上大括号 {} 来配置想要对数据进行的处理动作。 awk 可以处理后续接的文件,也可以读取来自前个命令的 standard output 。 但如前面说的,awk 主要是处理『每一行的栏位内的数据』,而默认的『栏位的分隔符号为 "空白键" 或 "[tab]键" 』!举例来说,我们用 last 可以将登陆者的数据取出来,结果如下所示:

    [root@www ~]# last -n 5 <==仅取出前五行root     pts/1   192.168.1.100  Tue Feb 10 11:21   still logged inroot     pts/1   192.168.1.100  Tue Feb 10 00:46 - 02:28  (01:41)root     pts/1   192.168.1.100  Mon Feb  9 11:41 - 18:30  (06:48)dmtsai   pts/1   192.168.1.100  Mon Feb  9 11:41 - 11:41  (00:00)root     tty1                   Fri Sep  5 14:09 - 14:10  (00:01)

    若我想要取出帐号与登陆者的 IP ,且帐号与 IP 之间以 [tab] 隔开,则会变成这样:

    [root@www ~]# last -n 5 | awk '{print $1 "\t" $3}'root    192.168.1.100root    192.168.1.100root    192.168.1.100dmtsai  192.168.1.100root    Fri

    上表是 awk 最常使用的动作!透过 print 的功能将栏位数据列出来!栏位的分隔则以空白键或 [tab] 按键来隔开。 因为不论哪一行我都要处理,因此,就不需要有 "条件类型" 的限制!

    另外,由上面这个例子你也会知道,在每一行的每个栏位都是有变量名称的,那就是 $1, $2... 等变量名称。以上面的例子来说, root 是 $1 ,因为他是第一栏嘛!至於 192.168.1.100 是第三栏, 所以他就是 $3 啦!后面以此类推~呵呵!还有个变量喔!那就是 $0 ,$0 代表『一整列数据』的意思~以上面的例子来说,第一行的 $0 代表的就是『root .... 』那一行啊! 由此可知,刚刚上面五行当中,整个 awk 的处理流程是:

    1. 读入第一行,并将第一行的数据填入 $0, $1, $2.... 等变量当中;
    2. 依据 "条件类型" 的限制,判断是否需要进行后面的 "动作";
    3. 做完所有的动作与条件类型;
    4. 若还有后续的『行』的数据,则重复上面 1~3 的步骤,直到所有的数据都读完为止。

    经过这样的步骤,你会晓得, awk 是『以行为一次处理的单位』, 而『以栏位为最小的处理单位』。好了,那么 awk 怎么知道我到底这个数据有几行?有几栏呢?这就需要 awk 的内建变量的帮忙啦~

    变量名称代表意义NF每一行 ($0) 拥有的栏位总数NR目前 awk 所处理的是『第几行』数据FS目前的分隔字节,默认是空白键

    我们继续以上面 last -n 5 的例子来做说明,如果我想要:

    • 列出每一行的帐号(就是 $1);
    • 列出目前处理的行数(就是 awk 内的 NR 变量)
    • 并且说明,该行有多少栏位(就是 awk 内的 NF 变量)

    则可以这样:

    Tips:
    要注意喔,awk 后续的所有动作是以单引号『 ' 』括住的,由於单引号与双引号都必须是成对的, 所以, awk 的格式内容如果想要以 print 列印时,记得非变量的文字部分,包含上一小节printf 提到的格式中,都需要使用双引号来定义出来喔!因为单引号已经是 awk 的命令固定用法了! 鸟哥的图示
    [root@www ~]# last -n 5| awk '{print $1 "\t lines: " NR "\t columns: " NF}'root     lines: 1        columns: 10root     lines: 2        columns: 10root     lines: 3        columns: 10dmtsai   lines: 4        columns: 10root     lines: 5        columns: 9# 注意喔,在 awk 内的 NR, NF 等变量要用大写,且不需要有钱字号 $ 啦!

    这样可以了解 NR 与 NF 的差别了吧?好了,底下来谈一谈所谓的 "条件类型" 了吧!


    与 bash shell 的变量不同,在 awk 当中,变量可以直接使用,不需加上 $ 符号。

    diff 就是用在比对两个文件之间的差异的,并且是以行为单位来比对的!一般是用在 ASCII 纯文字档的比对上。 由於是以行为比对的单位,因此 diff 通常是用在同一的文件(或软件)的新旧版本差异上! 举例来说,假如我们要将 /etc/passwd 处理成为一个新的版本,处理方式为: 将第四行删除,第六行则取代成为『no six line』,新的文件放置到 /tmp/test 里面,那么应该怎么做?

    [root@www ~]# mkdir -p /tmp/test <==先创建测试用的目录[root@www ~]# cd /tmp/test[root@www test]# cp /etc/passwd passwd.old[root@www test]# cat /etc/passwd | \> sed -e '4d' -e '6c no six line' > passwd.new# 注意一下, sed 后面如果要接超过两个以上的动作时,每个动作前面得加 -e 才行!# 透过这个动作,在 /tmp/test  里面便有新旧的 passwd 文件存在了!

    接下来讨论一下关於 diff 的用法吧!

    [root@www ~]# diff [-bBi] from-file to-file选项与参数:from-file :一个档名,作为原始比对文件的档名;to-file   :一个档名,作为目的比对文件的档名;注意,from-file 或 to-file 可以 - 取代,那个 - 代表『Standard input』之意。-b  :忽略一行当中,仅有多个空白的差异(例如 "about me" 与 "about     me" 视为相同-B  :忽略空白行的差异。-i  :忽略大小写的不同。范例一:比对 passwd.old 与 passwd.new 的差异:[root@www test]# diff passwd.old passwd.new4d3    <==左边第四行被删除 (d) 掉了,基准是右边的第三行< adm:x:3:4:adm:/var/adm:/sbin/nologin  <==这边列出左边(<)文件被删除的那一行内容6c5    <==左边文件的第六行被取代 (c) 成右边文件的第五行< sync:x:5:0:sync:/sbin:/bin/sync  <==左边(<)文件第六行内容---> no six line                      <==右边(>)文件第五行内容# 很聪明吧!用 diff 就把我们刚刚的处理给比对完毕了!


    cmp

    相对於 diff 的广泛用途, cmp 似乎就用的没有这么多了~ cmp 主要也是在比对两个文件,他主要利用『位组』单位去比对, 因此,当然也可以比对 binary file 罗~(还是要再提醒喔, diff 主要是以『行』为单位比对, cmp 则是以『位组』为单位去比对,这并不相同!)

    [root@www ~]# cmp [-s] file1 file2选项与参数:-s  :将所有的不同点的位组处都列出来。因为 cmp 默认仅会输出第一个发现的不同点。范例一:用 cmp 比较一下 passwd.old 及 passwd.new[root@www test]# cmp passwd.old passwd.newpasswd.old passwd.new differ: byte 106, line 4

    看到了吗?第一个发现的不同点在第四行,而且位组数是在第 106 个位组处!这个 cmp 也可以用来比对 binary 啦! ^_^


    • patch

    patch 这个命令与 diff 可是有密不可分的关系啊!我们前面提到,diff 可以用来分辨两个版本之间的差异, 举例来说,刚刚我们所创建的 passwd.old 及 passwd.new 之间就是两个不同版本的文件。 那么,如果要『升级』呢?就是『将旧的文件升级成为新的文件』时,应该要怎么做呢? 其实也不难啦!就是『先比较先旧版本的差异,并将差异档制作成为补丁档,再由补丁档升级旧文件』即可。 举例来说,我们可以这样做测试:

    范例一:以 /tmp/test 内的 passwd.old 与 passwd.new  制作补丁文件[root@www test]# diff -Naur passwd.old passwd.new > passwd.patch[root@www test]# cat passwd.patch--- passwd.old  2009-02-10 14:29:09.000000000 +0800 <==新旧文件的资讯+++ passwd.new  2009-02-10 14:29:18.000000000 +0800@@ -1,9 +1,8 @@   <==新旧文件要修改数据的界定范围,旧档在 1-9 行,新档在 1-8 行 root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin-adm:x:3:4:adm:/var/adm:/sbin/nologin      <==左侧文件删除 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin-sync:x:5:0:sync:/sbin:/bin/sync           <==左侧文件删除+no six line                               <==右侧新档加入 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin

    一般来说,使用 diff 制作出来的比较文件通常使用扩展名为 .patch 罗。至於内容就如同上面介绍的样子。 基本上就是以行为单位,看看哪边有一样与不一样的,找到一样的地方,然后将不一样的地方取代掉! 以上面表格为例,新文件看到 - 会删除,看到 + 会加入!好了,那么如何将旧的文件升级成为新的内容呢? 就是将 passwd.old 改成与 passwd.new 相同!可以这样做:

    [root@www ~]# patch -pN < patch_file    <==升级[root@www ~]# patch -R -pN < patch_file <==还原选项与参数:-p  :后面可以接『取消几层目录』的意思。-R  :代表还原,将新的文件还原成原来旧的版本。范例二:将刚刚制作出来的 patch file 用来升级旧版数据[root@www test]# patch -p0 < passwd.patchpatching file passwd.old[root@www test]# ll passwd*-rw-r--r-- 1 root root 1929 Feb 10 14:29 passwd.new-rw-r--r-- 1 root root 1929 Feb 10 15:12 passwd.old <==文件一模一样!范例三:恢复旧文件的内容[root@www test]# patch -R -p0 < passwd.patch[root@www test]# ll passwd*-rw-r--r-- 1 root root 1929 Feb 10 14:29 passwd.new-rw-r--r-- 1 root root 1986 Feb 10 15:18 passwd.old# 文件就这样恢复成为旧版本罗

    为什么这里会使用 -p0 呢?因为我们在比对新旧版的数据时是在同一个目录下, 因此不需要减去目录啦!如果是使用整体目录比对 (diff 旧目录 新目录) 时, 就得要依据创建 patch 文件所在目录来进行目录的删减罗!

    更详细的 patch 用法我们会在后续的第五篇的原始码编译 (第二十二章)再跟大家介绍, 这里仅是介绍给你,我们可以利用 diff 来比对两个文件之间的差异, 更可进一步利用这个功能来制作修补文件 (patch file) ,让大家更容易进行比对与升级呢!很不赖吧! ^_^



  • 0 0
    原创粉丝点击