5. 字符串常规操作

字符串操作包括取子串、查询子串、插入子串、删除子串、子串替换、子串比较、子串排序、子串进制转换、子串编码转换等。

取子串

取子串的方法主要有:

  • 直接到指定位置求子串
  • 字符匹配求子串

范例:按照位置取子串

比如从什么位置开始,取多少个字符

$ var="get the length of me"
$ echo ${var:0:3}
get
$ echo ${var:(-2)}   # 方向相反呢
me
$ echo `expr substr "$var" 5 3` #记得把$var引起来,否则expr会因为空格而解析错误
the
$ echo $var | awk '{printf("%s\n", substr($0, 9, 6))}'
length

awk$var 按照空格分开为多个变量,依次为 $1$2$3$4$5

$ echo $var | awk '{printf("%s\n", $1);}'
get
$ echo $var | awk '{printf("%s\n", $5);}'
me

差点略掉cut小工具,它用起来和awk类似,-d指定分割符,如同awk-F指定分割符一样;-f指定“域”,如同awk的$数字

$ echo $var | cut -d" " -f 5

范例:匹配字符求子串

用 Bash 内置支持求子串:

$ echo ${var%% *} #从右边开始计算,删除最左边的空格右边的所有字符
get
$ echo ${var% *} #从右边开始计算,删除第一个空格右边的所有字符
get the length of
$ echo ${var##* }  #从左边开始计算,删除最右边的空格左边的所有字符
me
$ echo ${var#* }  #从左边开始计算,删除第一个空格左边的所有字符
the length of me

删除所有 空格+字母组合 的字符串:

$ echo $var | sed 's/ [a-z]*//g'
get
$ echo $var | sed 's/[a-z]* //g'
me

sed 有按地址(行)打印(p)的功能,记得先用 tr 把空格换成行号:

$ echo $var | tr " " "\n" | sed -n 1p
get
$ echo $var | tr " " "\n" | sed -n 5p
me

tr 也可以用来取子串,它可以类似#% 来“拿掉”一些字符串来实现取子串:

$ echo $var | tr -d " "
getthelengthofme
$ echo $var | tr -cd "[a-z]" #把所有的空格都拿掉了,仅仅保留字母字符串,注意-c和-d的用法
getthelengthofme

说明:

  • %#删除字符的方向不一样,前者在右,后者在左,%%%### 的方向是前者是最大匹配,后者是最小匹配。(好的记忆方法见网中人的键盘记忆法:#$% 是键盘依次从左到右的三个键)
  • tr-c 选项是 complement 的缩写,即 invert,而 -d 选项是删除,tr -cd "[a-z]" 这样一来就变成保留所有的字母

对于字符串的截取,实际上还有一些命令,如果 headtail 等可以实现有意思的功能,可以截取某个字符串的前面、后面指定的行数或者字节数。例如:

$ echo "abcdefghijk" | head -c 4
abcd
$ echo -n "abcdefghijk" | tail -c 4
hijk

查询子串

子串查询包括:

  • 返回符合某个模式的子串本身
  • 返回子串在目标串中的位置

准备:在进行下面的操作之前,请准备一个文件 test.txt,里头有内容 "consists of",用于下面的范例。

范例:查询子串在目标串中的位置

expr index貌似仅仅可以返回某个字符或者多个字符中第一个字符出现的位置

$ var="get the length of me"
$ expr index "$var" t
3

awk却能找出字串,match还可以匹配正则表达式

$ echo $var | awk '{printf("%d\n", match($0,"the"));}'
5

范例:查询子串,返回包含子串的行

awksed 都可以实现这些功能,但是 grep 最擅长

$ grep "consists of" test.txt   # 查询文件包含consists of的行,并打印这些行
$ grep "consists[[:space:]]of" -n -H test.txt # 打印文件名,子串所在行的行号和该行的内容
$ grep "consists[[:space:]]of" -n -o test.txt # 仅仅打印行号和匹配到的子串本身的内容
$ awk '/consists of/{ printf("%s:%d:%s\n",FILENAME, FNR, $0)}' text  #看到没?和grep的结果一样
$ sed -n -e '/consists of/=;/consists of/p' text #同样可以打印行号

说明:

  • awkgrepsed 都能通过模式匹配查找指定字符串,但它们各有所长,将在后续章节中继续使用和比较它们,进而发现各自优点
  • 在这里姑且把文件内容当成了一个大的字符串,在后面章节中将专门介绍文件操作,所以对文件内容中存放字符串的操作将会有更深入的分析和介绍

子串替换

子串替换就是把某个指定的子串替换成其他的字符串,这里蕴含了“插入子串”和“删除子串”的操作。例如,想插入某个字符串到某个子串之前,就可以把原来的子串替换成”子串+新的字符串“,如果想删除某个子串,就把子串替换成空串。不过有些工具提供了一些专门的用法来做插入子串和删除子串的操作,所以呆伙还会专门介绍。另外,要想替换掉某个子串,一般都是先找到子串(查询子串),然后再把它替换掉,实质上很多工具在使用和设计上都体现了这么一点。

范例:把变量 var 中的空格替换成下划线

{} 运算符,还记得么?网中人的教程

$ var="get the length of me"
$ echo ${var/ /_}        #把第一个空格替换成下划线
get_the length of me
$ echo ${var// /_}       #把所有空格都替换成下划线
get_the_length_of_me

awkawk 提供了转换的最小替换函数 sub 和全局替换函数 gsub,类似 ///

$ echo $var | awk '{sub(" ", "_", $0); printf("%s\n", $0);}'
get_the length of me
$ echo $var | awk '{gsub(" ", "_", $0); printf("%s\n", $0);}'
get_the_length_of_me

sed,子串替换可是 sed 的特长:

$ echo $var | sed -e 's/ /_/'    #s <= substitude
get_the length of me
$ echo $var | sed -e 's/ /_/g'   #看到没有,简短两个命令就实现了最小匹配和最大匹配g <= global
get_the_length_of_me

有忘记 tr 命令么?可以用替换单个字符的:

$ echo $var | tr " " "_"
get_the_length_of_me
$ echo $var | tr '[a-z]' '[A-Z]'   #这个可有意思了,把所有小写字母都替换为大写字母
GET THE LENGTH OF ME

说明: sed 还有很有趣的标签用法呢,下面再介绍吧。

有一种比较有意思的字符串替换是:整个文件行的倒置,这个可以通过 tac 命令实现,它会把文件中所有的行全部倒转过来。在某种意义上来说,排序实际上也是一个字符串替换。

插入子串

在指定位置插入子串,这个位置可能是某个子串的位置,也可能是从某个文件开头算起的某个长度。通过上面的练习,我们发现这两者之间实际上是类似的。

公式:插入子串=把"old子串"替换成"old子串+new子串"或者"new子串+old子串"

范例:在 var 字符串的空格之前或之后插入一个下划线

用{}:

$ var="get the length of me"
$ echo ${var/ /_ }        #在指定字符串之前插入一个字符串
get_ the length of me
$ echo ${var// /_ }
get_ the_ length_ of_ me
$ echo ${var/ / _}        #在指定字符串之后插入一个字符串
get _the length of me
$ echo ${var// / _}
get _the _length _of _me

其他的还用演示么?这里主要介绍sed怎么用来插入字符吧,因为它的标签功能很有趣 说明:() 将匹配到的字符串存放为一个标签,按匹配顺序为\1,\2...

$ echo $var | sed -e 's/\( \)/_\1/'
get_ the length of me
$ echo $var | sed -e 's/\( \)/_\1/g'
get_ the_ length_ of_ me
$ echo $var | sed -e 's/\( \)/\1_/'
get _the length of me
$ echo $var | sed -e 's/\( \)/\1_/g'
get _the _length _of _me

看看 sed 的标签的顺序是不是 \1,\2...,看到没?\2\1 调换位置后,theget 的位置掉换了:

$ echo $var | sed -e 's/\([a-z]*\) \([a-z]*\) /\2 \1 /g'
the get of length me

sed 还有专门的插入指令,ai,分别表示在匹配的行后和行前插入指定字符

$ echo $var | sed '/get/a test'
get the length of me
test
$ echo $var | sed '/get/i test'
test
get the length of me

删除子串

删除子串:应该很简单了吧,把子串替换成“空”(什么都没有)不就变成了删除么。还是来简单复习一下替换吧。

范例:把 var 字符串中所有的空格给删除掉。

鼓励:这样一替换不知道变成什么单词啦,谁认得呢?但是中文却是连在一起的,所以中文有多难,你想到了么?原来你也是个语言天才,而英语并不可怕,你有学会它的天赋,只要有这个打算。

再用 {}

$ echo ${var// /}
getthelengthofme

再用 awk

$ echo $var | awk '{gsub(" ","",$0); printf("%s\n", $0);}'

再用 sed

$ echo $var | sed 's/ //g'
getthelengthofme

还有更简单的 tr 命令,tr 也可以把空格给删除掉,看

$ echo $var | tr -d " "
getthelengthofme

如果要删除第一个空格后面所有的字符串该怎么办呢?还记得 {}#% 用法么?如果不记得,回到这节的开头开始复习吧。(实际上删除子串和取子串未尝不是两种互补的运算呢,删除掉某些不想要的子串,也就同时取得另外那些想要的子串——这个世界就是一个“二元”的世界,非常有趣)

子串比较

这个很简单:还记得 test 命令的用法么? help test 。它可以用来判断两个字符串是否相等。另外,有发现“字符串是否相等”和“字符串能否跟另外一个字符串匹配 " 两个问题之间的关系吗?如果两个字符串完全匹配,那么这两个字符串就相等了。所以呢,上面用到的字符串匹配方法,也同样可以用到这里。

子串排序

差点忘记这个重要内容了,子串排序可是经常用到,常见的有按字母序、数字序等正序或反序排列。 sort 命令可以用来做这个工作,它和其他行处理命令一样,是按行操作的,另外,它类似 cutawk,可以指定分割符,并指定需要排序的列。

$ var="get the length of me"
$ echo $var | tr ' ' '\n' | sort   #正序排
get
length
me
of
the
$ echo $var | tr ' ' '\n' | sort -r #反序排
the
of
me
length
get
$ cat > data.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
41 45 44 44 26 44 42 20 20 38 37 25 45 45 45
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
44 20 30 39 35 38 38 28 25 30 36 20 24 32 33
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
41 33 51 39 20 20 44 37 38 39 42 40 37 50 50
46 47 48 49 50 51 52 53 54 55 56
42 43 41 42 45 42 19 39 75 17 17
$ cat data.txt | sort -k 2 -n
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
44 20 30 39 35 38 38 28 25 30 36 20 24 32 33
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
41 33 51 39 20 20 44 37 38 39 42 40 37 50 50
42 43 41 42 45 42 19 39 75 17 17
41 45 44 44 26 44 42 20 20 38 37 25 45 45 45
46 47 48 49 50 51 52 53 54 55 56

子串进制转换

如果字母和数字字符用来计数,那么就存在进制转换的问题。在 数值运算 一节,已经介绍了 bc 命令,这里再复习一下。

$ echo "ibase=10;obase=16;10" | bc
A

说明: ibase 指定输入进制,obase 指出输出进制,这样通过调整 ibaseobase,你想怎么转就怎么转啦!

子串编码转换

什么是字符编码?这个就不用介绍了吧,看过那些乱七八糟显示的网页么?大多是因为浏览器显示时的”编码“和网页实际采用的”编码“不一致导致的。字符编码通常是指:把一序列”可打印“字符转换成二进制表示,而字符解码呢则是执行相反的过程,如果这两个过程不匹配,则出现了所谓的”乱码“。

为了解决”乱码“问题呢?就需要进行编码转换。在 Linux 下,我们可以使用 iconv 这个工具来进行相关操作。这样的情况经常在不同的操作系统之间移动文件,不同的编辑器之间交换文件的时候遇到,目前在 Windows 下常用的汉字编码是 gb2312,而在 Linux 下则大多采用 utf8

$ nihao_utf8=$(echo "你好")
$ nihao_gb2312=$(echo $nihao_utf8 | iconv -f utf8 -t gb2312)