注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Koala++'s blog

计算广告学 RTB

 
 
 

日志

 
 

Sed 介绍和教程[2]  

2011-01-11 21:49:26|  分类: shell |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

在脚本中使用sedhere document

你可以用sed提示用户参数输入,然后用这些参数创建一个文件将这些参数填进去。你可创建一个有些待替换的值的文件,然后用sed来改变这些值。一个比较简单的方法是用”here is”是用here document,它将shell脚本当作标准输入使用。

#!/bin/sh

echo -n 'what is the value? '

read value

sed  's/XXX/'$value'/' <<EOF

The value is XXX

EOF

当执行之后,脚本会提示:

What is the value?

如果你输入123”,下一行就是:

The valu is 123

我承认这是一个牵强的例子,here document可用不用来sed来做,下面的例子完成的是相同的任务。

#!/bin/sh

echo -n 'what is the value? '

read value

cat <<EOF

The value is $value

EOF

但是结合sedhere document中可以完成一些复杂的操作,注意:

sed ‘s/XXX/’$vallue’/’ << EOF

会给出语法错误,如果用户输入空格,下面的写法更好:

sed ‘s/XXX/’”$value”’/’ << EOF

多个命令和执行顺序

随着我们的学习,sed命令将变的更加复杂,并且真正的执行顺序会变得让人糊涂。其实它是很简单的,先读取一行,每个命令按用户指定的顺序执行来对输入的行操作。在替换之后,下一个命令将会在相同的行上操作,但这一行是上一命令修改过的行。无论你何时有疑问,最好的方法就是创建一个小例子来试一个。当你写一个复杂的命令不能工作时,就设法使它简单。如果你不能使一个复杂的脚本工作,就将它拆成两个小的脚本,然后用管道将两个脚本连起来。

文本的位置和区间

你已经学习了一个命令了,但你已经领教了sed的强大,然而,它现在做的只是grep加上替换。即是每个替换命令只关心一行,而不管附近几行。如果可以有限制在特定的一些行内操作,那该多有用呀。一些有用的限制可以是:

1.    通过行号指定某行。

2.    通过行号指过行的区间。

3.    所有包含某一模式的行。

4.    从文件开始到某一正则的所有行。

5.    从某一正则到文件结尾的所有行。

6.    在两个正则之间的所有行。

Sed不但可以完成上述功能,而且可以做得更好,在sed中的每个命令都可以指定它的操作位置,区间或像上面所列的功能,命令操作范围的限制是下面的格式:

    restriction command

用行号限定

最简单的限制是用行号,如果你想删除第3行的第一个数字,只需要在命令前加3

sed ‘3 s/[0-9][0-9]*//’ < file > new

模式

许多Unix工具,如vimore用斜杠来搜索一个正则表达式。Sed遵循这一传统,你可以用一斜杠来结束正则表达式。下面的例子是删除以”#”开头的所有行的第一个数字。

sed ‘/^#/ s/[0-9][0-9]*//’

我在”/expression/”之后写了一个空格是为了方便阅读,它并不是必要的,但是不写空格,这个命令就比较难看懂,sed还提供了一些别的指定正则表达式的方法,但这个我稍后再讲,如果一个表达式以一个反斜杠开头,则下一个字符是一个分隔符,用逗号代替斜杠的写法如下:

sed ‘\,^#, s/[0-9][0-9]*//’

这种写法最大的好处是搜索斜杠,假设你想搜索”/usr/local/bin”,替换为”/common/all/bin”,你可以用反斜杠来转义斜杠:

sed '/\/usr\/local\/bin/ s/\/usr\/local/\/common\/all/'

如果你用下划线而不是斜杠来作为分隔符会更好理解一些,下面的例子在搜索命令和替换命令中都使用下划线做为分隔符:

sed '\_/usr/local/bin_ s_/usr/local_/common/all_'

这就解释了为什么sed脚本有隐晦的名声。我可以举出来一个恶心的例子:搜索所有以”g”开头的行,并将行中每个”g”替换为”s”

sed '/^g/s/g/s/g'

用一个空格将命令分开,并在替换命令中用下划线分隔会更容易理解:

sed '/^g/ s_g_s_g'

...,我收回我说的,它还是不好懂。这就是一个教训,如果在SunOS下写sed脚本,加上注释,你可以在别的操作系统上运行时删除它,你现在应该知道删除注释的方法了吧?注释是一件好事,当你写脚本时,你也许会完全理解脚本的含义,但过六个月之后,没有注释,它就是一堆乱码。

用行号限定范围

你可以用逗号分隔的行号指定范围,如果要限制替换在前100行,你可以用:

sed '1,100 s/A/a/'

如果你准确地知道一个文件中有多少行,你可以显式地说明处理剩余的行,如果是这样,假设你用wc命令知道这个文件有532行:

sed '101,532 s/A/a/'

一个更简单的办法是用一个特殊的字符”$”,它表示文件的最后一行。

sed '101,$ s/A/a/'

“$”符号表示最后也是一个传统,在cat –e vied工具中都是这样,用cat –e就将会多个文件行号累记,即:

sed '200,300 s/A/a/' f1 f2 f3 >new

等价于:

cat f1 f2 f3 | sed '200,300 s/A/a/' >new

用模式限定范围

你可以用两个正则表达式限定范围,假设以”#”开头的是注释,你想找一个关键词,你移除所有注释直到你看到第二个关键词,假设两个关键词是”start””stop”

sed '/start/,/stop/ s/#.*//'

第一个模式告诉sed开始对每行进行替换操作,第二个模式告诉sed停止对剩下的行进行操作。如果”start””stop”这两个模出现了两次,那么替换也会进行两次,如果”stop”模式没有匹配,那么替换就会对余下的所有行进行匹配。

你应该了解如果”start”模式如果找到了,替换就在包含”start”的行进行替换,它就像打开了一个面向行的开关,然后下一行被读入,进行替换,当然如果它包含”stop”,这个开关就关闭了,开头是面向行的,它并不是面向词的。

你可以结合行号和正则表达式来限定,下面的例子是从开始移除注释直到它找到”start”关键词:

sed -e '1,/start/ s/#.*//'

下面的例子展示了移除两个关键词以外的注释:

sed -e '1,/start/ s/#.*//' -e '/stop/,$ s/#.*//'

最后一个例子有一个重叠的区间”/start/,/stop/”,因为两个范围都在包含关键词的行上操作,我将会告诉你一行完成这个功能的方法。

在我继续介绍更多命令之前,我应该告诉你一些命令不能对一个范围内的行操作,在我介绍这些命令时,我会告诉你,在下节中我将会讲三个不能对一个范围的行进行操作的命令。

d删除

使用范围限定容易让人糊涂,所以你在写一个新的脚本时,你应该先试试。一个删除符合限定行的命令是:”d”。如果你想看文件的前10行,你可以用:

sed '11,$ d' <file

它与head命令的功能很像。如果你想删除一个信件的头部信息,也就是删除从文件开始直到一个空行的所有内容,可以用:

sed '1,/^$/ d' <file

你还可以模拟tail命令,假设你知道文件的长度,wc命令可以统计行数,expr可以对文件行数减10.一个Bourne Shell想查看文件最后10行,可许可以这样写:

#!/bin/sh

#print last 10 lines of file

# First argument is the filename

lines=`wc -l $1 | awk '{print $1}' `

start=`expr $lines - 10`

sed "1,$start d" $1

也可以用正则表达式来标记删除操作的开始和结束位置,或者也可以只用一个正则表达式,删除所有以”#”开头的行很容易:

sed '/^#/ d'

删除注释和空行需要两个命令,第一个删除从”#”到行尾的字符,第二个命令删除所有的空行:

sed -e 's/#.*//' -e '/^$/ d'

第三个例子再增加一个功能:删除所有行前面的空格和tab

sed -e 's/#.*//' -e 's/[ ^I]*$//' -e '/^$/ d'

“^I”字符是一个CTRL-I或是tab,你可以直接输入tab。注意上面操作的顺序,这样写是出于一个合理的理由,注释可能在前面有空格的一行的中间出现,所以注释第一个个被删除,这样就只剩空格在前面了,第二个命令就可把这样空格删除,最后一个命令会把空行删除了。

这展示了sed用模式空间来操作一行,sed真正的操作是:

1. 将输入行复制到模式空间。

使用第一个sed命令。

2.  如果这个行在限定范围内,sed命令在这行上操作。

3.  用下一个命令继续在这个模式空间上操作。

4.  当最后一个操作完成时,输出模式空间,然后读取下一行。

p打印

另一个有用的命令是打印命令”p”,如果sed不以”-n”开头,那么”p”会重复输入,命令:

sed ‘p’

会重复会一行,如果你想每行打印两次,用:

sed '/^$/ p'

加上”-n”选项会关闭打印,除非你明确要求要打印。另一个模拟head功能的方法是只打印你想要的行,下面的例子打印前10行。

sed -n '1,10 p' <file

sed也可以完成grep的功能,打印匹配正则表达式的行:

sed -n '/match/ p'

它等价于:

grep match

!对限定取反

有时你想对匹配一个正则表达式以外的行进某个操作,或是限定范围之外的行。”!”字符,在Unix系统中意义为,对位置限定取反。你应该记得:

sed -n '/match/ p'

它的功能和grep相似,grep命令中的-v操作表示不包括某一模式,sed可以用下面的方式做到这一点:

sed -n '/match/ !p' </tmp/b

d,p!之间的关系

正如你所注意到的一样,sed可以用多种方式来解决同一问题,这是因为printdelete有着相反的功能,似乎”!p””d”的功能相似,且”!d””p”功能相似,为这测试这一点,我建了一个20行的文件,尝试每一种组合,下表就是执行的结果,以来展示它们的差异:

d,p!之间的关系

Sed    Range      Command   Results

  --------------------------------------------------------

  sed -n   1,10      p        Print first 10 lines

  sed -n   11,$      !p       Print first 10 lines

  sed     1,10    !d       Print first 10 lines

  sed     11,$    d        Print first 10 lines

  --------------------------------------------------------

  sed -n   1,10      !p       Print last 10 lines

  sed -n   11,$      p        Print last 10 lines

  sed     1,10    d        Print last 10 lines

  sed     11,$    !d       Print last 10 lines

  --------------------------------------------------------

  sed -n   1,10      d        Nothing printed

  sed -n   1,10      !d       Nothing printed

  sed -n   11,$      d        Nothing printed

  sed -n   11,$      !d       Nothing printed

  --------------------------------------------------------

  sed     1,10    p        Print first 10 lines twice,

                           Then next 10 lines once

  sed     11,$    !p       Print first 10 lines twice,

                           Then last 10 lines once

  --------------------------------------------------------

  sed     1,10    !p       Print first 10 lines once,

                           Then last 10 lines twice

  sed     11,$    p        Print first 10 lines once,

                           then last 10 lines twice

这个表表明下面的命令是相似的:

sed -n '1,10 p'

sed -n '11,$ !p'

sed '1,10 !d'

sed '11,$ d'

它还显示了”!”命令对位置区间取反了,命令在取反后的空间上操作。

q退出命令

还有一个简单的命令可以限定操作在一个指定范围内,它就是”q”命令,第三种模拟head命令的方法是:

sed '11 q'

当执行到第11行时,就会退出,它个命令在你想到达某种条件后就退出编辑时特别有用。

“q”命令是一个不接受范围限定的命令,很显然下面的命令:

sed '1,10 q'

无法退出10次,相反:

sed '1 q'

或:

sed '10 q'

是正确的。

{}组合

大括号”{””}”sed中用来组合命令。

这个用法不太难,但是有一个难点,因为sed中的每个命令必须独起一行,下面的大括号和内嵌的sed命令必须在不同的行。

在前面,我向你介绍了如何移除了以”#”的注释,如果你想限定删除所有在”begin””end”关键词之间的行,你可以用:

#!/bin/sh

# This is a Bourne shell script that removes #-type comments

# between 'begin' and 'end' words.

sed -n '

    /begin/,/end/ {

         s/#.*//

         s/[ ^I]*$//

         /^$/ d

         p

    }

'

大括号是可以嵌套的,这也可以使你结合位置范围,你可以进行上面相同的操作,但只限操作前100行:

#!/bin/sh

# This is a Bourne shell script that removes #-type comments

# between 'begin' and 'end' words.

sed -n '

    1,100 {

       /begin/,/end/ {

            s/#.*//

            s/[ ^I]*$//

            /^$/ d

            p

       }

    }

'

你可以在一个大括号前写”!”,它可以反转地址范围,下面的例子是移除在beginend之间范围外的所有注释:

#!/bin/sh

sed '

    /begin/,/end/ !{

         s/#.*//

         s/[ ^I]*$//

         /^$/ d

         p

    }

'

’w’命令写一个文件

你也许还记得替换命令可以写入一个文件,这是另一个例子,它是将偶数开头(后面有一个空格)的行写入文件:

sed -n 's/^[0-9]*[02468] /&/w even' <file

我在命令的替换部分用&,这样行就不会被改。一个更简单的例子是用”w”命令,它和上面的例子有相同的功能:

sed -n '/^[0-9]*[02468]/ w even' <file

切记:命令后只能有一个空格,剩余的部分将会被当作是文件名。”w”命令也有相同的局限:最多只能打开10个文件。

’r’命令读一个文件

这也是一个读文件的命令,命令:

sed '$r end' <in>out

会将”end”文件追加到out尾部(位置”$”),下面的命令会在有”INCLUDE”单词的行后插入文件:

sed '/INCLUDE/ r file' <in >out

你可以用大括号来删除有”INCLUDE”的行:

#!/bin/sh

sed '/INCLUDE/ {

    r file

    d

}'

    删除命令”d”和读文件命令”r”的顺序是很重要的,如果把它们的顺序颠倒是不能正常工作的。这是因为两个微妙的原因,第一是”r”命令将文件写入输出流,文件不是被插入模式空间,所以不能被任何命令修改。所以删除命令不影响从文件中读到的数据。

    另一个微妙之处在”d”命令删除模式空间中的当前数据,一旦数据被删除,显然不应该再对它做任何操作了,所以大括号中的”d”命令会使后面的命令都不会被执行。在下面的例子中,替换命令不会被执行:

#!/bin/sh

# this example is WRONG

sed -e '1 {

    d

    s/.*//

}'

    下面是一个C预处理程序的粗糙版本,文件以一个预先定义的名字包含,这样就能让sed让一个变量(比如”\1”)很容易替换一个文件名,啊,sed没有这个功能,你在运行时使用sed命令来突破这个局限,或使用shell引号传递sed脚本参数,假设你想创建一个命令要包话一个如cpp的文件,但文件名即是这个脚本的参数,下面的脚本就是这样一个例子:

% include 'sys/param.h' <file.c >file.c.new

    那么shell脚本应该这样写:

#!/bin/sh

# watch out for a '/' in the parameter

# use alternate search delimiter

sed -e '\_#INCLUDE <'"$1"'>_{

    r '"$1"'

    d

}'

    让我再详细一点,如果你有一个文件包含:

Test first file

#INCLUDE <file1>

Test second file

#INCLUDE <file2>

    你可以如下使用命令:

sed_include1.sh file1<input|sed_include1.sh file2

    以来包含指定的文件。

sunOS#注释命令

    当我们更深入sed过程中,注释会使命令更容易理解,大多数sed版本只允许一行注释,但它必须是第一行,SunOS允许多行注释,并且这些注释不需要在开头,它可以写成下面这样:

#!/bin/sh

# watch out for a '/' in the parameter

# use alternate search delimiter

sed -e '\_#INCLUDE <'"$1"'>_{

 

    # read the file

    r '"$1"'

 

    # delete any characters in the pattern space

    # and read the next line in

    d

}'

添加,修改,添加新行

Sed有三个命令可以添加新行到输出流,因为一个整行要添加,这个新行必须在单独的一行,这是必须的。如果你注释许unix工具,你会期待sed遵循相同的传统:用”\”表示一行的继续,这个语法像”r””w”命令一样,也是很严格的。

’a’命令追加一行

“a”命令用于在一个模式范围之后追加一行,下面的例子可以在每个有”WORD”的行后添加一行:

#!/bin/sh

sed '

/WORD/ a\

Add this line after every line with WORD

'

如果你高兴,你也可以在shell中把它写成两行:

#!/bin/sh

sed '/WORD/ a\

Add this line after every line with WORD'

    我更喜欢第一行,因为它添加一个新命令时更容易,而且它的意图也更清晰,注意在”\”之后不能有空格。

i插入一行

你可以在一个模式前用i插入一个命令:

#!/bin/sh

sed '

/WORD/ i\

Add this line before every line with WORD

'

’c’改变一行

你可以用一个新行改变当前行。

#!/bin/sh

sed '

/WORD/ c\

Replace the current line with the line

'

一个”a”命令跟在”d”命令之后是不会被执行的,原因已经说过了,因为”d”命令会中止以后的所有操作,你可以用大括号将这个操作结合起来:

#!/bin/sh

sed '

/WORD/ {

i\

Add this line before

a\

Add this line after

c\

Change the line to this one

}'

sed脚本中开头的tab和空格

Sed会忽略所有命令开头的tabs和空格,但是空格开头的行如果后面是”a””c” 或是”i”命令,它们可能被忽略也可能不被忽略,在SunOS上,两种都有,Berkeley(linux)方式的sed是在/usr/bin下,AT&T版本(System V)是在/usr/5bin/

    更详细一点,/usr/bin/sed命令保留空格,而/usr/5bin/sed会忽略开头的空格,如果你想保留开头的空格,而不管是什么版本的sed,你可以在第一个字符前加上”\”字符:

#!/bin/sh

sed '

    a\

\   This line starts with a tab

'

  评论这张
 
阅读(2557)| 评论(2)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017