一个同学问我一个问题,说有以下文件内容,要求输出为特定的格式。这里就献丑给出一个处理的方法吧,由于时间关系可能我的答案并不是最好的,但是我尽量将我的答案讲解明白,让你理解处理的方法。如果您有简单明了的处理方法请不啬赐教!
题目
文件内容如下:
2016-12-08 00:09 血战钢锯岭
2016-12-08 03:01 你的名字
2016-12-08 04:00 长城
2016-12-08 04:01 萨利机长
2016-12-09 07:35 神奇动物在
2016-12-09 09:24 湄公河行动
2016-12-09 10:59 我不是潘金莲
2016-12-09 12:43 海洋奇缘
2016-12-09 14:29 神奇四侠 2015
2016-12-10 16:30 死侍
2016-12-10 16:31 加勒比海盗 5: 死
2016-12-10 16:36 三体
2016-12-10 18:04 阿凡达 2
2016-12-10 19:40 日落七次
要求输出结果为:
2016-12-08
00:09 血战钢锯岭
03:01 你的名字
04:00 长城
04:01 萨利机长
2016-12-09
07:35 神奇动物在
09:24 湄公河行动
10:59 我不是潘金莲
12:43 海洋奇缘
14:29 神奇四侠 2015
2016-12-10
16:30 死侍
16:31 加勒比海盗 5: 死
16:36 三体
18:04 阿凡达 2
19:40 日落七次
参考答案
看到题目后,发现文件内容中的规律,源文件主要由年月日、时分和电影名称组成三列,而目标文件年月日主要是去重,每个日期只出现了一次后换行,将当日要上映电影的时分和电影名称按行显示出来。
因为是文件处理,首先我想到了 sed 来处理, 使用 sed 加正则表达式将文件内容进行分组处理,然后去除重复的“年月日”,实现代码如下:
# sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})(\t)(.*)(\t)(.*)/\1\n\3\t\5/" file |sed -r ':1;N;s/^(\S+)((\n.*)*)\n\1$/\1\2/M;$!b1'2016-12-08
00:09 血战钢锯岭
03:01 你的名字
04:00 长城
04:01 萨利机长
2016-12-09
07:35 神奇动物在
09:24 湄公河行动
10:59 我不是潘金莲
12:43 海洋奇缘
14:29 神奇四侠 2015
2016-12-10
16:30 死侍
16:31 加勒比海盗 5: 死
16:36 三体
18:04 阿凡达 2
19:40 日落七次
答案剖析
首先,我们先看管道前的代码,主要是将“年月日”和“时分,电影名称”分成了两部分。我们看到 sed 的 -r 选项的意思是支持扩展的这规则表达式,类似 grep 的 -P 。其中 ([0-9]{4}-[0-9]{2}-[0-9]{2})(\t)(.*)(\t)(.*) 是正则表达式和分组。正则表达式是指用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串。就是用某种模式去匹配一类字符串的一个公式。 Sed 中的正则如下表所示:
^ 行的开始$
行的结尾
.
一个字符
*匹配 0 个或多个 * 前面的字符
[]方括号中的所有字符
\转义
{}
重复次数
()
分组,将匹配这个表达式的字符保存到一个临时区域(最多保存 9 个),它们可以用 \1 到 \9 来引用。
/./
匹配至少有一个字符
/../
匹配至少有两个字符的行
/^#/
匹配用 # 开头的行
/^$/
匹配空行
/}$/
匹配用 } 结尾的行 ( 没有空格在后面 )
/} *$/
匹配用 } 结尾的行 ( 可以有空格在后面 )
/[abc]/匹配小写的 a 或 b 或 c
/^[^abc]/匹配开头不是小写的 a 或 b 或 c
匹配该题目的正则表达式的含义如下:
([0-9]{4}-[0-9]{2}-[0-9]{2}) :匹配数字 0-9 重复 4 次,匹配年;匹配数字 0-9 重复 2 次匹配月和日,并使用 () 分组(\t) :匹配制表符,如果你使用的空格分隔,直接匹配空格,可以使用 (\ |\t) 匹配空格或者制表符
(.*) :匹配任意字符
接下来解释演示 /\1\n\3\t\5/ 的含义,为了减少篇幅,我复制一份文件名为 file1 ,内容如下:
# cat file1
2016-12-08 00:09 血战钢锯岭
2016-12-08 03:01 你的名字
2016-12-09 10:59 我不是潘金莲
执行如下命令,结果表明 \1 代表分组 1 ,自然 \3 和 \5 代表分组 3 和 5
# sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})(\ |\t)(.*)(\ |\t)(.*)/\1/" file12016-12-08
2016-12-08
2016-12-09
那么 \n 代表什么呢?我们在现有命令上加上 /n ,结果表明 \n 代表换行 , 如下所示:
# sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})(\ |\t)(.*)(\ |\t)(.*)/\1\n/" file12016-12-08
2016-12-08
2016-12-09
\t 不用解释了吧,完整的执行以下如下所示:
# sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})(\ |\t)(.*)(\ |\t)(.*)/\1\n\3\t\5/" file12016-12-08
00:09 血战钢锯岭
2016-12-08
03:01 你的名字
2016-12-09
10:59 我不是潘金莲
# sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})(\ |\t)(.*)(\ |\t)(.*)/\1\n\3\t\5/" file1 >>file2继续看管道后的命令的含义,之后的命令信息量有点稍微大。
# sed -r ':1;N;s/^(\S+)((\n.*)*)\n\1$/\1\2/M;$!b1' file2
2016-12-08
00:09 血战钢锯岭
03:01 你的名字
2016-12-09
10:59 我不是潘金莲
主要有多行模式空间的操作命令 N 、 D 、 P 和 sed 脚本流程控制命令 b 、 t 。
多行模式空间 (MultilinePattern Space) :就是在模式空间中放置输入文件的多个行内容,操作多行模式空间的有 N