首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

通用线程 -- sed 实例,第 3 部分

通用线程 -- sed 实例,第 3 部分

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




在该脚本中,'$' 规则表达式将与行的末尾匹配,而 '\r' 告诉 sed 在其之前插入一个回车。在换行之前插入回车,立即,每一行就以 CR/LF 结束。请注意,仅当使用 GNU sed 3.02.80 或以后的版本时,才会用 CR 替换 '\r'。如果还没有安装 GNU sed 3.02.80,请在我的 中查看如何这样做的说明。      
我已记不清有多少次在下载一些示例脚本或 C 代码之后,却发现它是 DOS/Windows 格式。虽然很多程序不在乎 DOS/Windows 格式的 CR/LF 文本文件,但是有几个程序却在乎 -- 最著名的是 bash,只要一遇到回车,它就会出问题。以下 sed 调用将把 DOS/Windows 格式的文本转换成可信赖的 UNIX 格式:
1
$ sed -e 's/.$//' mydos.txt > myunix.txt




该脚本的工作原理很简单:替代规则表达式与一行的最末字符匹配,而该字符恰好就是回车。我们用空字符替换它,从而将其从输出中彻底删除。如果使用该脚本并注意到已经删除了输出中每行的最末字符,那么,您就指定了已经是 UNIX 格式的文本文件。也就没必要那样做了!
反转行下面是另一个方便的小脚本。与大多数 Linux 发行版中包括的 "tac" 命令一样,该脚本将反转文件中行的次序。"tac" 这个名称可能会给人以误导,因为 "tac" 不反转行中字符的位置(左和右),而是反转文件中行的位置(上和下)。用 "tac" 处理以下文件:
1
foo bar oni




....将产生以下输出:
1
oni bar foo




可以用以下 sed 脚本达到相同目的:
1
$ 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 执行一些强大的数据转换。
sed QIF 魔法过去几个星期,我一直想买一份 来结算我的银行帐户。Quicken 是一个非常好的金融程序,当然会成功地完成这项工作。但是,经过考虑之后,我觉得自己可以轻易编写某个软件来结算我的支票簿。我想,毕竟,我是个软件开发人员!      
我开发了一个很好的小型支票簿结算程序(使用 awk),它通过分析包含我的所有交易的文本文件的语法来计算余额。略微调整之后,我将其改进,以便可以象 Quicken 那样跟踪不同的贷款和借款类别。但是,我还要添加一个特性。最近,我将帐户转移到一家有联机 Web 帐户界面的银行。有一天,我注意到,这家银行的 Web 站点允许以 Quicken 的 .QIF 格式下载我的帐户信息。我马上觉得,如果可以将该信息转换成文本格式,那就太棒了。
两种格式的故事在查看 QIF 格式之前,先看一下我的 checkbook.txt 格式:
1
2
28 Aug 2000     food    -       -       Y     Supermarket            
30.94 25 Aug 2000     watr    -       103     Y     Check 103          52.86




在我的文件中,所有字段都由一个或多个制表符分开,每个交易占据一行。日期之后的下一个字段列出支出类型(如果是收入项,则为 "-")。第三个字段列出收入类型(如果是支出项,则为 "-")。然后,是一个支票号字段(如果为空,则还是 "-"),一个交易完成字段("Y" 或 "N"),一个注释和一个美元金额字段。现在,让我们看一下 QIF 格式。当用文本查看器查看下载的 QIF 文件时,它看起来如下:
1
2
3
!Type:Bank D08/28/2000 T-8.15 N PCHECKCARD SUPERMARKET ^
D08/28/2000 T-8.25 N PCHECKCARD PUNJAB RESTAURANT ^
D08/28/2000 T-17.17 N PCHECKCARD SUPERMARKET




浏览过文件之后,不难猜出其格式 -- 忽略第一行,其余的格式如下:
1
2
3
4
5
D<数据>  
T<交易量>  
N<支票号>  
P<描述>  
^   (这是字段分隔符)




开始处理在处理象这样重要的 sed 项目时,不要气馁 -- sed 允许您将数据逐渐修改成最终形式。在进行当中,可以继续细化 sed 脚本,直到输出与预期的完全一样为止。无需在试第一次时就保证其完全正确。
要开始,首先创建一个名为 "qiftrans.sed" 的文件,然后开始修改数据:
1
1d /^^/d s/[[:cntrl:]]//g




第一个 '1d' 命令删除第一行,第二个命令从输出除去那些讨厌的 '^' 字符。最后一行除去文件中可能存在的任何控制字符。既然在处理外来文件格式,我想消除在中途遇到任何控制字符的风险。到目前为止,一切顺利。现在,要向该基本脚本中添加一些处理功能:
1
2
3
4
5
6
7
8
9
1d /^^/d s/[[: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 将这样一行读入其模式空间时,将按顺序执行花括号中的所有命令。
花括号中的第一个命令将把如下行:
1
D08/28/2000




变换成:
1
08/28/2000 OUTY    INNY




当然,现在的格式还不完美,但没关系。我们将在进行过程中逐渐细化模式空间的内容。后面 12 行的最后效果是将数据变换成三个字母的格式,最后一行从数据中除去三个斜杠。最后得到这一行:
1
Aug 28 2000    OUTY    INNY




OUTY 和 INNY 字段是占位符,以后将被替换。现在还不能确定它们,因为如果美元金额为负,将把 OUTY 和 INNY 设置成 "misc" 和 "-",但是,如果美元金额为正,将分别把它们更改成 "-" 和 "inco"。既然还没有读入美元金额,所以,需要暂时使用占位符。
细化现在进一步细化:
1
2
3
4
5
6
7
8
9
10
11
12
13
1d  /^^/d s/[[: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:
        N          N          N         
        s/\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' 命令导致将下三行附加到当前模式空间缓冲区,现在这一行看起来如下:      
1
28 Aug 2000    OUTY    INNY    \nT-8.15\nN\nPCHECKCARD SUPERMARKET




sed 的模式空间变得很难看 -- 需要除去额外的新行,并执行某些附加的格式化。要这样做,将使用替代命令。要匹配的模式为:
1
'\nT.*\nN.*\nP.*'




这将与后面依次跟有 'T'、零或多个字符、新行、'N'、任何数量的字符、新行、'P'、以及任何数量字符的新行匹配。呀!这个规则表达式将与刚刚附加到模式空间的三行的全部内容匹配。但我们要重新格式化该区域,而不是整个替换它。美元金额、支票号(如果有的话)和描述需要出现在替换字符串中。要这样做,我们用带有反斜杠的圆括号括起那些“感兴趣部分”,以便可以在替换字符串中引用它们(使用 '\1'、'\2\ 和 '\3' 来告诉 sed 将它们插入到何处)。以下是最后的命令:
1
s/\nT\(.*\)\nN\(.*\)\nP\(.*\)/NUM\2NUM\t\tY\t\t\3\tAMT\1AMT/




该命令将我们的行变换成:
1
28 Aug 2000  OUTY  INNY  NUMNUM    Y     CHECKCARD SUPERMARKET     AMT-8.15AMT




虽然该行正变得好一些,但是,有几件事一看就有点...啊...有趣。首先是那个愚蠢的 "NUMNUM" 字符串 -- 其目的何在?如果查看 sed 脚本的后两行,就会发现其目的,后两行将把 "NUMNUM" 替换成 "-",而把 "NUM"<number>"NUM" 替换成 <number>。如您所见,用愚蠢的标记括起支票号允许我们在该字段为空时方便地插入一个 "-"。



返回列表