通用线程--sed实例在本文章系列中,DanielRobbins将为您演示如何使用功能十分强大(但常被遗忘)的UNIX流编辑器sed.
sed是用批处理方式编辑文件或以十分有效的方式创建shell脚本以修改现有文件的理想工具.
挑选编辑器在UNIX世界中有很多文本编辑器可供我们选择.
思考一下--vi、emacs和jed以及很多其它工具都会浮现在脑海中.
我们都有自己已逐渐了解并且喜爱的编辑器(以及我们喜爱的组合键).
有了可信赖的编辑器,我们可以轻松处理任何数量与UNIX有关的管理或编程任务.
虽然交互式编辑器很棒,但却有其限制.
尽管其交互式特性可以成为强项,但也有其不足之处.
考虑一下需要对一组文件执行类似更改的情形.
您可能会本能地运行自己所喜爱的编辑器,然后手工执行一组烦琐、重复和耗时的编辑任务.
然而有一种更好的方法.
进入sed如果可以使编辑文件的过程自动化,以便用"批处理"方式编辑文件,甚至编写可以对现有文件进行复杂更改的脚本,那将太好了.
幸运的是,对于这种情况,有一种更好的方法--这种更好的方法称为"sed".
sed是一种几乎包括在所有UNIX平台(包括Linux)的轻量级流编辑器.
sed有许多很好的特性.
首先,它相当小巧,通常要比您所喜爱的脚本语言小很多倍.
其次,因为sed是一种流编辑器,所以,它可以对从如管道这样的标准输入接收的数据进行编辑.
因此,无需将要编辑的数据存储在磁盘上的文件中.
因为可以轻易将数据管道输出到sed,所以将sed用作强大的shell脚本中长而复杂的管道很容易.
试一下用您所喜爱的编辑器去那样做.
GNUsed对Linux用户来说幸运的是,最好的sed版本之一恰好是GNUsed,其当前版本是3.
02.
每一个Linux发行版都有(或至少应该有)GNUsed.
GNUsed之所以流行不仅因为可以自由分发其源代码,还因为它恰巧有许多对POSIXsed标准便利、省时的扩展.
另外,GNU没有sed早期专门版本的很多限制,如行长度限制--GNU可以轻松处理任意长度的行.
最新的GNUsed在研究这篇文章之时我注意到:几个在线sed爱好者提到GNUsed3.
02a.
奇怪的是,在ftp.
gnu.
org(有关这些链接,请参阅参考资料)上找不到sed3.
02a,所以,我只得在别处寻找.
我在alpha.
gnu.
org的/pub/sed中找到了它.
于是我高兴地将其下载、编译然后安装,而几分钟后我发现最新的sed版本却是3.
02.
80--可在alpha.
gnu.
org上3.
02a源代码旁边找到其源代码.
安装完GNUsed3.
02.
80之后,我就完全准备好了.
正确的sed在本系列中,将使用GNUsed3.
02.
80.
在即将出现的本系列后续文章中,某些(但非常少)最高级的示例将不能在GNUsed3.
02或3.
02a中使用.
如果您使用的不是GNUsed,那么结果可能会不同.
现在为什么不花些时间安装GNUsed3.
02.
80呢那样,不仅可以为本系列的余下部分作好准备,而且还可以使用可能是目前最好的sed.
sed示例sed通过对输入数据执行任意数量用户指定的编辑操作("命令")来工作.
sed是基于行的,因此按顺序对每一行执行命令.
然后,sed将其结果写入标准输出(stdout),它不修改任何输入文件.
让我们看一些示例.
头几个会有些奇怪,因为我要用它们演示sed如何工作,而不是执行任何有用的任务.
然而,如果您是sed新手,那么理解它们是十分重要的.
下面是第一个示例:$sed-e'd'/etc/services如果输入该命令,将得不到任何输出.
那么发生了什么在该例中,用一个编辑命令'd'调用sed.
sed打开/etc/services文件,将一行读入其模式缓冲区,执行编辑命令("删除行"),然后打印模式缓冲区(缓冲区已为空).
然后,它对后面的每一行重复这些步骤.
这不会产生输出,因为"d"命令除去了模式缓冲区中的每一行!
在该例中,还有几件事要注意.
首先,根本没有修改/etc/services.
这还是因为sed只读取在命令行指定的文件,将其用作输入--它不试图修改该文件.
第二件要注意的事是sed是面向行的.
'd'命令不是简单地告诉sed一下子删除所有输入数据.
相反,sed逐行将/etc/services的每一行读入其称为模式缓冲区的内部缓冲区.
一旦将一行读入模式缓冲区,它就执行'd'命令,然后打印模式缓冲区的内容(在本例中没内容).
我将在后面为您演示如何使用地址范围来控制将命令应用到哪些行--但是,如不使用地址,命令将应用到所有行.
第三件要注意的事是括起'd'命令的单引号的用法.
养成使用单引号来括起sed命令的习惯是个好注意,这样可以禁用shell扩展.
另一个sed示例下面是使用sed从输出流除去/etc/services文件第一行的示例:$sed-e'1d'/etc/services|more如您所见,除了前面有'1'之外,该命令与第一个'd'命令十分类似.
如果您猜到'1'指的是第一行,那您就猜对了.
与第一个示例中只使用'd'不同的是,这一次使用的'd'前面有一个可选的数字地址.
通过使用地址,可以告诉sed只对某一或某些特定行进行编辑.
地址范围现在,让我们看一下如何指定地址范围.
在本例中,sed将删除输出的第1到10行:$sed-e'1,10d'/etc/services|more当用逗号将两个地址分开时,sed将把后面的命令应用到从第一个地址开始到第二个地址结束的范围.
在本例中,将'd'命令应用到第1到10行(包括这两行).
所有其它行都被忽略.
带规则表达式的地址现在演示一个更有用的示例.
假设要查看/etc/services文件的内容,但是对查看其中包括的注释部分不感兴趣.
如您所知,可以通过以'#'字符开头的行在/etc/services文件中放置注释.
为了避免注释,我们希望sed删除以'#'开始的行.
以下是具体做法:$sed-e'/^#/d'/etc/services|more试一下该例,看看发生了什么.
您将注意到,sed成功完成了预期任务.
现在,让我们分析发生的情况.
要理解'/^#/d'命令,首先需要对其剖析.
首先,让我们除去'd'--这是我们前面所使用的同一个删除行命令.
新增加的是'/^#/'部分,它是一种新的规则表达式地址.
规则表达式地址总是由斜杠括起.
它们指定一种模式,紧跟在规则表达式地址之后的命令将仅适用于正好与该特定模式匹配的行.
因此,'/^#/'是一个规则表达式.
但是,它做些什么呢很明显,现在该复习规则表达式了.
规则表达式复习可以使用规则表达式来表示可能会在文本中发现的模式.
您在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打印模式空间.
就这样,将只打印匹配部分.
有关地址的更多内容目前为止,我们已经看到了行地址、行范围地址和regexp地址.
但是,还有更多的可能.
我们可以指定两个用逗号分开的规则表达式,sed将与所有从匹配第一个规则表达式的第一行开始,到匹配第二个规则表达式的行结束(包括该行)的所有行匹配.
例如,以下命令将打印从包含"BEGIN"的行开始,并且以包含"END"的行结束的文本块:$sed-n-e'/BEGIN/,/END/p'/my/test/file|more如果没发现"BEGIN",那么将不打印数据.
如果发现了"BEGIN",但是在这之后的所有行中都没发现"END",那么将打印所有后续行.
发生这种情况是因为sed面向流的特性--它不知道是否会出现"END".
C源代码示例如果只要打印C源文件中的main()函数,可输入:$sed-n-e'/main[[:space:p'sourcefile.
c|more该命令有两个规则表达式'/main[[:space:]]*(/'和'/^}/',以及一个命令'p'.
第一个规则表达式将与后面依次跟有任意数量的空格或制表键以及开始圆括号的字符串"main"匹配.
这应该与一般ANSICmain()声明的开始匹配.
在这个特别的规则表达式中,出现了'[[:space:]]'字符类.
这只是一个特殊的关键字,它告诉sed与TAB或空格匹配.
如果愿意的话,可以不输入'[[:space:]]',而输入'[',然后是空格字母,然后是-V,然后再输入制表键字母和']'--Control-V告诉bash要插入"真正"的制表键,而不是执行命令扩展.
使用'[[:space:]]'命令类(特别是在脚本中)会更清楚.
好,现在看一下第二个regexp.
'/^}'将与任何出现在新行行首的'}'字符匹配.
如果代码的格式很好,那么这将与main()函数的结束花括号匹配.
如果格式不好,则不会正确匹配--这是执行模式匹配任务的一件棘手之事.
因为是处于'-n'安静方式,所以'p'命令还是完成其惯有任务,即明确告诉sed打印该行.
试着对C源文件运行该命令--它应该输出整个main(){}块,包括开始的"main()"和结束的'}'.
本文是前一篇介绍sed文章的续篇.
替换!
让我们看一下sed最有用的命令之一,替换命令.
使用该命令,可以将特定字符串或匹配的规则表达式用另一个字符串替换.
下面是该命令最基本用法的示例:$sed-e's/foo/bar/'myfile.
txt上面的命令将myfile.
txt中每行第一次出现的'foo'(如果有的话)用字符串'bar'替换,然后将该文件内容输出到标准输出.
请注意,我说的是每行第一次出现,尽管这通常不是您想要的.
在进行字符串替换时,通常想执行全局替换.
也就是说,要替换每行中的所有出现,如下所示:$sed-e's/foo/bar/g'myfile.
txt在最后一个斜杠之后附加的'g'选项告诉sed执行全局替换.
关于's///'替换命令,还有其它几件要了解的事.
首先,它是一个命令,并且只是一个命令,在所有上例中都没有指定地址.
这意味着,'s///'还可以与地址一起使用来控制要将命令应用到哪些行,如下所示:$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试图在行中匹配规则表达式时,它要在行中查找最长的匹配.
在上述中这不成问题,因为我们使用的是'd'和'p'命令,这些命令总要删除或打印整行.
但是,在使用's///'命令时,确实有很大不同,因为规则表达式匹配的整个部分将被目标字符串替换,或者,在本例中,被删除.
这意味着,上例将把下行:ThisiswhatImeant.
变成:meant.
我们要的不是这个,而是:ThisiswhatImeant.
幸运的是,有一种简便方法来纠正该问题.
我们不输入"''字符结束"的规则表达式,而只需输入一个"''字符并以'>'字符结束"的规则表达式.
这将与最短、而不是最长的可能性匹配.
新命令如下:$sed-e's/g'myfile.
html在上例中,'[^>]'指定"非'>'"字符,其后的'*'完成该表达式以表示"零或多个非'>'字符".
对几个html文件测试该命令,将它们管道输出到"more",然后仔细查看其结果.
更多字符匹配'[]'规则表达式语法还有一些附加选项.
要指定字符范围,只要字符不在第一个或最后一个位置,就可以使用'-',如下所示:'[a-x]*'这将匹配零或多个全部为'a'、'b'、'c'.
.
.
'v'、'w'、'x'的字符.
另外,可以使用'[:space:]'字符类来匹配空格.
以下是可用字符类的相当完整的列表:字符类描述[:alnum:]字母数字[a-zA-Z0-9][:alpha:]字母[a-zA-Z][:blank:]空格或制表键[:cntrl:]任何控制字符[:digit:]数字[0-9][:graph:]任何可视字符(无空格)[:lower:]小写[a-z][:print:]非控制字符[:punct:]标点字符[:space:]空格[:upper:]大写[A-Z][:xdigit:]十六进制数字[0-9a-fA-F]尽可能使用字符类是很有利的,因为它们可以更好地适应非英语locale(包括某些必需的重音字符等等).
高级替换功能我们已经看到如何执行简单甚至有些复杂的直接替换,但是sed还可以做更多的事.
实际上可以引用匹配规则表达式的部分或全部,并使用这些部分来构造替换字符串.
作为示例,假设您正在回复一条消息.
下例将在每一行前面加上短语"ralphsaid:":$sed-e's/.
*/ralphsaid:&/'origmsg.
txt输出如下:ralphsaid:HiyaJim,ralphsaid:ralphsaid:Isurelikethissedstuff!
ralphsaid:该例的替换字符串中使用了'&'字符,该字符告诉sed插入整个匹配的规则表达式.
因此,可以将与'.
*'匹配的任何内容(行中的零或多个字符的最大组或整行)插入到替换字符串中的任何位置,甚至多次插入.
这非常好,但sed甚至更强大.
那些极好的带反斜杠的圆括号's///'命令甚至比'&'更好,它允许我们在规则表达式中定义区域,然后可以在替换字符串中引用这些特定区域.
作为示例,假设有一个包含以下文本的文件:foobaronieenymeenyminylarrycurlymoejimmytheweasel现在假设要编写一个sed脚本,该脚本将把"eenymeenyminy"替换成"Victoreeny-meenyVonminy"等等.
要这样做,首先要编写一个由空格分隔并与三个字符串匹配的规则表达式.
现在,将在其中每个感兴趣的区域两边插入带反斜杠的圆括号来定义区域:除了要定义三个可在替换字符串中引用的逻辑区域以外,该规则表达式的工作原理将与第一个规则表达式相同.
下面是最终脚本:$sed-e's/Victor\1-\2Von\3/'myfile.
txt如您所见,通过输入'\x'(其中,x是从1开始的区域号)来引用每个由圆括号定界的区域.
输入如下:Victorfoo-barVononiVictoreeny-meenyVonminyVictorlarry-curlyVonmoeVictorjimmy-theVonweasel随着对sed越来越熟悉,您可以花最小力气来进行相当强大的文本处理.
您可能想如何使用熟悉的脚本语言来处理这种问题--能用一行代码轻易实现这样的解决方案吗组合使用在开始创建更复杂的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-fmycommands.
sedmyfile.
txt这种方法虽然可能不太方便,但总是管用.
一个地址的多个命令有时,可能要指定应用到一个地址的多个命令.
这在执行许多's///'以变换源文件中的字和语法时特别方便.
要对一个地址执行多个命令,可在文件中输入sed命令,然后使用'{}'字符将这些命令分组,如下所示:1,20{s/[Ll]inux/GNU\/Linux/gs/samba/Samba/gs/posix/POSIX/g}上例将把三个替换命令应用到第1行到第20行(包括这两行).
还可以使用规则表达式地址或者二者的组合:1,/^END/{s/[Ll]inux/GNU\/Linux/gs/samba/Samba/gs/posix/POSIX/gp}该例将把'{}'之间的所有命令应用到从第1行开始,到以字母"END"开始的行结束(如果在源文件中没发现"END",则到文件结束)的所有行.
附加、插入和更改行既然在单独的文件中编写sed脚本,我们可以利用附加、插入和更改行命令.
这些命令将在当前行之后插入一行,在当前行之前插入一行,或者替换模式空间中的当前行.
它们也可以用来将多行插入到输出.
插入行命令用法如下:i\Thislinewillbeinsertedbeforeeachline如果不为该命令指定地址,那么它将应用到每一行,并产生如下的输出:Thislinewillbeinsertedbeforeeachlineline1hereThislinewillbeinsertedbeforeeachlineline2hereThislinewillbeinsertedbeforeeachlineline3hereThislinewillbeinsertedbeforeeachlineline4here如要在当前行之前插入多行,可通过在前一行之后附加一个反斜杠来添加附加行,如下所示:i\insertthisline\andthisone\andthisone\and,uh,thisonetoo.
附加命令的用法与之类似,但它将把一行或多行插入到模式空间中的当前行之后.
用法如下:a\insertthislineaftereachline.
Thanks!
:)另一方面,"更改行"命令将实际替换模式空间中的当前行,其用法如下:c\You'rehistory,originalline!
Muhahaha!
因为附加、插入和更改行命令需要在多行输入,所以将把它们输入到一个文本sed脚本中,然后通过使用'-f'选项告诉sed执行它们.
使用其它方法将命令传递给sed会出现问题.
强健的sed在上面中,我提供了一些示例来演示sed的工作原理,但是它们当中很少有示例能实际做特别有用的事.
在这篇sed系列的最后文章中,我要改变那种方式,并使用sed来做实际的事.
我将为您显示几个示例,它们不仅演示sed的能力,而且还做一些真正巧妙(和方便)的事.
例如,在本文的后半部,将为您演示如何设计一个sed脚本来将.
QIF文件从Intuit的Quicken金融程序转换成具有良好格式的文本文件.
在那样做之前,我们将看一下不怎么复杂但却很有用的sed脚本.
文本转换第一个脚本将UNIX风格的文本转换成DOS/Windows格式.
您可能知道,基于DOS/Windows的文本文件在每一行末尾有一个CR(回车)和LF(换行),而UNIX文本只有一个换行.
有时可能需要将某些UNIX文本移至Windows系统,该脚本将为您执行必需的格式转换.
$sed-e's/$/\r/'myunix.
txt>mydos.
txt在该脚本中,'$'规则表达式将与行的末尾匹配,而'\r'告诉sed在其之前插入一个回车.
在换行之前插入回车,立即,每一行就以CR/LF结束.
请注意,仅当使用GNUsed3.
02.
80或以后的版本时,才会用CR替换'\r'.
我已记不清有多少次在下载一些示例脚本或C代码之后,却发现它是DOS/Windows格式.
虽然很多程序不在乎DOS/Windows格式的CR/LF文本文件,但是有几个程序却在乎--最著名的是bash,只要一遇到回车,它就会出问题.
以下sed调用将把DOS/Windows格式的文本转换成可信赖的UNIX格式:$sed-e's/.
$//'mydos.
txt>myunix.
txt该脚本的工作原理很简单:替代规则表达式与一行的最末字符匹配,而该字符恰好就是回车.
我们用空字符替换它,从而将其从输出中彻底删除.
如果使用该脚本并注意到已经删除了输出中每行的最末字符,那么,您就指定了已经是UNIX格式的文本文件,也没必要那样做了!
反转行下面是另一个方便的小脚本.
与大多数Linux发行版中包括的"tac"命令一样,该脚本将反转文件中行的次序.
"tac"这个名称可能会给人以误导,因为"tac"不反转行中字符的位置(左和右),而是反转文件中行的位置(上和下).
用"tac"处理以下文件:foobaroni.
.
.
.
将产生以下输出:onibarfoo可以用以下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'之前的'$!
')以及将模式空间的内容(三行)打印到标准输出之外,重复同样的步骤.
现在,要用sed执行一些强大的数据转换.
sedQIF魔法过去几个星期,我一直想买一份Quicken来结算我的银行帐户.
Quicken是一个非常好的金融程序,当然会成功地完成这项工作.
但是,经过考虑之后,我觉得自己可以轻易编写某个软件来结算我的支票簿.
我想,毕竟,我是个软件开发人员!
我开发了一个很好的小型支票簿结算程序(使用awk),它通过分析包含我的所有交易的文本文件的语法来计算余额.
略微调整之后,我将其改进,以便可以象Quicken那样跟踪不同的贷款和借款类别.
但是,我还要添加一个特性.
最近,我将帐户转移到一家有联机Web帐户界面的银行.
有一天,我注意到,这家银行的Web站点允许以Quicken的.
QIF格式下载我的帐户信息.
我马上觉得,如果可以将该信息转换成文本格式,那就太棒了.
两种格式的故事在查看QIF格式之前,先看一下我的checkbook.
txt格式:28Aug2000food--YSupermarket30.
9425Aug2000watr-在我的文件中,所有字段都由一个或多个制表符分开,每个交易占据一行.
日期之后的下一个字段列出支出类型(如果是收入项,则为"-").
第三个字段列出收入类型(如果是支出项,则为"-").
然后,是一个支票号字段(如果为空,则还是"-"),一个交易完成字段("Y"或"N"),一个注释和一个美元金额字段.
现在,让我们看一下QIF格式.
当用文本查看器查看下载的QIF文件时,它看起来如下:!
Type:BankD08/28/2000T-8.
15NPCHECKCARDSUPERMARKET^D08/28/2000T-8.
25NPCHECKCARDPUNJABRESTAURANT浏览过文件之后,不难猜出其格式--忽略第一行,其余的格式如下:DTNP^(这是字段分隔符)开始处理在处理象这样重要的sed项目时,不要气馁--sed允许您将数据逐渐修改成最终形式.
在进行当中,可以继续细化sed脚本,直到输出与预期的完全一样为止.
无需在试第一次时就保证其完全正确.
要开始,首先创建一个名为"qiftrans.
sed"的文件,然后开始修改数据:1d/^^/ds/[[:cntrl:]]//g第一个'1d'命令删除第一行,第二个命令从输出除去那些讨厌的'^'字符.
最后一行除去文件中可能存在的任何控制字符.
既然在处理外来文件格式,我想消除在中途遇到任何控制字符的风险.
到目前为止,一切顺利.
现在,要向该基本脚本中添加一些处理功能:1d/^^/ds/[[:cntrl:]]//g/^D/{s/^D\(.
*\)/\1\tOUTY\tINNY\t/s/^01/Jan/s/^02/Feb/s/^03/Mar/s/^04/Apr/s/^05/May/s/^06/Jun/s/^07/Jul/s/^08/Aug/s/^09/Sep/s/^10/Oct/s/^11/Nov/s/^12/Dec/s:2\1\3:}首先,添加一个'/^D/'地址,以便sed只在遇到QIF数据字段的第一个字符'D'时才开始处理.
当sed将这样一行读入其模式空间时,将按顺序执行花括号中的所有命令.
花括号中的第一个命令将把如下行:D08/28/2000变换成:08/28/2000OUTYINNY当然,现在的格式还不完美,但没关系.
我们将在进行过程中逐渐细化模式空间的内容.
后面12行的最后效果是将数据变换成三个字母的格式,最后一行从数据中除去三个斜杠.
最后得到这一行:Aug282000OUTYINNYOUTY和INNY字段是占位符,以后将被替换.
现在还不能确定它们,因为如果美元金额为负,将把OUTY和INNY设置成"misc"和"-",但是,如果美元金额为正,将分别把它们更改成"-"和"inco".
既然还没有读入美元金额,所以,需要暂时使用占位符.
细化现在进一步细化:1d/^^/ds/[[:cntrl:]]//g/^D/{s/^D\(.
*\)/\1\tOUTY\tINNY\t/s/^01/Jan/s/^02/Feb/s/^03/Mar/s/^04/Apr/s/^05/May/s/^06/Jun/s/^07/Jul/s/^08/Aug/s/^09/Sep/s/^10/Oct/s/^11/Nov/s/^12/Dec/s:2\1\3:NNNs/\nT\(.
*\)\nN\(.
*\)\nP\(.
*\)/NUM\2NUM\t\tY\t\t\3\tAMT\1AMT/s/NUMNUM/-/s/NUM\([0-9]*\)NUM/\1/s/\([0-9]\),/\1/}后七行有些复杂,所以将详细讨论它们.
首先,连续使用三个'N'命令.
'N'命令告诉sed将下一行读入输入中,然后将其附加到当前模式空间.
这三个'N'命令导致将下三行附加到当前模式空间缓冲区,现在这一行看起来如下:28Aug2000OUTYINNY\nT-8.
15\nN\nPCHECKCARDSUPERMARKETsed的模式空间变得很难看--需要除去额外的新行,并执行某些附加的格式化.
要这样做,将使用替代命令.
要匹配的模式为:'\nT.
*\nN.
*\nP.
*'这将与后面依次跟有'T'、零或多个字符、新行、'N'、任何数量的字符、新行、'P'、以及任何数量字符的新行匹配.
呀!
这个规则表达式将与刚刚附加到模式空间的三行的全部内容匹配.
但我们要重新格式化该区域,而不是整个替换它.
美元金额、支票号(如果有的话)和描述需要出现在替换字符串中.
要这样做,我们用带有反斜杠的圆括号括起那些"感兴趣部分",以便可以在替换字符串中引用它们(使用'\1'、'\2\和'\3'来告诉sed将它们插入到何处).
以下是最后的命令:s/\nT\(.
*\)\nN\(.
*\)\nP\(.
*\)/NUM\2NUM\t\tY\t\t\3\tAMT\1AMT/该命令将我们的行变换成:28Aug2000OUTYINNYNUMNUMYCHECKCARDSUPERMARKETAMT-8.
15AMT虽然该行正变得好一些,但是,有几件事一看就有点.
.
.
啊.
.
.
有趣.
首先是那个愚蠢的"NUMNUM"字符串--其目的何在如果查看sed脚本的后两行,就会发现其目的,后两行将把"NUMNUM"替换成"-",而把"NUM""NUM"替换成.
如您所见,用愚蠢的标记括起支票号允许我们在该字段为空时方便地插入一个"-".
结束尝试最后一行除去数字后的逗号.
它把如"3,231.
00"这样的美元金额转换成我使用的格式"3231.
00".
现在,让我们看一下最终脚本:最终的"QIF到文本"脚本1d/^^/ds/[[:cntrl:]]//g/^D/{s/^D\(.
*\)/\1\tOUTY\tINNY\t/s/^01/Jan/s/^02/Feb/s/^03/Mar/s/^04/Apr/s/^05/May/s/^06/Jun/s/^07/Jul/s/^08/Aug/s/^09/Sep/s/^10/Oct/s/^11/Nov/s/^12/Dec/s:2\1\3:NNNs/\nT\(.
*\)\nN\(.
*\)\nP\(.
*\)/NUM\2NUM\t\tY\t\t\3\tAMT\1AMT/s/NUMNUM/-/s/NUM\([0-9]*\)NUM/\1/s/\([0-9]\),/\1//AMT-[0-9]*.
[0-9]*AMT/bfixnegss/AMT\(.
*\)AMT/\1/s/OUTY/-/s/INNY/inco/bdone:fixnegss/AMT-\(.
*\)AMT/\1/s/OUTY/misc/s/INNY/-/:done}附加的十一行使用替代和一些分支功能来美化输出.
首先看一下这行:/AMT-[0-9]*.
[0-9]*AMT/bfixnegs该行包含一个格式为"/regexp/blabel"的分支命令.
如果模式空间与规则表达式匹配,sed将分支到fixnegs标号.
您应该可以轻易找到该标号,它在代码中为":fixnegs".
如果规则表达式不匹配,则以常规方式继续处理下一个命令.
既然您理解该命令本身的工作原理,让我们看一下分支.
如果看一下分支规则表达式,将看到它与后面依次跟有'-'、任意数量的数字、一个'.
'、任意数量的数字和'AMT'的字符串'AMT'匹配.
就象我确信您已猜到一样,该规则表达式专门处理负的美元金额.
在这之前,用'ATM'括起美元金额,以便以后可以轻易找到它.
因为规则表达式只与以'-'开始的美元金额匹配,所以,该分支只在恰巧处理借款时才发生.
如果正处理贷款,应该将OUTY设置成'misc',将INNY设置成'-',并且应该除去贷款数量前面的负号.
如果跟踪代码的流程,将看到实际情况正是这样.
如果不执行分支,则用'-'替换OUTY,用'inco'替换INNY.
完成了!
现在输出行是完美的:28Aug2000misc--YCHECKCARDSUPERMARKET-8.
15别犯糊涂如您所见,只要循序渐进地解决问题,使用sed转换数据就没有那么难.
不要试图使用一个sed命令或一下子解决所有问题.
相反,要朝着目标逐步进行,并不断改进sed脚本,直到其输出正如您希望那样为止.
sed有许多功能,希望您已非常熟悉其内部工作原理并继续努力以进一步掌握它!
参考资料阅读developerWorks上Daniel的前两篇sed文章:通用线程:sed实例,第1部分和第2部分.
查看EricPement极佳的sedfaq.
可以在ftp.
gnu.
org找到sed3.
02资源.
将在alpha.
gnu.
org找到很好的新的sed3.
02.
80.
另外,EricPement还有一些方便的sed单行程序,任何有抱负的sed高手都应该看一下.
如果想看好的老式书籍,O'Reilly的sed&awk,2ndEdition将是极佳选择.
可能想阅读7theditionUNIX'ssedmanpage(大概1978!
).
阅读FelixvonLeitner短小的sedtutorial.
在usingregularexpressions中复习,发现和修改这个免费dW独家教程文本中的模式.
关于作者DanielRobbins居住在新墨西哥州的Albuquerque.
他是GentooTechnologies,Inc.
的总裁兼CEO,GentooLinux(用于PC的高级Linux)和Portage系统(Linux的下一代端口系统)的创始人.
他还是Macmillan书籍CalderaOpenLinuxUnleashed、SuSELinuxUnleashed和SambaUnleashed的作者.
Daniel自小学二年级起就与计算机结下不解之缘,那时他首先接触的是Logo程序语言,并沉溺于Pac-Man游戏中.
这也许就是他至今仍担任SONYElectronicPublishing/Psygnosis的首席图形设计师的原因所在.
Daniel喜欢与妻子Mary和新出生的女儿Hadassah一起共度时光.
可通过drobbins@gentoo.
org与Daniel联系.
百纵科技官网:https://www.baizon.cn/百纵科技:美国云服务器活动重磅来袭,洛杉矶C3机房 带金盾高防,会员后台可自助管理防火墙,添加黑白名单 CC策略开启低中高.CPU全系列E52680v3 DDR4内存 三星固态盘列阵。另有高防清洗!美国洛杉矶 CN2 云服务器CPU内存带宽数据盘防御价格1H1G10M10G10G19元/月 购买地址2H1G10M10G10G29元/月 购买...
Virmach商家我们是不是比较熟悉?速度一般,但是人家价格低,而且机房是比较多的。早年的时候有帮助一个有做外贸也许需要多个机房且便宜服务商的时候接触到这个商家,有曾经帮助够买过上百台这样的低价机器。这里需要提醒的,便宜但是速度一般,尤其是中文业务速度确实不快,如果是外贸业务,那肯定是没有问题。这几天,我们有看到Virmach推出了夏季优惠促销,VPS首年8折,最低年付仅7.2美元,多机房可选,如...
酷番云怎么样?酷番云就不讲太多了,介绍过很多次,老牌商家完事,最近有不少小伙伴,一直问我台湾VPS,比较难找好的商家,台湾VPS本来就比较少,也介绍了不少商家,线路都不是很好,有些需求支持Windows是比较少的,这里我们就给大家测评下 酷番云的台湾VPS,支持多个版本Linux和Windows操作系统,提供了CN2线路,并且还是原生IP,更惊喜的是提供的是无限流量。有需求的可以试试。可以看到回程...
linux格式化命令为你推荐
易pc华硕易PC怎么样?性价比到底怎么样?在线漏洞检测网站好像有漏洞,直接看代码可以找出来吗?免费开通黄钻能免费开通黄钻吗??奇虎论坛奇虎问答是什么创维云电视功能创维健康云电视有什么功能?安装迅雷看看播放器迅雷看看不能播放,说我尚未安装迅雷看看播放器安装迅雷看看播放器迅雷看看播放器安装安全漏洞什么是安全漏洞攻击??域名库求解:请将您的域名:别名(CNAME)主机解析到idc1.xiaodoutao.com网站排名靠前全国B2B网站排名靠前的有哪些
vps优惠码 美国主机论坛 godaddy优惠码 创宇云 gg广告 促正网秒杀 流量计费 如何用qq邮箱发邮件 卡巴斯基免费试用 绍兴电信 卡巴斯基免费试用版 安徽双线服务器 根服务器 下载速度测试 西安主机 重庆联通服务器托管 学生机 winserver2008r2 此网页包含的内容将不使用安全的https 服务器是什么 更多