shell 脚本之文本处理

我学习shell 最初目的就是用于文本处理,以及自动化处理一些繁杂的操作,shell 脚本在这方面正是非常好用的工具。

本文只介绍常见的文本处理,对于其中涉及到的命令,以及正则表达式则不过多介绍;如果想用好shell, linux 文本操作的一些命令,还有正则表达式都必须要掌握。学习正则表达式开始比较难,多动手练习后,其实还是非常有帮助的。在java 和一些脚本语言中也有正则表达式的概念,因此强烈建议学好它,而且非常值得。


本文围绕以下几个问题展开讨论,针对每个问题再细化出另外一些问题:

1、如何查找文本?

2、 如何提取文本?

3、 如何替换文本?

4、 如何删除文本?

5、 如何插入文本?

6、 二进制文件处理


一、 查找文本,过滤文本

查找文本非常有用, 比如在查看打印时可以从大量打印中筛选出我们需要的打印。

查找或过滤文本当然用grep,非常强大。

假设我手上有一个文件:onelife.txt

文件内容如下:

  You Have Olny One Life


There are moments in life when you miss someone so much that
you just want to pick them from your dreams and hug then for
real! Dream what your want to dream; to whree you want to go;
be what you want to be, because you have only one life and one
chance to do all the things you want to do.

hello# world

string test "start " end
start123end

1. 查找文件中是否出现life?

$ cat onelife.txt |grep 'life'

通过上面的命令可以将含有life的所有行都打印出来。

如果我们只需要知道结果是否包含该文本,则可以如下:

$ cat onelife.txt |grep 'life' > /dev/null

$ echo $?

通过返回值来判断,0 表示查找成功,1 表示查找失败。

2. 如何显示既包含life 的行又包含miss的行?

其实这个问题就是怎么表示逻辑与的问题

$ cat onelife.txt |grep 'life' |grep 'miss'

通过管道实现’与‘操作


3. 如何显示包含life 或者包含miss的行?

$cat onelife.txt |grep  -E 'life | miss'

这里使用-E参数 调用egrep,不调用egrep也可以, 在 | 符号前面加个转义字符就行。


4. 如何显示所有不包含 life的行?

cat onelife.txt|grep -v 'life'

这里借助grep 的-v 参数,来显示所有不匹配行。


5. 如何显示包含数字的行?

cat onelife.txt |grep -E '[0-9]+'

这重匹配一类的问题就需要使用到正则表达式了 


二、 提取文本

提取文本主要会用到grep/ cut /awk/, 对于二进制文件,需要结合od 命令

1. 如何提取出网页中的所有网页链接?

$ grep -E -o 'https?://www\.[a-zA-Z0-9_]+\.[a-zA-Z]{2,4}' baidu.txt |sort|uniq
https://www.baidu.com
http://www.baidu.com
http://www.beian.gov
http://www.hao123.com
http://www.nuomi.com
http://www.w3.org

这里的baidu.xtx文件是百度网页的源码(用360浏览器可以直接右键查看源码),只是提取了一级网页, 在上面用到了grep的-o 参数, 这个参数专门用于提取匹配成功的文本内容;在‘’单引号包含的部分是一个正则表达式。最后删除一些重复的网站。

同理, 也可以提取图片, 视频,电子邮件等等内容。 在部分网页中可能没有视频的下载地址,对于不太懂js的人来说,那么我们可以查看该网页的源码,然后保存成文本,再从中提取出视频地址。


2. 如何提取出双引号中包含的字符串?

其实这个问题和上一个一样,同样可以用grep来,只是因为比较常用,因此单独列出来。

$ echo '"hello" world' |grep -o '".*"'|sed 's/"/ /g'
 hello 

其中grep提取字符串, sed 用来删除双引号


*** 基本上只要能写出正则表达式的,都可以用grep来提取;但是,对于有些比较难写出正则表达式的就要借用分隔符来提取了。


3. 在linux下如何提取出用户ID?

 eg:

$ cat /etc/passwd |tail -n 5|cut -d':' -f1,3
lfc:1018
zhq:1019
ljl:1020
lpf:1006
test:1009

 passwd 文件下包含有用户名、ID和组ID等内容, 这个文件以:为分隔符,文本内容都比较相识,因此用正则表达式不好匹配,cut 或awk 正合适。

这个例子中我只提取最后五个用户的信息,提取了第一和第三个字段,也就是用户名和ID, 它们可以指定一个分隔符,然后对每个字段进行提取。

用awk 可以这样提取:

$ cat /etc/passwd |tail -n 5|awk -F ':' '{printf("%-10s\t%-10s\n",$1,$3)}'
printf用于格式化输出, 和C用法一样。

awk 非常强大,在处理分组,统计方面很有优势,甚至可以当成一门语言,它的语法格式非常简单,对于基本用法还是很容易掌握的。


三、 替换文本?

简单的字符串替换可以使用tr命令, 当然最好用的还是sed了,可以匹配正则表达式,sed可以完成所有你希望的替换工作。


1. 将 字符串"abcdefgHIJklmn" 全部转换成大写?

$ echo abcdefgHIJklmn |tr [a-z] [A-Z] 

ABCDEFGHIJKLMN

$ echo abcdefgHIJklmn |sed 's/[a-z]/\u&/g'
ABCDEFGHIJKLMN

用sed 和 tr都可以,tr比较直观,sed则需要掌握一些元字符的意义;\u用于将后面的字符转换成大写, \l 用于转换成小写。


2. 如何将单词的首字母变成大写该如何实现呢?

$ echo hello world |sed 's/\b\w/\u&/g'
Hello World

这里用tr就不太好实现了。


3. 如何将文本中的单引号转换成双引号? 

$ echo "hello '123' world"|sed 's/'\''/"/g'
hello "123" world
$ echo 'hello "123" world'|sed 's/"/'\''/g'
hello '123' world

上面 分别实现将123的单/双引号进行转换, 最外面的引号只是为了把这个字符串当成一个字符串处理,实际echo时并不会出现


4. 替换指定行

$ cat onelife.txt |sed '2c change line'

将第二行 替换成 change line

四、 删除文本

删除文本我经常使用tr 或sed, grep命令

1. 如何删除空行?

$ cat onelife.txt |tr -s '\n'

-s 用于缩减连续字符,只保留一个字符。

$ cat onelife.txt |sed '/^$/d'

sed 中有 d 命令,用于删除一行,^$ 用于匹配空行


2. 如 删除Windows文件“造成”的'^M'字符?

在window下编辑脚本,然后放到linux下执行,很容易出现莫名其妙的错误,其实就可能是\r字符导致的,删掉它就可以了。

因为window下换行符为 \r\n , linux下为\n

$ cat onelife.txt | tr -d "\r"

$ cat onelife.txt|sed 's/\r//'

需要注意的是, tr 删除的是“”中所有的字符, 如tr -d “abc”  会删除所有a b c , 而不是abc 字符串


3. 如何删除双引号内(包括双引号)的字符串?

$ echo 'hello "123" world'|sed 's/".*"//'
hello  world

如果要删除整行,可以用sed  d 命令, grep -v 参数


4. 如何删除指定行? 如删除第一行

$ cat onelife.txt |sed '1d'


五、 插入文本


1. 行内插入字符,行首和行尾插入一个双引号, 行内的数字字符串插入单引号,如 hello 123 word  变成 “hello '123' world”?

$ echo 'hello 123 word' |sed -e 's/^/"/; s/$/"/; s/[0-9]\+/'\''&'\''/' 
"hello '123' word"

^表示行首, $表示行尾, &表示匹配成功的pattern


2. 插入一行。 

a.  在指定行插入一行:

cat onelife.txt |sed '2i "insert line"'

在第二行插入 “insertline”


b. 在匹配行之前、之后插入一行

cat onelife.txt |sed -e '/dreams/ i\#if 0' -e '/dreams/ a\#endif'

在包含dreams 行的前面插入 #if 0, 后面一行插入#endif

用来注释某句话,还是很有用的哈, 虽然很少程序员会这样注释多个这样的行。


六、 二进制文件处理

假设现在有一个二进制文件data.bin, 其中有两个字节内容


1. 如果提取二进制文件中的第二个字节内容?

$ od -t xC dss.dat |head -1 |cut -d' ' -f3
02

od 命令用于查看二进制文件,默认为八进制显示,也可以指定显示格式, -t  x 表示以十六进制显示。C 字符表示按字符分割, 默认地od 将4个字节一起作为分割,也就是会将0002 当成一个单元来显示。

也可以直接用hexdump来查看十六进制文件。

$ hexdump -C dss.dat
00000000  00 02                                             |..|
00000002

2. 如何将文件分割?

先生成一个测试文件

dd if=/dev/zero bs=100k count=1 of=data.file

这个测试文件全是0, 在当前目录下 data.file


按大小分割:

$split -b 10k data.file -d -a 4 split_

 -d 表示后缀为数字, -a 表示后缀长度为4 ,指定文件名前缀为 split_ 

按行分割:

split -l data.file -d -a 2 split_

由于这个文件实际大小为0, wc 统计到的行数也为0, 因此这个 -l参数无效。


你可能感兴趣的:(shell脚本)