本文共 13005 字,大约阅读时间需要 43 分钟。
Shell脚本之sed篇
目录: 一、概述 二、sed基本语法格式 三、简单正则表达式 四、sed脚本指令(注释、替换、删除、追加、打印、下一行、读写文件、退出、多行模式) 五、综合案例 一、概述 1.sed是一款流编辑工具,用来对文本进行过滤与替换工作,特别是当你想要对几十个配置文件做统计修改时,你会感受到sed的魅力! sed通过输入读取文件内容,但一次仅读取一行内容进行某些指令处理后输出,所以sed更适合于处理大数据文件。 2.sed流程: * 通过文件或管道读取文件内容。 * sed并不直接修改源文件,而是将读入的内容复制到缓冲区中,我们称之为模式空间(pattern space)。 * 根据sed的指令对模式空间中的内容进行处理并输出结果,默认输出至标准输出即屏幕上。 输入 ------------------------------------------------ | | v v 读取一行内容,并复制到模式空间 <------------ sed 指令 | | v v ------------------------------------------------ 输出经过处理后的内容 ***************************************************************************************************** 二、sed基本语法结构 sed Options... [script] [inputfile...] sed 选项... [脚本指令] [输入文件] 如果没有输入文件,则sed默认对标准输入进行处理(即键盘输入)。脚本指令是第一个不以“-”开始的参数。 1.选项含义: --version 显示sed版本。 --help 显示帮助文档。 -n,--quiet,--silent 静默输出,默认情况下,sed程序在所有的脚本指令执行完毕后,将自动打印 模式空间中的内容,这些选项可以屏蔽自动打印。 -e script 允许多个脚本指令被执行。 -f script-file, --file=script-file 从文件中读取脚本指令,对编写自动脚本程序来说很棒! -i,--in-place 直接修改源文件,经过脚本指令处理后的内容将被输出至源文件(源文件被修改) 慎用! -l N, --line-length=N 该选项指定l指令可以输出的行长度,l指令用于输出非打印字符。 --posix 禁用GNU sed扩展功能。 -r, --regexp-extended 在脚本指令中使用扩展正则表达式 -s, --separate 默认情况下,sed将把命令行指定的多个文件名作为一个长的连续的输入流。 而GNU sed则允许把他们当作单独的文件, 这样如正则表达式则不进行跨文件匹配。 -u, --unbuffered 最低限度的缓存输入与输出。 2.简单案例: 以上仅是sed程序本身的选项功能说明,至于具体的脚本指令(即对文件内容做的操作)后面我们会详细描述, 这里就简单介绍几个脚本指令操作作为sed程序的例子。 a,append 追加 i,insert 插入 d,delete 删除 s,substitution 替换案例说明:灰色背景的内容为待处理的源文件,红色字体的文字为sed脚本,蓝色字体的文字为处理后的结果输出。
这里test.txt为样本文件: [jacob@localhost ~] #cat test.txtDEVICE=eth0 ONBOOT=yes BOOTPROTO=static IPADDR=192.168.0.1 NETMASK=255.255.255.0 GATEWAY=192.168.0.254 [jacob@localhost ~] #sed '2a TYPE=Ethernet' test.txt 第二行后添加TYPE=Ethernet [jacob@localhost ~] #sed '3i TYPE=Ethernet' test.txt 第三行前添加TYPE=Ethernet [jacob@localhost ~] #sed 's/yes/no/g' test.txt 将样本文件中的所有yes替换为no [jacob@localhost ~] #sed '3,4d' test.txt 删除第3至4行的内容 以上大多数操作指令,都依据行号定位操作对象(地址),如:2a即第二行后添加。 但实际情况可能大多数情况你并不确定你要操作对象(地址)的行号,这时更多的我们会使用正则表达式确定操作对象(地址)。 下面是使用正则表达式定位操作行的示例: [jacob@localhost ~] #sed '/ONBOOT/a TYPE=Ethernet' test.txt匹配到包含ONBOOT的行,并在其后添加TYPE=Ethernet
[jacob@localhost ~] #sed '/^GATEWAY/d' test.txt匹配以GATEWAY开始的行,并删除改行
另外我们的操作指令可以,写入到脚本文件中并通过sed的-f选项读取。 创建一个sed脚本,内容如下: [jacob@localhost ~] #cat sed.sh/^$/d 这条指令的作用是:匹配到空白行后,删除改行。 [jacob@localhost ~] #sed -f sed.sh test.txt 对test.txt文件执行sed.sh指令 另外,当你需要执行多个指令时,可以使用以下三种方法: 1. [jacob@localhost ~] #sed 's/yes/no/;s/static/dhcp/' test.txt 注:使用分号隔开指令。 2. [jacob@localhost ~] #sed -e 's/yes/no/' -e 's/static/dhcp/' test.txt 注:使用-e选项。 3. [jacob@localhost ~] #sed ' >s/yes/no/ >s/static/dhcp/' test.txt 注:利用分行指令。 然而在命令行上输入过长的指令是愚蠢的,这时就需要-f选项指定sed脚本文件,在脚本文件中可以包含多行指令,而且便于修改! ***************************************************************************************** 三、简单正则表达式 从以上案例中我们不难发现,我们编写的脚本指令需要指定一个地址来决定操作范围,如果不指定则默认对文件的所有行操作。 如:sed 'd' test.txt 将删除test.txt的所有行,而'2d'则仅删除第二行。 1.为sed指令确定操作地址: number 指定输入文件的唯一行号。 first~step 以first开始,并指定操作步长为step,如1~2,指定第一行,第三行,第五行... 为操作地址。 [jacob@localhost ~] #sed -n '1~2p' test.txt 打印文件的奇数行。 2~5,则可以指定第二行开始,每5行匹配一次操作地址。 $ 匹配文件的最后一行。 /regexp/ //中间包含的是正则表达式,通过正则表达式匹配操作地址。 注://空的正则表达式,匹配最近一次正则表达式的匹配地址,会在后面使用看出效果。 \cregexpc 匹配扩展正则表达式,c字符可以使用任意字符替代。 addr1,addr2 匹配从操作地址1到操作地址2的所有行。 [jacob@localhost ~] #sed '2,8d' test.txt 删除2至8中间的所有行。 addr1,+N 匹配地址1以及后面的N行内容。 2.正则表达式概述(对你要找内容的一种描述) char 字符本身就匹配字符本身,如/abc/就是定位包含abc的行。 * 匹配前面表达式出现了0或若干次,如/a*/可以帮你找到a,aa,aaa,... ...等等。 \+ 类似于*,但匹配前面表达式的1次或多次,这属于扩展正则表达式。 \? 类似于*,但匹配前面表达式的0次或1次,这属于扩展正则表达式。 \{i\} 类似于*,但匹配前面表达式的i次(i为整数),如:a\{3\}可以帮你找到aaa。 \{i,j\} 匹配前面表达式的i到j次,如a\{1,2\}可以帮你找到a或aa或aaa。 \{i,\} 匹配前面表达式至少i次。 \( \) 将\( \)内的模式存储在保留空间。最多可以存储9个独立子模式,可 通过转义\1至\9重复保留空间的内容至此点。 \n 转义\1至\9重复保留空间的内容至此点。 例:test.txt的内容为ssttss grep '\(ss\)tt\1' test.txt \1表示将ss重复在tt后面 该grep命令等同于grep ssttss test.txt 在test.txt文件中找ssttss . (点)匹配任意字符。 ^ 匹配行的开始,如^test 将匹配所有以test开始的行。 $ 匹配行的结尾,如test$ 将匹配所有以test结尾的行。 [] 匹配括号中的任意单个字符,如a[nt] 将匹配an或at。 [^] 匹配不包含在[]中的字符,如[^a-z] 将匹配除a-z以外的字符。 \n 匹配换行符。 \char 转义特殊字符,如\*,就是匹配字面意义上的星号。 ************************************************************************************** 四、sed脚本指令 常用的sed脚本指令有: *注释(#) *替换(s) *删除(d) *追加(a) *插入(i) *更改(c) *列印(l) *转换(y) *打印(p) *读写文件(r,w) *退出(q) *下一步(n) *Next(N) *Print(P) *Delete(D) *Hold(h,H) *Get(g,G) *branch,test sed脚本指令的基本格式: [address]command [地址]命令 备注:有些命令仅可以对一行操作,有些可以对多行操作。 命令可以用大括号进行组合以使命令序列可以作用于同一个地址: address{ command1 command2 command3 } 第一个命令可以和左大括号在同一行,但右大括号必须单独处于一行。 注意事项:命令后添加空格会产生错误。 1.注释(#): 注释行是以#开始的行,如果#后面的字符为n,则屏蔽sed程序的自动输出功能,等同于命令选项-n。 2.替换(s,Substitution): [address]s/pattern/replacement/flags address为操作地址,s为替换指令,/pattern/匹配要替换的内容,/replacement/为替换的内容。 flags可以是: n 1至512之间的数字,表示对模式空中指定模式的第n次出现进行替换。 如一行中有3个A,而只想替换第二个A。 g 对模式空间的匹配进行全局更改。没有g则仅第一次匹配被替换,如一行中有3个A,则仅替换第一个A。 p 打印模式空间的内容。 w file 将模式空间的内容写到文件file中。 replacement为字符串,用来替换与正则表达式匹配的内容。 在replacement部分,只有下列字符有特殊含义: & 用正则表达式匹配的内容进行替换。 \n 匹配第n个子串,这个子串之前在pattern中用\( \)指定。 \ 转义(转义替换部分包含的:&, \等)。 ----------------------------------------------------------------------------------------------- 示例1所使用的样本文件为: [jacob@localhost ~] #cat test.txt<html> <title>First Web</title> <body>Hello the World! <body> </html> 示例1:将样本文件中的第二个<body>替换为</body> 编写sed脚本为: [jacob@localhost ~] #cat sed.sh/body/{ s//\/body/2 注:替换与行匹配相同的内容即body,替换为/body,但仅替换第二个body为/body。} 执行sed程序的结果: [jacob@localhost ~] #sed -f sed.sh test.txt<html> <title>First Web</title> <body>Hello the World!</body> </html> --------------------------------------------------------------------------------------------- 示例2所使用的样本文件为: [jacob@localhost ~] #cat test.txt<html> <title>First Web</title> <body> h1Helloh1 h2Helloh2 h3Helloh3 </body> </html> 示例2:给所有第一个的h1,h2等添加<>;第二个h1,h2添加</> 编写sed脚本为: [jacob@localhost ~] #cat sed.sh/h[0-9]/{ 注:匹配h紧跟一个数字的行 s//\<&\>/1 注:替换与上一行中匹配内容相同的内容,即替换h[0-9],替换为<&>,其中&即前面要替换的内容。s//\<\/&\>/2 注:上一条指令仅替换第一个h1,h2...,本行指令用来替换第二个h1,h2...。} 执行sed程序的结果: [jacob@localhost ~] #sed -f sed.sh test.txt<h1>Hello</h1> <h2>Hello</h2> <h3>Hello</h3> --------------------------------------------------------------------------------------------- 技巧:关于 's///' 命令的另一个妙处是 '/' 分隔符有许多替换选项。 如果规则表达式或替换字符串中有许多斜杠,则可以通过在 's' 之后指定一个不同的字符来更改分隔符。 示例:$ sed -e 's:/usr/local:/usr:g' mylist.txt 这里:为分隔符。 3.删除(d ,delete): 删除指令删除匹配的行,而且删除命令还会改变sed脚本中命令的执行顺序。因为: 匹配的行一旦被删除,模式空间将变为“空”,自然不会再执行sed脚本后续的命令。 删除命令将导致读取新的输入行(下一行),而sed脚本中的命令则从头开始执行。 注意事项:删除将删除整个行,而不只是删除匹配的内容。(如要删除匹配的内容,可以使用替换) 示例1所使用的样本文件为: [jacob@localhost ~] #cat test.txtDEVICE=eth0 ONBOOT=yes BOOTPROTO=static IPADDR=192.168.0.1 NETMASK=255.255.255.0 GATEWAY=192.168.0.254 示例1:删除文件中的空白行 编写sed脚本为: [jacob@localhost ~] #cat sed.sh/.*/{ /^$/d} 执行sed程序的结果: [jacob@localhost ~] #sed -f sed.sh test.txtDEVICE=eth0 ONBOOT=yes BOOTPROTO=static IPADDR=192.168.0.1 NETMASK=255.255.255.0 GATEWAY=192.168.0.254 4.追加(a):在匹配行后追加内容 示例所使用的样本文件为: [jacob@localhost ~] #cat test.txtDEVICE=eth0 ONBOOT=yes BOOTPROTO=static NETMASK=255.255.255.0 GATEWAY=192.168.0.254 示例程序:sed '/static/a IPADDR=192.168.0.1' test.txt 注:在static行后添加一行。 5.插入(i):在匹配行前插入内容 示例程序:sed '/NETMASK/i IPADDR=192.168.0.1' test.txt 注:在NETMASK行前添加一行。 6.更改(c):更改匹配行的内容 示例程序:sed '/ONBOOT/c ONBOOT=yes' test.txt 注:包含ONBOOT的行(整行)替换为ONBOOT=yes。 7.列印(l):显示模式空间中的内容,显示非打印字符,一般与-n一起使用,否则会输出两次。 示例程序:sed -n '1,2l' test.txt 注:如果使用sed脚本文件,需要#n屏蔽自动输出 结果如下: DEVICE=eth0$ ONBOOT=yes$ 8.转换(y):按字符的转换(Transform) [address]y/source-chars/dest-chars/ 示例所使用的样本文件为: [jacob@localhost ~] #cat test.txtDEVICE=eth0 ONBOOT=yes BOOTPROTO=static netmask=255.255.255.0 GATEWAY=192.168.0.254 编写sed脚本为: [jacob@localhost ~] #cat sed.sh /.*/{ /netmask/y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/ 将小写转换为大写} 执行sed程序的结果: [jacob@localhost ~] #sed -f sed.sh test.txtDEVICE=eth0 ONBOOT=yes BOOTPROTO=static NETMASK=255.255.255.0 GATEWAY=192.168.0.254 9.打印(p):作用类似与l(列印),但不显示非显示字符,一般与-n配合使用,脚本文件中需要#n sed -n '1,2p' test.txt 仅显示第一、第二行的内容。 10.读写文件(r,w):[line-address]r file, [address]w file 示例所使用的样本文件为: [jacob@localhost ~] #cat name.txtJacob Tom Jerry [jacob@localhost ~] #cat mail.txtjacob@gmail.com tom@gmail.com jerry@gmail.com 编写sed脚本为: [jacob@localhost ~] #cat sed.sh/.*/{ $r mail.txt} 执行sed程序的结果: [jacob@localhost ~] #sed -f sed.sh name.txtJacob Tom Jerry jacob@gmail.com tom@gmail.com jerry@gmail.com 11.退出(q): 匹配地址后退出sed脚本 sed '10q' test.txt 打印文件前10行内容(到第10行后退出sed脚本指令)。 12.下一步(n): [address]n 输出模式空间中的内容,然后读取输入的下一行。 next命令改变了正常的sed脚本指令的流程,默认只有到sed脚本的底部才会输出模式空间的内容。 next命令导致输入的下一行取代模式空间中的当前行,sed脚本中的后续命令将应用于替换后的行,而不是当前行。 示例所使用的样本文件为: [jacob@localhost ~] #cat name.txtJacob Tom Jerry 编写sed脚本为: [jacob@localhost ~] #cat sed.sh/.*/{ n 读取下一行后删除(删除偶数行)。 /.*/d} 执行sed程序的结果: [jacob@localhost ~] #sed -f sed.sh name.txtJacob Jerry 13.Next(N): 多行Next(N)命令通过读取新的输入行,并将它添加到模式空间的现有内容之后,来创建多行模式空间。 模式空间的最初内容与新的输入行之间用换行符分隔。在模式空间中插入的换行符可以使用\n匹配。 示例1: [jacob@localhost ~] #cat test.txt111 222 222 333 [jacob@localhost ~] #cat sed.sh#n /222/{ N l} [jacob@localhost ~] #sed -f sed.sh test.txt 结果如下:222\n222 示例2: [jacob@localhost ~] #cat test.txt111 222 222 222 333 [jacob@localhost ~] #cat sed.sh#n /222/{ N l} [jacob@localhost ~] #sed -f sed.sh test.txt222\n222 222\n333 14.Print(P):多行打印(P)与打印(p)稍有不同,该命令仅输出多行模式空间中的第一部分,直到第一个插入的\n换行符为止。 示例: #cat test.txt #cat test.txt #cat test.txt #cat test.txt111 111 111 111 222 222 222 222 333 333 333 333 444 444 444 444 555 555 555 555 666 666 666 666 ----------------------------------------------------------------------------------------------------- #cat sed.sh #cat sed.sh #cat sed.sh #cat sed.sh/.*/{ /.*/{ /.*/{ /.*/{ N N N N } l P p } } } ------------------------------------------------------------------------------------------------------ 结果: 结果: 结果: 结果:111 111\n222$ 111 111 222 111 111 222 333 222 222 111 444 333\n444$ 333 222 555 333 333 333 666 444 444 444 555\n666$ 555 333 555 555 444 666 666 555 666 555 666 ------------------------------------------------------------------------------------------------------- 流程说明:1.sed脚本文件使用了N,但后续没有任何指令,所以仅按sed自动输出,即输出源文件所有内容。 2.sed脚本问使用了N,l表示显示模式空间的内容,即111\n222(N的作用为把下一行追加至行尾), 同时sed脚本的自动输出功能会把源文件内容显示出来即111,222。 依次类推,sed脚本继续读取第三行333,并使用N将444追加至行尾,使用l显示模式空间的内容, sed自动输出再把源文件内容显示出来。 3.sed脚本文件使用了N将下一行追加至行尾,现在模式空间中的内容为111\n222,而P指令的作用是 打印模式空间中的第一部分内容直到\n结尾,即仅打印111,这时sed的自动输出功能把源文件 内容打印出来111,222。依次类推读取第三行333,N将444追加至行尾,P打印\n前的内容, 同时sed脚本自动将源问内容输出。 4.原理类似与第三个sed脚本,但p打印时\n看作是回车换行,所以打印出来的是111回车222。 ----------------------------------------------------------------------------------------------------- 15.Delete(D): 删除命令(d)删除模式空间中的内容并导致读入新的输入行,而sed脚本重新执行。 删除命令(D)删除模式空间中直到第一个插入的换行符(\n)前的这部分内容,它不会读入新的输入行,并返回sed脚本的顶端, 使得剩余指令继续应用于模式空间中剩余的内容。 16.Hold(h,H),Get(g,G): 模式空间是存放当前输入行的缓冲区。还有一个称为保持空间(hold space)的缓冲区。 模式空间的内容可以复制到保持空间,保持空间的内容同样可以复制到模式空间。 有一组命令用于在两者之间移动数据。 Hold(h|H) 将模式空间的内容复制或追加到保持空间 Get(g|G) 将保持空间的内容复制或追加到模式空间 Exchange(x) 交换保持空间与模式空间的内容 示例:反转部分行 [jacob@localhost ~] #cat test.txt1 2 11 22 111 222 [jacob@localhost ~] #cat sed.sh/1/{ h d } /2/{ G } 结果:2 1 22 11 222 111 17.branch,test 分支(b),测试(t)命令将sed脚本中的控制转移到包含特殊标签的行,如果没有指定的标签,则将控制转移到脚本结尾处。 分支命令是无条件转移,测试命令用于有条件转移,测试只有当替换命令改变当前行成功时才会执行。 标签是任意不多于7个字符的序列,标签本身占据一行并以冒号开始: :mylable 冒号与标签之间不允许有空格 当分支或测试命令中指定标签时,在命令与标签之间允许有空格: b mylabel 注:不要在标签后插入空格 五、综合案例(案例摘自GNU sed官网) 综合案例1:重命名文件名为小写 [jacob@localhost ~] #cat sed.sh #! /bin/sh # rename files to lower/upper case... # # usage: # move-to-lower * # move-to-upper * # or # move-to-lower -R . # move-to-upper -R . # help() { cat << eof Usage: $0 [-n] [-r] [-h] files... -n do nothing, only see what would be done -R recursive (use find) -h this message files files to remap to lower case Examples: $0 -n * (see if everything is ok, then...) $0 * $0 -R . eof } apply_cmd='sh' finder='echo "$@" | tr " " "\n"' files_only= while : do case "$1" in -n) apply_cmd='cat' ;; -R) finder='find "$@" -type f';; -h) help ; exit 1 ;; *) break ;; esac shift done if [ -z "$1" ]; then echo Usage: $0 [-h] [-n] [-r] files... exit 1 fi LOWER='abcdefghijklmnopqrstuvwxyz' UPPER='ABCDEFGHIJKLMNOPQRSTUVWXYZ' case `basename $0` in *upper*) TO=$UPPER; FROM=$LOWER ;; *) FROM=$UPPER; TO=$LOWER ;; esac eval $finder | sed -n ' # remove all trailing slashes s/\/*$// # add ./ if there is no path, only a filename /\//! s/^/.\// # save path+filename h # remove path s/.*\/// # do conversion only on filename y/'$FROM'/'$TO'/ # now line contains original path+file, while # hold space contains the new filename x # add converted file name to line, which now contains # path/file-name\nconverted-file-name G # check if converted file name is equal to original file name, # if it is, do not print nothing /^.*\/\(.*\)\n\1/b # now, transform path/fromfile\n, into # mv path/fromfile path/tofile and print it s/^\(.*\/\)\(.*\)\n\(.*\)$/mv "\1\2" "\1\3"/p ' | $apply_cmd 综合案例2:获取bash环境变量 #!/bin/sh set | sed -n ' :x # if no occurrence of ‘=()’ print and load next line /=()/! { p; b; } / () $/! { p; b; } # possible start of functions section # save the line in case this is a var like FOO="() " h # if the next line has a brace, we quit because # nothing comes after functions n /^{/ q # print the old line x; p # work on the new line now x; bx '
本文转自丁丁历险51CTO博客,原文链接:http://blog.51cto.com/manual/908002 ,如需转载请自行联系原作者