文章目录

  • 前言
  • 查找文本
    • 简单的grep
  • 正则表达式
      • 什么是正则表达式
      • POSIX方括号表达式
    • 基本正则表达式
      • 匹配单个字符
      • 后向引用
      • 单个表达式匹配多字符
      • 文本匹配锚点
      • BRE运算符优先级
      • 匹配单个字符
    • 正则表达式的扩展
    • 程序与正则表达式
  • 在文本文件里进行替换
    • 基本用法
    • 替换细节
    • sed运作
    • 打印与否
    • 匹配特定行
  • 字段处理
    • 使用cut选定字段
    • 使用join连接字段
    • 使用awk重新编排
      • 模式与操作
      • 字段
      • 设置字段与分割字符
      • 打印行
      • 起始与清除

前言

这章主要讨论的是编写Shell脚本时经常用到的两个基本操作:文本查找——寻找含特定文本的行,文本替换——更换找到的文本。

虽然你可以使用简单的固定文本字符串完成很多工作,但是正则表达式能够提供更强大的标记法,以单个表达式匹配各种实际的文本段。本章会介绍两种由不同的UNIX程序所提供的正则表达式风格,然后进一步介绍提取文本与重新编排文本的几个重要工具。

查找文本

grep程序查找文本是相当方便的。在POSIX系统上,grep可以在两种正则表达式风格中选择一种,或是执行简单的字符串匹配。

传统上,有三种程序,可以用来查找整个文本:

  • grep

    • 最早的文本匹配程序。使用POSIX定义的基本正则表达式,后面会介绍
  • egrep

    • 扩展grep。这个程序使用扩展正则表达式——这是一套功能更强大的正则表达式,使用它的代价就是会耗掉更多的资源。
  • fgrep

    • 快速grep。这个版本匹配固定字符串而非正则表达式,它使用优化的算法,能更有效的匹配固定字符串。最初的版本,也是唯一可以并行地匹配多个字符串的版本;也就是说,grep和qgrep只能匹配单个正则表达式;而fgrep使用不同的算法,却能匹配多个字符串,有效地测试每个输入行里,是否有匹配的查找字符串。

POSIX标准将这三个改版整合一个grep程序,它的行为是通过不同的选项加以控制。POSIX版本可以匹配多个模式——不管是BRE还是ERE。fgrep与egrep两者还是可用,只是标记为不推荐使用,即他们有可能在往后的标准里删除。

简单的grep

grep最简单的用法就是使用固定字符串:

上面的例子中使用-F选项,以查找固定字符串pts。事实上,只要匹配的模式里未含有正则表达式的meta字符,则grep默认行为模式就是等同于使用了-F,即带-F是默认行为。

正则表达式

本节提供有关正则表达式构造与匹配方式的概述。特别是会体积POSIX BRE与ERE构造,因为他们想要将大部分UNIx工具的两种正则表达式基本风格加以正式化。

什么是正则表达式

正则表达式是一种表示方式,让你可以查找匹配特定准则的文本,比如,以字母a开头。此表示法让你可以写一个表达式,选定或匹配多个数据字符串。

除了传统的UNIX正则表达式表示法之外,POSIX表达式还可以做到:

  • 编写正则表达式,它表示特定于local的字符序列顺序和等价字符。

  • 编写正则表达式,而不必关心底层系统的字符集是什么。

很多的UNIX工具程序沿用某一种正则表达式形式来强化本身的功能。这里列举一部分例子:

  • 用来寻找匹配文本行的grep工具族:grep与egrep,以及非标准但很好用的agrep工具。

  • 用来改变输入流的sed流编辑器

  • 字符串处理程序语言,例如awk、Icon、Perl、Python、Ruby,Tcl。

  • 文件查看程序,例如more,page与pg,都常出现在商用UNIX系统上,另外广受欢迎的less分页程序。

  • 文本编辑器,例如历史悠久的ed行编辑器,标准的vi屏幕编辑器器,还有一些插件(add-on)编辑器,例如emacs、jed、jove、vile、vim等。

从根本上来看,正则表达式是由两个基本组成部分所建立:一般字符与特殊字符。一般字符指的是任何没有特殊意义的字符,正如下表所的定义的。在某些情况下,特殊字符也可以视为一般字符。特殊字符常被称为元字符(后面以meta字符表示)。

范例

POSIX方括号表达式

为配合非英语的环境,POSIX标准强化其字符集范围的能力,以匹配非英文字母字符。

我们早先看到的范围表达式在UNIX里通常称为字符集,在POSIX的标准下,现在叫做方括号表达式在方括号表达式里,除了字面上的字符(例如z、;等等)之外,另外额外的组成部分,包括:

  • 字符集

    • 以[:与:]将关键字组合括起来的POSIX字符集。关键字描述各种不同的字符集,例如英文字母字符、控制字符等。
  • 排序符号

    • 排序符号指的是将多字符序列视为一个单位。它使用[.与.]将字符组合括起来。排序符号在系统所使用的特定locale上各有其定义。
  • 等价字符集

    • 等价字符集列出的是应视为等值的一组字符。它由取自locale的名字元素组成,以[= 与=]括住。

这三种构造都必须使用方括号表达式。例如[[:alpha:]!]匹配任一英文字母字符或惊叹号。

POSIX字符集

基本正则表达式

BRE 是由多个组成部分所构建,一开始提供数种匹配单个字符的方式,而后又结合额外的meta字符,进行多字符匹配。

匹配单个字符

最先开始是匹配单个字符。可采用集中方式做到:以一般字符、以转移字符的meta字符、以点号.meta字符,或是方括号表达式

排序(collating)是指基于成组的项目排序顺序的操作。一个POSIX的排序元素由当前locale中的元素名称组成,并由[.与.]括起来。假定[.ch.]是存在的,那么正则表达式[ab[.ch.]de]则匹配字符a、b、d或e,或者是成对的ch;而单独的c或h字符则不匹配。

后向引用

BRE提供一种叫后向引用的机制,指的是“匹配于正则表达式匹配的先前的部分”。使用后向引用的步骤有两个。第一步是将子表达式包围在\(与\)里;单个模式里可包括至多9个子表达式,且可为嵌套结构。

下一步是在同一模式之后使用\digit,digit指的是介于1至9的数字,指的是“匹配于第n个先前括号内子表达式匹配成功的字符”。举例如下:

后向引用在寻找重复字以及匹配引号时特别好用:

单个表达式匹配多字符

最常用的修饰符为星号*,表示“匹配0个或多个前面的单个字符”。因此,ab*c表示的是“匹配1个a、0或多个b字符以及a,c”。这个正则表达式匹配的有ac、abc、abbc、abbbc等。

文本匹配锚点

^$它们叫做锚点,因为其用途在限制正则表达式匹配时,针对要被匹配字符串的开始或结尾处进行匹配(^在此处的用法和方括号的用法是不同的)。

正则表达式内锚点的范例

假定现在有一串要进行匹配的字:abcABCdefDEF

BRE运算符优先级

由高到低


扩展正则表达式

ERE(Extend Regular Expressions)的含义就如同其名字所示:拥有比基本正则表达式更多的功能。BRE与ERE在大多数meta字符与功能应用上几乎是一致的,但ERE里面有写meta字符看起来与BRE类似,却具有完全不同的意义。

匹配单个字符

本质上与BRE是一致的。较有名的一个例外出现在awk里:其\符号在方括号表达式内表示其他的含义。因此,如需匹配方括号、连字符、右方括号或是反斜号,你应该使用[\[\-\]\\]。这是使用上的经验法则。

正则表达式的扩展

很多程序提供正则表达式语法扩展。这类扩展大多采取反斜杠加一个字符,以形成新的运算符。类似POSIX BRE里\(…)与\{…}的反斜杠。

最常见的扩展为\ <与\ >运算符,分别匹配“单词(word)”的开头与结尾。单词是由字母、数字及下划线组成的。我们称这类字符为单词组成。

单词的开头要么出现在行起始处,要么出现在第一个后面紧跟一个非单词组字符的单词组成字符。同样的,单词的结尾要么出现在一行的结尾处,要么出现在一个非单词组成字符之前的最后一个单词组成字符。同样的,单词的结尾要么出现在一行的结尾处,要么出现在一个非单词组成字符之前的最后一个单词组成字符。

实际上,单词的匹配其实相当直接易懂。正则表达式\<chop匹配于use chopsticks但是eat a lambchop则不匹配;同样的chop\>则匹配于第二个字符串,第一个则不匹配。需要特注意的是,在\<chop\>表达式下,两个字符串都不匹配。

额外的GNU正则表达式运算符

程序与正则表达式

UNIX程序及其正则表达式类型

lex是一个很特别的工具,通常用于处理语言处理器中的词法分析器的构建。虽然已纳入POSIX,但我们不会在这里进一步讨论,因为它与Shell脚本无关。lesspg虽然是POSIX的一部分,但是它们也支持正则表达式。有些系统会有page程序,它本质上和more是相同的,只是在每个充满屏幕的输出画面之间,会清楚屏幕。

在文本文件里进行替换

很多Shell脚本的工作都从通过grepegrep取出所需的文本开始。正则表达式查找的最初结果,往往就成了要拿来作进一步处理的“原始数据”。通常,文本替换至少需要做一件事,就是将一些字以另一些字取代,或是删除匹配行的某个部分。

一般来说,执行文本替换的正确程序应该是sed——流编辑器(Stream Editor)。sed的设计就是用来以批处理的方式而不是交互的方式来编辑文件。当你知道要做好几个变更——不管是对一个还是数个文件时,比较简单的方式是将这些变更部分写到一个编辑中的脚本中,再将此脚本应用到所有必须修改的文件。sed存在的目的就在这里(虽然你也可以使用ed或ex编辑脚本,但是它们来处理会比较麻烦,而且用户通常不会记得要存储原先的文件)。

基本用法

你可能会常在管道中使用sed,以执行替换操作。做法是使用s命令——要求正则表达式寻找,用替换文本替换匹配的文本,以及可选用的标志:

sed 's/:.*//' /etc/passwd | sort -u
#删除第一个冒号之后的所有东西排序列表并删除重复部分

在这里,/字符扮演定界符的角色,从而分割正则表达式与替换文本。在本例中,替换文本是空的,实际上会有效的删除匹配的文本。虽然/是最常用的定界符,但任何可显示的字符都能作为定界符。在处理文件名称时,通常都会以标点符号字符作为定界符(例如分号、冒号或逗号):

find /home/tolstoy -type d -print |   # 寻找所有目录
sed 's;/home/tolstoy/;/home/lt/;' |   #修改名称;注意:这里使用分号作为定界符
sed 's/^/mkdir /'                 |   #插入mkdir命令
sh -x                                 #以Shell跟踪模式执行

上述脚本是将/home/tolstoy目录结构建立一份副本在/home/lt下(可能是为备份而做的准备。通过find命令产生的内容通过sed命令将/home/tolstoy替换为/home/lt并在其前方添加mkdir 。最后以跟踪模式指定这些命令。

替换细节

先前已经提过,除斜杠外还可以使用其他任意字符作为定界符;在正则表达式或替代文本里,也能转定定界符,不过这么做可能会让命令变得很难看懂:

sed 's/\/home/\tolstoy\//\/home\/lt\//'

前面已经说明后向引用在正则表达式里的用法。sed了解后向引用,而且它们还能用于替代文本,以表示“从这里开始替换匹配第n个圆括号里子表达式的文本”:

$echo /home/tolstoy/ | sed 's;\(/home\)/tolstoy/;\1/lt/;'/home/lt

sed将\1替代为匹配于正则表达式的/home部分。在这里的例子中,所有的字符都表示它自己,不过,任何正则表达式都可括在\\(与\)之间,且后向引用最多用到9个。

sed运作

sed的工作方式相当直接。命令行上的文件名会一次打开与读取。如果没有文件,则使用标准输入,文件名“_”(单个破折号)可用于表示标准输入。

sed读取每个文件,一次读一行,将读取的行放到内存的一个区域——称之为模式空间。这就像程序语言的变量一样:内存的一个区域在编辑命令的指示下可以修改,所有编辑上的操作都会应用到模式空间的内容。当所有操作完成后,sed会将模式空间的最后内容打印到标准输出,再回到开始处,读取另一个输入行。

这一工作过程如下图所示。脚本使用两条命令,将The UNIX System替代为The UNIX Operating System。

打印与否

-n选项修改了sed的默认行为。当提佛那个此选项时,sed将不会在操作完成后打印模式空间的最后内容。反之,若在脚本里使用p,则会明白地将此行显示出来。举例来说,我们可以这样模拟grep

sed -n '/<HTML>/p' *.html 仅显示这行

虽然例子很简单,但这个功能在复杂的脚本里非常好用。如果你使用一个脚本文件,可通过特殊的首行来打开此功能:

#n     关闭自动打印
/<HTML>/p     仅打印含<HTML>的行

在Shell脚本中,与很多其他UNIX脚本语言一样:#是注释的意思。sed注释必须出现在单独的行里,因为它们是语法型命令,意思是:它们是什么事也不做的命令。虽然POSIX指出,注释可以放在脚本里的任何位置,但很多旧版sed仅允许出现在首行,GNU sed则无此限制。

匹配特定行

如前所述,sed默认地会将每一个编辑命令应用到每个输入行。而现在我们要告诉你的是:还可以限制一条命令要应用到那些行,只要在命令前置一个地址即可。因此,sed命令的完整形式是:

address command

例子:使用sed命令的head命令

# head ---打印前n行
#
# 语法: head N file
count=$1
sed ${count}q "$2"

当你引用head 10 foo.xml之后,sed会转换成sed 10q foo.xml。q命令要求sed马上离开;不要读取其他输入,或是执行任何命令。

迄今为止,我们看到的都是sed以/字符隔开模式以便查找。在这里,我们要告诉你如何在模式内使用不同的定界符:者通过在字符前面加上一个反斜线实现:

$ grep tolstoy /etc/passwd   # 显示原始行$ sed -n '\:tolstoy: s;;Tolstoy;p' /etc/passwd #改变定界符

本例中,以冒号隔开要查找的模式,而分好则扮演s命令的定界符角色。

字段处理

很多的应用程序,会将数据是为记录与字段的结合,以便于处理。一条记录指的是相关信息的单个集合,例如以企业来说,记录可能含有顾客,供应商以及员工等数据,以学校来说,则可能有学生数据。而字段指的就是记录的组成部分,例如姓、名或者街道地址。

使用cut选定字段

cut命令是用来剪下文本文件里的数据,文本文件可以是字段类型或是字符类型。后一种数据类型在遇到需要从文件里剪下特定的列时,特别方便。请注意:一个制表符在此被视为单个字符。

举例来说,下面的命令可显示系统上每个用户的登录名称及其全名:

通过选择其他字段编号,还可以取出每个用户的根目录:

通过字段列表做剪下操作有时很方便的。例如,你只要取出命令ls -l的输出结果中的文件权限字段:

使用join连接字段

join命令可以将多个文件结合在一起,每个文件里的每条记录,都共享一个键值,键值指的是记录中的主字段,通常会是用户名称,个人姓氏、员工编号之类的数据。举例来说,有两个文件,一个列出所有业务员销售业绩,一个列出每个业务员应实现的业绩:

例子:

每条记录都有两个字段:业务员的名字与相对应的量。在本例中,列与列之间有多个空白,从而可以排列整齐。

为了让join运作得到正确结果,输入文件必须完成排序。程序merge-sales.sh即为使用join结合两个文件。

#! /bin/sh
# merge-sales.sh
#
# 结合配额与业务员数据# 删除注释并排序数据文件
sed '/^#/d' quotas | sort > qotas.sorted
sed '/^#/d' sales | sort > sales.sorted
# 以第一个键值作为结合,将结果产生至标准输出
join quotas.sorted sales.sorted

使用awk重新编排

awk本身所提供的功能完备,已经是一个很好用的程序语言了。后面会详细介绍该语言的精髓。虽然awk能做的事很多,但它主要设计的是要在Shell脚本中发挥所长:做一些简易的文本处理,例如取出字段并重新编排这一类。

模式与操作

awk的基本模式不同于大多数的程序语言。他其实比较类似于sed

awk 'program' [file...]

awk读取命令行上所指定的各个文件(若无,则为标准输入),一次读取一条记录(行)。再针对每一行,应用程序所指定的命令。awk程序基本框架为:

pattern {action}
pattern {action}

pattern部分几乎可以是任何表达式,但是在单命令行程序里,它通常是由斜杆括起来的ERE。action是任意的awk语句,但是在单命令行程序里,通常是一个直接明了的print语句。

pattern或是action都能省略。省略pattern,则会对每一条输入执行action;省略action则等同{print},将打显示整条记录。

对于每条记录来说,awk会测试程序里的每个pattern。若模式为真(例如某条记录匹配于某正则表达式,或是一般表达式计算为真),则awk便执行action内的程序代码。

字段

awk设计的号宗地按就在字段与记录上:awk读取输入记录(通常是一些行),然后自动将各个记录切分为字段。awk将每条记录内的字段数目,存储到内建变量NF。

默认以空白分割字段——例如空格与制表符(两者混用),像json一样。

awk特别之处就是:也可以设置它为一个完整的ERE,这种情况下,每个匹配在该ERE的文本都将视为字段分割字符。如需字段值,则是搭配$字符。通常$之后会接着一个数值常数,也可能是接着一个表达式,不过多半是使用变量名称。举例如下:

设置字段与分割字符

在一些简单程序中,你可以使用-F选项修改字段分割字符。例如,显示/etc/passwd文件里的用户名称与全名,你可以:

-F选项会自动设置FS变量。请注意,程序不比直接参照FS变量,也不用必须管理读取的记录并将它们分割为字段:awk会自动完成这些事。

你可能已经发现,每个输出字段是以一个空格来分隔的——即便是输入字段的分割字符为冒号。awk的输入、输出分隔字符用法是分开的,这点与其他工具程序不同。也就是说,必须设置OFS变量,改变输出字段分隔字符。方式是在命令行里使用-v选项,这会设置awk变量。其值可以是任意的字符串。例如:

打印行

就像我们已经介绍过的:大多数时候,你只是想把选定的字段显示出来,或者重新安排其顺序。简单的打印可使用print语句做到,只要提供给它需要打印的字段列表、变量或字符串即可:

简单明了的print语句,如果没有任何参数,则等同于print $0,即显示整条记录。

注意:awkprintprintf的区别就是print会自动在输出末尾加上\nprintf需要程序员自己手动添加。

起始与清除

BEGIN与END这两个特殊的“模式”,它们提供awk程序起始(startup)与清除(cleanup)操作。常见与大型awk程序中,且通常写在个别文件里,而不是命令行上:

BEGIN {起始操作程序代码(start code)}
pattern1 {action1}
pattern2 {action2}
END   {清除操作程序代码{cleanup code}}

BEGIN与END的语句块是可选的。如需设置,习惯上他们应分别至于awk车供需的开头与结尾处。

Shell脚本学习指南(二)——查找与替换相关推荐

  1. linux 脚本 查找替换,Shell脚本学习指南之查找与替换介绍

    3.1 查找文本 grep:使用POSIX定义的基本正则表达式(BRE). egrep:使用扩展正则表达式(ERE). fgrep:快速grep.使用优化的算法,匹配固定字符串而非正则表达式. 199 ...

  2. linux shell脚本学习指南,shell脚本学习指南[二](Arnold Robbins Nelson H.F. Beebe著)

    该进入第四章了,刚才看到一个帖子标题:我空有一身泡妞的好本领,但可惜自己是个妞.汗-这个...音乐无国界嘛,这个不应该也没性别界么? 第四章文本处理工具 书中先说明了以下排序的规则,数值的就不用说了, ...

  3. Shell脚本学习-阶段二

    文章目录-Shell脚本学习阶段二 前言 shell脚本实操2 1.获取随机字符串或数字 2.定义一个颜色输出字符串函数 3.批量创建用户 4.检查软件包是否安装 5.检查服务状态 6.检查主机存活状 ...

  4. Shell脚本学习指南-查找与替换篇

    一.查找与替换 编写 Shell脚本时经常用到的两个基本操作: 1.文本查找 (searching) - 寻找含有特定文本的行 2.文本替换(substitution)- 更换找到的文本 可以使用固定 ...

  5. Shell脚本学习-阶段二十七-命令解释三

    文章目录 前言 quyotastats repquota convertquota swapoff swapon sync chroot getent last lastb lastlog logro ...

  6. Shell脚本学习-阶段二十七-命令解释二

    文章目录-命令解释二 前言 emacs jed joe nano================ pico sed=================== vi,vim============ mtyp ...

  7. Shell脚本学习指南(三)——文本处理工具

    文章目录 排序文本 行的排序 以字段的排序 文本块排序 sort的效率 sort的稳定性 sort小结 删除重复 重新格式化段落 计算行数.字数以及字符数 打印 打印技术的演化 其他打印软件 提取开头 ...

  8. Shell脚本学习-阶段二十七-命令解释一

    文章目录-命令解释一 前言 as expr gcc gcov gdb indent ld ldconfig ldd make mktemp nm objdump perl php protoize u ...

  9. Shell脚本学习指南(六)——输入/输出、文件与命令执行

    文章目录 前言 标准输入.标准输出与标准错误输出 使用read读取行 关于重定向 额外的重定向运算符 文件描述符处理 printf的完整介绍 波浪号展开与通配符 波浪号展开 使用通配符 命令替换 为h ...

最新文章

  1. oracle 10g安装
  2. prometheus之docker监控与告警系列(二)
  3. (转)腾讯2011.10.15校园招聘会笔试题
  4. 把控站外seo效果的几个操作点
  5. python true_True关键字,带Python示例
  6. php 列表 单击事件,首页gt; PHPgt;如何添加点击事件到jstree的(jQuery插件)异步列表?...
  7. RSS Feed Generator for PHP (兼有podcast rss - iTunes )
  8. toad如何查看表字段备注(表字段的说明)
  9. 春节传统元素素材,帮助设计师完成中国风海报设计
  10. Spring Board View
  11. Ubuntu20.04安装 Redis 并配置 phpRedisAdmin
  12. python教学笔记_python学习笔记(一)
  13. 嵌入式Linux移植实验
  14. USTC高级软件工程课程学习心得
  15. FutureMapping:空间人工智能的计算结构
  16. .NET连接IMB DB2数据库的一些问题及最终完美解决方案!
  17. 怎么样用阿里云RDS数据库
  18. FLUENT仿真精典案例#351-螺旋槽干气密封仿真
  19. Redis开发设计规范及案例分析
  20. 掘金 Web 沸点优化 1.1 版本上线啦~

热门文章

  1. 【每日英文】2021.8.14
  2. Plink GWAS学习笔记
  3. tagVARIANT、VARIANT、_variant_t和COleVariant
  4. lyrebird 一分钟模仿你的声音
  5. 状态机StateMachine使用小记
  6. u盘中毒了格式化有用吗 u盘中毒了文件被隐藏了怎么办恢复
  7. Linux 中改变主机名的 4 种方法
  8. 2020年有效的rtsp流媒体测试地址整理汇总
  9. CocosCreator-Label
  10. Invalid Provisioning Profile