当前位置:  开发笔记 > 编程语言 > 正文

Linux.Shell编程笔记-基本文本处理

第五章基本文本处理排序文本文本处理是UNIXLINUXShell编程中几乎最重要的一部分。在UNIXLINUX的设计中,一切都是文件,而系统中许多程序的协同工作是通过文木或者文本流来实现的。因此,UNIXL

第五章 基本文本处理

排序文本

文本处理是UNIX/LINUXShell编程中几乎最重要的一部分。在UNIX/LINUX 的设计中,一切都是文件,而系统中许多程序的协同工作是通过文木或者文本流来实现的。因此,UNIX/LINUX 中的文本处理以及文本流的设计就成了重要的环节.

管道是UNIX/LINUX中的一个重要发明,管道连接了各种处理工具,组建文本流。在UNIX/LINUX 中,文本处理工具常常被设计成过滤器的形式。通过管道连接不同过滤器,这样,很简单的拼接就能实现需求的功能。

Sort命令的行排序

许多数据(文本)文件都按照一定的格式组织,这些文木文件以可一读的方式提供信息检索和处理。一般来说,这种有格式的文本文件都可以排序。排序后的文木文件更利于检索。

常见的排序算法很多,例如胃泡排序、归并排序、快速排序等,化是排序效率各不相同。

UNIX/LINUX下提供的排序工具sort可以高效工作,并且,通常情况下,它比你写的排序算法高效很多。因此,即使不了解排序的具体实现细节,你也一可以放心人胆地使用sort排序。

sort命令将输入看成是具有多条记录的数据流,而记录由字段组成,记录是以换行符为界定符号,每行对应于一条记录。字段则是以空白字符为界定,sort命令一也提供用户指定字段界定字符的参数。

在未提供参数的情况下,sort命令会根据当前字符集(locale )所定义的次序排序。例如,在传统的C looal。中,sort命令会按照ASCII顺序排序,如果需要改变排序规则,可以修改当前字符集。

[houchangren@ebsdi-23260-oozie data]$ catfruits.txt   //查看文件内容
apple
%%banae
banana
apple
Presimmon
Banana
orange
presimmon
[houchangren@ebsdi-23260-oozie data]$ sortfruits.txt   //默认排序
apple
apple
%%banae
banana
Banana
orange
presimmon
Presimmon
[houchangren@ebsdi-23260-oozie data]$ echo$LANG  //查看默认设置字符集
zh_CN.UTF-8
[houchangren@ebsdi-23260-oozie data]$LANG=EN_US sort fruits.txt  //设置成EN_Us的排序
%%banae
Banana
Presimmon
apple
apple
banana
orange
presimmon

-d参数 是按照字典排序(默认)

-f 是排序前都按照都写来排序(小写全转成大写)

 [houchangren@ebsdi-23260-oozie data]$ sort -d-f fruits.txt    
apple
apple
%%banae
banana
Banana
orange
presimmon
Presimmon

-u 去重复,相同行去掉

[houchangren@ebsdi-23260-oozie data]$ sort-d -f -u fruits.txt
apple
%%banae
banana
orange
Presimmon
Sort命令的字段排序

sort命令还可以对字段进行排序。在sort的参数列表中,-k参数可以选定排序字段,而-t参数可以选择字段分界符,如果没有设定,则默认是空白字符。

[houchangren@ebsdi-23260-oozie data]$ cat sort.txt
keyDataXSZKCZY:XXXSCK:604834:AFS:3636351      d_20131220170748-1600299142     2
keyDataXSZKCZY:XXXSCK:604836:AFS:3636353      d_20131220170748-1600299142     3
keyData XSZKCZY:XXXSCK:604838:AFS:3636355       d_20131220170748-1600299142     1
keyDataXSZKCZY:XXXSCK:605180:AFS:3639304      d_20131220170748-1600299142     6
keyDataXSZKCZY:XXXSCK:606728:AFS:3658757      d_20131220170748-1600299142     5
keyDataXSZKCZY:XXXSCK:607072:AFS:3661194       d_20131220170748-1600299142     2
keyDataXSZKCZY:XXXSCK:607188:AFS:3661453      d_20131220170748-1600299142     23
keyDataXSZKCZY:XXXSCK:607195:AFS:3661460      d_20131220170748-1600299142     4
keyDataXSZKCZY:XXXSCK:607197:AFS:3661462      d_20131220170748-1600299142     2
keyDataXSZKCZY:XXXSCK:607199:AFS:3661464      d_20131220170748-1600299142     2
[houchangren@ebsdi-23260-oozie data]$ sort -t $'\t' -k4 sort.txt    //因为\t比较特殊所以要用$
keyDataXSZKCZY:XXXSCK:604838:AFS:3636355      d_20131220170748-1600299142     1
keyDataXSZKCZY:XXXSCK:604834:AFS:3636351      d_20131220170748-1600299142     2
keyDataXSZKCZY:XXXSCK:607072:AFS:3661194      d_20131220170748-1600299142     2
keyDataXSZKCZY:XXXSCK:607197:AFS:3661462      d_20131220170748-1600299142     2
keyDataXSZKCZY:XXXSCK:607199:AFS:3661464      d_20131220170748-1600299142     2
keyDataXSZKCZY:XXXSCK:607188:AFS:3661453      d_20131220170748-1600299142     23
keyDataXSZKCZY:XXXSCK:604836:AFS:3636353      d_20131220170748-1600299142     3
keyDataXSZKCZY:XXXSCK:607195:AFS:3661460      d_20131220170748-1600299142     4
keyDataXSZKCZY:XXXSCK:606728:AFS:3658757      d_20131220170748-1600299142     5
keyDataXSZKCZY:XXXSCK:605180:AFS:3639304      d_20131220170748-1600299142     6
 
[houchangren@ebsdi-23260-oozie data]$ sort -t $'\t' -k4 -n sort.txt   
keyDataXSZKCZY:XXXSCK:604838:AFS:3636355      d_20131220170748-1600299142     1
keyDataXSZKCZY:XXXSCK:604834:AFS:3636351      d_20131220170748-1600299142     2
keyData XSZKCZY:XXXSCK:607072:AFS:3661194       d_20131220170748-1600299142     2
keyDataXSZKCZY:XXXSCK:607197:AFS:3661462      d_20131220170748-1600299142     2
keyDataXSZKCZY:XXXSCK:607199:AFS:3661464      d_20131220170748-1600299142     2
keyData XSZKCZY:XXXSCK:604836:AFS:3636353       d_20131220170748-1600299142     3
keyDataXSZKCZY:XXXSCK:607195:AFS:3661460      d_20131220170748-1600299142     4
keyDataXSZKCZY:XXXSCK:606728:AFS:3658757      d_20131220170748-1600299142     5
keyData XSZKCZY:XXXSCK:605180:AFS:3639304       d_20131220170748-1600299142     6
keyDataXSZKCZY:XXXSCK:607188:AFS:3661453      d_20131220170748-1600299142     23

-n参数让sort命令按照整数数字进行比较。之前的排序很好的说明了,23在中间

Sort 小结

sort是一个重要的命令,你可以在许多重要的UNIX/LINUX shell中见到它的身影:只要有数据存在,并有检索的需求,往往就有sort的支持。如果给UNIX/LINUX的命令论资排辈,sort绝对可以进入前十名。

上边所做的只是sort命令冰山一角,sort命令的强大之处远不止于此。除了对文本

行和字段进行排序,sort命令和其他命令结合使用〔它本身就被设计成过滤器的模式)将实现强大的功能,例如,可以对文本块进行排序处理等.

并且,sort命令的效率是值得称道的:自从它问世起,已经有许多人对它进行研究、优化和调整。它肯定比你写的排序算法工作得更好,相信它,而不要尝试去重复发明轮子〔编写冗余的脆弱的排序算法)。

但是,注意,sort命令是不稳定的口排序算法的稳定性指的是:两条相同的记录输入顺序和输出顺序保持不变。

排序字段都相同,但是输出和输入的顺序却不一致。所以sort不是稳定的排序实现。GUN的coreutils包中的sort命令弥补了这个不足。现在。你可以通过一stable选项来使输入输出的相同记录顺序不变了。但是,这个参数相应会降低sort命令的效率(采用稍微低效的排序算法来获得稳定性).

当sort作为过滤器在管道中使用时,排序算法的稳定性就变得重要了。因此,你需要根据稳定性(stable )是否对自己的命令重要,来决定是否采用--stable参数。

文本去重

由于 sort -u去重复的命令只是对指定排序的列相同就会去重,但是可能其他字段不同,所以有时候可能不是我们想要的。

UlSIX\Linux系统中有另一条命令用于数据一记录去重,它足uniq.uniq命令会去除数据流中重复的记录,只留下第一条记录.它常常被用于管道中,例如,接在sort命令之后用于去重。

uniq命令主要有3个选项,-c 用于显示出现重复行的计数,-d选项仅显示重复行,-u选项仅显示不重复的行

[houchangren@ebsdi-23260-ooziedata]$ cat fruit.txt
%%banae 
banana  
apple
Presimmon
%%banae 
apple 
Banana  
orange  
presimmon
[houchangren@ebsdi-23260-ooziedata]$ sort fruit.txt | uniq -c     //这个地方两个apple因为后边的空格导致认为是两个记录
      1 apple
      1 apple 
      2 %%banae 
      1 banana  
      1 Banana  
      1 orange  
      1 presimmon
      1 Presimmon
[houchangren@ebsdi-23260-ooziedata]$ sort fruit.txt | uniq -c -d  //仅显示重复行
      2 %%banae 
[houchangren@ebsdi-23260-ooziedata]$ sort fruit.txt | uniq -c -u    //仅显示不重复行
      1 apple
      1 apple 
      1 banana  
      1 Banana  
      1 orange  
      1 presimmon
      1 Presimmon
[houchangren@ebsdi-23260-ooziedata]$

统计文本行数、字数以及字符数

Unix/linux中的wc命令可以提供文本的行数、字数、字符数统计。wc命令也是POSIX中标准的一部分,你可以放心大胆地使用。

-c 参数含义是让WC命令显示字符的个数。

-w 参数含义是让WC命令显示单词的个数。

-l 参数含义是让WC命令文本行的行的个数。

[houchangren@ebsdi-23260-oozie data]$ wc/etc/passwd
 61   92 2867 /etc/passwd
[houchangren@ebsdi-23260-oozie data]$ wc -c/etc/passwd
2867 /etc/passwd
[houchangren@ebsdi-23260-oozie data]$ wc -c-w /etc/passwd
  922867 /etc/passwd
[houchangren@ebsdi-23260-oozie data]$ wc -c-w -l /etc/passwd
 61   92 2867 /etc/passwd
[houchangren@ebsdi-23260-oozie data]$

//查找上层目录中name是.sh结尾的个数

[houchangren@ebsdi-23260-oozie data]$ find../ -iname "*.sh" |wc -l
11 

//找出/etc/passwd文件中包含bash字符串的行的个数。一般来说,就是系统中启动

shell为bash的用户的个数。

[houchangren@ebsdi-23260-oozie data]$ grepbash /etc/passwd | wc -l
25

//用纯grep达同样效果

[houchangren@ebsdi-23260-oozie data]$ grep-c bash /etc/passwd
25 

// WC命令统计当前目录下所有以.sh结尾的文件,统计它们中的字符数、单词数和行

数,并且在最一行将总计的结果打印出来。

[houchangren@ebsdi-23260-oozie shell]$ wc*.sh
  6    7   51 add.sh
 19   29  337 a.sh
  7   16   85 checkUserIsExist.sh
  8   11   55 if.sh
 16   58  284 testalg.sh
  8   20  206 testcase.sh
   6   7   42 testfor.sh
 26   51  319 testwhile.sh
 11   27  132 user_login.sh
 15   26  193 whileexample2.sh
 17   38  253 whileexample.sh
 139  2901957 总计

Note

Wc命令的执行会随着locale的设定有不同的结果。因为不同的locale会影响Wc命令解释字节序列时的字符/单词分隔器。

打印和格式化输出

Linux下打印和格式化文本的工具很多,例如pr, fmt, fold等。它们各有不同的用途。

使用pr打印文件

UNIX/Linux的 pr命令可以用来将文本转换成适合打印的文件。这个工具的一个基本用途就是将较大的文件分割成多个页面,并为每个页面添加标题。  

  比如,pr可以将一个150行文本的文件转换成三个文本页,然后让用户进行打印。

  在默认情况下,每个页面会包含66行文本,不过通过pr的-l参数,用户可以改变这一规则。  

  可以用来控制文本输出效果的参数很多,一般来说,每页的标题就是这个文档的文件名。当然,用户也可以自行定义标题,比如:

  $ pr -h "My report" file.txt

  如果不使用上面的-h参数,打印的页面会用“file.txt”作为标题,而加上-h参数后,页面会使用该参数后指定的“My report”作为标题。

  用户还可以使用pr命令将文本分列打印。这对于语句短小的文本来说比较有用,如果语句比较长,pr会在适当的位置进行换行。比如,要将file.txt文件按两列打印,可以使用以下命令:

  $ pr -2 -h "My report" file.txt  

  默认情况下,pr会为每个页面加入换行符(比如空行),不过用户也可以使用制表符来代替空行。可以下面这段命令使制表符来代替空行:

  $ pr -f file.txt  

  如果用户只是想打印文件,而不想保存它,那么这个功能比较合适,但是如果用户同时也要保存文件,那么添加的制表符会让文件看起来比较乱。

  需要记住的是,pr是一个标准的输出工具,可以直接输出到打印机,如果你希望将结果保存在文件中,则需要重定向它的输出,如下面这个例子:

$ pr file.txt>file.output

测试实例:

[houchangren@ebsdi-23260-ooziedata]$ pr -f  fruit.txt
2014-01-1810:56                    fruit.txt                     Page 1
%%banae 
banana  
apple
Presimmon
%%banae 
apple 
Banana  
orange  
presimmon
[houchangren@ebsdi-23260-ooziedata]$ pr -f -c1  fruit.txt
2014-01-1810:56                    fruit.txt                     Page 1
%%banae 
banana  
apple
Presimmon
%%banae 
apple 
Banana  
orange  
presimmon
[houchangren@ebsdi-23260-ooziedata]$ pr -f -c1 -h "test" fruit.txt
2014-01-18 10:56                       test                       Page 1
%%banae 
banana  
apple
Presimmon
%%banae 
apple 
Banana  
orange  
presimmon
[houchangren@ebsdi-23260-ooziedata]$ pr -f -c1 -t fruit.txt
%%banae 
banana  
apple
Presimmon
%%banae 
apple 
Banana  
orange  
presimmon

在pr命令中 –c 参数是显示多少栏, -t 参数表示不显示标题

NOTE

在pr命令里,直接使用-10的效果和使用-c10是一样的‘这是因为pr有一个参数是-Column,这个参数即直接在连字符后面接数字,表示输出的栏数.

使用fmt命令格式化文本

除了pr命令,UNIX/Linux下还有一条fmt命令可以格式化文本段落,使文本不要超出可见的屏幕范围口fmt指令会从指定的文件里读取内容,将其依照指定格式重新编排后,输出到标准输出设备。若指定的文件名为“一”,则fmt指令会从标准输入设备读取数据口

-w 参数告诉fmt命令每行的最大字符数。

-s参数告诉fmt命令只拆开字数超出每列字符数的列,但不合并字数不足每列字符数的列

NOTE

警告,无论是pr还是fmt,在不同版本系统中的行为都不尽相同。因此你需要在不同版本的系统上

通过查阅manpage,来确定格式化输出工具的功能。

使用fold限制文本宽度

LINIXILinux的fold指令会从指定的文什里读取内容,将超过限定列宽的列加入增列字符

后,输出到标准输出设备。若不指定任何文件名称,或是所给予的文件名为”-“,则fold指令会从标准输入设备读取数据。

Note

注意了,fold的-w参数和fmt的-w参数并不相同。fold的-w参数很生硬地将文本输出行截断,但是并不判断是否单词也被截断。而fmt的-w.参数会判断单词是否能够正常显示〔不被截断显示),遇到无法正常显示的,fmt会将该单词整体移到下一行,而fold会将之拦腰截断。

提取文本开头和结尾

//查看tomcat前20行日志

Head 查看文件的开头

head -n 指定行

[root@ebsdi-23260-oozie logs]# head -20  catalina.out

tail命令查看文件的结尾

tail –n 指定行

[root@ebsdi-23260-oozie logs]# tail  -20 catalina.out    

-f参数动态查看

[root@ebsdi-23260-oozie logs]# tail -f catalina.out

字段处理

Cut取文本中的某个字段

-d参数规定了cut命令接受的字段分隔符。

-f参数规定了cut命令获取的字段列,后边跟数字多个用逗点隔开。

[houchangren@ebsdi-23260-oozie data]$ cut-d  ':' -f 1,7 /etc/passwd | grep bash |head -10
root:/bin/bash
mapred:/bin/bash
hdfs:/bin/bash
wangjuntao:/bin/bash
yangzhi:/bin/bash
huanghu:/bin/bash
zhangguochen:/bin/bash
houchangren:/bin/bash
hadoop:/bin/bash
neil:/bin/bash
join 连接字段

Linux一下的join命令可以连接不同的文件,使得具有相同key值的记录信息连到一起。它会根据指定栏位,找到两个文件中指定栏位内容相同的行,将它们合并,并根据要求的格式输出内容。该命令对于比较两个文件的内容很有帮助。

[houchangren@ebsdi-23260-oozie data]$ catstuff.txt
1      zhangsan
2       lisi
3      wangwu
6      maliu
[houchangren@ebsdi-23260-oozie data]$ catsalary.txt
1      1000
2      2000
3      2350
5      2400
[houchangren@ebsdi-23260-oozie data]$ joinstuff.txt salary.txt
1 zhangsan 1000
2 lisi 2000
3 wangwu 2350
[houchangren@ebsdi-23260-oozie data]$ joinstuff.txt salary.txt  -a1
1 zhangsan 1000
2 lisi 2000
3 wangwu 2350
6 maliu
[houchangren@ebsdi-23260-oozie data]$ joinstuff.txt salary.txt  -a2
1 zhangsan 1000
2 lisi 2000
3 wangwu 2350
5 2400
其他字段处理方法

Awk,后边会专有章节介绍

文本替换

UNIX\Linux下的文本替换有很多种实现方法,例如,可以通过sed命令。如果在文本编辑器

中,则有更多的选择。但是,最简单的命令还是tr.

使用tr替换字符

tr命令从标准输入删除或替换字符,并将结果写入标准输出。在命令需要小范围内做文本替换时,tr命令非常有用。

tr命令格式

a.      tr str1 str2
b.      tr {-d|-s} str1

tr完成的操作

a. 转换字符
如果str 1和str2两者都已指定,但-d标志没有指定,tr命令就会从标准输入中将str1中所包含的每一个字符都替换成str2中相同位置上的字符。

b. 使用-d标志删除字符
如果-d标志已经指定,tr命令就会从标准输入中删除str1中包含的每一个字符。

c. 使用-s标志除去序列

如果-s标志已经指定,tr命令就会除去包含在str1或str2中的任何字符串系列中的除

第一个字符以外的所有字符。对于包含在str1中的每一个字符,tr命令会从标准输出中除去除第一个出现的字符以外的所有字符。对于包含在str2中的每一个字符,tr命令除去标准输出的字符序列中除第一个出现的字符以外的所有字符。

[houchangren@ebsdi-23260-oozie data]$ cat fruit.txt
%%banae 
banana  
apple
Presimmon
%%banae 
apple 
Banana  
orange  
presimmon
[houchangren@ebsdi-23260-oozie data]$ tr'a-z' 'A-Z' < fruit.txt > fruit.txt.upper
[houchangren@ebsdi-23260-oozie data]$ catfruit.txt.upper
%%BANAE 
BANANA  
APPLE
PRESIMMON
%%BANAE 
APPLE 
BANANA  
ORANGE  
PRESIMMON
[houchangren@ebsdi-23260-oozie data]$ catfruit.txt | tr -d 'a' > fruit.txt.rm
[houchangren@ebsdi-23260-oozie data]$ catfruit.txt.rm
%%bne 
bnn  
pple
Presimmon
%%bne 
pple 
Bnn  
ornge  
presimmon
[houchangren@ebsdi-23260-oozie data]$ catfruit.txt.rm |tr -s '%%' '$$' > fruit.txt.rep
[houchangren@ebsdi-23260-oozie data]$ catfruit.txt.rep
$bne 
bnn  
pple
Presimmon
$bne 
pple 
Bnn  
ornge  
persimmon
其他实例
#若要将大括号转换为小括号,请输入
>>> tr '{}' '()' < textfile> newfile
#若要将大括号转换成方括号,请输入:
>>> tr '{}' '\[]'  newfile
#若要创建一个文件中的单词列表,请输入;
>>> tr -cs '[:lower:][:upper:]''[\n*]'  newfile
#若要从某个文件中删除所有空字符,请输入:
>>> tr -d '\0'  newfile
#若要用单独的换行替换每一序列的一个或多个换行,清输入
>>> tr -s  '\n' < textfile >new file
或者
>>> tr -s '\012' newfile
#若要以“?”(问号)替换每个非打印字符〔有效控制字符除外),请输入:
>>> tr -c '[:print:][:cntrl:]''[?*]'  newfile
#若要以单个“非”字符替换字符类中的每个字符序列,请输入:
>>> tr -s '[:space:]' '[#*]'其他选择

其实,tr的文本替换功能仅仅实现了最简单的操作,如果有更复杂的需求,例如有条件判断等逻辑的,就需要更强大和复杂的工具了。而这类工具,往往都可以称之为语言。

Linux下常见的具有这类功能的工具有如下这些项。

perl强大的正则表达式文持在UNIX/linux世界无出其右,文木替换自然是小菜;

sed sed工具处理文本流,亦可以轻松地实现文木替换.

awk awk语言具有逻辑判断与循环等特性支持,并且,在文本处理时根据需求,强大的可定制性也是其长盛不衰的原因。

python作为目前UNIX、Linux社区最流行的语言之一,python的文本处理模块相当强大,

亦可以完成几乎你想要的替换功能。但是,python的执行效率比较慢,并且,使用python这么庞大的语一言处理简单的文本操作似乎有点大材小用.

一个稍微复杂的例子

需求:获取一天中访问最多的前100名

步骤:cut -> tr-> cut-> sort->uniq->sort->head

第一个字段后是\t第二字段后是空白符(多个)所以得截取了两次

[houchangren@ebsdi-23260-oozie data]$ catips.txt
 
20:32  10.12.165.1     1238723
20:31  10.12.165.7     123923
20:32  10.12.165.18    128323
20:12  10.12.165.20    1234623
20:32  10.12.165.25    1232443
20:32  10.12.165.26    1232433
20:32  10.12.165.31    1234523
20:32  10.12.165.32    123
20:32  10.12.165.33    12345
20:32  10.12.165.33    123245
20:32  10.12.165.36    123333
20:32  10.12.165.37    123423
20:32  10.12.165.38    12423
20:32  10.12.165.39    12623
20:32  10.12.165.40    12523
20:32  10.12.165.255   12423
20:32  224.0.0.2       12123
20:32  224.0.0.22      12223
20:32  224.0.0.251     12322
20:32  224.0.0.252     12322
[houchangren@ebsdi-23260-oozie data]$ cut-d $'\t' -f 2 ips.txt |tr -s '[" "]' $'\t'|cut -d $'\t' -f1|sort|uniq -c|sort -r|head -100
     2 10.12.165.33
     1 224.0.0.252
     1 224.0.0.251
     1 224.0.0.22
     1 224.0.0.2
     1 10.12.165.7
     1 10.12.165.40
     1 10.12.165.39
     1 10.12.165.38
     1 10.12.165.37
     1 10.12.165.36
     1 10.12.165.32
     1 10.12.165.31
     1 10.12.165.26
     1 10.12.165.255
     1 10.12.165.25
     1 10.12.165.20
     1 10.12.165.18
     1 10.12.165.1
     1
推荐阅读
爱唱歌的郭少文_
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有