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


前言

这章主要讨论的是编写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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D48D3dyO-1667636453441)(file://C:\Users\g700382\AppData\Roaming\marktext\images\2022-11-04-17-24-35-image.png)]

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车供需的开头与结尾处。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Shell脚本学习指南是一本介绍Shell脚本编程的书籍,其涵盖了Shell脚本的基础语法、控制结构、变量和数组、函数、正则表达式、文本处理、调试技巧等内容。这本书还介绍了常见的Unix/Linux命令,如grep、awk、sed等,并给出了一些例子来演示这些命令的使用方法。这本书适合那些想要学习Shell脚本编程的初学者,也适合那些已经有一些编程经验的人。如果您想学习Shell脚本编程,这本书是一个不错的选择。 是的,您是正确的。我的回答是关于《Shell脚本学习指南》这本书的介绍。它是一本介绍Shell脚本编程的书籍,适合初学者和有一些编程经验的人学习。该书涵盖了Shell脚本的基础语法、控制结构、变量和数组、函数、正则表达式、文本处理、调试技巧等内容,并介绍了常见的Unix/Linux命令和它们的使用方法。如果您想学习Shell脚本编程,这本书是一个不错的选择。好的,以下是关于Shell脚本学习的指南: Shell是一种命令行解释器,它可以解释执行用户输入的命令并与操作系统进行交互。Shell脚本则是由一系列Shell命令组成的脚本文件,可以实现批量处理、自动化脚本等功能。学习Shell脚本可以提高工作效率,尤其是在Linux和Unix系统下。 以下是Shell脚本学习的指南: 1. 学习Shell语法和基本命令。了解Shell的语法、变量、循环、条件判断、函数等基本语法结构,同时掌握常用的Shell命令,例如ls、cd、mkdir、rm、cp等。 2. 编写简单的Shell脚本。通过编写一些简单的脚本,例如输出hello world、实现文件的复制、批量处理文件等,来熟悉Shell脚本的语法和命令。 3. 学习Shell脚本高级特性。掌握Shell脚本的高级特性,例如正则表达式、awk、sed、grep等,可以让脚本更加强大和灵活。 4. 掌握Shell脚本调试技巧。学会使用调试工具和技巧,例如set -x、set -e、调试器等,可以快速定位脚本中的错误并进行调试。 5. 学习Shell脚本编程风格和规范。编写规范的Shell脚本可以提高代码的可读性、可维护性和可重用性,例如变量命名规范、缩进风格、注释规范等。 6. 练习实战项目。通过编写一些实际的项目,例如备份脚本、日志分析脚本、系统监控脚本等,来提高自己的Shell脚本编程水平。 希望这些指南对您学习Shell脚本有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jacky~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值