让文本飞

在这里插入图片描述

使用grep在文件中搜索文本

在文件中进行搜索是文本处理中一项重要工作。我们也许需要在某些多达上千行的文件中查找所需要的数据。这则攻略将教你如何从大量数据中定位符合规格的数据。

1、实战演练

grep命令作为Unix中用于文本搜索的神奇工具,能够接受正则表达式,生成各种格式的输出。除此之外,它还有大量有趣的选项。让我们看看具体的用法。

(1)搜索包含特定模式的文本行:

$ grep pattern filename 
this is the line containing pattern

或者

$ grep "pattern" filename 
this is the line containing pattern 

(2) 也可以像下面这样从stdin中读取:

$ echo -e "this is a word\nnext line" | grep word  
this is a word 

(3)单个grep命令也可以对多个文件进行搜索:

$ grep "match_text" file1 file2 file3 ...  

(4) 用 - -color选项可以在输出行中着重标记出匹配到的单词:

$ grep word filename --color=auto 
this is the line containing word 

(5) grep命令只解释match_text中的某些特殊字符。如果要使用正则表达式,需要添加-E选项——这意味着使用扩展(extended)正则表达式。或者也可以使用默认允许正则表达式的grep命令——egrep。例如:

$ grep -E "[a-z]+" filename 

或者:

$ egrep "[a-z]+" filename 

(6)只输出文件中匹配到的文本部分,可以使用选项 -o:

$ echo this is a line. | egrep -o "[a-z]+\." 
line. 

(7)要打印除包含match_pattern行之外的所有行,可使用:

$ grep -v match_pattern file 

选项-v可以将匹配结果进行反转(invert)。

(8)统计文件或文本中包含匹配字符串的行数:

$ grep -c "text" filename 
10

需要注意的是-c只是统计匹配行的数量,并不是匹配的次数。例如:

$ echo -e "1 2 3 4\nhello\n5 6" | egrep  -c "[0-9]" 
2

尽管有6个匹配项,但命令只打印出2,这是因为只有两个匹配行。在单行中出现的多次匹配只被统计为一次。

(9) 要文件中统计匹配项的数量,可以使用下面的技巧:

$ echo -e "1 2 3 4\nhello\n5 6" | egrep -o "[0-9]" | wc -l 
6

(10)打印出包含匹配字符串的行号:

$ cat sample1.txt 
gnu is not unix 
linux is fun 
bash is art 

$ cat sample2.txt 
planetlinux 

$ grep linux -n sample1.txt 
2:linux is fun

或者

$ cat sample1.txt | grep linux -n 

如果涉及多个文件,它也会随着输出结果打印出文件名:

$ grep linux -n sample1.txt sample2.txt 
sample1.txt:2:linux is fun 
sample2.txt:2:planetlinux 

(11)打印模式匹配所位于的字符或字节偏移:

$ echo gnu is not unix | grep -b -o "not" 
7:not

一行中字符串的字符偏移是从该行的第一个字符开始计算,起始值是0。在上面的例子中,"not"的偏移值是7(也就是说,not是从该行的第7个字符开始的,即“gnu is not unix”这一行)。

选项 -b总是和 -o配合使用。

(12)搜索多个文件并找出匹配文本位于哪一个文件中:

$ grep -l linux sample1.txt sample2.txt 
sample1.txt 
sample2.txt 

和-l相反的选项是-L,它会返回一个不匹配的文件列表。

2、补充内容

我们已经看过了grep命令的基本用法。不过grep的本事可不止如此,它还有更多的特性。 接着看看grep的其他选项。

(1)递归搜索文件

如果需要在多级目录中对文本进行递归搜索,可以使用:

$ grep "text" . -R -n 

grep的选项-R和-r功能一样。

例如:

$ cd src_dir 
$ grep "test_function()" . -R -n 
./miscutils/test.c:16:test_function();

test_function()位于miscutils/test.c的第16行。

这是开发人员使用多的命令之一。它用于查找某些文本位于哪些源码文件中。

(2)忽略样式中的大小写

选项 -i 可以使匹配样式不考虑字符的大小写,例如:

$ echo hello world | grep -i "HELLO" 
hello 

(3)用grep匹配多个样式

在进行匹配的时候通常只指定一个样式。然而,我们可以用选项 -e 来指定多个匹配样式:

$ grep -e "pattern1" -e "pattern" 

例如:

$ echo this is a line of text | grep -e "this" -e "line" -o 
this 
line 

还有另一种方法也可以指定多个样式。我们可以提供一个样式文件用于读取样式。在样式文件中逐行写下需要匹配的样式,然后用选项 -f 执行grep:

$ grep -f pattern_filesource_filename 

例如:

$ cat pat_file 
hello 
cool

$ echo hello this is cool | grep -f pat_file 
hello this is cool 

(4)在grep搜索中指定或排除文件

grep可以在搜索过程中指定(include)或排除(exclude)某些文件。我们通过通配符来指定所include文件或exclude文件。

目录中递归搜索所有的 .c 和 .cpp 文件:

$ grep "main()" . -r  --include *.{c,cpp} 

注意,some{string1,string2,string3}会扩展成somestring1 somestring2 somestring3。

在搜索中排除所有的README文件:

$ grep "main()" . -r --exclude "README"    

如果需要排除目录,可以使用 --exclude-dir选项。

如果需要从文件中读取所需排除的文件列表,使用 --exclude-from FILE。

(5) 使用0值字节作为后缀的grep与xargs

xargs命令通常用于将文件名列表作为命令行参数提供给其他命令。当文件名用作命令行参数时,建议用0值字节作为文件名终止符,而非空格。因为一些文件名中会包含空格字符,一旦它被误解为终结符,那么单个文件名就会被认为是两个文件名(例如,New file.txt 被解析成 New 和 file.txt 两个文件名)。这个问题可以利用0值字节后缀来避免。我们使用xargs以便从诸如grep、 find中接收stdin文本。这些命令可以将带有0值字节后缀的文本输出到stdout。为了指明输入的文件名是以0值字节(\0)作为终止符,需要在xargs中使用 -0 。

创建测试文件:

$ echo "test" > file1 
$ echo "cool" > file2 
$ echo "test" > file3 

在下面的命令序列中,grep使用-Z选项输出以0值字节作为终结符的文件名(\0)。xargs -0 读取输入并用0值字节终结符分隔文件名:

$ grep "test" file* -lZ | xargs -0 rm 

-Z 通常和 -l 结合使用。

(6)grep的静默输出

有时候,我们并不打算查看匹配的字符串,而只是想知道是否能够成功匹配。这可以通过设置grep的静默选项(-q)来实现。在静默模式中,grep命令不会输出任何内容。它仅是运行命令,然后根据命令执行成功与否返回退出状态。

如果命令运行成功会返回0,如果失败则返回非0值。

让我们来看一个脚本,这个脚本利用grep的静默模式来测试文本匹配是否存在于某个文件中。

#!/bin/bash  
# 文件名: silent_grep.sh 
# 用途: 测试文件是否包含特定的文本内容 

if [ $# -ne 2 ]; then
   echo "Usage: $0 match_text filename"
   exit 1 
fi 

match_text=$1  
filename=$2 
grep -q "$match_text" $filename 

if [ $? -eq 0 ]; then
   echo "The text exists in the file" 
else
   echo "Text does not exist in the file" 
fi

这个silent_grep.sh脚本使用一个用于匹配的单词(Student)和一个文件名(student_data.txt)作为 命令参数:

$ ./silent_grep.sh Student student_data.txt  
The text exists in the file 

(7)打印出匹配文本之前或之后的行

基于上下文的打印是grep的特色之一。假设已经找到了给定文本的匹配行,通常情况下grep 只会打印出这一行。但我们也许需要匹配行之前或之后的n行,也可能两者皆要。这可以在grep 中用前后行控制选项来实现。来看看具体的做法。

要打印匹配某个结果之后的3行,使用 -A 选项:

$ seq 10 | grep 5 -A 3 
5 
6 
7 
8 

要打印匹配某个结果之前的3行,使用 -B 选项:

$ seq 10 | grep 5 -B 3 
2 
3 
4 
5 

要打印匹配某个结果之前以及之后的3行,使用 -C 选项:

$ seq 10 | grep 5 -C 3 
2 
3 
4 
5 
6 
7 
8 

如果有多个匹配,那么使用 - - 作为各部分之间的定界符:

$ echo -e "a\nb\nc\na\nb\nc" | grep a -A 1 
a 
b 
-- 
a 
b 

使用sed进行文本替换

sed是流编辑器(stream editor)的缩写。它是文本处理中不可或缺的工具,能够配合正则表达式使用,功能不同凡响。sed命令众所周知的一个用法是进行文本替换。这则攻略包括了sed 命令大部分的常用技术。

1、实战演练

(1) sed可以替换给定文本中的字符串。

 $ sed 's/pattern/replace_string/' file 

或者

$ cat file | sed 's/pattern/replace_string/' 

该命令从stdin中读取输入。
如果你用的是vi编辑器,你会发现它用于替换文本的命令和 sed的非常相似。

(2) 在默认情况下,sed只会打印替换后的文本。如果需要在替换的同时保存更改,可以使用 -i 选项,可以将替换结果应用于原文件。很多用户在进行替换之后,会借助重定向来保存文件:

$ sed 's/text/replace/' file >newfile 
$ mv newfile file 

其实只需要一行命令就可以搞定,例如:

$ sed -i 's/text/replace/' file 

(3) 上面看到的sed命令会将每一行中第一处符合模式的内容替换掉。但是如果要替换所有内容,我们需要在命令尾部加上参数 g,其方法如下:

$ sed 's/pattern/replace_string/g' file

后缀/g意味着sed会替换每一处匹配。但是有时候我们只需要从第n处匹配开始替换。对此,可以使用 /Ng 选项。

请看下面的命令:

$ echo thisthisthisthis | sed 's/this/THIS/2g'  
thisTHISTHISTHIS 
$ echo thisthisthisthis | sed 's/this/THIS/3g'  
thisthisTHISTHIS 
$ echo thisthisthisthis | sed 's/this/THIS/4g'  
thisthisthisTHIS 

字符/在sed中被作为定界符使用。我们可以像下面一样使用任意的定界符:

$ sed 's:text:replace:g' 
$ sed 's|text|replace|g' 

当定界符出现在样式内部时,我们必须用前缀 \ 对它进行转义:

$ sed 's|te\|xt|replace|g'

\|是一个出现在样式内部并经过转义的定界符。

2、补充内容

sed命令包含大量可用于文本处理的选项。将这些选项以合理的次序组合,可以只用一行命令就解决很多复杂的问题,比如下面的选项。

(1)移除空白行

用sed移除空白行不过是小菜一碟。空白行可以用正则表达式 ^$ 进行匹配:

$ sed '/^$/d' file 

/pattern/d 会移除匹配样式的行。
在空白行中,行尾标记紧随着行首标记。

(2)直接在文件中进行替换

如果将文件名传递给sed,它会将文件内容输出到stdout。如果我们想修改文件内容,可以使用 -i 选项:

$ sed 's/PATTERN/replacement/' -i filename 

例如,使用指定的数字替换文件中所有3位数的数字:

$ cat sed_data.txt 
11 abc 111 this 9 file contains 111 11 88 numbers 0000

$ sed -i 's/\b[0-9]\{3\}\b/NUMBER/g' sed_data.txt 
$ cat sed_data.txt 
11 abc NUMBER this 9 file contains NUMBER 11 88 numbers 0000 

上面的单行命令替换了所有的3位数字。正则表达式 \b[0-9]{3}\b 用于匹配3位数字。[0-9] 表示数位取值范围,也就是说从0~9。{3}表示匹配之前的字符3次。{3} 中的 \ 用于转义{ 和 }。 \b表示单词边界。

一种有益的做法是先使用不带 -i 选项的sed命令,以确保正则表达式没有问题,一旦结果符合要求,再加入 -i 选项将更改写入文件。另外,你也可以使用下列形式的sed:

$ sed –i .bak 's/abc/def/' file 

这时的sed不仅执行文件内容替换,还会创建一个名为file.bak的文件,其中包含着原始文件内容的副本。

(3)已匹配字符串标记(&)

在sed中,我们可以用 & 标记匹配样式的字符串,这样就能够在替换字符串时使用已匹配的内容。

例如:

$ echo this is an example | sed 's/\w\+/[&]/g' 
[this] [is] [an] [example] 

正则表达式 \w+ 匹配每一个单词,然后我们用[&]替换它。& 对应于之前所匹配到的单词。

(4)子串匹配标记(\1)

& 代表匹配给定样式的字符串。但我们也可以匹配给定样式的其中一部分。来看看具体的做法。

$ echo this is digit 7 in a number | sed 's/digit \([0-9]\)/\1/' 
this is 7 in a number 

这条命令将digit 7替换为7。样式中匹配到的子串是7。(pattern) 用于匹配子串。模式被包括在使用斜线转义过的 () 中。对于匹配到的第一个子串,其对应的标记是 \1,匹配到的第二个子串是 \2,往后依次类推。下面的示例中包含了多个匹配:

$ echo seven EIGHT | sed 's/\([a-z]\+\) \([A-Z]\+\)/\2 \1/' 
EIGHT seven

([a-z]+) 匹配第一个单词,([A-Z]+) 匹配第二个单词。\1 和 \2 用来引用它们。这种引用被称为向后引用(back reference)。在替换部分,它们的次序被更改为 \2 \1,因此结果就呈现出逆序的形式。

(5)组合多个表达式

可以利用管道组合多个sed命令:

$ sed 'expression' | sed 'expression'

它等价于:

$ sed 'expression; expression' 

或者:

$ sed -e 'expression' -e expression' 

例如:

$ echo abc | sed 's/a/A/' | sed 's/c/C/' 
AbC 
$ echo abc | sed 's/a/A/;s/c/C/' 
AbC 
$ echo abc | sed -e 's/a/A/' -e 's/c/C/' 
AbC

(6)引用

sed表达式通常用单引号来引用。不过也可以使用双引号。双引号会通过对表达式求值来对其进行扩展。当我们想在sed表达式中使用一些变量时,双引号就能派上用场了。

例如:

$ text=hello 
$ echo hello world | sed "s/$text/HELLO/"  
HELLO world

$text的求值结果是hello。

使用awk进行高级文本处理

awk是一款设计用于数据流的工具。它颇有玩头的原因就在于可以对列和行进行操作。awk 有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处。灵活性是awk大的优势。

1、预备知识

awk脚本的结构基本如下所示:

awk ' BEGIN{  print "start" } pattern { commands } END{ print "end" } file 

awk命令也可以从stdin中读取。
awk脚本通常由3部分组成。BEGIN,END和带模式匹配选项的常见语句块。这3个部分都是可选项,在脚本中可省略任意部分。

2、实战演练

以下awk脚本被包含在单引号或双引号之间:

awk 'BEGIN { statements } { statements } END { end statements }' 

也可以使用: (单双引号的区别)

awk "BEGIN { statements } { statements } END { end statements }" 

例如:

$ awk 'BEGIN { i=0 } { i++ } END{ print i}' filename 

或者:

$ awk "BEGIN { i=0 } { i++ } END{ print i }" filename 

3、工作原理

awk命令的工作方式如下所注:

  1. 执行BEGIN { commands } 语句块中的语句。
  2. 从文件或stdin中读取一行,然后执行pattern { commands }。重复这个过程,直到文件全部被读取完毕。
  3. 当读至输入流末尾时,执行END { commands } 语句块。

BEGIN语句块在awk开始从输入流中读取行之前被执行。这是一个可选的语句块,诸如变量初始化、打印输出表格的表头等语句通常都可以写入BEGIN语句块中。

END语句块和BEGIN语句块类似。END语句块在awk从输入流中读取完所有的行之后即被执行。像打印所有行的分析结果这类汇总信息,都是在END语句块中实现的常见任务(例如,在比较过所有的行之后,打印出大数)。它也是一个可选的语句块。

最重要的部分就是pattern语句块中的通用命令。这个语句块同样是可选的。如果不提供该语句块,则默认执行{ print },即打印所读取到的每一行。awk对于每一行,都会执行这个语句块。这就像一个用来读取行的while循环,在循环体中提供了相应的语句。

每读取一行,awk就会检查该行和提供的样式是否匹配。样式本身可以是正则表达式、条件语句以及行匹配范围等。如果当前行匹配该样式,则执行{ }中的语句。

样式是可选的。如果没有提供样式,那么awk就认为所有的行都是匹配的,并执行{ }中的语句。 让我们看看下面的例子:

$ echo -e "line1\nline2" | awk 'BEGIN{ print "Start" } { print } END{ print "End" } ' 
Start 
line1 
line2 
End 

当使用不带参数的print时,它会打印出当前行。关于print,需要记住两件重要的事情: 当print的参数是以逗号进行分隔时,参数打印时则以空格作为定界符。在awk的print语句中, 双引号是被当做拼接操作符(concatenation operator)使用的。

例如:

$ echo | awk '{ var1="v1"; var2="v2"; var3="v3"; print var1,var2,var3 ; }' 

该语句将按照下面的格式打印变量值:

v1 v2 v3 

echo命令向标准输出写入一行,因此awk的 { } 语句块中的语句只被执行一次。如果awk的标准输入包含多行,那么 { } 语句块中的命令就会被执行多次。

拼接的使用方法如下:

$ echo | awk '{ var1="v1"; var2="v2"; var3="v3"; print var1 "-" var2 "-" var3 ; }' 

输出为:

v1-v2-v3 

{ }类似于一个循环体,会对文件中的每一行进行迭代。

我们通常将变量初始化语句(如var=0)以及打印文件头部的语句放入 BEGIN语句块中。在END{}语句块中,往往会放入打印结果等语句。

4、补充内容

awk命令具有丰富的特性。要想洞悉awk编程的精妙之处,首先应该熟悉awk重要的选项和功能。让我们来看看awk的一些重要功能。

(1)特殊变量

以下是可以用于awk的一些特殊变量:

  • NR:表示记录数量,在执行过程中对应于当前行号。
  • NF:表示字段数量,在执行过程中对应于当前行的字段数。
  • $0:这个变量包含执行过程中当前行的文本内容。
  • $1:这个变量包含第一个字段的文本内容。
  • $2:这个变量包含第二个字段的文本内容

例如:

$ echo -e "line1 f2 f3\nline2 f4 f5\nline3 f6 f7" | awk '{ print "Line no:"NR",No of fields:"NF, "$0="$0, "$1="$1,"$2="$2,"$3="$3  }'  
Line no:1,No of fields:3 $0=line1 f2 f3 $1=line1 $2=f2 $3=f3  
Line no:2,No of fields:3 $0=line2 f4 f5 $1=line2 $2=f4 $3=f5  
Line no:3,No of fields:3 $0=line3 f6 f7 $1=line3 $2=f6 $3=f7 

我们可以用print $NF打印一行中后一个字段,用 $(NF-1)打印倒数第二个字段,其他字段依次类推即可。awk的printf()函数的语法和C语言中的同名函数一样。我们也可以用这个函数来代替 print。

再来看awk的一些基本用法。打印每一行的第2和第3个字段:

$awk '{ print $3,$2 }'  file 

要统计文件中的行数,使用下面的命令:

$ awk 'END{ print NR }' file 

这里只使用了END语句块。每读入一行,awk会将NR更新为对应的行号。当到达后一行时, NR中的值就是后一行的行号,于是,位于END语句块中的NR就包含了文件的行数。

你可以将每一行中第一个字段的值按照下面的方法进行累加:

$ seq 5 | awk 'BEGIN{ sum=0; print "Summation:" }  { print $1"+"; sum+=$1 } END { print "=="; print sum }'  
Summation:  
1+  
2+  
3+  
4+  
5+  
== 
15 

(2)将外部变量值传递给awk

借助选项 -v,我们可以将外部值(并非来自stdin)传递给awk:

$ VAR=10000 
$ echo | awk -v VARIABLE=$VAR '{ print VARIABLE }' 
10000 

还有另一种灵活的方法可以将多个外部变量传递给awk,例如:

$ var1="Variable1" ; var2="Variable2" 
$ echo | awk '{ print v1,v2 }' v1=$var1 v2=$var2 
Variable1 Variable2 

当输入来自于文件而非标准输入时,使用下列命令:

$ awk '{ print v1,v2 }' v1=$var1 v2=$var2 filename 

在上面的方法中,变量之间用空格分隔,以键–值对的形式(v1=$var1 v2=$var2)作为awk的命名行参数紧随在BEGIN、{}和END语句块之后。

(3)用getline读取行

awk通常默认读取一个文件的所有行。如果只想读取某一行,可以使用getline函数。有时候,我们需要从BEGIN语句块中读取第一行。

语法:getline var。变量var就包含了特定行的内容。如果调用不带参数的getline,我们可以用 $0、$1和$2访问文本行的内容。

例如:

$ seq 5 | awk 'BEGIN { getline; print "Read ahead first line", $0 } { print $0 }' 
Read ahead first line 1 
2 
3 
4 
5 

(4)使用过滤模式对awk处理的行进行过滤

我们可以为需要处理的行指定一些条件,例如:

$ awk 'NR < 5' # 行号小于5的行 
$ awk 'NR==1,NR==4' # 行号在1到5之间的行 
$ awk '/linux/' # 包含样式linux的行(可以用正则表达式来指定模式) 
$ awk '!/linux/' # 不包含包含模式为linux的行

(5)设置字段定界符

默认的字段定界符是空格。我们也可以用 -F "delimiter"明确指定一个定界符:

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

或者:

$ awk 'BEGIN { FS=":" } { print $NF }' /etc/passwd 

在BEGIN语句块中则可以用OFS="delimiter"设置输出字段的定界符。

(6)从awk中读取命令输出

在下面的代码中,echo会生成一个空白行。变量cmdout包含命令grep root /etc/passwd 的输出,该命令会打印出包含root的行。

将命令的输出结果读入变量output的语法如下:

"command" | getline output ; 

例如:

$ echo | awk '{ "grep root /etc/passwd" | getline cmdout ; print cmdout }' 
root:x:0:0:root:/root:/bin/bash 

通过使用getline,我们将外部shell命令的输出读入变量cmdout。awk支持以文本作为索引的关联数组。

(7)在awk中使用循环

在awk中可以使用for循环,其格式如下:

for(i=0;i<10;i++) { print $i ; } 

或者:

for(i in array) { print array[i]; } 

(8)awk内建的字符串控制函数

awk有很多内建的字符串控制函数,让我们认识一下其中部分函数。

  • length(string):返回字符串的长度。
  • index(string, search_string):返回search_string在字符串中出现的位置。
  • split(string, array, delimiter):用定界符生成一个字符串列表,并将该列表存入数组。
  • substr(string, start-position,end-position):在字符串中用字符起止偏移量生成子串,并返回该子串。
  • sub(regex, replacement_str, string):将正则表达式匹配到的第一处内容替换成replacment_str。
  • gsub(regex, replacment_str, string):和sub()类似。不过该函数会替换正则表达式匹配到的所有内容。
  • match(regex,string):检查正则表达式是否能够匹配字符串。如果能够匹配,返回非0值;否则,返回0。match()有两个相关的特殊变量,分别是RSTART和RLENGTH。变量RSTART包含正则表达式所匹配内容的起始位置,而变量RLENGTH包含正则表达式所匹配内容的长度。

统计特定文件中的词频

统计文件中使用单词的频率是一个不错的练习,在这里能够应用我们业已习得的文本处理技巧。词频统计的方法有很多。让我们看看具体的做法。

1、 预备知识

我们可以使用关联数组、awk、sed、grep等不同的方式来解决这个问题。单词是由空格或句号分隔的字母组合。首先我们应该解析出给定文件中出现的所有单词,然后统计每个单词的出现的次数。单词解析可以用正则表达式配合sed、awk或grep等工具来完成。

2、 实战演练

脚本如下word_freq.sh :

#!/bin/bash 
# 文件名:word_freq.sh 
# 用途: 计算文件中单词的词频 
if [ $# -ne 1 ]; then
   echo "Usage: $0 filename";
   exit -1
fi
filename=$1
egrep -o "\b[[:alpha:]]+\b" $filename | \
 
awk '{ count[$0]++ } 
END{ printf("%-14s%s\n","Word","Count") ; 
for(ind in count) 
{  
    printf("%-14s%d\n",ind,count[ind]);  } 
}'

测试文本文件test.txt:

[root@localhost test]# cat test.txt 
hello
hello
use
word
word
word
this
this
[root@localhost test]# 

输出如下:

[root@localhost test]# ./word_freq.sh test.txt 
Word          Count
hello         2
this          2
use           1
word          3
[root@localhost test]# 

3、工作原理

egrep -o “\b[[:alpha:]]+\b” $filename只用于输出单词。用 -o选项打印出由换行符分隔的匹配字符序列。这样我们就可以在每行中列出一个单词。

\b 是单词边界标记符。[:alpha:] 是表示字母的字符类。awk命令用来避免对每一个单词进行迭代。因为awk默认会逐行执行{}块中的语句,所以我们就不需要再为同样的事编写循环了。 借助关联数组,当执行count[$0]++时,单词计数就增加。最后,在END{}语句块中通过迭代所有的单词,就可以打印出单词及它们各自出现的次数。

按列合并多个文件

很多时候我们需要按列拼接文件,比如要将每一个文件的内容作为单独的一列。而cat命令所实现的拼接通常是按照行来进行的。

1、实战演练

可以用paste命令实现按列拼接,其语法如下:

$ paste file1 file2 file3 … 

让我们来尝试一下:

$ cat file1.txt 
1 
2 
3 
4 
5 

$ cat file2.txt 
slynux 
gnu 
bash 
hack 

$ paste file1.txt file2.txt 
1slynux 
2gnu 
3bash 
4hack 
5 

默认的定界符是制表符,也可以用 -d明确指定定界符,例如:

$ paste file1.txt file2.txt -d "," 
1,slynux 
2,gnu 
3,bash 
4,hack 
5, 

解析文本中的电子邮件地址和URL

从给定的文件中解析出所需要的文本是从事文本处理时常见的一项任务。诸如电子邮件地址、URL等都能够借助适合的正则表达式找出来。我们通常需要从一个包含大量无关字符及单词的电子邮件客户列表或HTML网页中将电子邮件地址解析并提取出来。

1、实战演练

能够匹配一个电子邮件地址的正则表达式如下:

[A-Za-z0-9._]+@[A-Za-z0-9.]+\.[a-zA-Z]{2,4} 

例如:

$ cat url_email.txt 
this is a line of text contains,<email> #slynux@slynux.com. </email> and email address, blog "http://www.google.com", test@yahoo.com dfdfdfdddfdf;cool.hacks@gmail.com<br /> <a href="http://code.google.com"><h1>Heading</h1> 

因为用到了扩展正则表达式(例如+),所以得使用egrep命令。

$ egrep -o '[A-Za-z0-9._]+@[A-Za-z0-9.]+\.[a-zA-Z]{2,4}'  url_email.txt 
slynux@slynux.com  
test@yahoo.com  
cool.hacks@gmail.com 

匹配HTTP URL的egrep正则表达式如下:

http://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,4} 

例如:

$ egrep -o "http://[a-zA-Z0-9.]+\.[a-zA-Z]{2,3}" url_email.txt 
http://www.google.com  
http://code.google.com

2、工作原理

如果逐个部分进行设计,这些正则表达式其实很简单。在匹配电子邮件地址的正则表达式中, 我们都知道电子邮件地址可以采用name@domain.some_2-4_letter这种形式。那么,在编写 正则表达式时,也要遵循同样的规则:

[A-Za-z0-9.]+@[A-Za-z0-9.]+\.[a-zA-Z]{2,4} 

[A-Za-z0-9.]+ 表示在表示字面意义的字符 @ 出现之前,[]中的字符组合应该出现一次或多次(这也正是 + 的含义)。然后 [A-Za-z0-9.] 同样应该出现一次或多次(+)。样式\ . 表示应该呈现一个表示字面意义的 “.”(点号),而[a-zA-Z]{2,4} 表示字母的长度应该在2到4之间(包括2和4)。

匹配HTTP URL与匹配电子邮件地址类似,只是不需要匹配name@部分:

http://[a-zA-Z0-9.]+\.[a-zA-Z]{2,3} 

对目录中的所有文件进行文本替换

我们经常需要将某个目录中的所有文件内的一部分文本替换成另一部分。例如在网站的源文件目录中替换一个URI。解决这个问题快的方法就是利用shell脚本。

1、实战演练

首先使用find定位需要进行文本替换的文件。然后使用sed进行实际的替换操作。假设我们希望将所有.cpp文件中的Copyright替换成Copyleft:

$ find . -name *.cpp -print0 |  xargs -I{} -0 sed -i 's/Copyright/Copyleft/g' {} 

2、工作原理

我们使用find在当前目录下查找所有的.cpp文件,然后使用print0打印出以null字符(\0)作为分隔符的文件列表。(这可以避免文件名中的空格所带来的麻烦。)接着使用通道将列表传递给xargs,后者将对应的文件作为sed的参数,由sed对文件内容进行修改。

3、补充内容

find有一个选项-exec,它可以对每个find查找到的文件执行命令。我们可以使用该选项实现同样的效果:

$ find . -name *.cpp -exec sed -i 's/Copyright/Copyleft/g' \{\} \; 

或者:

$ find . -name *.cpp -exec sed -i 's/Copyright/Copyleft/g' \{\} \+ 

尽管这两个命令作用相同,但第一个命令会为每个查找到的文件调用一次sed,而在第二个命令中,find会将多个文件名一并传递给sed。

使用正则表达式

正则表达式是基于模式匹配的文本处理技术的关键所在。想要在编写文本处理工具方面驾轻就熟,你就必须对正则表达式有一个基本的理解。通配符能够匹配的文本范围相当有限。正则表达式是一种用于文本匹配的形式小巧、具有高度针对性的编程语言。[a-z0-9_]+ @[a-z0-9]+.[a-z]+就是一个能够匹配电子邮件地址的正则表达式。

1 实战演练

正则表达式是由字面文本和具有特殊意义的符号组成的。我们可以根据具体需求,使用它们构造出适合的正则表达式来匹配文本。因为正则表达式是一种匹配文本的通用语言,因此在这则攻略中我们不再介绍其他工具.

来看几个文本匹配的例子。

(1)要匹配给定文本中的所有单词,可以使用下面的正则表达式:

( ?[a-zA-Z]+ ?) 

“?”用于匹配单词前后可能出现的空格。[a-zA-Z]+代表一个或多个字母(a~z和A~Z)。

(2)要匹配一个IP地址,可以使用下面的正则表达式:

[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}

或者

[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3} 

我们知道IP地址通常的书写形式是192.168.0.2,它是由点号分割的4个整数(每一个整数 的取值范围从0~255)。 [0-9]或[:digit:]匹配数字0~9。{1,3}匹配1到3个数字,\.匹配"."。

这个正则表达式可以匹配所处理文本中的IP地址,但它并不检查地址的合法性。例如,形如123.300.1.1的IP地址可以被正则表达式匹配,但这却是一个非法的IP地址。不过在解析文本流时,通常目标仅仅是找出IP地址而已。

2、工作原理

正则表达式的基本组成部分:

在这里插入图片描述
3、 补充内容

下面就看一看如何在正则表达式中赋予某些字符特殊含义。

(1)处理特殊字符

正则表达式用 $ 、 ^ 、 . 、 * 、 + 、 { 、 } 等作为特殊字符。但是如果我们希望将这些字符作为非特殊字符(表示普通字面含义的字符),应该怎么做呢?来看一个正则表达式的例子:a.txt。

它会匹配字符a,然后是任意字符,接着是字符串txt。但是我们希望 ‘.’ 能够匹配字面意义上的 ‘.’ ,而非任意字符。所以我们在这个字符之前加上了一个反斜杠\(这叫做“将该字符进行转义”)。这表明正则表达式希望匹配的是字面字符,而不是它所代表的特殊含义。因此,最终的正则表达式就变成了a\.txt。

(2) 可视化正则表达式
正则表达式有时很难理解,但是人们更易于理解带有图示的事物,因此就出现了一些将正则表达式进行可视化的工具。你可以通过http://www.regexper.com找到同类工具中的一个。你可以在这个工具中输入正则表达式,然后它会创建出一个漂亮的图示来帮助你理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值