sed

来源:互联网 发布:windows主题模板 编辑:程序博客网 时间:2024/04/20 01:53
 http://www.ibm.com/developerworks/cn/linux/shell/sed/sed-1/
http://www.ibm.com/developerworks/cn/linux/shell/sed/sed-2/
http://www.ibm.com/developerworks/cn/linux/shell/sed/sed-3/

1, 什么是sed

如果可以使编辑文件的过程自动化,以便用“批处理”方式编辑文件,甚至编写可以对现有文件进行复杂更改的脚本,那将太好了。幸运的是,对于这种情况,有一种更好的方法 -- 这种更好的方法称为 "sed"。

sed 是一种几乎包括在所有 UNIX 平台(包括 Linux)的轻量级流编辑器。sed 有许多很好的特性。首先,它相当小巧,通常要比您所喜爱的脚本语言小很多倍。其次,因为 sed 是一种 编辑器,所以,它可以对从如管道这样的标准输入接收的数据进行编辑。因此,无需将要编辑的数据存储在磁盘上的文件中。因为可以轻易将数据管道输出到 sed,所以,将 sed 用作强大的 shell 脚本中长而复杂的管道很容易。试一下用您所喜爱的编辑器去那样做。


2, sed如何工作

sed 通过对输入数据执行任意数量用户指定的编辑操作(“命令”)来工作。sed 是基于行的,因此按顺序对每一行执行命令。然后,sed 将其结果写入标准输出 (stdout),它不修改任何输入文件。

$ sed -e 'd' /etc/services

如果输入该命令,将得不到任何输出。那么,发生了什么?在该例中,用一个编辑命令 'd' 调用 sed。sed 打开/etc/services文件,将一行读入其模式缓冲区,执行编辑命令(“删除行”),然后打印模式缓冲区(缓冲区已为空)。然后,它对后面的每一行重复这些步骤。这不会产生输出,因为 "d" 命令除去了模式缓冲区中的每一行!

在该例中,还有几件事要注意。首先,根本没有修改/etc/services。这还是因为 sed 只读取在命令行指定的文件,将其用作输入 -- 它不试图修改该文件。第二件要注意的事是 sed是面向行的。'd' 命令不是简单地告诉 sed 一下子删除所有输入数据。相反,sed 逐行将 /etc/services的每一行读入其称为模式缓冲区的内部缓冲区。一旦将一行读入模式缓冲区,它就执行 'd'命令,然后打印模式缓冲区的内容(在本例中没有内容)。但是,如果不使用地址,命令将应用到 所有行










3,规则表达式
sed -e '/^#/d' myfile
/^#/即为规则表达式

可以使用规则表达式来表示可能会在文本中发现的模式。您在 shell 命令行中用过 '*' 字符吗?这种用法与规则表达式类似,但并不相同。下面是可以在规则表达式中使用的特殊字符:

字符 描述
与行首匹配
与行末尾匹配
与任一个字符匹配
将与 前一个字符的零或多个出现匹配 [ ]与 [ ] 之内的所有字符匹配

感受规则表达式的最好方法可能是看几个示例。所有这些示例都将被 sed 作为合法地址接受,这些地址出现在命令的左边。下面是几个示例:

规则 表达式 描述 /./将与包含至少一个字符的任何行匹配/../将与包含至少两个字符的任何行匹配/^#/将与以 '#' 开始的任何行匹配/^$/将与所有空行匹配/}^/将与以 '}'(无空格)结束的任何行匹配/} *^/将与以 '}' 后面跟有 或多个空格结束的任何行匹配 /[abc]/将与包含小写 'a'、'b' 或 'c' 的任何行匹配/^[abc]/将与以 'a'、'b' 或 'c' 开始的任何行匹配

在这些示例中,鼓励您尝试几个。花一些时间熟悉规则表达式,然后尝试几个自己创建的规则表达式。可以如下使用 regexp:

$ sed -e '/regexp/d' /path/to/my/test/file | more

这将导致 sed 删除任何匹配的行。然而,通过告诉 sed 打印regexp 匹配并删除不匹配的内容,而不是与之相反的方法,会更有利于熟悉规则表达式。可以用以下命令这样做:

$ sed -n -e '/regexp/p' /path/to/my/test/file | more

请注意新的 '-n' 选项,该选项告诉 sed 除非明确要求打印模式空间,否则不这样做。您还会注意到,我们用 'p' 命令替换了 'd' 命令,如您所猜想的那样,这明确要求 sed 打印模式空间。就这样,将只打印匹配部分。


[[:space:]]表示匹配所有空格或制表符


4, sed 应用

  1),删除  如上所示

  2), 替换

    sed -e 's/linux/unix/' myfile.txt

  上面的命令将 myfile.txt 中每行第一次出现的 'linux'(如果有的话)用字符串 'unix' 替换,然后将该文件内容输出到标准输出        sed -e 's/linux/unix/g' myfile   

关于 's///' 替换命令,还有其它几件要了解的事。首先,它是一个命令,并且只是一个命令,在所有上例中都没有指定地址。这意味着,'s///' 还可以与地址一起使用来控制要将命令应用到哪些行,如下所示:

 $ sed -e '1,10s/enchantment/entrapment/g' myfile2.txt 
$ sed -e '/^$/,/^END/s/hills/mountains/g' myfile3.txt

关于 's///' 命令的另一个妙处是 '/'分隔符有许多替换选项。如果正在执行字符串替换,并且规则表达式或替换字符串中有许多斜杠,则可以通过在 's'之后指定一个不同的字符来更改分隔符。例如,下例将把所有出现的 /usr/local 替换成 /usr:

 $ sed -e 's:/usr/local:/usr:g' mylist.txt 

该例中,使用冒号作为分隔符。如果需要在规则表达式中指定分隔符字符,可以在它前面加入反斜杠。

以下 sed 命令将匹配从 '<' 开始、到 '>' 结束、并且在其中包含任意数量字符的短语。下例将删除该短语(用空字符串替换):

 $ sed -e 's/<.*>//g' myfile.html  
当 sed 试图在行中匹配规则表达式时,它要在行中查找最长的匹配。因此,上句不会好好工作。
那如何与最短匹配呢?
我们不输入“'<' 字符后面跟有一些字符并以 '>' 字符结束”的规则表达式,而只需输入一个“'<' 字符后面跟有任意数量非 '>' 字符并以 '>' 字符结束”的规则表达式。
$ sed -e 's/<[^>]*>//g' myfile.html 
'[^>]' 指定“非 '>'”字符,其后的 '*' 完成该表达式以表示“零或多个非 '>' 字符”。

高级替换功能:
$ sed -e 's/.*/ralph said: &/' origmsg.txt
该例的替换字符串中使用了 '&' 字符,该字符告诉 sed 插入整个匹配的规则表达式。因此,可以将与 '.*' 匹配的任何内容(行中的零或多个字符的最大组或整行)插入到替换字符串中的任何位置,甚至多次插入

's///' 命令甚至比 '&' 更好,它允许我们在规则表达式中定义 区域,然后可以在替换字符串中引用这些特定区域。作为示例,假设有一个包含以下文本的文件:

 foo bar oni eeny meeny miny larry curly moe jimmy the weasel  

现在假设要编写一个 sed 脚本,该脚本将把 "eeny meeny miny" 替换成 "Victor eeny-meeny Von miny" 等等。要这样做,首先要编写一个由空格分隔并与三个字符串匹配的规则表达式。

 '.* .* .*'  

现在,将在其中每个感兴趣的区域两边插入带反斜杠的圆括号来定义区域:

 '/(.*/) /(.*/) /(.*/)'  

除了要定义三个可在替换字符串中引用的逻辑区域以外,该规则表达式的工作原理将与第一个规则表达式相同。下面是最终脚本:

 $ sed -e 's//(.*/) /(.*/) /(.*/)/Victor /1-/2 Von /3/' myfile.txt  

如您所见,通过输入 '/x'(其中,x 是从 1 开始的区域号)来引用每个由圆括号定界的区域。输入如下:

Victor foo-bar Von oni Victor eeny-meeny Von miny Victor larry-curly Von moe Victor jimmy-the Von weasel

在开始创建更复杂的 sed 脚本时,需要有输入多个命令的能力。有几种方法这样做。首先,可以在命令之间使用分号。例如,以下命令系列使用'=' 命令和 'p' 命令,'=' 命令告诉 sed 打印行号,'p' 命令明确告诉 sed 打印该行(因为处于 '-n' 模式)。

 $ sed -n -e '=;p' myfile.txt  

无论什么时候指定了两个或更多命令,都按顺序将每个命令应用到文件的每一行。在上例中,首先将 '=' 命令应用到第 1 行,然后应用 'p'命令。接着,sed 继续处理第 2 行,并重复该过程。虽然分号很方便,但是在某些场合下,它不能正常工作。另一种替换方法是使用两个 -e选项来指定两个不同的命令:

 $ sed -n -e '=' -e 'p' myfile.txt  

然而,在使用更为复杂的附加和插入命令时,甚至多个 '-e' 选项也不能帮我们的忙。对于复杂的多行脚本,最好的方法是将命令放入一个单独的文件中。然后,用 -f 选项引用该脚本文件:

 $ sed -n -f mycommands.sed myfile.txt 

有时,可能要指定应用到一个地址的多个命令。这在执行许多 's///' 以变换源文件中的字和语法时特别方便。要对一个地址执行多个命令,可在文件中输入 sed 命令,然后使用 '{ }' 字符将这些命令分组,如下所示:

 1,20{ s/[Ll]inux/GNU//Linux/g s/samba/Samba/g s/posix/POSIX/g }  

上例将把三个替换命令应用到第 1 行到第 20 行(包括这两行)。还可以使用规则表达式地址或者二者的组合:

 1,/^END/{         s/[Ll]inux/GNU//Linux/g         s/samba/Samba/g         s/posix/POSIX/g p } 

该例将把 '{ }' 之间的所有命令应用到从第 1 行开始,到以字母 "END" 开始的行结束(如果在源文件中没发现 "END",则到文件结束)的所有行。

既然在单独的文件中编写 sed 脚本,我们可以利用附加、插入和更改行命令。这些命令将在当前行之后插入一行,在当前行之前插入一行,或者替换模式空间中的当前行。它们也可以用来将多行插入到输出。插入行命令用法如下:

i/ This line will be inserted before each line  

如果不为该命令指定地址,那么它将应用到每一行,并产生如下的输出:

This line will be inserted before each line line 1 here 
This line will be inserted before each line line 2 here
This line will be inserted before each line line 3 here
This line will be inserted before each line line 4 here

如果要在当前行之前插入多行,可以通过在前一行之后附加一个反斜杠来添加附加行,如下所示:

 i/ insert this line/ and this one/ and this one/ and, uh, this one too.  

附加命令的用法与之类似,但是它将把一行或多行插入到模式空间中的当前行之后。其用法如下:

 a/ insert this line after each line.  Thanks! :)  

另一方面,“更改行”命令将实际 替换模式空间中的当前行,其用法如下:

 c/ You're history, original line! Muhahaha! 

因为附加、插入和更改行命令需要在多行输入,所以将把它们输入到一个文本 sed 脚本中,然后通过使用 '-f' 选项告诉 sed 执行它们。使用其它方法将命令传递给 sed 会出现问题。


5,字符匹配
'[ ]' 规则表达式语法还有一些附加选项。要指定字符范围,只要字符不在第一个或最后一个位置,就可以使用 '-',如下所示:
'[a-x]*'

这将匹配零或多个全部为 'a'、'b'、'c'...'v'、'w'、'x' 的字符。另外,可以使用 '[:space:]' 字符类来匹配空格。以下是可用字符类的相当完整的列表:

字符类 描述 [:alnum:]字母数字 [a-z A-Z 0-9][:alpha:]字母 [a-z A-Z][:blank:]空格或制表键[:cntrl:]任何控制字符[:digit:]数字 [0-9][:graph:]任何可视字符(无空格)[:lower:]小写 [a-z][:print:]非控制字符[:punct:]标点字符[:space:]空格[:upper:]大写 [A-Z][:xdigit:]十六进制数字 [0-9 a-f A-F]

尽可能使用字符类是很有利的,因为它们可以更好地适应非英语 locale(包括某些必需的重音字符等等).



    



原创粉丝点击