龙之介大人

linux下文本比对sed与awk使用方法
10.1 sed 工具的使用sed 本身也是一个管线命令,可以分析 standard input ! 而且 sed...
扫描右侧二维码阅读全文
23
2019/08

linux下文本比对sed与awk使用方法

10.1 sed 工具的使用

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

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

动作说明: [n1[,n2]]function
n1, n2 :不见得会存在,一般代表『选择进行动作的行数』,举例来说,如果我的动作
        是需要在 10 到 20 行之间进行的,则『 10,20[动作行为] 』

function 有底下这些参数:
a :    新增, a 的后面可以接字符串,而这些字符串会在新的一行出现(目前的下一行)~ 
c :    取代, c 的后面可以接字符串,这些字符串可以取代 n1,n2 之间的行!
d :    删除,因为是删除啊,所以 d 后面通常不接任何东西;
i :    插入, i 的后面可以接字符串,而这些字符串会在新的一行出现(目前的上一行); 
p :    打印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运作~
s :    取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正则表达式!
       例如 1,20s/old/new/g 就是!

10.1.1 以行为单位的新增/删除功能

sed指令的练习1
#范例一:将 /etc/passwd 的内容列出并且打印行号,同时,请将第 2~5 行删除!
[root@study xiaoqi]# cat -n /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
......

sed 的动作为'2,5d',那个 d 就是删除!因为 2-5 行给他删除了,所以显示的数据就没有 2-5 行!

注意一下,原本应该是要下达sed -e才对,没有-e也行啦!同时也要注意的是,sed后面接的动作,请务必以''两个单引号括住

如果题型变化一下,举例来说:

如果只要删除第 2 行,可以使用nl /etc/passwd | sed '2d'来达成,至于若是要删除第 3 到最后一行,则是nl /etc/passwd | sed '3,$d',那个$ 代表 最后一行!

#范例二:承上题,在第二行后(亦即是加在第三行)加上『drink tea?』字样!
[root@study xiaoqi]# cat -n /etc/passwd | sed '2a drink tea?'
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:bin:/bin:/sbin/nologin
drink tea?
     3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
......

在 a 后面加上的字符串就已将出现在第二行后面!

那如果是要在第二行前呢?nl /etc/passwd | sed '2i drink tea'就行了.就是将a变成i即可。

增加一行很简单,那如果是要增将两行以上呢?

#范例三:在第二行后面加入两行字,例如『Drink tea or .....』与『drink beer?』
[root@study xiaoqi]# cat -n /etc/passwd | sed '2a drink tea or ......\
> drink beer ?'
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:bin:/bin:/sbin/nologin
drink tea or ......
drink beer ?
     3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
......

这个范例的重点是我们可以新增不只一行!可以新增好几行但是每一行之间都必须要以反斜杠\来进行新行的增加.所以,上面的例子中,我们可以发现在第一行的最后面就有\存在! 在多行新增的情况下,\是一定要的使用的~

10.1.2 以行为单位的取代与显示功能

刚刚是介绍如何新增与删除,那么如果要整行取代呢?看看底下的范例:

#范例四:我想将第 2-5 行的内容取代成为『No 2-5 number』呢?
[root@study xiaoqi]# nl /etc/passwd | sed '2,5c No 2-5 number'
     1  root:x:0:0:root:/root:/bin/bash
No 2-5 number
     6  sync:x:5:0:sync:/sbin:/bin/sync
......

透过这个方法我们就能够将数据整行取代了!

sed 还有更好用的东西!我们以前想要列出第 11~20 行,得要透过head -n 20 | tail -n 10之类的方法来处理,很麻烦sed则可以简单的直接取出你想要的那几行!是透过行号来捉的!看看底下的范例:

#范例五:仅列出 /etc/passwd 文件内的第 5-7 行
[root@study xiaoqi]# cat -n /etc/passwd | sed -n '5,7p'       
     5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
     6  sync:x:5:0:sync:/sbin:/bin/sync
     7  shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
......

上述的指令中有个重要的选项-n按照说明文件,这个-n代表的是安静模式! 那么为什么要使用安静模式呢?你可以自行下达 sed '5,7p' 就知道了(5-7 行会重复输出)! 有没有加上-n的参数时,输出的数据可是差很多的.

10.1.3 部分数据的搜寻并取代的功能

除了整行的处理模式之外, sed 还可以用行为单位进行部分数据的搜寻并取代的功能! 基本上 sed 的搜寻与取代的与 vi 相当的类似!他有点像这样:

sed 's/要被取代的字符串/新的字符串/g'

上面第一个和最后一个字母为关键词,请记下来!至于三个斜线分成两栏就是新旧字符串的替换! 我们使用底下这个取得 IP 数据的范例,

#步骤一:先观察原始讯息,利用 /sbin/ifconfig 查询 IP 
eno16780032: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.10.1.150  netmask 255.255.255.0  broadcast 10.10.1.255
        inet6 fd23:c0b6:cf03:0:20c:29ff:fe61:1d7f  prefixlen 64  scopeid 0x0<global>
......
#也就是 10.10.1.150 那一行而已!先利用关键词捉出那一行!


#步骤二:利用关键词配合 grep 撷取出关键的一行数据
[root@study xiaoqi]# ifconfig eno16780032  | grep 'inet '
        inet 10.10.1.150  netmask 255.255.255.0  broadcast 10.10.1.255
#当场仅剩下一行!要注意, CentOS 7 与 CentOS 6 以前的 ifconfig 指令输出结果不太相同,
#就是像底下这样:

~inet~ 192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255

#上面的删除关键在于『 ^.*inet 』


#步骤三:将 IP 前面的部分予以删除
[root@study xiaoqi]# ifconfig eno16780032  | grep 'inet ' | sed 's/^.*inet //g'
10.10.1.150  netmask 255.255.255.0  broadcast 10.10.1.255
#仔细与上个步骤比较一下,前面的部分不见了!接下来则是删除后续的部分,亦即:

192.168.1.100 ~netmask 255.255.255.0 broadcast 192.168.1.255~

#此时所需的正则表达式为:『 ' *netmask.*$ 』


#步骤四:将 IP 后面的部分予以删除
[root@study xiaoqi]# ifconfig eno16780032  | grep 'inet ' | sed 's/^.*inet //g' | sed 's/ *netmask.*$//g'
10.10.1.150

透过这个范例的练习也建议依据此一步骤来研究你的指令!就是先观察,然后再一层一层的试做,如果有做不对的地方,就先予以修改,改完之后测试,成功后再往下继续测试。

让我们再来继续研究 sed 与正则表达式的配合练习!假设我只要 MAN 存在的那几行数据,但是含有 # 在内的批注我不想要,而且空白行我也不要!此时该如何处理呢?

可以透过这几个步骤来试试看看:

#步骤一:先使用 grep 将关键词 MAN 所在行取出来
[root@study xiaoqi]# cat /etc/man_db.conf  | grep 'MAN'
#MANDATORY_MANPATH             manpath_element
#MANPATH_MAP       path_element    manpath_element
#MANDB_MAP         global_manpath  [relative_catpath]
#every automatically generated MANPATH includes these fields
....

步骤二:删除掉批注之后的数据!
[root@study xiaoqi]# cat /etc/man_db.conf  | grep 'MAN' | sed s/^#.*$//g





MANDATORY_MANPATH               /usr/man
MANDATORY_MANPATH               /usr/share/man
......
#从上面可以看出来,原本批注的数据都变成空白行!所以,接下来要删除掉空白行


[root@study xiaoqi]# cat /etc/man_db.conf  | grep 'MAN' | sed s/^#.*$//g | sed '/^$/d' 
MANDATORY_MANPATH               /usr/man
MANDATORY_MANPATH               /usr/share/man
MANDATORY_MANPATH               /usr/local/share/man
......

10.1.4 直接修改文件内容(危险动作)

你以为 sed 只有这样的能耐吗?不!

sed 甚至可以直接修改文件的内容呢!而不必使用管线命 令或数据流重导向! 不过,由于这个动作会直接修改到原始的文件,所以请你千万不要随便拿系统配置文件来测试!

#范例六:利用 sed 将 regular_express.txt 内每一行结尾若为 . 则换成 !
[root@study xiaoqi]# sed -i 's/\.$/\!/g' regular_express.txt 
#上头的 -i 选项可以让你的 sed 直接去修改后面接的文件内容而不是由屏幕输出! 
#这个范例是用在取代!请您自行 cat 该文件去查阅结果!

[root@study xiaoqi]# cat regular_express.txt 
"Open Source" is a good mechanism to develop programs!
apple is my favorite food!


#范例七:利用 sed 直接在 regular_express.txt 最后一行加入『# This is a test』
[root@study xiaoqi]# sed -i '$a # This is a test' regular_express.txt 
[root@study xiaoqi]# cat regular_express.txt    
......
#This is a test
#由于 $ 代表的是最后一行,而 a 的动作是新增,因此该文件最后新增!

sed 的『 -i 』选项可以直接修改文件内容,这功能非常有帮助!举例来说,如果你有一个 100 万行 的文件,你要在第 100 行加某些文字,此时使用 vim 可能会疯掉!因为文件太大了!

那怎办?就利用 sed !透过 sed 直接修改/取代的功能,你甚至不需要使用 vim 去修改!

10.2 正则表达式的延伸

事实上,一般读者只要了解基础型的正则表达式大概就已经相当足够了,不过,某些时刻为了要简化 整个指令操作, 了解一下使用范围更广的延伸型正则表达式的表示式会更方便!

好了,在上节的例题三的最后一个例子中,我们要去除空白行与行首为 # 的行列,使用的是:grep -v '^$' regular_express.txt | grep -v '^#'需要使用到管线命令来搜寻两次!

那么如果使用延伸型的正则表达式,我们可以简化为:egrep -v '^$|^#' regular_express.txt

延伸型正则表达式可以透过群组功能『 | 』来进行一次搜寻!那个在单引号内的管线意义为『或 or』.

此外,grep 预设仅支持基础正则表达式,如果要使用延伸型正则表达式, 你可以使用 grep -E , 不过更建议直接使用 egrep !直接区分指令比较好记忆!其实 egrep 与 grep -E 是类似命令别名的关系.

  • 正则表达式延伸表
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
                                    
()                    意义:找出『群组』字符串
                    范例:将『AxyzxyzxyzxyzC』用echo输出,然后再使用如下的方法搜寻一下!
                    echo 'AxyzxyzxyzxyzC' | egrep 'A(xyz)+C'
                    上面的例子意思是说,我要找开头是 A 结尾是 C ,中间有一个以上的 "xyz" 字符串的意思~|

以上这些就是延伸型的正则表达式的特殊字符。另外,要特别强调的是,那个 ! 在正则表达式当中 并不是特殊字符, 所以,如果你想要查出来文件中含有 ! 与 > 的字行时,可以这样:

grep -n '[!>]' regular_express.txt

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

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

底下这些动作可以将你的讯息进行排版的动作,不需要重新以 vim 去编辑,透过数据流重导向配合底下介绍的 printf 功能,以及 awk 指令,就可以让你的讯息以你想要的模样来输出了!

10.3.1 格式化打印: printf

在很多时候,我们可能需要将自己的数据给他格式化输出的! 举例来说,考试卷分数的输出,姓名与科目及分数之间,总是可以稍微作个比较漂亮的版面配置吧? 例如我想要输出底下的样式:

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 警告声音输出
    \b 退格键(backspace)
    \f 清除屏幕 (form feed)
    \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.2f 啰!

接下来我们来进行几个常见的练习。假设所有的数据都是一般文字 (这也是最常见的状态),因此最 常用来分隔数据的符号就是 [Tab]!因为 [Tab] 按键可以将数据作个整齐的排列!那么如何利用 printf 呢?

  • 参考底下这个范例:
范例一:将刚刚上头数据的文件 (printf.txt) 内容仅列出姓名与成绩:(用 [tab] 分隔)
[root@study xiaoqi]# printf '%s\t %s\t %s\t %s\t %s\t \n' $(cat printf.txt)
Name  Chinese    English         Math  Average   
DmTsai  80       60  92  77.33   
VBird   75       55  80  70.00   
Ken   60  90     70  73.33  

由于 printf 并不是管线命令,因此我们得要透过类似上面的功能,将文件内容先提出来给 printf 作 为后续的资料才行。如上所示,我们将每个数据都以 [tab]作为分隔,但是由于 Chinese 长度太长,导致 English 中间多了一个 [tab] 来将资料排列整齐!结果就看到资料对齐结果的差异了!

另外,在 printf 后续的那一段格式中,%s 代表一个不固定长度的字符串,而字符串与字符串中间就 以 t 这个 [tab] 分隔符来处理!你要记得的是,由于 t 与 %s 中间还有空格,因此每个字符串间会有一个 [tab] 与一个空格键的分隔!

既然每个字段的长度不固定会造成上述的困扰,那我将每个字段固定就好啦!没错没错!这样想非常 好! 所以我们就将数据给他进行固定字段长度的设计!

范例二:将上述资料关于第二行以后,分别以字符串、整数、小数点来显示:
[root@study xiaoqi]# printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt | grep -v Name)
    DmTsai    80    60    92    77.33 
     VBird    75    55    80    70.00 
       Ken    60    90    70    73.33 

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

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

如上所述,全部的宽度仅有 8 个字符,整数部分占有 5 个字符,小数点本身 (.) 占一位,小数点下 的位数则有两位。

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

范例三:列出 16 进位数值 45 代表的字符为何?
[root@study xiaoqi]# printf '\x45\n'
E
#可以自行测试一下,由 20~80 之间的数值代表的字符是啥

printf 的使用相当的广泛喔!包括等一下后面会提到的 awk 以及在 C 程序语言当中使用的屏幕输出,都是利用 printf!

10.3.2 数据处理工具: awk

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

[dmtsai@study ~]$ awk '条件类型 1{动作 1} 条件类型 2{动作 2} ...' filename

awk 后面接两个单引号并加上大括号 {} 来设定想要对数据进行的处理动作。 awk 可以处理后续接 的文件,也可以读取来自前个指令的 standard output 。

但如前面说的, awk 主要是处理『每一行 的字段内的数据』,而默认的『字段的分隔符为 "空格键" 或 "[tab]键" 』!举例来说,我们用 last 可以将登入者的数据取出来,结果如下所示:

[root@study xiaoqi]# last  -n 5  <--仅取5行
xiaoqi   pts/0        macbookpro.lan   Fri Aug 23 11:59   still logged in   
xiaoqi   pts/1        macbookpro.lan   Thu Aug 22 20:03 - 20:03  (00:00)    
xiaoqi   pts/0        macbookpro.lan   Thu Aug 22 08:13 - 23:36  (15:23)    
xiaoqi   pts/0        macbookpro.lan   Wed Aug 21 14:13 - 22:37  (08:23)    
xiaoqi   pts/0        macbookpro.lan   Wed Aug 21 12:52 - 14:13  (01:20) 

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

[root@study xiaoqi]# last  -n 5 | awk '{print $1 "\t" $3}'
xiaoqi macbookpro.lan
xiaoqi macbookpro.lan
xiaoqi macbookpro.lan
xiaoqi macbookpro.lan
xiaoqi macbookpro.lan

wtmp Fri

上表是 awk 最常使用的动作!透过 print 的功能将字段数据列出来!字段的分隔则以空格键或 [tab] 按键来隔开。

因为不论哪一行我都要处理,因此,就不需要有 "条件类型" 的限制!我所想要的是 第一栏以及第三栏, 但是,第五行的内容怪怪的~这是因为数据格式的问题!

所以~使用 awk 的时候,请先确认一下你的数据当中,如果是连续性的数据,请不要有空格或 [tab] 在内,否则,就会像这个例子这样,会发生误判!

另外,由上面这个例子你也会知道,在 awk 的括号内,每一行的每个字段都是有变量名称的,那就 是 $1, $2... 等变量名称。

以上面的例子来说,xiaoqi 是 $1,因为他是第一栏嘛!至于 macbookpro.lan第三栏,所以他就是 $3 啦!后面以此类推~

还有个变量喔!那就是 $0$0 代表一整列数据的意思~以上面的例子来说,第一行的 $0 代表的就是xiaoqi ....那一行!

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 的指令固定用法了!

[root@study xiaoqi]# last  -n 5 | awk '{print $1 "\t lines:" NR "\t columns: " NF}'
xiaoqi  lines:1  columns: 10
xiaoqi  lines:2  columns: 10
xiaoqi  lines:3  columns: 10
xiaoqi  lines:4  columns: 10
xiaoqi  lines:5  columns: 10
  lines:6        columns: 0
wtmp  lines:7    columns: 7
#注意喔,在 awk 内的 NR, NF 等变量要用大写,且不需要有 $ 符号!

这样可以了解 NR 与 NF 的差别了吧!

10.3.3 awk 的逻辑运算字符

既然有需要用到 "条件" 的类别,自然就需要一些逻辑运算~例如底下这些:

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

值得注意的是那个『 == 』的符号,因为:

  • 逻辑运算上面亦即所谓的大于、小于、等于等判断式上面,习惯上是以『 == 』来表示;
  • 如果是直接给予一个值,例如变量设定时,就直接使用 = 而已。

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

[root@study xiaoqi]# cat /etc/passwd | awk '{FS=":"} $3<10 {print $1 "\t " $3}'
root:x:0:0:root:/root:/bin/bash  
bin   1
daemon  2
adm   3
lp  4
sync  5
shutdown  6
halt  7
mail  8

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

利用 BEGIN 这个关键词喔!这样做:

[root@study xiaoqi]# cat /etc/passwd | awk 'BEGIN {FS=":"} $3<10 {print $1 "\t " $3}' 
root  0
bin   1
daemon  2
adm   3
lp  4
sync  5
shutdown  6
halt  7
mail  8

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

利用 awk 这个玩意儿,就可以帮我们处理很多日常工作了呢!真是好用的很~ 此外, awk 的输出格式当中,常常会以 printf 来辅助,所以,最好你对 printf也稍微熟悉一下比较好啦!另外, awk 的动作内 {} 也是支持 if (条件) 的喔! 举例来说,上面的指令可以修订成为这样:

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

你可以仔细的比对一下上面两个输入有啥不同~从中去了解两种语法!我个人是比较倾向于使用第一种语法,因为会比较有统一性!

10.4 文件比对工具

什么时候会用到文件的比对啊?通常是『同一个软件包的不同版本之间,比较配置文件与原始档的差 异』。很多时候所谓的文件比对,通常是用在 ASCII 纯文本档的比对上的!那么比对文件的指令有哪些?最常见的就是 diff ! 另外,除了 diff 比对之外,我们还可以藉由 cmp 来比对非纯文本档!同时,也能够藉由 diff 建立的分析文件,以处理补丁 (patch) 功能的文件呢!

10.4.1 diff

iff 就是用在比对两个文件之间的差异的,并且是以行为单位来比对的!

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

[root@study xiaoqi]# mkdir -p /tmp/testpw   <--建立测试文件夹
[root@study xiaoqi]# cd /tmp/testpw/
[root@study testpw]# cp /etc/passwd passwd.old
[root@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 的差异:
[root@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 比对文件真的是很简单喔!不过,你不要用 diff 去比对两个完全不相干的文件,因为比不出 个啥咚咚! 另外,diff 也可以比对整个目录下的差异喔!举例来说,我们想要了解一下不同的开机 执行等级 (runlevel) 内容有啥不同?假设你已经知道执行等级 0 与 5 的启动脚本分别放置到 /etc/rc0.d 及 /etc/rc5.d , 则我们可以将两个目录比对一下:

[root@study testpw]# diff /etc/rc0.d/ /etc/rc5.d/
只在 /etc/rc0.d/ 存在:K90network
只在 /etc/rc5.d/ 存在:S10network

我们的 diff 很聪明吧!还可以比对不同目录下的相同文件名的内容,这样真的很方便~

10.4.2 cmp

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

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


#范例一:用 cmp 比较一下 passwd.old 及 passwd.new
[root@study testpw]# cmp passwd.old passwd.new 
passwd.old passwd.new 不同:第 106 字节,第 4 行

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

10.4.3 patch

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

#范例一:以 /tmp/testpw 内的 passwd.old 与 passwd.new 制作补丁文件
[root@study testpw]# diff -Naur passwd.old passwd.new > passwd.patch
[root@study testpw]# cat passwd.patch 
--- passwd.old  2019-08-23 20:50:06.120160327 +0800    <--新旧文件的信息
+++ passwd.new  2019-08-23 20:50:48.284280422 +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 相同!可以这样做:

#安装patch 这个软件
[root@study testpw]# yum install patch


[dmtsai@study ~]$ patch -pN < patch_file <==更新 
[dmtsai@study ~]$ patch -R -pN < patch_file <==还原
选项与参数:
-p :后面可以接『取消几层目录』的意思。
-R :代表还原,将新的文件还原成原来旧的版本。


#范例二:将刚刚制作出来的 patch file 用来更新旧版数据
[root@study testpw]# ll passwd*
-rw-r--r--. 1 root root 2216 8月  23 20:50 passwd.new
-rw-r--r--. 1 root root 2216 8月  23 21:20 passwd.old
-rw-r--r--. 1 root root 2273 8月  23 20:50 passwd.old.orig
#文件就这样恢复成为旧版本啰

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

10.5 文件打印准备: pr

如果你曾经使用过一些图形接口的文字处理软件的话,那么很容易发现,当我们在打印的时候,可以同时选择与设定每一页打印时的标头吧!也可以设定页码呢!那么,如果我是在 Linux 底下打印 纯文本档呢?可不可以具有标题?可不可以加入页码?

当然可以啊!使用 pr 就能够达到 这个功能了。不过, pr 的参数实在太多了,使用最简单的方式来处理就行.

举例来说,如果想要打印 /etc/man_db.conf 呢?

[root@study testpw]# pr /etc/man_db.conf 


2014-06-10 05:35                /etc/man_db.conf                 第 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
#their PATH environment variable. For details see the manpath(5) man page.
....

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


  1. a-z
最后修改:2019 年 08 月 23 日 09 : 43 PM

发表评论