`
linsea
  • 浏览: 87713 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Sed 学习小记

阅读更多

Sed 学习小记

 

查找匹配

sed 示例

$ sed -e 'd' /etc/services

 

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

 

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

 

第三件要注意的事是括起 'd' 命令的单引号的用法。养成使用单引号来括起 sed 命令的习惯是个好注意,这样可以禁用 shell 扩展。

 

 

 

$ sed -e '1,10d' /etc/services | more

当用逗号将两个地址分开时,sed 将把后面的命令应用到从第一个地址开始、到第二个地址结束的范围。在本例中,将 'd' 命令应用到第 1 10 行(包括这两行)。

 

假设要查看 /etc/services 文件的内容,但对以 '#' 字符开头的注释行不感兴趣。

$ sed -e '/^#/d' /etc/services | more

 

 

规则表达式

 

/./ 将与包含至少一个字符的任何行匹配

/../ 将与包含至少两个字符的任何行匹配

/^#/ 将与以 '#' 开始的任何行匹配

/^$/ 将与所有空行匹配

/}^/ 将与以 '}'(无空格)结束的任何行匹配

/} *^/ 将与以 '}' 后面跟有 零或多个空格结束的任何行匹配 

/[abc]/ 将与包含小写 'a''b' 'c' 的任何行匹配

/^[abc]/ 将与以 'a''b' 'c' 开始的任何行匹配 

 

 

 

$ 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 打印模式空间。就这样,将只打印匹配部分。

 

我们可以指定两个用逗号分开的规则表达式,sed 将与所有从匹配第一个规则表达式的第一行开始,到匹配第二个规则表达式的行结束(包括该行)的所有行匹配。例如,以下命令将打印从包含 "BEGIN" 的行开始,并且以包含 "END" 的行结束的文本块:

$ sed -n -e '/BEGIN/,/END/p' /my/test/file | more

如果没发现 "BEGIN",那么将不打印数据。如果发现了 "BEGIN",但是在这之后的所有行中都没发现 "END",那么将打印所有后续行。发生这种情况是因为 sed 面向流的特性 -- 它不知道是否会出现 "END"

 

如果只要打印 C 源文件中的 main() 函数,可输入:

$ sed -n -e '/main[[:space:]]*(/,/^}/p' sourcefile.c | more

该命令有两个规则表达式 '/main[[:space:]]*(/' '/^}/',以及一个命令 'p'。第一个规则表达式将与后面依次跟有任意数量的空格或制表键以及开始圆括号的字符串 "main" 匹配。这应该与一般 ANSI C main() 声明的开始匹配。

第二个 regexp'/^}' 将与任何出现在新行行首的 '}' 字符匹配。如果代码的格式很好,那么这将与 main() 函数的结束花括号匹配。如果格式不好,则不会正确匹配 -- 这是执行模式匹配任务的一件棘手之事。

 

 

 

替换

 

$ sed -e 's/foo/bar/' myfile.txt

上面的命令将 myfile.txt 中每行第一次出现的 'foo'(如果有的话)用字符串 'bar' 替换,然后将该文件内容输出到标准输出。请注意,我说的是每行第一次出现,尽管这通常不是您想要的。在进行字符串替换时,通常想执行全局替换。也就是说,要替换每行中的 所有出现,如下所示:

$ sed -e 's/foo/bar/g' myfile.txt 

在最后一个斜杠之后附加的 'g' 选项告诉 sed 执行全局替换。

 

$ sed -e '1,10s/enchantment/entrapment/g' myfile2.txt

上例将导致用短语 'entrapment' 替换所有出现的短语 'enchantment',但是只在第一到第十行(包括这两行)上这样做。

 

$ sed -e '/^$/,/^END/s/hills/mountains/g' myfile3.txt

该例将用 'mountains' 替换 'hills',但是,只从空行开始,到以三个字符 'END' 开始的行结束(包括这两行)的文本块上这样做。

 

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

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

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

 

 

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

$ sed -e 's/<.*>//g' myfile.html  

这是要从文件除去 HTML 标记的第一个很好的 sed 脚本尝试,但是由于规则表达式的特有规则,它不会很好地工作。原因何在?当 sed 试图在行中匹配规则表达式时,它要在行中查找 最长的匹配。

 

但是,在使用 's///' 命令时,确实有很大不同,因为规则表达式匹配的整个部分将被目标字符串替换,或者,在本例中,被删除。这意味着,上例将把下行:

<b>This</b> is what <b>I</b> meant.  

变成:

meant.  

我们要的不是这个,而是:

This is what I meant.  

幸运的是,有一种简便方法来纠正该问题。我们不输入“'<' 字符后面跟有一些字符并以 '>' 字符结束”的规则表达式,而只需输入一个“'<' 字符后面跟有任意数量非 '>' 字符并以 '>' 字符结束”的规则表达式。这将与最短、而不是最长的可能性匹配。新命令如下:

$ sed -e 's/<[^>]*>//g' myfile.html 

在上例中,'[^>]' 指定“非 '>'”字符,其后的 '*' 完成该表达式以表示“零或多个非 '>' 字符”。对几个 html 文件测试该命令,将它们管道输出到 "more",然后仔细查看其结果。

 

 

字符类

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

'[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]

 

高级替换功能

下例将在每一行前面加上短语 "ralph said: "

$ sed -e 's/.*/ralph said: &/' origmsg.txt

该例的替换字符串中使用了 '&' 字符,该字符告诉 sed 插入整个匹配的规则表达式。

带反斜杠的圆括号:

假设要编写一个 sed 脚本,该脚本将把 "eeny meeny miny" 替换成 "Victor eeny-meeny Von miny" 等等。

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

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

 

$ sed -n 's/\(love\)able/\1rs/p' example

love被标记为1,所有loveable会被替换成lovers,而且替换的行会被打印出来。

 

 

 

组合使用

在开始创建更复杂的 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  

 

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

 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 会出现问题。

 

 

文本转换

第一个实际脚本将 UNIX 风格的文本转换成 DOS/Windows 格式。您可能知道,基于 DOS/Windows 的文本文件在每一行末尾有一个 CR(回车)和 LF(换行),而 UNIX 文本只有一个换行。有时可能需要将某些 UNIX 文本移至 Windows 系统,该脚本将为您执行必需的格式转换。

$ sed -e 's/$/\r/' myunix.txt > mydos.txt 

在该脚本中,'$' 规则表达式将与行的末尾匹配,而 '\r' 告诉 sed 在其之前插入一个回车。在换行之前插入回车,立即,每一行就以 CR/LF 结束。请注意,仅当使用 GNU sed 3.02.80 或以后的版本时,才会用 CR 替换 '\r'

 

以下 sed 调用将把 DOS/Windows 格式的文本转换成可信赖的 UNIX 格式:

$ sed -e 's/.$//' mydos.txt > myunix.txt 

该脚本的工作原理很简单:替代规则表达式与一行的最末字符匹配,而该字符恰好就是回车。我们用空字符替换它,从而将其从输出中彻底删除。如果使用该脚本并注意到已经删除了输出中每行的最末字符,那么,您就指定了已经是 UNIX 格式的文本文件。也就没必要那样做了!

 

 

反转行

下面是另一个方便的小脚本。与大多数 Linux 发行版中包括的 "tac" 命令一样,该脚本将反转文件中行的次序。"tac" 这个名称可能会给人以误导,因为 "tac" 不反转行中字符的位置(左和右),而是反转文件中行的位置(上和下)。用 "tac" 处理以下文件:

Foo

Bar

Oni

 

将产生以下输出:

Oni

Bar

Foo

 

可以用以下 sed 脚本达到相同目的:

$ sed -e '1!G;h;$!d' forward.txt > backward.txt 

如果登录到恰巧没有 "tac" 命令的 FreeBSD 系统,将发现该 sed 脚本很有用。虽然方便,但最好还是知道该脚本为什么那样做。让我们对它进行讨论。

反转解释

首先,该脚本包含三个由分号隔开的单独 sed 命令:'1!G''h' '$!d'。现在,需要好好理解用于第一个和第三个命令的地址。如果第一个命令是 '1G',则 'G' 命令将只应用第一行。然而,还有一个 '!' 字符 -- '!' 字符 忽略该地址,即,'G' 命令将应用到除第一行之外的 所有行。'$!d' 命令与之类似。如果命令是 '$d',则将只把 'd' 命令应用到文件中的最后一行('$' 地址是指定最后一行的简单方式)。然而,有了 '!' 之后,'$!d' 将把 'd' 命令应用到除最后一行之外的 所有行。现在,我们所要理解的是这些命令本身做什么。

 

当对上面的文本文件执行反转脚本时,首先执行的命令是 'h'。该命令告诉 sed 将模式空间(保存正在处理的当前行的缓冲区)的内容复制到保留空间(临时缓冲区)。然后,执行 'd' 命令,该命令从模式空间中删除 "foo",以便在对这一行执行完所有命令之后不打印它。

 

现在,第二行。在将 "bar" 读入模式空间之后,执行 'G' 命令,该命令将保留空间的内容 ("foo\n") 附加到模式空间 ("bar\n"),使模式空间的内容为 "bar\n\foo\n"'h' 命令将该内容放回保留空间保护起来,然后,'d' 从模式空间删除该行,以便不打印它。

 

对于最后的 "oni" 行,除了不删除模式空间的内容(由于 'd' 之前的 '$!')以及将模式空间的内容(三行)打印到标准输出之外,重复同样的步骤。

 

 

 

参考:

http://www.ibm.com/developerworks/cn/linux/shell/sed/sed-1/index.html

http://www.ibm.com/developerworks/cn/linux/shell/sed/sed-2/index.html

http://www.ibm.com/developerworks/cn/linux/shell/sed/sed-3/index.html

http://blog.chinaunix.net/u/13392/showart.php?id=133128

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics