zoukankan      html  css  js  c++  java
  • 正则表达式与文件格式化处理(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私房菜-基础学习篇(第四版)>>

  • 相关阅读:
    VisualSVN-Server windows 版安装时报错 "Service 'VisualSVN Server' failed to start. Please check VisualSVN Server log in Event Viewer for more details."
    Pytest 单元测试框架之初始化和清除环境
    Pytest 单元测试框架入门
    Python(email 邮件收发)
    Python(minidom 模块)
    Python(csv 模块)
    禅道简介
    2020年最好的WooCommerce主题
    Shopify网上开店教程(2020版)
    WooCommerce VS Magento 2020:哪个跨境电商自建站软件更好?
  • 原文地址:https://www.cnblogs.com/freedom-try/p/12120388.html
Copyright © 2011-2022 走看看