• 正则表达式与文件格式化处理(3)-正则表达式延伸,awk(主)


    它的前一篇:正则表达式与文件格式化处理(2)-sed工具(主)

    1 延伸正则表达式

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

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

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

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

    由于下面的范例还是有使用到 regular_express.txt 可以重新下载.

    wget http://linux.vbird.org/linux_basic/0330regularex/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" 字串的意思.

    2 文件的格式化与相关处理

        接下来让我们来将文件进行一些简单的编排吧!下面这些动作可以将你的讯息进行排版的动作,
    不需要重新以 vim 去编辑,通过数据流重导向配合下面介绍的 printf 功能,以及 awk 指令,
    就可以让你的讯息以你想要的模样来输出了!试看看吧!

        举例来说,考试卷分数的输出,姓名与科目及分数之间,总是可以稍微作
    个比较漂亮的版面配置吧? 例如我想要输出下面的样式:

    Name Chinese English Math Average
    DmTsai 80 60 92 77.33
    VBird 75 55 80 70.00
    Ken 60 90 70 73.33
    

        上表的数据主要分成五个字段,各个字段之间可使用 tab 或空白键进行分隔.
    请将上表的数据转存成为 printf.txt 文件名,等一下我们会
    利用这个文件来进行几个小练习的。 因为每个字段的原始数据长度其实并非是如此固定的
    ( Chinese 长度就是比 Name 要多), 而我就是想要如此表示出这些数据,此时,
    就得需要打印格式管理员 printf 的帮忙了! printf 可以帮我们将数据输出的结果格式化,
    而且而支持一些特殊的字符~下面我们就来看看!

    [dmtsai@study ~]$ printf '打印格式' 实际内容
    选项与参数:
    关于格式方面的几个特殊样式:
    a 警告声音输出
     倒退键(backspace)
    f 清除屏幕 (form feed)
    
     输出新的一行
    
     亦即 Enter 按键
    	 水平的 [tab] 按键
    v 垂直的 [tab] 按键
    xNN NN 为两位数的数字,可以转换数字成为字符。
    关于 C 程序语言内,常见的变量格式
    %ns 那个 n 是数字, s 代表 string ,亦即多少个字符;
    %ni 那个 n 是数字, i 代表 integer ,亦即多少整数码数;
    %N.nf 那个 n 与 N 都是数字, f 代表 floating (浮点),如果有小数码数,
    假设我共要十个位数,但小数点有两位,即为 %10.2f 啰!
    

    2.1 范例一:将刚刚上头数据的文件 ( printf.txt ) 内容仅列出姓名与成绩:(用 [tab] 分隔)

    printf '%s	 %s	 %s	 %s	 %s	 
    ' $(cat printf.txt)
    

        由于 printf 并不是管线命令,因此我们得要通过类似上面的功能,将文件内容先提出来给 printf 作为后续的数据才行。 如上所示,我们
    将每个数据都以 [tab] 作为分隔,但是由于 Chinese 长度太长,导致 English 中间多了一个 [tab] 来将数据排列整齐!啊~结果就看到数据对齐
    结果的差异了!
        另外,在 printf 后续的那一段格式中, %s 代表一个不固定长度的字串,而字串与字串中间就以 这个 [tab] 分隔符号来处理!你要记得
    的是,由于 与 %s 中间还有空格,因此每个字串间会有一个 [tab] 与一个空白键的分隔喔!
        既然每个字段的长度不固定会造成上述的困扰,那我将每个字段固定就好啦!没错没错!这样想非常好! 所以我们就将数据给他进行固
    定字段长度的设计吧!

    2.2 范例二:将上述数据关于第二行以后,分别以字串、整数、小数点来显示:

    printf '%10s %5i %5i %5i %8.2f 
    ' $ ( cat printf.txt | grep -v Name )
    

        上面这一串格式想必您看得很辛苦!没关系!一个一个来解释!上面的格式共分为五个字段, %10s 代表的是一个长度为 10 个字符的
    字串字段, %5i 代表的是长度为 5 个字符的数字字段,至于那个 %8.2f 则代表长度为 8 个字符的具有小数点的字段,其中小数点有两个字符宽
    度。我们可以使用下面的说明来介绍 %8.2f 的意义:

    字符宽度: 12345678
    %8.2f意义:00000.00

        如上所述,全部的宽度仅有 8 个字符,整数部分占有 5 个字符,小数点本身 ( . ) 占一位,小数点下的位数则有两位。 这种格式经常使
    用于数值程序的设计中!这样了解乎?自己试看看如果要将小数点位数变成 1 位又该如何处理?

        printf 除了可以格式化处理之外,他还可以依据 ASCII 的数字与图形对应来显示数据喔 [3] ! 举例来说 16 进位的 45 可以得到什么 ASCII
    的显示图 (其实是字符啦)?

    2.3 范例三:列出 16 进位数值 45 代表的字符为何?

     printf 'x45
    '
    

    3 awk :好用的数据处理工具

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

    awk '条件类型 1{ 动作 1} 条件类型 2{ 动作 2} ...' filename
    

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

    [dmtsai@study ~]$ last -n 5
    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 )

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

    [dmtsai@study ~]$ last -n 5 | awk '{print $1 "	" $3}'
    dmtsai 192.168.1.100
    dmtsai 192.168.1.100
    dmtsai 192.168.1.100
    dmtsai 192.168.1.100
    dmtsai Fri
    

        由上面这个例子你也会知道,在 awk 的括号内,每一行的每个字段都是有变量名称的,那就是 $1, $2... 等变量名称。以上面的例
    子来说, dmtsai 是 $1 ,因为他是第一栏嘛!至于 192.168.1.100 是第三栏, 所以他就是 $3 啦!后面以此类推~呵呵!还有个变量喔!那就
    是 $0 , $0 代表 “ 一整列数据 ” 的意思~以上面的例子来说,第一行的 $0 代表的就是 “dmtsai .... ” 那一行啊! 由此可知,刚刚上面五行当中,整个 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 变量)

    则可以这样
    要注意喔,awk 后续的所有动作是以单引号 ' 括住的,由于单引号与双引号都必须是成对的, 所以, awk 的格式内容如果想要以 print
    打印时,记得非变量的文字部分,包含上一小节 printf 提到的格式中,都需要使用双引号来定义出来喔!因为单引号已经是 awk 的指令固定用法了!

    [dmtsai@study ~]$ last -n 5| awk '{print $1 "	 lines: " NR "	 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 等变量要用大写,且不需要有钱字号 $ 啦!
    

    awk 的逻辑运算字符

    运算单元 代表意义
    > 大于
    < 小于
    >= 大于或等于
    <= 小于或等于
    == 等于
    != 不等于

        好了,我们实际来运用一下逻辑判断吧!举例来说,在 /etc/passwd 当中是以冒号 ":" 来作为字段的分隔, 该文件中第一字段为帐号,
    第三字段则是 UID 。那假设我要查阅,第三栏小于 10 以下的数据,并且仅列出帐号与第三栏, 那么可以这样做:

    $ cat /etc/passwd | awk '{FS=":"} $3 <10 {print $1 "	" $3}'
    root:x:0:0:root:/root:/bin/bash
    daemon  1
    bin     2
    sys     3
    sync    4
    games   5
    man     6
    lp      7
    mail    8
    news    9
    

        有趣吧!不过,怎么第一行没有正确的显示出来呢?这是因为我们读入第一行的时候,那些变量 $1, $2... 默认还是以空白键为分隔的,
    所以虽然我们定义了 FS=":" 了, 但是却仅能在第二行后才开始生效。那么怎么办呢?我们可以预先设置 awk 的变量啊! 利用 BEGIN 这个关
    键字喔!这样做:

    $ cat /etc/passwd | awk 'BEGIN{FS=":"} $3 < 10 {print $1 "	" $3}'
    root    0
    daemon  1
    bin     2
    sys     3
    sync    4
    games   5
    man     6
    lp      7
    mail    8
    news    9
    

        很有趣吧!而除了 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 以后处理)
    $ cat pay.txt |
    > awk 'NR==1{printf "%10s %10s %10s %10s %10s
    ",$1,$2,$3,$4,"Total"}
    > NR>=2{total = $2 + $3 + $4
    > printf "%10s %10d %10d %10d %10.2f
    ",$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 的格式设置当中,务必加上 ,才能进行分行!
    • 与 bash shell 的变量不同,在 awk 当中,变量可以直接使用,不需加上 $ 符号。

    另外, awk 的动作内 {} 也是支持 if (条件) 的喔! 举例来说,上面的指令可以修订成为这
    样:

    $ cat pay.txt |
    > awk '{if(NR==1) printf "%10s %10s %10s %10s %10s
    ", $1,$2,$3,$4,"Total"}
    > NR>=2{total=$2+$3+$4
    > printf "%10s %10d %10d %10d %10.2f
    ",$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
    

    4 文件比对工具

    4.1 diff

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

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

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

    [dmtsai@study ~]$ 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 的差异:
    [dmtsai@study testpw]$ diff passwd.old passwd.new
    4d3 <==左边第四行被删除 (d) 掉了,基准是右边的第三行
    < adm:x:3:4:adm:/var/adm:/sbin/nologin <==这边列出左边(<)文件被删除的那一行内容
    6c5 <==左边文件的第六行被取代 (c) 成右边文件的第五行
    < sync:x:5:0:sync:/sbin:/bin/sync <==左边(<)文件第六行内容
    ---
    > no six line <==右边(>)文件第五行内容
    # 很聪明吧!用 diff 就把我们刚刚的处理给比对完毕了!
    

    另外, diff 也可以比对整个目录下的差异喔!举例来说,假设你已经知道执行等级 0 与 5 的启动脚
    本分别放置到 /etc/rc0.d 及 /etc/rc5.d , 则我们可以将两个目录比对一下:

    [dmtsai@study ~]$ diff /etc/rc0.d/ /etc/rc5.d/
    Only in /etc/rc0.d/: K90network
    ...
    Only in /etc/rc5.d/: S10network
    ...
    

    4.2 cmp

        cmp 主要也是在比对两个文件,他主要利用 字节 单位去比对, 因此,当然
    也可以比对 binary file 啰~(还是要再提醒喔, diff 主要是以 为单位比对, cmp 则是以 字节 为单位去比对,这并不相同!)

    [dmtsai@study ~]$ cmp [-l] file1 file2
    选项与参数:
    -l :将所有的不同点的字节处都列出来。因为 cmp 默认仅会输出第一个发现的不同点。
    

    范例一:用 cmp 比较一下 passwd.old 及 passwd.new

    [dmtsai@study testpw]$ cmp passwd.old passwd.new
    passwd.old passwd.new differ: char 106, line 4
    

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

    4.3 patch

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

    范例一:以 /tmp/testpw 内的 passwd.old 与 passwd.new 制作补丁文件

    [dmtsai@study testpw]$ diff -Naur passwd.old passwd.new > passwd.patch
    [dmtsai@study testpw]$ cat passwd.patch
    --- passwd.old 2015-07-14 22:37:43.322535054 +0800 <==新旧文件的信息
    +++ passwd.new 2015-07-14 22:38:03.010535054 +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 相同!可以这样做:

    [dmtsai@study ~]$ patch -pN < patch_file <==更新
    [dmtsai@study ~]$ patch -R -pN < patch_file <==还原
    选项与参数:
    -p :后面可以接“取消几层目录”的意思。
    -R :代表还原,将新的文件还原成原来旧的版本。
    范例二:将刚刚制作出来的 patch file 用来更新旧版数据
    [dmtsai@study testpw]$ patch -p0 < passwd.patch
    patching file passwd.old
    [dmtsai@study testpw]$ ll passwd*
    -rw-rw-r--. 1 dmtsai dmtsai 2035 Jul 14 22:38 passwd.new
    -rw-r--r--. 1 dmtsai dmtsai 2035 Jul 14 23:30 passwd.old <==文件一模一样!
    范例三:恢复旧文件的内容
    [dmtsai@study testpw]$ patch -R -p0 < passwd.patch
    [dmtsai@study testpw]$ ll passwd*
    -rw-rw-r--. 1 dmtsai dmtsai 2035 Jul 14 22:38 passwd.new
    -rw-r--r--. 1 dmtsai dmtsai 2092 Jul 14 23:31 passwd.old
    # 文件就这样恢复成为旧版本啰
    

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

    5 文件打印准备: pr

        如果你曾经使用过一些图形接口的文书处理软件的话,那么很容易发现,当我们在打印的时候, 可以同时选择与设置每一页打印时的标
    头吧!也可以设置页码呢!那么,如果我是在 Linux 下面打印纯文本文件呢 可不可以具有标题啊?可不可以加入页码啊?呵呵!当然可以啊!
    使用 pr 就能够达到这个功能了。不过, pr 的参数实在太多了,鸟哥也说不完,一般来说,鸟哥都仅使用最简单的方式来处理而已。举例来
    说,如果想要打印 /etc/man_db.conf 呢?

    [dmtsai@study ~]$ 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.
    .....(以下省略)......
    

        上面特殊字体那一行呢,其实就是使用 pr 处理后所造成的标题啦!标题中会有 “ 文件时间 ” 、 “ 文件文件名 ” 及 “ 页码 ” 三大项目。 更多的 pr
    使用,请参考 pr 的说明啊! _

    参考: <<鸟哥的Linux私房菜-基础学习篇(第四版)>>

  • 相关阅读:
    C#操作Word完全功略
    Ubuntu安装BackExec Remote Agent for Linux
    curl快速实现网速测试
    GitHub已将持续集成服务器Janky开源
    串行(Sequential)、并发(Concurrent)、并行(parallel)与分布式(distributed)
    使用ld的wrap选项替换已有库函数
    Linux获取程序编译参数
    配置Apache+Tomcat实现SSO(单点登录)
    ipad+Blackberry构建临时网络访问
    巧解 JavaScript 中的嵌套替换
  • 原文地址:https://www.cnblogs.com/freedom-try/p/12120388.html
Copyright © 2020-2023  润新知