第11章 正规表示法与文件格式化处理

发布时间 2023-07-31 11:13:09作者: Evan-whc

第十一章 正规表示法与文件格式化处理

  正规表示法(Regular Expression,RE,或称为正规表示法)是透过一些特殊字符的排列,用以【搜寻/取代/删除】一列或多列文字字符串,简单地说,正规表示法就是用在字符串的处理上面的一项【表示式】。正规表示法并不是一个工具程序,而是一个字符串处理的标准依据,如果您想要以正规表示法的方式处理字符串,就得要使用正规表示法的工具程序才行,这类的工具程序很多,例如vi,sed,awk等等。

11.2 基础正规表示法

11.2.1 语系对正规表示法的影响

  使用正规表示法时,需要特别留意当时环境的语系为何,否则可能会发现与别人不相同的撷取结果喔!
  由于一般我们在练习正规表示法时,使用的是兼容于POSIX的标准,因此就使用【C】这个语系!因此,底下的很多练习都是使用【LANG=C】这个语系数据来进行呼叫喔!另外,为了要避免这样编码所造成的英文与数字的撷取问题,因此有些特殊的符号我们得要了解一下的!这些符号主要有底下这些意义:

特殊符号 代表意义
:alnum: 代表英文大小写字符及数字
:alpha: 代表任何英文大小写字符
:blank: 代表空格键与[tab]按键两者
:cntrl: 代表键盘上面的控制按键
:digit: 代表数字而已
:graph: 除了空格键(空格与tab)外的其他所有按键
:lower: 代表小写字符
:print: 代表任何可以被打印出来的字符
:punct: 代表标点符号
:upper: 代表大写字符
:space: 任何会产生空白的字符
:xdigit: 代表16进位的数字类型

  尤其上表中的[:alnum:],[:alpha:],[:upper:],[:lower:],[:digit:]这几个一定要知道代表什么意思,因为他要比a-z或A-Z的用途要确定的很!好了,底下就让我们开始来玩玩进阶版的grep吧!

11.2.2 grep的一些进阶选项

grep [-A] [-B] [--color=auto] '搜寻字符串' filename
选项与参数:
-A: 后面可加数字,为after的意思,除了列出该行外,后续的n行也列出来
-B: 后面可加数字,为befor的意思,除了列出该行外,前面的n行也列出来
--color=auto可将正确的那个撷取数据列出颜色

11.2.4 基础正规表示法字符汇整(characters)

RE字符 意义与范例
^word 待搜寻的字符串在行首
word$ 待搜寻的字符串在行尾
. 代表一定有一个任意字符的字符
| 跳脱字符,将特殊符号的特殊意义去除
* 重复零个到无穷多个的前一个RE字符
[list] 字符集合的RE字符,里面列出想要撷取的字符范围
n1-n2 字符集合的RE字符,里面列出想要撷取的字符范围
[^list] 字符集合的RE字符,里面列出不要的字符串或范围
连结n到m个的“前一个RE字符”

  再次强调,正规表示法的特殊字符与一般在指令列输入指令的”通配符“并不相同。

11.2.5 sed工具

  sed本身也是一个管线命令,可以分析standard input的啦!而且sed还可以将数据进行取代、删除、新增、撷取特定行等等的功能呢!很不错吧~

sed [-nefr] [动作]
选项与参数:
-n: 使用安静(silent)模式。在一般sed的用法中,所有来自STDIN的数据一般都会被列出到屏幕上。但如果加上-n参数后,则只有经过sed特殊处理的那一行(或者运作)才会被列出来。
-e: 直接在指令列模式上进行sed的动作编辑
-f: 直接将sed的动作写在一个文件内,-f filename则可以执行filename内的sed动作
-r: sed的动作支持是延伸型正规表示法的语法。(预设是基础正规表示法)
-i: 直接修改读取的文件内容,而不是由屏幕输出。

动作说明:[n1[,n2]]function
n1,n2:不见得会存在,一般代表【选择进行动作的行数】
funcion有底下这些咚咚:
a: 新增,a的后面可以接字符串,而这些字符串会在新的一行出现(目前的下一行)
c: 取代,c的后面可以接字符串,这些字符串可以取代n1,n2之间的行!
d: 删除,因为是删除啊,所以d后面通常不接任何咚咚
i: 插入,i的后面可以接字符串,而这些字符串会在新的一行出现(目前的上一行)
p: 打印,亦即将某个选择的数据印出。通常p会与参数sed -n一起运作
s: 取代,可以直接进行取代的工作哩!通常这个s的动作可以搭配正规表示法!
  • 以行为单位的新增/删除功能
    nl /etc/passwd | sed '2,5d'
    添加一行以上内容时,每一行之间都必须要以反斜杠\来进行新行的增加喔!
nl /etc/passwd | sed '2a Drink tea or ......\
drink beer ?'
  • 以行为单位的取代与显示功能
    nl /etc/passwd | sed '2,5c No 2-5 number'
    nl /etc/passwd | sed -n '5,7p' #列出文件内的5-7行
  • 部分数据的搜寻并取代的功能
    除了事先的处理模式之外,sed还可以用行为单位进行部分数据的搜寻并取代的功能喔!基本上sed的搜寻与取代与vi相当的类似!他有点像这样:
    set 's/要被取代的字符串/新的字符串/g'
  • 底下这个范例从[ifconfig]指令的输出中撷取出ip地址字段!
一、观察命令输出
/sbin/ifconfig eth0
......
    inet 192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255
......

二、配合grep撷取出一行数据
/sbin/ifconfig eth0 | grep 'inet '
   inet 192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255

三、将IP前面的部分予以删除
/sbin/ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g'
192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255

四、将IP后面的部分予以删除
/sbin/ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g' \
| sed 's/ *netmask.*$//g'
192.168.1.100

11.3 延伸正规表示法

  事实上,一般读者只要了解基础型的正规表示法大概就已经相当足够了,不过,某些时刻为了要简化整个指令操作,了解一下使用更广的延伸型正规表示法会更方便呢!
  如果要使用延伸型正规表示法,你可以使用grep -E,不过更建议直接使用egrep!
  延伸型正规表示法的特殊符号如下?

RE字符 意义与范例
+ 重复一个或一个以上的前一个RE字符
? 零个或一个的前一个RE字符
| 用或(or)的方式找出数个字符串

  另外,要强调的是,那个!在正规表示法当中并不是特殊字符,所以,如果你想要查出来文件中含有!与>的字行时,可以这样:
grep -n '[!>] regular_express.txt

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

11.4.1 格式化打印:printf

printf '打印格式' 实际内容
选项与参数:
  关于格式化方面的几个特殊样式:
    \a 警告声音输出
    \b 退格键
    \f 清除屏幕
    \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个位数,但小数点有两位,即为%10.2f啰!
范例:
printf '%s\t %s\t %s\t %s\t %s\t \n' $(cat printf.txt)

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

  awk也是一个非常棒的数据处理工具!相较于sed常常作用于一整个行的处理,awk则比较倾向于一行当中分成数个【字段】来处理。因此,awk相当的适合处理小型的数据处理呢!awk通常运作的模式是这样的:
awk '条件类型1{动作1} 条件类型2{动作2}...' filename
  awk后面接两个单引号并加上大括号{}来设定想要对数据进行的处理动作。awk可以处理后续接的文件,也可以读取来自前个指令的standard output。但如前面说的,awk主要是处理【第一行的字段内的数据】,而默认的【字段的分隔符为“空格键”或“[tab]”】!举例来说,我们用last可以将登入者的数据取出来,若我想要取出帐号与登入者的IP,且帐号与IP之间以[tab]隔开,则会变成这样:
last -n 5 | awk '{print $1 "\t" $3}'
  在awk的括号内,每一行的每个字段都是有变量名称的,那就是$1,$2...等变量名称。注意$0代表【一整列资料】的意思。
  awk怎么知道这个数据到底有几行?有几栏呢?这就需要awk的内建变量的帮忙啦~

变量名称 代表意义
NF 每一行($0)拥有的字段总数
NR 目前awk所处理的是[第几行]数据
FS 目前的分隔字符,默认是空格键
  • awk的逻辑运算字符

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

  举例来说,在/etc/passwd当中是以冒号""来作为字段的分隔,该文件中第一字段为帐号,第三字段则是UID。那假设我要查阅,第三栏小于10以下的数据,并且仅列出帐号与第三栏,那么可以这样做:
cat /etc/passwd | awk '{FS=":"}$3 < 10 {print $1 "\t " $3}'
  不过,怎么第一行没有正确的显示出来呢?这是因为我们读入第一行的时候,那些变数$1,$2...默认还是以空格键为分隔的,所以虽然我们定义了FS=":"了,但是却仅能在第二行后才开始生效。那么怎么办呢?我们可以预先设定awk的变量啊!利用BEGIN这个关键词喔!这样做:cat /etc/passwd | awk 'BEGIN {FS=":"} $3<10 {print $1 "\t " $3}'

11.4.3 文件比对工具

  • diff

    diff [-bBi] from-file to-file
    选项与参数:
    from-file: 一个档名,作为原始比对文件的档名;
    to-file: 一个档名,作为目的比对文件的档名;
    注意,from-file或to-file可以-取代,那个 - 代表Standard input之意
    -b: 忽略一行当中,仅有多个空白的差异
    -B:忽略空白行的差异
    -i: 忽略大小写的不同
    
  • cmp
      cmp主要利用【字节】去比对两个文件。

    cmp [-l] file1 file2
    选项与参数:
    -l:将所有的不同点的字节都列出来。因为cmp预设仅会输出第一个发现的不同点。
    
  • patch
    patch这个指令与diff可是有密不可分的关系啊!我们前面提到,diff可以用来分辨两个版本之间的差异,举例来说,刚刚我们所建立的passwd.old及passwd.new之间就是两个不同版本的文件。那么,如果要【升级】呢?就是【将旧的文件升级成为新的文件】时,应该要怎么做呢?其实也不难啦!就是【先比较新旧版本的差异,并将差异档制作成为补丁档,再由补丁档更新旧文件】即可。例如:
    diff -Naur passwd.old passwd.new > passwd.patch

    patch -pN < patch_file # 更新
    patch -R -pN < patch_file # 还原
    选项与参数:
    -p: 后面可以接【取消几层目录】的意思。
    -R: 代表还原,将新的文件还原成原来旧的版本。