您的位置:首页 > 运维架构 > Shell

shell 字符串处理汇总(查找,替换等等)

2014-11-21 12:23 288 查看
字符串:

简称“串”。有限字符的序列。数据元素为字符的线性表,是一种数据的逻辑结构。在计算机中可有不同的存储结构。在串上可进行求子串、插入字符、删除字符、置换字符等运算。

字符:

计算机程序设计及操作时使用的符号。包括字母、数字、空格符、提示符及各种专用字符等。

一般字符的运算包括:

第一、找出字符或者字符串的类型,是数字、字母还是其他特定字符,是可打印字符,还是不可打印字符(一些控制字符)。

第二、找出组成字符串的字符个数和字符串的存储结构(比如数组)。

第三、对串的常规操作:求子串、插入字符、删除字符、置换字符、字符串的比较等。

第四、对串的一些比较复杂而有趣的操作,这里将在最后介绍一些有趣的范例。

1.字符串的属性

字符有可能是数字、字母、空格、其他特殊字符,而字符串有可能是它们任何一种或者多种的组合,在组合之后还可能形成一个具有特定意义的字符串,诸如邮件地址,URL地址等。
概要示例:下面我们来看看如何判断字符的类型。

说明:
[1]/dev/null和/dev/zero是非常有趣的两个设备,它们都犹如一个黑洞,什么东西掉进去都会消失殆尽;后者则是一个能源箱,你总能从那里取到0,直到你退出。
[2][[:space:]]是grep用于匹配空格或者TAB键类型字符串的一种标记,其他类似的标记请查看grep的帮助,mangrep。
[3]上面都是用grep来进行模式匹配,实际上sed,awk都可以用来做模式匹配,关于匹配中用到的正则匹配模式知识,大家可以参考正则匹配模式,更多相关资料请看参考资料。
[4]如果仅仅想判断字符串是否为空,即判断字符串的长度是否为零,那么可以简单的通过test命令的-z选项来判断,具体用法见test命令,mantest.

概要示例:判断字符是否可打印?如何控制字符在终端的显示。

更多关于字符在终端的显示控制方法,请参考资料[20]和字符显示实例[21]:用shell实现的一个动态时钟。

1.2字符串的长度

概要示例:除了组成字符串的字符类型外,字符串还有哪些属性呢?组成字符串的字符个数。下面我们来计算字符串的长度,即所有字符的个数,并简单介绍几种求字符串中指定字符个数的方法。

说明:

${}操作符在Bash里头一个“大牛”,能胜任相当多的工作,具体就看看网中人的《shell十三问》之《Shell十三问》之"$(())與$()還有${}差在哪?"吧。

在一个文本文件的段落之间插入空行

索引

exprindex$string$substring

在字符串$string中所匹配到的$substring第一次所出现的位置.

这与C语言中的strchr()函数非常相似.

1.3字符串的存储

在我们看来,字符串是一连串的字符而已,但是为了操作方便,我们往往可以让字符串呈现出一定的结构。在这里,我们不关心字符串在内存中的实际存储结构,仅仅关系它呈现出来的逻辑结构。比如,这样一个字符串:"getthelengthofme",我们可以从不同的方面来呈现它。

1.3.1通过字符在串中的位置来呈现它

这样我们就可以通过指定位置来找到某个子串。这在c语言里头通常可以利用指针来做。而在shell编程中,有很多可用的工具,诸如expr,awk都提供了类似的方法来实现子串的查询动作。两者都几乎支持模式匹配(match)和完全匹配(index)。这在后面的字符串操作中将详细介绍。

匹配字符串开头的子串长度

exprmatch"$string"'$substring'

$substring是一个正则表达式.

expr"$string":'$substring'

$substring是一个正则表达式.

1.3.2根据某个分割符来取得字符串的各个部分

这里最常见的就是行分割符、空格或者TAB分割符了,前者用来当行号,我们似乎已经司空见惯了,因为我们的编辑器就这样“莫名”地处理着行分割符(在unix下为\n,在其他系统下有一些不同,比如windows下为\r\n)。而空格或者TAB键经常用来分割数据库的各个字段,这似乎也是司空见惯的事情。

正是因为这样,所以产生了大量优秀的行编辑工具,诸如grep,awk,sed等。在“行内”(姑且这么说吧,就是处理单行,即字符串里头不再包含行分割符)的字符串分割方面,cut和awk提供了非常优越的“行内”(处理单行)处理能力。

1.3.3更方便地处理用分割符分割好的各个部分

同样是用到分割符,但为了更方便的操作分割以后的字符串的各个部分,我们抽象了“数组”这么一个数据结构,从而让我们更加方便地通过下标来获取某个指定的部分。bash提供了这么一种数据结构,而优秀的awk也同样提供了它,我们这里将简单介绍它们的用法。

概要示例:利用数组存放"getthelengthofme"的用空格分开的各个部分。

2.字符串常规操作

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

2.1取子串

概要示例:取子串的方法主要有:直接到指定位置求子串,字符匹配求子串。

${string:position}

在$string中从位置$position开始提取子串.

如果$string是"*"或者"@",那么将会提取从位置$position开始的位置参数.[1]

${string:position:length}

在$string中从位置$position开始提取$length长度的子串.

如果$string参数是"*"或"@",那么将会从$position位置开始提取$length个位置参数,但是由于可能没有$length个位置参数了,那么就有几个位置参数就提取几个位置参数.

exprsubstr$string$position$length

在$string中从$position开始提取$length长度的子串.

exprmatch"$string"'\($substring\)'

从$string的开始位置提取$substring,$substring是正则表达式.

expr"$string":'\($substring\)'

从$string的开始位置提取$substring,$substring是正则表达式.

exprmatch"$string"'.*\($substring\)'

从$string的结尾提取$substring,$substring是正则表达式.

expr"$string":'.*\($substring\)'

从$string的结尾提取$substring,$substring是正则表达式.

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

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

2.2.查询子串

概要示例:子串查询包括:返回符合某个模式的子串本身和返回子串在目标串中的位置。

准备:在进行下面的操作之前,请把http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1385.html链接中的内容复制到一个文本text里头,用于下面的操作。

//查询子串在目标串中的位置
$var="getthelengthofme"
$exprindex"$var"t#貌似仅仅可以返回某个字符或者多个字符中第一个字符出现的位置
3
$echo$var|awk'{printf("%d\n",match($0,"the"));}'#awk却能找出字串,match还可以匹配正则表达式
5

//查询子串,返回包含子串的行(awk,sed都可以实现这些功能,但是grep最擅长)
$grep"consistsof"text#查询text文件包含consistsof的行,并打印这些行
$grep"consists[[:space:]]of"-n-Htext#打印文件名,子串所在行的行号和该行的内容
$grep"consists[[:space:]]of"-n-otext#仅仅打印行号和匹配到的子串本身的内容
$awk'/consistsof/{printf("%s:%d:%s\n",FILENAME,FNR,$0)}'text#看到没?和grep的结果一样
$sed-n-e'/consistsof/=;/consistsof/p'text#同样可以打印行号

2.3.子串替换

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

${string/substring/replacement}

使用$replacement来替换第一个匹配的$substring.

${string//substring/replacement}

使用$replacement来替换所有匹配的$substring.

${string/#substring/replacement}

如果$substring匹配$string的开头部分,那么就用$replacement来替换$substring.

${string/%substring/replacement}

如果$substring匹配$string的结尾部分,那么就用$replacement来替换$substring.

概要示例:下面我们把变量var中的空格替换成下划线看看。

//用{}运算符,还记得么?网中人的教程。
$var="getthelengthofme"
$echo${var//_}#把第一个空格替换成下划线
get_thelengthofme
$echo${var///_}#把所有空格都替换成了下划线了
get_the_length_of_me

//用awk,awk提供了转换的最小替换函数sub和全局替换函数gsub,类似/和//
$echo$var|awk'{sub("","_",$0);printf("%s\n",$0);}'
get_thelengthofme
$echo$var|awk'{gsub("","_",$0);printf("%s\n",$0);}'
get_the_length_of_me

//用sed了,子串替换可是sed的特长
$echo$var|sed-e's//_/'#s<=substitude
get_thelengthofme
$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]'#这个可有意思了,把所有小写字母都替换为大写字母
GETTHELENGTHOFME

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

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

在替换子串时再提供一些tr的用法:

tr示例:

2.4.插入子串

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

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

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

//用{}
$var="getthelengthofme"
$echo${var//_}#在指定字符串之前插入一个字符串
get_thelengthofme
$echo${var///_}
get_the_length_of_me
$echo${var//_}#在指定字符串之后插入一个字符串
get_thelengthofme
$echo${var///_}
get_the_length_of_me

//其他的还用演示么?这里主要介绍sed怎么用来插入字符吧,因为它的标签功能很有趣
$echo$var|sed-e's/\(\)/_\1/'#\(和\)将不匹配到的字符串存放为一个标签,按匹配顺序为\1,\2...
get_thelengthofme
$echo$var|sed-e's/\(\)/_\1/g'
get_the_length_of_me
$echo$var|sed-e's/\(\)/\1_/'
get_thelengthofme
$echo$var|sed-e's/\(\)/\1_/g'
get_the_length_of_me

//看看sed的标签的顺序是不是\1,\2....,看到没?\2和\1掉换位置后,the和get的位置掉换了
$echo$var|sed-e's/\([a-z]*\)\([a-z]*\)/\2\1/g'
thegetoflengthme
//sed还有专门的插入指令,a和i,分别表示在匹配的行后和行前插入指定字符
$echo$var|sed'/get/atest'
getthelengthofme
test
$echo$var|sed'/get/itest'
test
getthelengthofme

2.5.删除子串

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

概要示例::把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

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

2.6.子串比较

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

2.7.子串排序

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

$var="getthelengthofme"
$echo$var|tr'''\n'|sort#正序排
get
length
me
of
the
$echo$var|tr'''\n'|sort-r#反序排
the
of
me
length
get
2.7.子串进制转换

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

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

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

2.7.子串编码转换

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

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

$nihao_gb2312=$(echo"你好"|iconv-futf8-tgb2312)
$echo$nihao_gb2312
????
$nihao_utf8=$(echo$nihao_gb2312|iconv-fgb2312-tutf8)
$PS1="$"
$echo$nihao_utf8
你好

说明:我的终端默认编码是utf8,所以结果如上。

3.字符串操作范例

实际上,在用Bash编程时,大部分时间都是在处理字符串,因此把这一节熟练掌握非常重要。

3.1处理一个非常有意义的字符串:URL地址

范例演示:处理URL地址

URL地址(URL(UniformResoureLocator:统一资源定位器)是WWW页的地址)几乎是我们日常生活的玩伴,我们已经到了无法离开它的地步啦,对它的操作很多,包括判断URL地址的有效性,截取地址的各个部分(服务器类型、服务器地址、端口、路径等)并对各个部分进行进一步的操作。

下面我们来具体处理这个URL地址:ftp://anonymous:ftp@mirror.lzu.edu.cn/software/scim-1.4.7.tar.gz
$url="ftp://anonymous:ftp@mirror.lzu.edu.cn/software/scim-1.4.7.tar.gz"
//匹配URL地址,判断URL地址的有效性
$echo$url|grep"ftp://[a-z]*:[a-z]*@[a-z\./-]*"
//截取服务器类型
$echo${url%%:*}
ftp
$echo$url|cut-d":"-f1
ftp
//截取域名
$tmp=${url##*@};echo${tmp%%/*}
mirror.lzu.edu.cn
//截取路径
$tmp=${url##*@};echo${tmp%/*}
mirror.lzu.edu.cn/software
//截取文件名
$basename$url
scim-1.4.7.tar.gz
$echo${url##*/}
scim-1.4.7.tar.gz
//截取文件类型(扩展名)
$echo$url|sed-e's/.*[0-9].\(.*\)/\1/g'
tar.gz

有了上面的知识,我们就可以非常容易地进行这些工作啦:修改某个文件的文件名,比如调整它的编码,下载某个网页里头的所有pdf文档等。这些就作为练习自己做吧,如果遇到问题,可以在回帖交流。相应地可以参考这个例子:

[1]用脚本下载某个网页中的英文原著(pdf文档)http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1228.html
3.2处理格式化的文本:/etc/passwd

平时做工作,大多数时候处理的都是一些“格式化”的文本,比如类似/etc/passwd这样的有固定行和列的文本,也有类似tree命令输出的那种具有树形结构的文本,当然还有其他具有特定结构的文本。

关于树状结构的文本的处理,可以考虑看看这两个例子:
[1]用AWK转换树形数据成关系表http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1260.html[2]用Graphviz进行可视化操作──绘制函数调用关系图http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1425.html实际上,只要把握好特性结构的一些特点,并根据具体的应用场合,处理起来就不会困难。

下面我们来介绍具体有固定行和列的文本的操作,以/etc/passwd文件为例。关于这个文件的帮忙和用户,请通过man5passwd查看。下面我们对这个文件以及相关的文件进行一些有意义的操作。

//选取/etc/passwd文件中的用户名和组ID两列
$cat/etc/passwd|cut-d":"-f1,4
//选取/etc/group文件中的组名和组ID两列
$cat/etc/group|cut-d":"-f1,3
//如果想找出所有用户所在的组,怎么办?
$join-o1.1,2.1-t":"-14-23/etc/passwd/etc/group
root:root
bin:bin
daemon:daemon
adm:adm
lp:lp
pop:pop
nobody:nogroup
falcon:users
//先解释一下:join命令用来连接两个文件,有点类似于数据库的两个表的连接。-t指定分割符,"-14-23"指定按照第一个文件的第4列和第二个文件的第3列,即组ID进行连接,"-o1.1,2.1"表示仅仅输出第一个文件的第一列和第二个文件的第一列,这样就得到了我们要的结果,不过,可惜的是,这个结果并不准确,再进行下面的操作,你就会发现:
$cat/etc/passwd|sort-t":"-n-k4>/tmp/passwd
$cat/etc/group|sort-t":"-n-k3>/tmp/group
$join-o1.1,2.1-t":"-14-23/tmp/passwd/tmp/group
halt:root
operator:root
root:root
shutdown:root
sync:root
bin:bin
daemon:daemon
adm:adm
lp:lp
pop:pop
nobody:nogroup
falcon:users
games:users
//可以看到这个结果才是正确的,所以以后使用join千万要注意这个问题,否则采取更保守的做法似乎更能保证正确性,更多关于文件连接的讨论见参考资料[14]

上面涉及到了处理某格式化行中的指定列,包括截取(如SQL的select用法),连接(如SQL的join用法),排序(如SQL的orderby用法),都可以通过指定分割符来拆分某个格式化的行,另外,“截取”的做法还有很多,不光是cut,awk,甚至通过IFS指定分割符的read命令也可以做到,例如:

$IFS=":";cat/etc/group|whilereadC1C2C3C4;doecho$C1$C3;done

因此,熟悉这些用法,我们的工作将变得非常灵活有趣。

到这里,需要做一个简单的练习,如何把按照列对应的用户名和用户ID转换成按照行对应的,即把类似下面的数据:

$cat/etc/passwd|cut-d":"-f1,3--output-delimiter=""
root0
bin1
daemon2

转换成:

$cata
rootbindaemon
012

并转换回去,有什么办法呢?记得诸如tr,paste,split等命令都可以使用。

参考方法:
*正转换:先截取用户名一列存入文件user,再截取用户ID存入id,再把两个文件用paste-s命令连在一起,这样就完成了正转换。
*逆转换:先把正转换得到的结果用split-1拆分成两个文件,再把两个拆分后的文件用tr把分割符"\t"替换成"\n",只有用paste命令把两个文件连在一起,这样就完成了逆转换。

在做shell批处理程序时候,经常会涉及到字符串相关操作。有很多命令语句,如:awk,sed都可以做字符串各种操作。其实shell内置一系列操作符号,可以达到类似效果,大家知道,使用内部操作符会省略启动外部程序等时间,因此速度会非常的快。

一、判断读取字符串值

表达式含义
${var}变量var的值,与$var相同
${var-DEFAULT}如果var没有被声明,那么就以$DEFAULT作为其值*
${var:-DEFAULT}如果var没有被声明,或者其值为空,那么就以$DEFAULT作为其值*
${var=DEFAULT}如果var没有被声明,那么就以$DEFAULT作为其值*
${var:=DEFAULT}如果var没有被声明,或者其值为空,那么就以$DEFAULT作为其值*
${var+OTHER}如果var声明了,那么其值就是$OTHER,否则就为null字符串
${var:+OTHER}如果var被设置了,那么其值就是$OTHER,否则就为null字符串
${var?ERR_MSG}如果var没被声明,那么就打印$ERR_MSG*
${var:?ERR_MSG}如果var没被设置,那么就打印$ERR_MSG*
${!varprefix*}匹配之前所有以varprefix开头进行声明的变量
${!varprefix@}匹配之前所有以varprefix开头进行声明的变量
加入了“*”的意思是:如果变量var已经被设置的话,那么其值就是$var.

表达式含义
${#string}$string的长度
${string:position}在$string中,从位置$position开始提取子串
${string:position:length}在$string中,从位置$position开始提取长度为$length的子串
${string#substring}从变量$string的开头,删除最短匹配$substring的子串
${string##substring}从变量$string的开头,删除最长匹配$substring的子串
${string%substring}从变量$string的结尾,删除最短匹配$substring的子串
${string%%substring}从变量$string的结尾,删除最长匹配$substring的子串
${string/substring/replacement}使用$replacement,来代替第一个匹配的$substring
${string//substring/replacement}使用$replacement,代替所有匹配的$substring
${string/#substring/replacement}如果$string的前缀匹配$substring,那么就用$replacement来代替匹配到的$substring
${string/%substring/replacement}如果$string的后缀匹配$substring,那么就用$replacement来代替匹配到的$substring
说明:"*$substring”可以是一个正则表达式.

性能比较



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: