分享

提取数据grep, cut, awk, sed, join, paste

 dwlinux_gs 2014-12-25

有关从标准 Linux 文件中提取宝贵数据片段的几点建议

Linux
操作系统由许多文件组成。其中包括配置设置文件、文本文件、文档文件、日志文件等等。通常,这些文件包含为查找重要数据而需要访问的信息。尽管您可以轻松
地使用标准实用程序(如 cat、more 等)将大多数文件的内容打印到屏幕,但某些实用程序更适合于分析相关值。

本文介绍了 grepcut
paste、join、sed
awk
等实用程序,但它并不是仅仅重复参考手册页以及介绍每个实用程序存在的原因。相反,我们将首先了解每个实用程序及其选项,将该实用程序应用于 Linux
文件中所通用的冒号分隔的文件格式,然后了解每个工具为什么以及如何用于从文件中提取数据。

grep
的强大功能

名称 grep 是 Global Regular Expression
Print(全局正则表达式打印)的缩写,它实际上源自下面这个用于打印与指定模式匹配的所有行的命令:

g/re/p

其中,“re”表示“正则表达式”。自从 UNIX 及其各种同类产品(包括
Linux)推出以来,grep 衍生了两个其他变体:fgrep(固定 grep)和 egrep(扩展 grep)。fgrep 实用程序只搜索固定字符串,而 egrep 因采用了一个更复杂的内部算法而提供了更多功能。

在了解 grep、fgrepegrep 的功能之前,必须先了解正则表达式的概念及其用法。

使用正则表达式。就其最简
单的形式而言,正则表达式是用于在文件中定位文本的搜索条件。例如,要查找所有包含单词“add”的行,您可以搜索“add”—这样,“add”便构成了
一个正则表达式。如果不但要查找该单词,而且还要将其更改为“sum”,则可以在实用程序中提供相应的命令将“add”替换为“sum”—这
样,“add”和“sum”便构成了正则表达式。

问题是“addition”将更改为“sumition”,“address”将更改为
“sumress”。要解决此问题,可以指定仅当某些项两侧各包含一个空格时才替换这些项—“ add ”变为“ sum
”。该方法在大多数情况下均有效,仅当“add”是某一行的第一个或最后一个单词(意味着它的两侧没有所需的空格来实现匹配)时,才会出现问题。

使用正则表达式可以增强灵活性。例如,如果只希望在“add”是一行中的前三个字符的情况下进行更
改,则可以使用插入符号 (^) 表示行首:“^add ”。相反,如果只希望在指定的字母是一行中的后三个字母时进行更改,则可以使用美元符号 ($)
表示行尾:“ add$”。

以下是正则表达式遵循的基本规则:

  • 任何单个字符或字符序列可用于与自身进行匹配,如以上的“add”示例所示。

  • 插入符号表示行首;美元符号表示行尾。

  • 要按字面搜索一行中的特殊字符(如美元符号或插入符号),请在这些字符前加上一个反斜杠
    (\)。例如,“\$”搜索“$”而不是行尾。

  • 句点 (.)
    表示任何单个字符。例如,“uu3..2”表示六个字符项,其中的前三个字符为“uu3”,最后一个字符为“2”。中间的两个字符可以是任意字符,但个数
    只能为两个。

  • 如果正则表达式包含在斜线中(如“/re/”),则将按从头到尾的顺序搜索文件。如果它
    括在问号中(“?re?”),则按反向搜索文件。

  • 方括号 ([]) 可用于表示多个值,而减号 (-)
    则表示介于两个值之间的值。例如,[0-9] 与 [0123456789] 相同,而 [a-z] 与
    [abcdefghijklmnopqrstuvwxyz] 相同。如果列表的第一个字符是插入符号,则它匹配列表之外的任何字符。下表总结了这些匹配:

    表达式匹配

    示例说明
    [abc]匹配 a、b、c 中的一个
    [a-z]匹配 a 到 z 中的任意一个小写字母
    [A-Z]匹配 A 到 Z 中的任意一个大写字母
    [0-9]匹配 0 到 9 中的任意一个数字
    [^0-9]从 0 到 9 的任意字符
    [-0-9]从 0 到 9 的任意字符或“-”
    [0-9-]从 0 到 9 或“-”中的任意字符
    [^-0-9]除 0 到 9 的和“-”外的任意字符
    [a-zA-Z0-9]匹配任意字母字符或数字字符

grep 一样看世界。有许多工具与 grep 实用程序相似,如大多数其他操作系统中的 find 命令或一些编程语言中的 instr
命令。它用于在文件内查找与指定模式匹配的项。

为了解释其用法和结果,我们需要一个用来处理的核心文件。下框代表一个名为 MEMO
的文件,将用于多个示例中:

TO: All Employees

FROM: Human Resources

Dist. Via:


73945REARICKRALPH

73947KELLEYPHIL

73949DUGANGEORGE

73950WAGNEREVAN

73951BOWLGORDON

73952PALMERSCOTT

73954MAYJAN

73955HARRISSHANNON

73956BOYCEWILLIAM

73958BRUMMERRICHARD

73960WADDYRICHARD

73962CHEESMANBOB

In order to better serve the needs
of our mass market customers, Alden Publishing is integrating the
groups selling to this channel for Alden General Reference and Alden
Computer Publishing.This change will allow us to better coordinate our
selling and marketing efforts, as well as simplify Alden’s relationships
with these customers in the areas of customer service, co-op
management, and credit and collection.Two national account managers,
Susan April and Bob Cheesman, have joined the sales team as result of
these changes.

To achieve this goal, we have also
organized the new mass sales group into three distinct teams reporting
to our current sales directors, Phil Kelley and Ralph Rearick.I have
outlined below the national account managers and their respective
accounts in each of the teams.We have also hired two new national
account managers and a new sales administrator to complete our account
coverage.They include

Jan May, who joins us from Acme
Consumer Electronics as a national account manager covering traditional
mass merchants

Richard Waddy, who comes to us via
PepsiCo and Proctor & Gamble and will be responsible for managing
our West Coast territory

Shannon Harris, who will become an
account administrator for our warehouse clubs business and joins us
from Westvaco’s Envelope division

Please join me in welcoming each
of our new team members.

作为一个简单的示例,要查找包含单词“welcoming”的行,最好的方法是

$ grep welcoming MEMOPlease join me in welcoming each of our new team members. $

如果要查找单词“market”,结果只略有不同:

$ grep market MEMO In order to better serve the needs of the mass market customers, Alden Publishing is integrating This change will allow us to better coordinate our selling and marketing efforts, as well as $

grep
实用程序在一个文件(或多个文件)的每一行中搜索给定字符串的第一个匹配项。如果找到该字符串,则打印此行;否则不打印此行。以这种方式,在请求的
“market”和“marketing”之间找到匹配项。如果该文档中存在“marketable”、“marketed”、“markets”以及其
他类似单词,则还将找到这些单词的匹配项。可以使用通配符和元字符,强烈建议将这些字符放在引号中,以便 shell 不会将其当作命令。

要查找所有包含数字的行,请使用以下方法:

$ grep "[0-9]" MEMO73945   REARICK RALPH73947   KELLEY PHIL    73949   DUGAN GEORGE73950   WAGNER EVAN    73951   BOWL GORDON73952   PALMER SCOTT73954   MAY JAN      73955   HARRIS SHANNON 73956   BOYCE WILLIAM 73958   BRUMMER RICHARD 73960   WADDY RICHARD 73962   CHEESMAN BOB      $

要查找所有包含“the”的行,请使用以下方法:

$ grep the MEMOIn order to better serve the needs of the mass market customers,Alden Publishing is integratingthe groups selling to this channel for Alden General Reference andAlden Computer Publishing. simplify Alden's relationships with these customers in the areasof customer service, and Bob Cheesman have joined the sales team as result of thesechanges.To achieve this goal, we have also organized the new mass salesgroup into three distinct teamsthe national account managers and their respective accounts ineach of the teams.We have also$

找到了六行,其中一行实际上包含单词“these”,余下几行包含单词“the”的精确匹配。grep 实用程序(几乎与其他每个
UNIX/Linux 实用程序一样)区分大小写,这意味着查找“The”(而不是“the”)将产生完全不同的结果:

$ grep The MEMOcoverage.They include:$

如果不考虑文件中使用的大小写而要查找某个特定词组,则可以采用下面两种方法。第一种方法是使用方
括号查找“The”和“the”:

$ grep "[T,t]he" MEMOIn order to better serve the needs of the mass market customers,Alden Publishing is integratingthe groups selling to this channel for Alden General Reference andAlden Computer Publishing. simplify Alden's relationships with these customers in the areasof customer service, and Bob Cheesman have joined the sales team as result of thesechanges.To achieve this goal, we have also organized the new mass salesgroup into three distinct teamsthe national account managers and their respective accounts ineach of the teams.We have alsocoverage.They include: $

第二种方法是使用“-i”选项,该选项指示 grep 不区分大小写:

$ grep -i the MEMOIn order to better serve the needs of the mass market customers,Alden Publishing is integratingthe groups selling to this channel for Alden General Reference andAlden Computer Publishing. simplify Alden's relationships with these customers in the areasof customer service, and Bob Cheesman have joined the sales team as result of thesechanges.To achieve this goal, we have also organized the new mass salesgroup into three distinct teamsthe national account managers and their respective accounts ineach of the teams.We have alsocoverage.They include: $

除“-i”以外,您还可以使用几个其他选项来更改输出。最相关的选项包括:

  • “-c”—抑制标准输出;相反,打印每个输入文件的匹配行数。

  • “-l”—抑制标准输出;相反,打印通常从中打印输出的每个输入文件的名称。

  • “-n”—使用其输入文件中的行号为输出的每一行加上前缀。

  • “-v”—颠倒匹配的含义,选择不匹配的行。

使用 fgrep
egrepfgrep 实用程序也可以称作快速 grep 和固定 grep。同 grep
一样,它在文件中搜索字符串,并打印所有包含该字符串的行。与 grep
不同的是,fgrep
搜索字符串,而不是搜索与表达式匹配的模式。可以将 fgrep 看作是具有几个增强功能的 grep

  • 您可以同时搜索多个对象。

  • fgrep
    实用程序通常要比 grep 快很多。

  • 您不能使用 fgrep 搜索带有模式的正则表达式。

例如,假设您要从前面的 MEMO
文件中找出大写名称。要查找“HARRIS”和“BOB”,必须发出两个不同的 grep 命令:

$ grep HARRIS MEMO73955   HARRIS SHANNON $

$ grep BOB MEMO73962 CHEESMAN BOB $

您可以只使用一个 fgrep 命令来完成同一操作:

$ fgrep "HARRIS> BOB" MEMO73955   HARRIS SHANNON 73962   CHEESMAN BOB      $

注意,在各项之间需要有一个回车符。如果没有回车符,搜索将在每行上查找与“HARRIS
BOB”匹配的包含 10
个字母的字符串。如果有回车符,则查找“HARRIS”的匹配项和“BOB”的匹配项。还应注意,必须在目标文本两侧使用引号。这样做是为了区分文本和文
件名。

您不必在命令行上指定搜索项,而是可以将其置于文件中并使用该文件的内容搜索其他文件。使用 -f
选项,该技术使您可以创建一个包含您经常搜索的名称或数字的主文件,并重复使用它:

$ cat  pull_listHARRISBOB$

$ fgrep -f pull_list MEMO73955 HARRIS SHANNON 73962 CHEESMAN BOB $

同其前辈 grepfgrep 一样,egrep用于在文件中搜索指定文本的匹配项。从本质上而言,您可以将它看作是 grep
的一个更强大的版本,通过它一次可以搜索多个对象。要搜索的对象由回车符分隔(与 fgrep 一样)或由管道符号 (|) 分隔:

$ egrep "HARRIS> BOB" MEMO73955   HARRIS SHANNON 73962   CHEESMAN BOB      $

$ egrep "HARRIS|BOB" MEMO73955 HARRIS SHANNON 73962 CHEESMAN BOB $

除了能够搜索多个对象以外,egrep 提供的 grep 所不具备的功能还包括能够搜索重复项和组:

搜索前面字符的零个或一个重复项
+ 搜索前面字符的一个或多个重复项
( ) 表示一个组。

例如,假设您不知道 Jan 的姓是“May”还是“Mays”:

$ egrep "MAYS?"MEMO73954   MAY  JAN      $

该方法查找“MAY”和“MAYS”的匹配项,而

$ egrep "HARRIS+" MEMO73955   HARRIS SHANNON $

匹配“HARRIS”、“HARRISS”、“HARRISSS”,依此类推。如果要搜索某个单词
及其可能的派生词,则将派生词的特征字符括在括号中:

$ egrep -i "electron(ic)?s" MEMOJan May, who joins us from Acme Consumer Electronics as a National Account Manager$

该方法查找“electrons”和“electronics”的匹配项。因此,后跟“+”的正则
表达式查找此正则表达式的一个或多个匹配项,而后跟“?”的正则表达式查找此正则表达式的零个或一个匹配项。由“|”或回车符分隔的多个正则表达式查找由
其中任意表达式所查找的字符串,且正则表达式可以用括号 ( ) 进行分组。可以使用的参数有 -c、
-f、-i、-l、-n
-v

实例。grep 实用程序系列可以用于任何文本格式的系统文件,在其行中查找匹配项。例如,要在 /etc/passwd
文件中查找任何名为 Kristin 的用户项,可以使用以下方法:

$ grep kristin /etc/passwdkristin:petKv.fLWG/Ig:506:100:kristin dulaney:/home/kristin:/bin/bashkscott:#Jknidies^v:610:100:kristin scott:/home/kscott:/bin/bash$

注意:本文的很多示例都使用了
/etc/passwd,这是因为它代表了通用冒号分隔文件。与其相关的规则可以适用于任何类似文件。

由于它在行中的任意位置搜索匹配项,因此它将找到 Kristin Dulaney 和
Kristin Scott 两项。如果只希望搜索用户名(第一个域)为 kristin 的项,则可以按如下所示修改此命令:

$ grep "^kristin" /etc/passwd kristin:petKv.fLWG/Ig:506:100:kristindulaney:/home/kristin:/bin/bash$

使用 cut、paste
join

cut 实用程序能够将可能构成数据域的列与文件分离。默认分隔符为制表符,-f
选项用于指定所需的域。例如,如果名为“august”的文本文件包含三列,如下所示:

one two  threefour five  sixseven eight  nineten eleven twelve

则命令

$ cut -f2 august

将返回

twofiveeighteleven

$ cut -f1,3 august

将返回相反的结果:

one threefour sixseven nineten twelve

该命令有多个可用选项。除 -f 以外的两个比较常见的选项是

-c—用于指定字符而非域
-d—用于指定除制表符以外的分隔

其他 cut 选项。 ls -l
命令显示权限、链接数、所有者、组、大小、日期和文件名—以上所有内容均由空格分隔,并在权限与链接之间包含两个字符。如果您只想知道谁将文件保存在目录
中,而不关注其他数据,则可以使用

$ ls -l | cut -d" " -f5

该命令忽略权限(第一个域)、两组空格(第二和第三个域)以及链接数(第四个域)。随后,它将显示
所有者(第五个域),并忽略其后的所有内容。获得此结果的另一种方法是使用 ls -l,权限通常由 10 个字符组成,其中三个 3 字符空格,其后是链接数和空格。所有者的名称通常从显示的第 16
个字符开始,并延续整个名称的长度。命令

$ ls -l | cut -c16

返回第 16
个字符,即所有者名称的第一个字母。如果我们假设大多数用户将使用八个或八个以下的字符表示其名称,则命令

$ ls -l | cut -c16-24

将返回名称域中的那些项。

文件名从第 55
个字符开始,但您无法确定该字符之后的字符数,这是因为某些文件名要比其他文件名长很多。解决该问题的方法是从第 55
个字符开始,但不指定结束字符(这意味着将占用行的全部剩余位置),如下所示:

$ ls -l | cut -c55-

cut
实用程序从文件中提取域,然后可以使用 pastejoin 合并这些域。这两个实用程序中,paste 相对比较简单;它只从源文件中提取一行,并将其与从其他源文件中提取的另一行合并。例如,如果第一个文件的内容为

IndianapolisColumbusPeoriaLivingstonScottsdale

第二个文件的内容为

IndianaOhioIllinoisMontanaArizona

则以下内容(包括提示)将为生成的结果:

$ paste fileone filetwoIndianapolis IndianaColumbus OhioPeoria  IllinoisLivingston MontanaScottsdale Arizona$

如果第一个文件中的行数比第二个文件多,则只粘贴制表符之后的空白项。制表符是默认分隔符,但可以
使用 -d
选项将其更改为其他分隔符:

$ paste -d", " fileone filetwoIndianapolis, IndianaColumbus, OhioPeoria, IllinoisLivingston, MontanaScotttsdale, Arizona$

也可以使用 -s 选项将第一个文件的所有数据输出到一行,其后依次是一个回车符和第二个文件:

$ paste -s fileone filetwoIndianapolis Columbus Peoria Livingston Scotttsdale Indiana Ohio Illinois Montana Arizona$

可以将 join 实用程序看作是 paste 的显著增强版本。但该实用程序只有在所联接的文件存在一个公共域时才起作用,这一点非常重要。例如,如果像前面 paste 示例中那样使用 join,则结果将如下所示:

$ join fileone filetwo$

换言之,不显示任何内容。join
实用程序必须在所针对的两个文件中找到一个公共域,并且在默认情况下,它要求此公共域是第一个域。例如,假设第一个文件现在包含以下项:

11111Indianapolis
22222Columbus
33333Peoria
44444Livingston
55555Scottsdale

第二个文件的内容包括

11111Indiana500 race
22222OhioBuckeye State
33333IllinoisWrigley Field
44444MontanaYellowstone Park
55555ArizonaGrand Canyon

以下内容(包括提示)是生成的结果:

$ join fileone filetwo11111 Indianapolis Indiana 500 race22222 Columbus Ohio Buckeye State33333 Peoria Illinois Wrigley Field44444 Livingston Montana Yellowstone Park55555 Scottsdale Arizona Grand Canyon$

已识别第一个域相同,并合并了匹配项。paste
盲目地从每个文件中提取一行以创建显示,而 join 只合并匹配的行(这一点至关重要),且该匹配必须是与另一个文件中相应行的完全匹
配。对这一点怎么强调都不过分。例如,如果第二个文件在中间额外增加了一行:

11111Indiana500 race
22222OhioBuckeye State
66666TennesseeSmokey Mountains
33333IllinoisWrigley Field
44444MontanaYellowstone Park
55555ArizonaGrand Canyon

则以下内容(包括提示)是生成的结果:

$ join fileone filetwo11111 Indianapolis Indiana 500 race22222 Columbus Ohio Buckeye State$

只要文件不再匹配,就不再执行操作。检查而且仅检查两个文件中的对应行,在默认域上查找匹配。如果
找到匹配,则将其合并到结果中;否则不合并。使用原始的第二个文件再进行一次演示:

$ tac filetwo > filethree$ join fileone filethree55555 Scottsdale Arizona Grand Canyon$

尽管这两个文件中的每一行都匹配,但只找到一个匹配项。强烈建议您首先对每个要使用的文件进行排序
以使其采用相同的顺序来解决 join 存在的问题。

您不必保留 join 的默认设置,即只查看前几个域是否匹配,或输出所有列。-1 选项使您能够指定将哪个域用作第一个文件中的匹配域,-2
选项使您能够指定将哪个域用作第二个文件中的匹配域。例如,如果第一个文件的第二个域匹配第二个文件的第三个域,则语法为

$ join -1 2 -2 3 fileone filethree

-o 选项用于以格式
{file.field} 指定输出域。因此,要在匹配行上打印第一个文件的第二个域和第二个文件的第三个域,则语法为

$ join -o 1.2 2.3 fileone filethreeIndianapolis 500Columbus BuckeyePeoria WrigleyLivingston YellowstoneScottsdale Grand$

实例。由于 cut
可用于从文件中提取域,因此它对于处理系统文件是不可或缺的。例如,要获取系统上所有用户的列表,可以从先前示例中使用的 /etc/passwd
文件中只提取第一个域:

$ cut -d":"-f1 /etc/passwdrootdaemonbinsysadmuucpmailkristin$

要收集用户名及其相应的主目录,可以提取第一个和第六个域:

$ cut -d":"-f1 /etc/passwdroot:/daemon:/bin:/usr/binsys:/adm:/var/admuucp:/usr/lib/uucpmail:/etc/mailkristin:/home/kristin$

join
实用程序最明显的示例是从 /etc/passwd 文件中提取用户名和相应的主目录,并从 /etc/group 文件中提取 alpha
组名称。组以数字格式显示在 /etc/passwd 文件的第四个域(该格式就是组在 /etc/group 文件的第三个域中的显示格式)中:

$ join -1 4 -2 3 -o 1.1 2.1 1.6 -t :/etc/passwd /etc/group

千万别忽视 sed
awk

资源

下载用于 Linux 的 Oracle 数据库 10g
Oracle 数据库 10g 第 1 版
(10.1.0.2) 现在可用于 Linux x86 和 Linux Itanium 平台;请在此从 OTN 上免费下载。

访问 Linux 技术中心
收藏本页,以获取有关 Linux
系统管理员最佳实践的一般技术信息,以及有关 Oracle-on-Linux 产品群的具体技术信息。

相关文章
Linux 相关技术文章存档

Linux 工具中两个最强大的实用程序是 sedawksed 流编辑器可用于编辑大量数据,而不需要任何的手动干预。awk 实用程序本身实际上是一种编程语言,可用于复杂的逻辑语句以及简化文本片段的提取。

这两个实用程序是先前文章的主题;建议您在进行下一个示例前先
查看这些文章。

实例。 要从
/etc/passwd 文件中提取第六个域,请使用命令

$ awk -F:'{print $6}' /etc/passwd

要打印该文件(使用短划线代替域之间的分隔符)并只打印第一个和第六个域(按相反的顺序),请使用
命令

$ awk -F":"'{OFS="-"}{print $6,$1}}' /etc/passwd

结论

本文重点介绍了用于从标准 Linux
文件中提取数据的实用程序。提取数据后,可以整理数据以查看和打印,或将数据导入其他数据库。知道如何使用这几个工具可以帮助您减少普通任务所需的时间,
使您成为一个更高效的管理员。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多