一个同学问我一个问题,说有以下文件内容,要求输出为特定的格式。这里就献丑给出一个处理的方法吧,由于时间关系可能我的答案并不是最好的,但是我尽量将我的答案讲解明白,让你理解处理的方法。如果您有简单明了的处理方法请不啬赐教!

题目

文件内容如下:

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        海洋奇缘

2016-12-10

16:30        死侍

16:31        加勒比海盗5:死

16:36        三体

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/" file1

2016-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/" file1

2016-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/" file1

2016-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、D、P含义如下:

·N命令:是将下一行也输入到模式空间中,当前行与下一行之间插入一个’\n’,以下为示意图

·D命令:仅删除Multiline Space中第一个’\n’之前的内容,如上图,即删除“The UnixOperating System”,而“Is A interestingSystem”仍然存在。同时,它使得脚本的控制流转到脚本文件的第一行,跳过该命令的后续命令。

·P命令:仅打印Multiline Space中第一个’\n’之前的内容,如上图,即仅打印“The UnixOperating System”。

我们看下N的范例,还是使用file1文件中的内容,命令执行结果如下:

# sed ‘N’ file1

2016-12-08       00:09        血战钢锯岭

2016-12-08       03:01        你的名字

2016-12-09       10:59        我不是潘金莲

#

看到结果后,有同学会说,这和Sed不加任何的选项和命令,执行的结果相同。如下所示:

# sed '' file1

2016-12-08       00:09        血战钢锯岭

2016-12-08       03:01        你的名字

2016-12-09       10:59        我不是潘金莲

#

使用肉眼咋一看真发现不了区别,首先我们回顾下sed的工作过程:

sed会先读取文本中的第一行,到模式空间,然后执行sed命令,处理完成后,将结果发送到屏幕上。sed每处理完一行就将其从模式空间中删除,接着会读取文本中的第二行,到模式空间,然后执行sed命令,处理完成后,将结果发送到屏幕上。重复此过程,直到文本中的最后一行,sed便结束运行。

了解sed的工作原理,我们发现没有使用N命令时候,sed依次将文本中的行读取到模式空间中,sed没有做任何的命令操作,他就直接显示到屏幕上了。

当使用N命令后,sed执行过程是sed会先读取文本中的第一行 “2016-12-08  00:09  血战钢锯岭$”到模式空间,然后执行sed命令N,模式空间中的第一行内容后追加第二行内容生成多行模式空间的第一行内容,多行模式空间变为“2016-12-08  00:09  血战钢锯岭\n2016-12-08  03:01  你的名字$”,处理完成后,将结果发送到屏幕上。sed继续向模式空间读取下一行内容,本例中就是第三行,然后再次追加下一行内容,生成多行模式空间中的第二行内容,以此类推。由于本例中第三行下没有内容,这时候执行N命令后就不会生成多行模式空间的第二行内容,所以模式空间中有“2016-12-09         10:59        我不是潘金莲”。处理完成后,将结果发送到屏幕上。

结果表明,我们使用N命令后,前两行输出的是多行模式空间的内容,最后一行是模式空间的内容。由于多行模式空间合并的第一行和第二行之间有\n,所以看到输出的格式没有变化,为了证明这个说法,我们将\n 替换成空格,如下所示:

# sed 'N;s/\n/ /' file1

2016-12-08       00:09        血战钢锯岭 2016-12-08         03:01        你的名字

2016-12-09       10:59        我不是潘金莲

#

通常,sed是将编辑命令从上到下依次应用到读入的行上,N命令能够在一定程序上改变默认的执行流程,甚至利用N命令可以形成一个强大的循环处理流程。除此之外,其实sed还提供了分支命令(b)和测试(test)两个命令来控制流程,这两个命令可以跳转到指定的标签(label)位置继续执行命令。标签是以冒号开头的标记,标签名称可以自定义。例如:定义一个名称为:label标签,如下所示:

:label

command1

/pattern/b label

command2

当执行到/pattern/b top时,如果匹配pattern,则跳转到:label标签所在的位置,继续执行下一个命令command1。

上面的例子用到了分支命令,分支命令的跳转是无条件的。而与之相对的是测试命令,测试命令的跳转是有条件的,当且仅当当前行发生成功的替换时才跳转。

为了明白测试命令的用法,我们用它来实现file1中的内容:

# sed  -e ':1;s/2016/2017/;t1;'  file1

2017-12-08       00:09        血战钢锯岭

2017-12-08       03:01        你的名字

2017-12-09       10:59        我不是潘金莲

#

我们定义了一个标签为:1,然后在最后利用测试命令跳转到该标签。可能,你会觉得这里也可以使用分支命令,但是事实上分支命令会导致死循环,因为在它里他没有结束的条件。

但是测试命令就不同了,这一点直到最后才体现出来。当最后一行被s/2016/2017/命令读入之后,2016替换成2017,此时ta继续跳转到最开头,因为模式空间中的2016已经全部被替换成2017,所以替换也不会发生。之前我们说过,当且仅当当前行发生成功的替换时测试命令才跳转。所以此时跳转不会发生,退出sed命令。

到此,你能看明白后半句的意思吗?欢迎留言!

欢迎使用微信关注“运维爱好者”公共号,第一时间了解本博客动态!

shell问题(转)的更多相关文章

  1. Shell替换

    如果表达式中包含特殊字符,Shell 将会进行替换.例如,在双引号中使用变量就是一种替换,转义字符也是一种替换. #!/bin/bash a= echo -e "Value of a is ...

  2. Shell特殊变量

    $ 表示当前Shell进程的ID,即pid $echo $$ 运行结果 特殊变量列表 变量 含义 $0 当前脚本的文件名 $n 传递给脚本或函数的参数.n 是一个数字,表示第几个参数.例如,第一个参数 ...

  3. shell变量

    定义变量 定义变量时,变量名不加美元符号($),如: variableName="value" 注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样.同时,变量名 ...

  4. 第一个shell脚本

    打开文本编辑器,新建一个文件,扩展名为sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好. #!/bin/bash echo "Hello World !" &quo ...

  5. shell简介

    Shell作为命令语言,它交互式地解释和执行用户输入的命令:作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支. shell使用的熟练程度反映了用户对U ...

  6. Shell碎碎念

    1. 字符串如何大小写转换 str="This is a Bash Shell script." 1> tr方式 newstr=`tr '[A-Z]' '[a-z]' < ...

  7. MongoDB学习笔记二—Shell操作

    数据类型 MongoDB在保留JSON基本键/值对特性的基础上,添加了其他一些数据类型. null null用于表示空值或者不存在的字段:{“x”:null} 布尔型 布尔类型有两个值true和fal ...

  8. 使用C#给Linux写Shell脚本

    在这个逼格决定人格,鄙视链盛行的年头,尤其是咱们IT界,请问您今天鄙视与被鄙视的次数分别是多少?如果手中没有一点压箱的本事,那就只有看的份了.今天我们也要提升下自己的格调,学习些脑洞大开的东西,学完之 ...

  9. Linux环境下shell和vim中乱码原因及消除办法

    shell和vim中乱码原因及消除办法 作者:Jack47 在Linux下开发,经常遇到乱码问题:shell或者vim中显示不了中文,或者能够显示,但不能输入中文.每次都是上网去搜,或者同事告诉我一些 ...

  10. 【说解】在shell中通过mkfifo创建命名管道来控制多个进程并发执行

    背景: 工作中有两个异地机房需要传数据,数据全名很规范,在某个目录下命名为统一的前缀加上编号.如/path/from/file.{1..100}.而机房间的专线对单个scp进程的传输速度是有限制的,比 ...

随机推荐

  1. 能源项目xml文件 -- springMVC-servlet.xml -- default-lazy-init

    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w ...

  2. Centos6 使用yum安装 mysql 5.7

    直接使用yum安装默认安装的是mysql 5.1版本,要想安装mysql 5.7 需要设置yum源 1.检查系统默认mysql,并删除 yum list installed | grep mysql ...

  3. JavaScipt的秘密花园

    对象使用和属性 JavaScript 中所有变量都可以当作对象使用,除了两个例外 null 和undefined. false.toString(); // 'false'[1, 2, 3].toSt ...

  4. poj1192 最优连通子集(树形dp)

    题目链接:poj1192 最优连通子集 求一棵无向树的最大子树和..类似于求最大子段和的办法,树形dp. dp[i][0]:以i为根,不包括 i 结点的子树最大权 dp[i][1]:以i为根,包括 i ...

  5. mysql分组查询取分组后各分组中的最新一条记录

    SELECT * FROM ( SELECT * FROM `CFG_LOGIN_LOG` ORDER BY LOGTIME DESC ) test GROUP BY login_name DESC

  6. 【转载】 ionic 的 下拉刷新 与 上拉加载

    这篇文章是讲解 Ioinc中怎么实现 下拉刷新和上拉加载的.也是我们日常做项目是必不可少的功能.有兴趣的小伙伴可以来学习一下. 更多关于 IONIC 的资源: http://www.aliyue.ne ...

  7. 省常中模拟 day2

    第一题: 题目大意: 有mn颗糖,要装进k个盒子里,使得既可以平均分给n个人,也可以平均分给m个人. 求k的最小值. 解题过程: 1.先看一组小数据(13,21).那么根据贪心的原则很容易想到先拿13 ...

  8. ASP.NET Web API 入门示例详解

    REST服务已经成为最新的服务端开发趋势,ASP.NET Web API即为.NET平台的一种轻量级REST架构. ASP.NET Web API直接借鉴了ASP.NET MVC的设计,两者具有非常类 ...

  9. spark0.9.1 assembly build-RedHat6.4 YARN 2.2.0

    1. Install git on RedHat6.4: 1.1. setup your local yum repo 1.2. yum install git 2. Install JDK and ...

  10. 设置ubuntu12.04桌面版开机进入命令行模式

    1)命令:sudo gedit /etc/default/grub 找到GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" 将"quite ...