1. 自动化文本处理——流编辑器sed和gawk:
1) 在自动化处理大量文本时普通的交互式编辑器(如Vim等)显得力不从心,而Unix中的流编辑器sed和gawk可以基于一组命令和脚本自动化处理大量数据;
2) 流编辑器的处理过程:
i. 先基于命令行或写在命令文件中的命令来处理文本;
ii. 然后逐行处理文件中的各行,读取一行、处理一行最后在终端(或指定的文件中)输出一行;
iii. 每读取一行便形成一条数据流,针对一条数据流处理一次,一行一行反复地处理直到全部行处理完毕;
iv. 流编辑器对文本的处理不会改变源文件中的内容,在处理的时候源文件被视为只读的,将读出的数据形成副本,然后对副本进行处理,处理完的副本可以输出到控制台也可以另保存在一个文件中;
3) sed和gawk:
i. sed是Unix下最基本的流编辑器,包含上面所说的最基本的功能,但是局限在于只能把它当普通的命令来使用,不能结构化、复杂化地组织其形式来达到更高级的功能;
ii. gawk是Unix下高级的流编辑器,不仅具有sed的优势,同时也提供了一种类编程环境,允许修改和重新组织文件中的数据,它提供的是一种编程语言而不只是简单的命令;
2. sed的基本用法:
1) 基本命令格式:sed options script/script_file file
!!script就是直接在命令行上写出处理命令,script_file就是保存命令集合的文件
2) 默认情况(无任何选项)下sed会将指定的命令应用到STDIN的输入流上:echo 'aaaa' | sed 's/a/b/'
!!这里必须使用echo,因为最后的那个处理目标的那个参数在命令行中指示的是文件名,想在命令行上之间输入待处理的字符串,必须用echo形成数据流作为待处理的文件并通过管道输送给sed;
3) 如果要一次性在命令行上输入多个处理命令就要使用-e命令:
i. 命令格式:sed -e script file
ii. script也是用''括起来的sed命令字符串,只不过在里面可以包含多条命令,多条命令之间用分号隔开;
iii. 如果不用-e的普通命令只能使用一条sed命令;
iv. 例如:sed -e 's/a/b/; s/c/d/' data_file
v. 如果在一行中输入太多命令不美观,可以分多行输入:
$ sed -e ' > s/a/b/ > s/c/d/ > s/f/g/' data_file!秘诀就是将引号隔开,在第一个引号后回车即可换行输出;
4) 但是无论如何直接从命令行中输入sed命令可能还不是特别美观,可以将sed命令写在一个文件中,然后使用-f命令指定sed命令所在的文件:
$ cat script_file s/a/b/ s/c/d/ $ sed -f script_file data_file
!!注意sed命令书写时的分号:不管是把命令写在文件中还是写在命令行上,如果多条命令写在一行上,中间都要用分号隔开,否则就一行一条命令不用加分号!!
3. sed的替换命令:
1) 替换命令:
i. 即s命令,substitute的缩写;
ii. 命令格式:s/pattern/replacement[/flags]
iii. pattern就是匹配的模式串,replacement就是替换后的字符串;
iv. 最后的flags是可选的,它就是替换标记,即substitution flags,可以指示替换时具体的行为;
2) 替换命令的标记:不写任何标记则在替换时将只替换每行中第一个出现的模式串,而还有其它一些常用的标记可以使用
a. 数字n:只替换每行中第n个出现的模式串
b. g:global的缩写,替换每行中所有的模式串
c. p:print的缩写,将原来的行打印出来
d. w file:write file的缩写,如果匹配成功则将修改后的行输出(追加到)指定文件中,没匹配上的行不输出到文件中
!!如果要同时使用多个标记,只要在各个标记之间加上空格隔开即可!!
!!-n和p的组合:就是在sed的选项中加-n,然后在sed替换命令中加p标记,这样组合就可以只输出被sed命令修改过的行,注意!只输出修改后的结果,修改前的和没有匹配上的行统统都不输出;
!-n就是no output的缩写,这样就不会输出到STDOUT上了;
3) sed命令的分隔符:
i. 有时需要替换的字符包含正斜杠'/',比如想把/bin/bash替换成/bin/csh;
ii. 但是/是sed命令字符串中的分隔符,因此需要使用反斜杠\来转义,比如sed 's/\/bin\/bash/\/bin\/csh/' /etc/passwd,但是这样使用会非常麻烦;
iii. 其实可以设置当前sed命令中使用的命令字符串分隔符,s命令后面出现的第一个字符就会被当成命令字符串的分隔符:sed 's!/bin/bash!/bin/csh!' /etc/passwd
!!在这里,由于!是第一个出现在命令s后的字符,因此!就会被当做命令字符串的分隔符;
4. 行寻址:
1) 可以将sed命令的作用范围限定在某几行内;
2) 使用格式:
i. 命令前缀:[address]command
ii. 组合命令前缀:
address { command1 command2 ... }!!命令前缀形式中address是可选的,如果不选则照常处理;
!![address]的格式在命令前缀和组合命令前缀里都是一样的;
3) [address]的格式:
i. 定点:只给出一个数字n,n指定只处理第n行,行数从1开始计,比如2s/a/b/,就表示值替换第2行的数据;
ii. 范围:该处一个数字区间top,bottom,只处理行号[top,bottom](闭区间)内的行,比如2,5s/a/b/,替换2~5行的数据;
iii. 射线:给出的区间是start,$,表示一直从start开始的行处理到底,$是末尾符号,比如2,$s/a/b/,替换从第2行开始的所有行;
3) 使用模式匹配来过滤行:
i. 有时不一定要用行号的形式来过滤要处理的行,有时只想处理包含某种字符串的行,比如只处理包含字符串'abc'的行;
ii. 此时就要使用文本模式过滤的方式了,使用格式:/pattern/command
iii. pattern就是正则表达式,也就是说只有能被pattern匹配上的行才能用后面的command进行处理;
iv. 比如:sed '/Peter/s/abc/cde/' data_file,这里就表示只替换那些包含字符串Peter的行;
5. 删除命令:
1) 很简单就是一个d,即delete的缩写,没有其它属性和标记;
2) 删除只需要关注寻址模式就行,光一个d就是删除整个文本了:sed 'd' data_file
!!不管是什么sed命令,就算是删除命令d,也不会修改源文件,只是对读出的数据流(源文件的副本)进行修改!
!!所以流编辑器是安全的,不会因为程序员的bug导致源文件损坏;
3) 关于要删哪几行,之前讲过的行寻址模式都可以使用,例如:sed '/abc/d' data_file,就是将所有包含字符串abc的行都删去;
4) 这里再介绍一中之前没介绍过的寻址模式,那就是文本范围过滤模式,例如:sed '/abc/, /xyz/d' data_file,这种就是利用正则表达式来匹配某一范围内的行;
!处理时还是一行一行读取并处理,当检查到包含字符abc的行时就会开启处理功能(这里是删除),并将接下来读取的每一行都处理掉(删掉),直到读取到包含字符串xyz的那一行才关闭处理功能(包含abc和xyz的两行也会删除),然后接下来继续读取剩下的行,重复上述过程;
!因此命令中出现的两个模式,第一个叫起始模式,第二个叫终止模式,如果给出的起始模式始终在文本中没出现过,那么处理功能始终都不会开启,如果起始模式能匹配到,但终止模式在文中没有,那么处理功能开启后就永远都不会关闭,一直从起始模式的那一行处理到整个文本的结束;
6. 插入和附加行:
1) 插入的命令是i,即insert的缩写,附加的命令是a,即append的缩写;
2) i会在指定的行前加入一个新行,a会在指定的行后加入一个新行;
3) i和a的命令格式比较奇葩,使用\作为命令分隔符:sed '[address]command\newline' data_file
4) 如果不加寻址模式,则会在源文件中的每一行前或行后添加一新行;
5) 加入寻址模式后,比如:
i. 2i就是在第2行前加入一新行;
ii. 3a就是在第3行后加入一新行;
iii. /abc/a:就是在所有包含abc字符串的行后加入一新行;
iv. $a就是在最后一行后插入一新行,有时不知道一个文件具体有多少行,但就是想在最后一行后加入新的一行就用这种方法;
6) 在命令行的sed命令中添加多行——\其实是行分隔符:
i. \在i和a命令中其实是行分隔符,\就表示一行的开始;
ii. 示例:
$ sed '5i\ > Line 1\ > Line 2' data_file!!必须用\加回车的形式另起一行,如果不回车换行则即使有\分隔符也会让Line 1和Line 2变成一行;
!!但是只插入一行数据就不必在第一个\后加回车换行;
7. 替换行:
1) 使用c命令,即change的缩写,可以用新的行来替换指定的整个行;
2) 命令格式和i、a一样:sed '[address]c\newline' data_file
3) c命令的寻址模式只有一点需要特别声明,那就是使用行号区间替换行时,会把指定区间的所有行用一个新行替换,而不是每行都替换一遍!!
4) 其余寻址模式都会替换所有匹配到的行;
5) 举例:
$ cat data 1 2 3 4 5 $ sed '2,4c\x' data 1 x 5!!行号范围内的所有行只用一行来替换!
$ cat data a abc abc c abc $ sed '/abc/c\xxx' data a xxx xxx c xxx!!其余寻址模式都是正常的,就是匹配到一行就替换掉一行;
8. 字符转换命令:
1) y命令可以转换数据流中的单个字符,转换的单词是transform,这里y不是transform的缩写,只是一个随便取的命令名;
2) 使用格式:[address]y/inchars/outchars/
3) inchars序列中的第一个字符会被转换成outchars序列的第一个字符,inchars的第二个字符会转换成outchars的第二个字符,依次类推,这是一个一一映射;
4) 该命令只能对[address]定址的那些行进行处理,但不能限定到一行中的第几个到第几个字符!!
5) 使用该命令时需要注意的两个错误:
i. inchars和outchar是长度必须相同,否则会直接在命令行中报错;
ii. 一个outchar可以对应多个inchar,这是理所当然的,但是一个inchar只能对应一个outchar,这在逻辑上是没问题;
iii. 但是有时候会错误地将一个inchar对应到不同的outchar上了,比如aba -> 123,像这里a分别对应到了1和3上了,那么此时sed会如何处理呢?
iv. 首先sed不会报错!替换时以后面出现的那对映射为准,因为a -> 3出现在a -> 1之后,所以最终a会被替换成3;
9. 打印命令p:
1) 打印命令还是p,即print的缩写,之前讲过的p是打印标记,是替换命令s的最后一个参数,用来打印替换前的行;
2) 而命令p直接强行打印指定的行,其使用格式就是简单的:[address]p,仅仅用来打印出指定的行
3) 通常p和sed的-n结合使用,只打印出想要的行,例如:sed -n '/Peter/p' data_file,这时就只会打印出包含字符串Peter的行;
4) -n和p标记组合只能打印出匹配行修改后的样子,但是如果再在次基础上加一个p命令就可以将修改前和修改后的样子同时打印出来:其它没匹配上的不打印(-n)
$ cat test.txt line 1 line 2 line 3 line 4 $ cat script /line 3/ { p s/line/test/p } $ sed -nf script test.txt line 3 test 3!第一个p命令打印修改之前的行,第二个p标记打印修改之后的行(和-n结合之后就变成了打印修改之后的行了);
10. 打印行号:
1) 使用=命令来打印行号,使用格式:[address]=
2) 使用该命令时注意,行号会单独成一行,出现在该行之前,例如:
$ sed -n '/line 4/ { = p } ' data_file 4 line 4!可以看到行号是单独占一行的;
11. 列出行:
1) 即l命令,即list的缩写;
2) 该命令也是强行打印匹配上的行,但不过也会把一些不可打印的控制字符也打印出来,只不过是用一些特殊符号或者是反斜杠加八进制值来表示;
3) 使用格式:[address]l
4) 示例:
$ cat test.txt a $ b c $ sed -n 'l' test.txt a\t$\tb\tc$!这里制表符就以\t来表示了,还有就是结尾的换行符\n并不是直接用\n来表示,而是用$表示一行的结束;
!!注意p命令和l命令,这两个命令不管匹配还是没匹配上,原来的行都会打印出来,如果不想看到其余多余的行,这两个命令必须都和-n结合起来,如果不使用-n,在匹配上时,显示结果会出现在原来的行之前;
12. 文件I/O:
1) 将数据流写入文件:
i. 使用命令w,即write的缩写;
ii. 格式:[address]w filename
iii. 例如:sed '1,3w out.txt' in.txt,这就将in.txt中的1到3行写入out.txt了;
2) 从文件中读入数据流插入到当前数据流中:
i. 命令时r,即read的缩写;
ii. 格式:[adress]r filename
iii. 会将filename文件中的全部数据插入到[address]指定的行之后
iv. [adress]只能指定具体的行号或者是文本匹配,不能使用任何范围匹配!!!因为它使插入到某个行后面而不能插入到某个范围后面!!这是理所应当的;
v. 例如:sed '3r in.txt' test.txt,这就将in.txt中的所有内容都插入到test.txt的第3行后,如果想插入在最后一行后,则使用$进行定址;
vi. 对于模式匹配定址,每匹配到一行就在该行后插入读取的文本;
3) 读取命令和删除命令结合使用——替换占位文本:
i. 替换占位文本就用一个例子来说明吧:有一个名单目录框架,目录是这样的
The following people will attend the meeting:
LIST
Please report to the office.
里面的LIST的位置是需要用具体的人名来填充的,而该文件只是一个框架,有起始语句和结束语句,只不过中间具体的人名先用一个占位符LIST来取代,当你取得具体名单后将名单中的人名列表填入到LIST占位符处
ii. 实现这种替换只需要这样做:
$ cat list Peter Tom Marry $ cat template Hello! LIST Good bye! $ cat script /LIST/ { r list d } $ sed -f script template Hello! Peter Tom Marry Good bye!!!注意!r命令一定要写在d命令之前,否则先删除掉的话就变成该消失,无法匹配,那么也就无法将数据插入到一个匹配不到的行上了,所以一定要r命令先写,后再删除!