[bash]sed流编辑器及其基本命令

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命令先写,后再删除!

你可能感兴趣的:(bash)