用awk命令快速分割文本
Linux 04月 13th, 2009下了一堆小说,每部都是一个txt文件,放到手机上看不方便,需要把它们按章节分割开来,文件名要是章节名称,不但要文件名是章节名称,而且要每部小说分别放到同名的文件夹里去。手动分割是不可能的,太麻烦了。程序员嘛,就要编程来解决。
看起来难度挺高的,所以我准备写脚本来解决了,开始尝试用grep、do、while、read、sed判断每行内容分别处理,之前折腾合并字幕时,就觉得这样很慢很没效率,那是觉得awk相当的强悍,折腾了半天,查了无数次手册,最后还是一句awk命令解决。
注意:因为WPMU有讨厌的引号转换问题,目前无法解决,所以下面有几个命令因引号被转换成中文引号而失效,点击这里查看脚本原文
用vim解决
很久之前我是用vim来搞定的,就一句命令,够快了吧,但我还是觉得不够快,因为这个方式是不断查找另存的动作,所以分割时vim画面不断闪动,看起来挺酷的,一部《三国演义》要几秒钟,不过我还是想静默执行。当时我就用vim分别打开十几个文件来分割,画面闪动消耗的时间还是有点不爽,分割后还是需要手动移动到新目录里去,所以这个方法我已经放弃了。
测试例子
还是用之前的三国演义测试文本来做例子吧,复制下面内容到文件名“test.txt”,这个文本要按章节分割开来
前言
文字文字文字文字文字文字
文字文字文字文字文字文字
文字文字文字文字文字文字
第一回 宴桃园英雄三结义 斩黄巾英雄首立功
卓问三人现居何职。玄德曰:“白身。”卓甚轻之,不为礼。玄德出,张飞大怒曰:“我等亲赴血战,救了这厮,他却如此无礼。若不杀之,难消我气!”便要提刀入帐来杀董卓。正是:人情势利古犹今,谁识英雄是白身?安得快人如翼德,尽诛世上负心人!毕竟董卓性命如何,且听下文分解。第二回 张翼德怒鞭督邮 何国舅谋诛宦竖
主薄陈琳曰:“不可!俗云:掩目而捕燕雀,是自欺也,微物尚不可欺以得志,况国家大事乎?今将军仗皇威,掌兵要,龙骧虎步,高下在心:若欲诛宦官,如鼓洪炉燎毛发耳。但当速发雷霆,行权立断,则天人顺之。却反外檄大臣,临犯京阙,英雄聚会,各怀一心:所谓倒持干戈,授人以柄,功必不成,反生乱矣。”何进笑曰:“此懦夫之见也!”傍边一人鼓掌大笑曰:“此事易如反掌,何必多议!”视之,乃曹操也。正是:欲除君侧宵人乱,须听朝中智士谋。不知曹操说出甚话来,且听下文分解。第三回 议温明董卓叱丁原 馈金珠李肃说吕布
李儒劝卓早定废立之计。卓乃于省中设宴,会集公卿,令吕布将甲士千余,侍卫左右。是日,太傅袁隗与百官皆到。酒行数巡,卓按剑曰“今上暗弱,不可以奉宗庙;吾将依伊尹、霍光故事,废帝为弘农王,立陈留王为帝。有不从者斩!”群臣惶怖莫敢对。中军校尉袁绍挺身出曰:“今上即位未几,并无失德;汝欲废嫡立庶,非反而何?”卓怒曰:“天下事在我!我今为之,谁敢不从!汝视我之剑不利否?”袁绍亦拔剑曰:“汝剑利,吾剑未尝不利!”两个在筵上对敌。正是:丁原仗义身先丧,袁绍争锋势又危。毕竟袁绍性命如何,且听下文分解。第四回 废汉帝陈留践位 谋董贼孟德献刀
当夜,行数里,月明中敲开客店门投宿。喂饱了马,曹操先睡。陈宫寻思:“我将谓曹操是好人,弃官跟他;原来是个狼心之徒!今日留之,必为后患。”便欲拔剑来杀曹操。正是:设心狠毒非良士,操卓原来一路人。毕竟曹操性命如何,且听下文分解。第五回 发矫诏诸镇应曹公 破关兵三英战吕布
三人围绕战多时,遮拦架隔无休歇。喊声震动天地翻,杀气迷漫牛斗寒。吕布力穷寻走路,遥望家山拍马还。倒拖画杆方天戟,乱散销金五彩幡。顿断绒绦走赤兔,翻身飞上虎牢关。”三人直赶吕布到关下,看见关上西风飘动青罗伞盖。张飞大叫:“此必董卓!追吕布有甚强处?不如先拿董贼,便是斩草除根!”拍马上关,来擒董卓。正是:擒贼定须擒贼首,奇功端的待奇人。未知胜负如何,且听下文分解。第六回 焚金阙董卓行凶 匿玉玺孙坚背约
表曰:“汝若要我听信,将随军行李,任我搜看。”坚怒曰:“汝有何力,敢小觑我!”方欲交兵,刘表便退。坚纵马赶去,两山后伏兵齐起,背后蔡瑁、蒯越赶来,将孙坚困在垓心。正是:玉玺得来无用处,反因此宝动刀兵。毕竟孙坚怎地脱身,且听下文分解。
验证章节标题
因为有些小说未必严格按照章节标题单独站一行,可能把后边的段落连在章节标题后面了,等下分割时就会把文件名弄乱,所以要先验证一下,假设这里正则表达式为"^ *第.+回"
grep -n '^ *第.\+回' ./*.txt
这条命令就输出小说的每个章节标题了,显示那个文件那一行,如果有错,用文本编辑器把有错的改好,通常就是几个。
用awk按章节分割
我是要按开头“第XX回”来分割,用如下awk命令
awk '{if ($0 ~ "^ *第.+回") {cpt=$0} {print($0) > cpt".txt"}}' test.txt
运行后就在当前目录下看到分割好后的文本,跟vim的一样,没错,就这么简单,速度飞快,说一下原理吧
- 遇到符合正则表达式的那行,把这样存到cpt变量了,cpt是章节英文“Chapter”的简写
- 把每一行内容追加到由cpt变量构成的文件名(也就是最近的章节里)。这里的重定向符用“>”,没错的,不要以为输出结果是最后一行,是全部内容的。
{if ($0 ~ "^ *第.+回") {cpt=$0}
{print($0) > cpt".txt"}}
-
保留前言
-
去掉文件名前空格
-
输出数字前缀
-
格式化文件名
-
分割文本放入到新目录
其实上面的命令是有bug的,前言部分给忽略掉了,因为awk在遇到第一个“第XX回”时cpt变量才有值,才开始输出到文件,可以修复嘛?当然可以,给awk加个参数“-vcpt="前言"”,“-v”声明变量,事先把cpt变量赋值,这样就有输出了
awk -vcpt="前言" '{if ($0 ~ "^ *第.+回") {cpt=$0} {print($0) > cpt".txt"}}' test.txt
上面的命令输出的文件名前是空格,有点不爽,虽然也可以再用脚本处理一下,也可以在awk里搞定,把“cpt=$0”换成“cpt=gensub("^ +","","g")”,三个参数分别是:查找的正则表达式,替换内容、替换全部。其实还有第四个参数,要处理的字符串,如果是“$0”的话可以省略。
awk -vcpt="前言" '{if ($0 ~ "^ *第.+回") {cpt=gensub("^ +","","g")} {print($0) > cpt".txt"}}' test.txt
分割出来的文件名是确实是“第XX回”,不过XX是中文数字,排起序来不爽,加个阿拉伯数字前缀方便排序,添加一个变量i,把“cpt".txt"”重定向文件换成“"["i"]"cpt".txt"”这样就带方括号的数字前缀
awk -vi=0 -vcpt="前言" '{if ($0 ~ "^ *第.+回") {i++; cpt=gensub("^ +","","g")} {print($0) > "["i"]"cpt".txt"}}' test.txt
上边的文件件名还不够完美,输出的个位数字前缀是“[1]”、“[2]”,但我需要的是“[01]”,“[02]”,也就是个位补零,把“"["i"]"cpt".txt"”换成“sprintf("[%02d]%s.txt",i,cpt)”,类似C语言的字符串格式化
awk -vi=0 -vcpt="前言" '{if ($0 ~ "^ *第.+回") {i++; cpt=gensub("^ +","","g")} {print($0) > sprintf("[%02d]%s.txt",i,cpt)}}' test.txt
上面的命令是把分割文本输出到当前文件夹,搞定后还要收工移动到新目录,批处理起来很麻烦,下面几条命令执行这一过程,建立同名目录,分割文本,输出到同名目录,删除原来的文件。
i=test.txt
fd=${i%.txt} && mkdir $fd
awk -vfd=$fd -vi=0 -vcpt="前言" '{if ($0 ~ "^ *第.+回") {i++; cpt=gensub("^ +","","g")} {print($0) > sprintf("%s/[%02d]%s.txt",fd,i,cpt)}}' $i
rm $i
批量处理
搞定上面步骤后,开始批处理了,假设当前文件夹几十个待分割txt,章节题目统一,现在要按章节分割,每个txt单独一个输出文件夹,搞定后删除原来的txt,就这么一条命令
for i in *;do fd=${i%.txt} && mkdir $fd && awk -vfd=$fd -vi=0 -vcpt="前言" '{if ($0 ~ "^ *第.+回") {i++; cpt=gensub("^ +","","g")} {print($0) > sprintf("%s/[%02d]%s.txt",fd,i,cpt)}}' $i && rm $i; done
花了1秒钟就搞定几十部小说了,awk太强悍了。虽然是一条命令,不过这条命令太长了,迟点再把上边几种情况写成脚本,方便按需调用。

04月 13th, 2009 at 5:49 pm
你手机多半是 Windows mobile 那样的烂系统,要是 s60 或者 palm,看几兆的 txt 都不会有问题
04月 13th, 2009 at 6:49 pm
我的是比S60更烂的S40V3,-_-#
04月 13th, 2009 at 9:23 pm
拜啊。。。
04月 14th, 2009 at 8:53 pm
哈哈,我的手机(摩托罗拉e680i)有busybox提供的awk和sed,爽吧。我看书就用opera(手机默认的浏览器),把小说转成html,就可以设置背景颜色和字体颜色了。
诺基亚的手机软件确实多。
04月 15th, 2009 at 9:59 am
@chenfengyuan
摩托罗拉e680i啊,当时就就嫌这型号摄像头像素太低,就买了诺基亚的6131,看我同学玩模拟器玩得爽,现在那个后悔啊。
11月 4th, 2009 at 3:00 pm
[...] 提示:这个方法现在看起来有点笨,用awk处理速度更快 [...]