碎碎念

2007, August 27

用SED打印斐波那契序列

Filed under: sed

整理邮件时看到了这个SED脚本(2006年1月),好像是我写的最后一篇关于SED的文章……

# 脚本开始
# SED打印斐波那契序列

# 调用:seq 10 | sed -f fib.sed # 打印前10个数字(运行较慢,不要打印太多)
# yes | sed -f fib.sed # 打印序列直到溢出

# 这个脚本用模拟的方式进行数字运算。有关这种方法的更
# 多细节可以参考Greg Ubben写的教程:
# http://sed.sourceforge.net/grabbag/tutorials/

1{
# 下面字母串的长度决定了可打印的序列长度
# 可以修改下面的字串使之支持更多的序列
# s/.*/zyxwvutsrqponmlkjihgfedcba/
s/.*/kjihgfedcba/
# 初始化序列为“1, 1”
s/.*/ &a &a /; h; d
}

# 从hs取最后两组数字串进行运算
g
s/.* \(.*\) \(.*\) $/\1\2/

# 进行进位操作前先对两组数字加总(其实是排序)
s/\(\(.\)\2*\)/\1 /g
:lpsort
s/\(\([^ ]\)\2\{0,\}\)\( .*\)\2/\1\2\3/
tlpsort
s/ //g

# 调整加总的结果
# 加总时我们进行如下运算:
# edcbaa + edcbaaa = edcbaaaa
# 1 + 2 = 3
# 但实际在加总(排序)后我们等到的结果是:
# edcbaa + edcbaaa = eeddccbbaaaaa
# 我们需要将eeddccbbaaaaa转换为edcbaaaa

s/\(.\)\(\1\{1,19\}\)/\2/g

# 进行进位操作。
# baaaaaaaaaaa进位后成为bba——10

:doCarry
s/\(.\)\(\(.\)\3\{9\}\)\3/\1\1\3/
t doCarry

# 简单(粗糙)的溢出检验
# 因为第一个字母如果超过9个说明没有正常进位——即溢出
/^\(.\)\1\{9\}/{g;s/$/ ++OVERFLOW++/; b2digi}

# 将这一轮的结果保存到hold space。记得在最后加上一个空格。
G
s/\(.*\)\n\(.*\)/\2\1 /
$!{h;d}
g

# 运算完后将模拟结果转为数字表示的结果
:2digi
s/\([a-z]\)\1\{9\}/9/g
s/\([a-z]\)\1\{8\}/8/g
s/\([a-z]\)\1\{7\}/7/g
s/\([a-z]\)\1\{6\}/6/g
s/\([a-z]\)\1\{5\}/5/g
s/\([a-z]\)\1\{4\}/4/g
s/\([a-z]\)\1\{3\}/3/g
s/\([a-z]\)\1\1/2/g
s/\([a-z]\)\1/1/g
s/\([a-z]\)/0/g
s/ 0*/ /g
# 溢出检验需要q命令
q
# 脚本结束

2006, April 1

在sed中使用循环

Filed under: sed

在sed中使用循环

循环通常都可以写为条件转移的形式。sed没有专门的循环语句,但提供了转移的命令,因而我们仍然可以实现循环。本篇中总结用sed进行循环的几种方式。sed处理文本的方式本身就是一种循环:

do while not EOF
 read line
   ... do sth
 end do。

1 在sed中进行判断

因为sed只处理字符和行号,它只能通过式样来作字串的匹配判断或者对行号进行判断。所以判断的条件需要以字串或行号的形式出现。

1.1 用holdspace储存标志位

在hs中存储字串作为标志位,如:

# 进行6次操作
1{x; s/^/654321/; x}
 :a
 x;
 /./{ s/.//; x; s/reg/ex/; ba}
 x;
 do sth else;
	
# 对每一行进行6次替换操作
1{x; s/^/654321/; x}
G
:a
/\n./{ s/reg/ex/; s/\n.$//; tb; s/.$//; ba}
:b
do sth else

1.2 用pattern space储存标志位

用pattern space(标志位附加在前面或后面),与hs的方式基本一样只是将标志位放在ps中。

1{ s/^/654321\n/ }
:a
/.\n/{ s/.//; s/reg/ex/; ba }
do sth else

1.3 用地址进行判断

地址(常用的地址是1,$)。当循环的条件与地址或行号有关时可以以这种方式。

sed '/./{H;d};x;/re/p' # 显示某个段落
2,8{H;d}; $G  # 类似ed中的:2,8m$。d在这里有两个作用a清空PS。b强制进入下一cycle。

1.4 用式样进行判断

以当前ps的内容作为判断的标准,当循环条件与输入的内容有关时可以用这种方法。这样方法与标志位的方法相似,所不同的是我们并不人为地设置标志而是以当前ps的内容作为标志。请参考下面的例子:

:a
 do sth with regexp
/regexp/ba

如果中间有s命令我们常用t来跳转,因而上面的可以写为:
:a
s/regexp/blah/
ta

2 循环常用的命令解析

q, b, t, T, d, n, N, :label

2.1 b/t控制循环

b都是sed中的分支(跳转)命令。它的格式是`b label‘,也可以去掉中间的空格,写作`blabel‘。上面命令的作用是从:label处继续执行脚本。label必须在脚本中定义,方法是在前面加上冒号(:),如`:loop‘`:lable‘。大部分sed对标签的长度有限制,具体的限制可以参考sed faq。如果b后没有带标签,则默认转到脚本结束处。
要注意的是许多的sed不允许在后面接其他命令因而:
gsed 'b abc;s/^/eee/;:abc;…'
这样的命令在其他版本的sed中要写成:

sed 'b abc
      s/^/eee/
     :abc'
或:
sed -e 'b abc' -e 's/^/eee/; :abc'

t命令与b相似。不同点在于t是以前s命令的成功与否来决定是否跳转,如成功则跳转。如果在一个s命令后使用了多个条件跳转则第二个及其后的t都会失败。gnu sed还提供了与t相反的T命令。

$ echo abc|sed 's/a/a/;bb;:b;tc;s/^/zzz/;:c'
abc
	
$ echo abc|sed 's/a/a/;tb;:b;tc;s/^/zzz/;:c'
zzzabc
	
$ echo abc|sed 's/a/a/;tb;:b;tc;s/^/zzz/;:c'
zzzabc
	

b/t控制循环
s/re/&/;t
s/re/&/;T

/re/b
/re/!b
是等价的。但在下面的内容中我们会看到s/t的组合可以用在一些更复杂的情况。

当作用b进行循环时常用下面的方式退出循环:
在b前使用式样或地址(循环条件):

:a
...
/regexp/ba

上面的情况通常要求中间的语句对/regexp/进行修改或有另外的退出命令,才不会成为死循环。

2.1.0.1 退出条件

使用b/t循环时,为了避免出现死循环通常要设置退出的条件。如:

:a
...
/xxx/b
...
/regexp/ba

除了用b外还可以用t, N, q, d作为退出的命令,用式样或行号作为退出的条件。当前面有成功的替换时,可用`t‘转移动脚本中的任意位置——当然也可以跳出循环体。在最后一行使用N会退出脚本,利用这一点我们可以退出循环体。不过不同版本的sed在最后一行使用`N‘时的结果不同——有的会显示ps的内容,有的则不会。`q‘命令用来退出脚本,当然也就不会再循环了。`d‘和`D‘命令的副作用是强制脚本进入下cycle,这使得我们可以用这两个命令来形成行与行之间的循环,如果下一循环中不满足循环的进入条件则循环中止——所以这两个命令既是进行循环的命令也是退出的命令。当然这些行前面都可以使用式样或行号作为运行命令的条件。

如果中间的语句没有对/regexp/修改则结果类似:

:a
...
ba

例:要将输入中#号后的所有xxx删除。

输入:
abc#efxxxghxxxxijxxx
输出:
abc#efghxij
	
sed ':a
s/\(#.*\)xxx/\1/  # 修改了循环标志
/#.*xxx/ba'

当然这时t可以派上用场了:

sed ':a; s/\(#.*\)xxx/\1/; ta'

下面是一些实例:

echo -e "abc\nefg" | sed ':a;/re/b;ba'
echo -e "abc\nefg" | sed ':a;/re/!b;ba'
echo -e "abc\nefg" | sed ':a;s/re/&/;t;ba'
echo -e "abc\nefg" | gsed ':a;s/re/&/;T;ba'
用以控制循环:
echo -e "abc\nefg" | sed ':a;/re/ba'
echo -e "abc\nefg" | sed ':a;/re/!ba'
echo -e "abc\nefg" | sed ':a;s/re/&/;ta'
echo -e "abc\nefg" | sed ':a;s/re/&/;Ta'
echo -e "abc\nefg" | sed '/re/b; :'

2.2 d/D控制循环

用d来控制循环也是sed脚本中比较常用的技巧。d之所以能用来控制循环主要是因为它在删除完之后不会执行接下来的命令而会直接进入下一个cycle中。例:

sed 'd; s/^/abc/' # s命令将不会被执行

2.3 标志位控件循环

前面我们已经说过sed中进行条件判断的一些方法。这些方法除了是进入循环的条件也可以作为退出循环的条件。这里举个例子:

G;s/$/123456789/ # 循环9次
:loop
s/\n$//;t break  # 退出循环
... do sth       # 进行操作
s/.$//           # 减一操作
b loop           # next

2.4 N作为退出条件

N添加下一行到当前PS。如果在最后一行时运行了N,sed通常会退出。如果在循环中使用则会退出循环。需要注意的是不同的N对最后一行的处理是不同的,一些版本在最后一行执行N时会安静的退出。而另一些版本如GNU sed将会默认显示PS的内容再退出。
见下面的例子:

echo -e "abc\nefg" | sed ':a;ba'
echo -e "abc\nefg" | sed ':a;n;ba'
echo -e "abc\nefg" | sed ':a;N;ba'
echo -e "abc\nefg" | sed ':a;$!n;ba'
echo -e "abc\nefg" | sed ':a;$!N;ba'

第一个例子中sed会进入死循环。第二三个例子会正常退出。其中第二个例子会显示输入,而第三个例子的行为与sed的版本有关,GNU sed会显示输入。最后一个例子会进入死循环,前面说过在$!N可以让N在所有的sed版本中显示结果。但仍要判断使用的时机。

3 实例

在sed中使用循环的例子相当多,这里举两个例子。

# “sed单行脚本”中的一个例子
# 以79个字符为宽度,将所有文本右对齐
sed -e :a -e 's/^.\{1,78\}$/ &/;ta'  # 78个字符外加最后的一个空格
	
# 将2至8行移到文件末尾
# 类似ed中的:2,8m$。
# d在这里有两个作用,a、清空PS。b、强制进入下一cycle。
# do while 2<=linenum<=8; H; end do
sed '2,8{H;d}; $G'

[–结束–]

2006, March 29

sed命令示例-`r'

Filed under: sed

sed命令示例-`r'

`r'命令用来读入文本。与`a'的作用相似,但文本是放在独立的文件而不是放在脚本中。用`r'的好处在于将数据与脚本独立,使脚本更易读并且修改数据文件更方便。

用法

这是GNU sed自带文档中对`r'命令的说明:

`r filename
Queue the contents of filename to be read and inserted into the output stream at the end of the current cycle, or when the next input line is read. Note that if filename cannot be read, it is treated as if it were an empty file, without any error indication.

GNU对这个命令进行了扩展:

  • 接受两个地址。可以对一定的地址区间进行操作
  • 允许使用/dev/stdin作为文件名
  • 新增了一个`R'命令。这个命令用法与`r'一样但一次只读入一行。

另外一些需要注意的地方:

  • 文件不能读取时,则当成空文件处理不会有出错信息。
  • r 命令之后不能再使用其他命令,因而如果后面的命令要另起一行或以`-e'选项隔开。
  • r是在当前的cycle结束后才在最后加入内容的,所以在无法在cycle中对读入的内容进行操作的。

实例

我一向认为一个例子比得上千言万语。看个例子先:

现有一个网页要进行如下修改,

  • 在<head>标签下增加<meta>标签及相应内容。
  • 在<body></body>的结束标签前增加版权声明的内容。

假设所有的<meta>标签都放在meta.tag文件中。而版权声明在copyleft.tag文件中。
它们的内容如下:

$ cat meta.tag
<meta name="description" content="...">
<meta name="keywords" content="..., ... ">
	
$ cat copyleft.tag
<div class="copyleft">
版权所有……
</div>
	

可以用r来读入相应文件的内容。我们需要的命令是:

sed '/<\/title>/r meta.tag
      /^<\/body>/{
            # 为了在标签前添加内容,我们先将当前行保存
            x
            r copyleft.tag
            # 将刚才保存的内容,置于读入的内容后
            G
      }'  samp.html

写成单行的形式就是:

sed -e '/<\/title>/r meta.tag' -e '/<\/body>/{x;r copyleft.tag' -e 'G}'  samp.html

如果你在Windows的命令行上使用的话,那就根据你所用的版本将单引号相应的改为双引号。不过请务必注意上面脚本是一个简化的情形,因为标签也可能有大写字母1(虽然W3C的XHTML中要求标签必须是小写,但这种可能性是存在的视你自已的情况而定。)如果你用的是GNU sed的话那恭喜,你可以在式样后使用`I'修改符(modifier):/<\/title>/I。否则:/<\/[tT][iI][tT][lL][eE]>/当然偶尔可以偷懒:/<\/[tT].[tT].[eE]>/。

实例:批量修改

当然我们想修改的通常不会只是一个文件,于是我们需要用到shell(注意文件的读写权限):

$ for a in `find . -name "*.html"`
do
 mv $a $a.bak
 sed -e '/<\/title>/r meta.tag' -e '/<\/body>/{x;r copyleft.tag' -e 'G}' $a.bak >$a
done

find有一个-exec,但是如果参数太多会出错因而这里使用了for命令。
如果是Windows平台:

for /R %a in (*.html) do @copy %a %a.bak & @sed -e "..." %a.bak>%a

在批处理中使用时记得要用`%%'代替`%'。

如果你用的是GNU sed 4.0或以上版本,那你可以使用-i选项代替`mv'命令:

$ for a in `find . -name "*.html"`
do
 gsed -i.bak -e '
       /<\/title>/r meta.tag
       /<\/body>/{
          x
          r copyleft.tag
          G}' $a
done

结语

在批量修改文件时sed可以节省很多时间。r命令的好处就是它让我们在sed中可以方便地插入大量的文本。ia也可以用来插入文本,但当内容较多时这两个命令不适合在命令行中使用。更重要的是,使用r可以保持脚本的可读性——这样也符合数据与脚本分离的原则。

现在你可以想一下这个问题。像上面的批量替换你自己会用什么方式来做?所需要的工具(软件)、它们的价钱(共享/免费?)、它们占用的磁盘空间、完成任务所需要的时间、掌握工具所需的时间?你可能会发现简单容易的方式没什么效率,而有效率的方式要么要花掉磁盘空间要么花掉学习时间(或者是金钱)。这就是sed的好处:“以相对合理的学习成本获得较高的工作效率!”


Footnotes

[1] 更复杂的情形是title后的方括号也许在下面一行。

2006, March 28

文本工具箱——“sed”篇

Filed under: sed

文本工具箱——“sed”篇

鉴于旧Blog已经删了,新Blog上的前几篇将是旧Blog上帖过的文章。首先是:文本工具箱——“sed”篇(原名:我喜爱的工具——编辑器篇:sed)。与原来的相比会做一些更动

hq00e

什么是SED?简单来说,SED是个编辑器。就像Windows下的记事本一样是用来编辑文本的,但是从操作方式来看它是个很特殊的编辑器!sed(Stream EDitor)是“流编辑器”的意思。最早是Unix平台的工具,但已经被移植到各个平台上了。它有两个主要的属性:
一它是个编辑器; 二它这个编辑器是面向“流”的。

名称:SED (Stream EDitor)
描述:命令行下运行的流编辑器
平台:Unix,Linux,MacOS,Windows,DOS,……
大小:根据平台和版本的不同从13KB-200KB不等(超过100K的一般都是加了多语言支持的)
PS:sed的版本有很多样,推荐使用GNU sed(gsed)或在gsed基础上修改的支持perl正则表达式的ssed。不同的版本对表达式,及表达式使用的细节上有些不同,如hhsed和csed不严格执行最左侧最大长度匹配原则,Pattern Space和Hold Space的支持的大小也不同,这些细节在使用时要注意。
下载:http://sed.sourceforge.net/#download 在这里可以找到不同版本的SED下载
Unix和Linux一般已带有sed,鉴于Windows平台上编译SED不方便,加上不容易找这里找供一个ssed的下载地址

  • 首先,它是个编辑器,它的用途便是对文本进行编辑。对一些重复性的编辑工作而言sed可以为你节省很多时间。
  • 其次,它是面向流的。它的工作流程为:读入行,处理,移到下一行,读入行,处理,移到下一行……并且这个过程是依你所给的命令(或脚本)自动进行的。在这个编辑过程中文本以行为单位不断的移动(读入)就如同水在流动一般,因而称为流编辑器。不过你也许更喜欢这个解释:它支持管道操作接受前一个命令的输出作为sed的输入并写入至标准输出(通过管道操作,这又可以成为下一个命令的输入),就像是工厂的流水线一样,所以叫它流编辑器。此外,它是据给定的命令或脚本“自动”逐行处理文本的,这意味着它是非交互式的编辑器——在它处理的过程中不须人工干预。

1 与交互式编辑器的不同:

  • 它是非交互式的,所有的操作必须以命令(脚本)的方式给出。交互式编辑器让你可以即时的看到结果并对其修改,现在的交互编辑器通常还有图形介面及菜单工具栏。
  • 它是one pass,sed单向的处理文件流。除非读入多行到Pattern Space或Hold Space中,不然你没办法先处理第6行来修改第2行。
  • 交互式编辑器可用来做一些不确定的和无规律的(但很多看似无规律的东东其实都系有规律的)编辑工作,如找找语病,检查文本——在开始之前并不知道那些地方要修改。但是如果你事先已经知道哪些地方要修改了,那也许可以考虑用sed。

2 它能做得到的交互式编辑器能做得到吗?

一部分可以。交互式用的是人工——双手万能!但很累而且没效率。而且当要处理的文件数量或文件大小超过了一定的范围后,要使用交互式编辑器来做编辑修改的工作就不大现实了。此外sed可以结合Unix下面的Shell编程或Win平台下的批处理与脚本编程中使用(主要是因为sed对管道操作的支持)从而将它的应用扩展到文本编辑以外的领域。

3 它的主要用途?它与ed、AWK或Perl的关系?

sed能做多种不同的编辑工作(还能用来玩游戏),但最经常的用途是用做“批量查找替换”的工具。

既然你读了这篇文章那也许你没听过ed、AWK,但是如果你想了解SED的话ED和AWK也应该了解一下。
ed是Linux下的行编辑器。它与sed很相似:它是命令行工具、支持正则表达式、与SED相似的命令、支持脚本。不同的是ed是交互式编辑器同时它缺少作为一种编程语言所需的控制结构。而且对于同样的任务而sed通常要比ed快。
Perl是一种通用语言。你几乎可以用Perl来做任何事,因为Perl的一些特性使得它同样适合作命令行的文本编辑器。
AWK相较sed而言是一种更完整的编程语言。含有更多编程特性如变量、数组、函数等。它与SED的命令结构相似。
很多人认为AWK可以完成SED可以完成的所有任务所以不需要SED。但是正如我们不会因为Perl而不使用AWK或者因为SED而不用Head,Cut,Paste……等其他Unix文本工具一样,我们应该只用最适合的工具!话说回来AWK与SED还是很不一样的,在它们有不同的适用场合——不过这里不会有深度剖析。

4 为什么喜欢它?

效率: sed能将用户从重复的编辑中工作解放出来。如批量替换网页中的特定链接;整理出日志中来自特定IP段的访问记录;删除特定行等,用SED都可以轻易地完成。SED能读入脚本文件意味着一个脚本可以被重复用或者经过少量修改后就可以再次使用。SED的效率还来源于它支持管道操作(*这点非常重要*)能以上一个命令的输出作为输入,并将结果写回“标准输出”(通常是电脑屏幕),这表示SED可以方便地将处理完的结果传到下一个工具中。SED当然不是万能的,使用管道操作SED只要完成自己的那一部份工作然后传给下一个工具。最后,如果要从一个“大”文件中“抽取”一部份(符合式样的)文本的话,交互式编辑器光在打开文件时就要占用相当多的时间和磁盘空间了……
PS:就算是简单的替换而言,sed也能省下一些时间。比如要将“abc.txt”中的所有“hqooe”改为“hq00e”(中间是数字0不是字母o)。我只要在命令行下输入:sed "s/hqooe/hq00e/g" abc.txt abc2.txt。而在交互式的编辑器中我需要先打开文件,运行“替换”,输入hqooe和hq00e,按“确定”,另存为abc2.txt,关闭编辑器。当然我不坚持这种情况下一定要用SED: )

乐趣:使用sed本身就是一种乐趣:你要事先想好要进行的操作并用脚本的形式写出来,试运行,修改,再运行……搞定。对一些编辑任务而言要使用非交互的方式来完成是有一定难度的如果此时还能写出一个可行的SED脚本(AWK也一样)的话会——非常有成就感!此时完成任务成了一种享受。而使用交互式工具的话,意味着你舍弃了用一种更优雅的方式完成任务的可能性,完成任务只是一种体力活(如果文件够大的话)。
PS:Greg Ubben用sed写了一个计算器;还有很多人写了SED的游戏——请注意SED只是一个流编辑器。想知道他们是怎么做的吗?看下这个网址:http://sed.sourceforge.net/grabbag/

成本:sed是开源的,自由的。简单说就是SED是免费的。
空间成本:不同版本的sed大小不尽相同但大一点的也就180K左右基本不占磁盘空间。没有图形介面以省下了不少屏幕空间和内存:-)
学习成本:sed的学习成本几乎都在“正则表达式”上,但学了正则表达式后你可以在支持“正则表达式”的其他的编辑器上(如Vim、UltraEdit32、EmEditor等——不过他们支持的正则表达式有些细微的差别),Perl,.NET编程和一大堆的GNU工具中使用。这样平摊下来学习成本也很低的。而且SED是跨平台的你现在学的无论你将来用的是Solaris、Linux、MacOS还是Win都能派上用场。

5 它的缺点?

SED的优点很明显缺点也是。最主要的缺点就是大部分人不用命令行来编辑文本。其次是很少人愿意花时间去学正则表达式。不过,好消息是SED相对AWK或C语言要容易得多。

6 sed的基本用法和一些使用的例子

sed可在命令行下直接输入使用SED命令,或将SED命令写成SED脚本在运行SED时读取脚本。
sed -e "命令" abc.txt
–>第一种:直接在SED后使用SED命令,在Unix平台下使用单引号而不是双引号。很多的sed版本允许省略前面的“-e”选项(开关)。
sed -f script.txt abc.txt
–>第二种:从脚本script.txt中读取SED命令,脚本文件其实也是文本文件,但它的扩展名是什么并不重要。使用脚本文件通常因为命令是比较复杂写在脚本中方便写也方便调试或者是命令经常要用到通过脚本可以方便的再次调用。
注意:sed 不会对输入文件做任何的改动,在上面的例子中它只是读入abc.txt的内容处理然后输出到屏幕上,并不会改变abc.txt的内容。如果要保存修改的结果可以使用重定向操作符“>”。如“sed "命令" abc.txt 新abc.txt”将修改的结果保存到“新abc.txt”中。

第一种用法的例子:

     #删除文件中的所有空行
     sed "/^$/d"
     #abc.txt中所有的hqooe都替换成hq00e
     sed "s/hqooe/hq00e/g"
     #所有行缩进两格
     sed "s/^/  /"
     #显示包含“姚明”这个词的行
     sed -n "/姚明/p"
     #移除大多数的html标签
     sed ":a;s/<[^>]*>//g;/</N;//ba"

第二种用法——脚本。脚本的例子较长,附录中的“sed百宝箱”中提供了许多经典的脚本。

7 如何学sed:

  1. 首先是参考资料。GNU SED的文档加上sed自带的帮助就差不多了。
  2. 学些简单的正则表达式和正则表达式中的一些常用字元(^$[]{}().*?\1)
  3. 再学几个常用命令 s、i、d、p 等
  4. 练习。在实践中练习。
  5. 遇到简单命令不能应付的任务,学更多命令。
  6. 不懂的话找人问问看。建议订阅sed的邮件列表。
              订阅方式,发送空白邮件至 sed-users-subscribe@yahoogroups.com
              退订邮件列表,发送空白邮件至 sed-users-unsubscribe@yahoogroups.com
              这个邮件列表里有一群热心的sed用户会为你提供帮助。
              当然如果能帮得上忙的话,我也很乐意帮忙的。
         

附录: 一些sed资源

Get free blog up and running in minutes with Blogsome
Theme designed by Jay of onefinejay.com