介绍字符串处理是shell脚本的关键部分,因为shell脚本的主要工作是处理文件或其他程序,数据格式通常是文本,并且处理没有统一格式的文本文件非常复杂,许多shell命令处理文本。如果使用bash处理文本,由于其功能有限,经常需要调用awk、sed、grep、cat、cut、comm、dirname、basename、expr、sort、uniq、head、tail、tac、tr等命令,wc,没注意脚本就变成指挥党了。命令的用法不一样,有的很简单(比如cut、tr、wc),看man就可以使用;有些非常复杂(比如awk、sed、grep),用了很多年你基本上只会用到一小部分功能。互相配合也很容易出现各种问题(比如可怕的空格和换行),很难调试,调用命令的开销也很大。而且如果zsh用得好,可以大大减少这些命令的使用(不能完全避免,因为有些场景确实更适合用这样的命令处理,比如处理一个大的文本文件),脚本的性能可以有很大的提升(主要是因为减少了进程启动的开销,比如一个简单的字符串替换,调用外部命令的时间比内部实现长几个数量级)。但是正因为如此,zsh的字符串处理功能非常复杂。可以说zsh的字符串处理功能比大多数编程语言自带的字符串函数库或者类库都要强大(在不依赖外部命令的情况下)。同时,各种用法也相当怪异,很多时候简洁性和可读性之间存在矛盾,很难平衡。shell的使用场景决定了不能牺牲简单性。即使使用Python这样比较简洁的语言来处理字符串,很多时候也只能写冗长的代码,而zsh往往一行就可以搞定(有人可能会想到perl,perl在处理文本方面确实有明显的优势,但是使用perl也比较费力),如果再加上适当的使用外部命令,基本可以应付大部分的字符串处理场景。因为字符串处理的内容比较丰富,我会分多篇来写。本文只涉及最基本最常用的字符串操作,包括字符串的拼接、切片、截断、查找、遍历、替换、匹配、大小写转换、分离等。字符串的定义和简单的比较,我在上一篇文章中已经提到,现在进入正题。字符串长度%str=abcde%echo$#str5#读取函数或脚本第一个参数的长度%echo$#1字符串拼接%str1=abc%str2=def%str2+=$str1%echo$str2defabc%str3=$str1$str2abcdefabcstringslicingstringslicing之前也提到过,这里简单回顾一下。逗号前后不能有空格。字符位置从1开始计算。%str=abcdef%echo$str[2,4]bcd%echo$str[2,-1]bcdef#$1是文件或函数的第一个参数echo${1[2,4]}stringslice也有另一种风格的方法,bash风格,它做同样的事情。通常不需要使用它,并且由于字符位置是从0开始计算的,因此可能会造成混淆。%str=abcdef%echo${str:1:3}bcd%echo${str:1:-1}bcde字符串截断%str=abcdeabcde#删除左端匹配的内容,最小匹配%echo${str#*b}cdeabcde#删除右端匹配的内容,最小匹配%echo${str%d*}abcdeabc#删除左端匹配的内容,最大匹配%echo${str##*b}cde#删除右端匹配%echo${str%%d*}abcstring的内容找到子串位置。%str=abcdef#这里用的是大写的i,不是小写的L%echo$str[(I)cd]3#i是从右往左查找,没有找到就为0,方便判断%(($str[(I)cd]))&&echogoodgood#0%如果没有找到如果小于则返回数组的大小+1%echo$str[(i)cd]3%echo$str[(i)cdd]7遍历字符%str=abcd%fori({1..$#str}){>echo$str[i]>}abcd字符串替换按内容替换删除字符。%str=abcabc#只替换找到的第一个%echo${str/bc/ef}aefabc#删除第一个匹配的%echo${str/bc}aabc#替换所有找到的%echo${str//bc/ef}aefaef#删除所有匹配的%echo${str//bc}aa%str=abcABCabcABCabc#/#只从字符串开头开始匹配,${str/#abc}相同%echo${str/#abc/123}123ABCabcABCabc#/%只从字符串末尾开始匹配,echo${str/%abc}相同%echo${str/%abc/123}abcABCabcABC123%str=abc#如果匹配到,则会输出一个空字符串%echo${str:#ab*}#不匹配则输出原字符串%echo${str:#ab}abc#加上(M)后效果相反%echo${(M)str:#ab}按位置删除字符。%str=abcdef#删除指定位置的字符%str[1]=%echo$strbcdef#多个%str[2,4]=%echo$strbf可以按位置替换字符。%str=abcdefg#一对一替换%str[2]=1%echo$stra1cdefg#多对多(包括一对多和多对一)字符可以替换,数字两边的字符不必相同。#将第二个和第三个字符替换为2345%str[2,3]=2345%echo$stra2345defg判断字符串变量是否存在如果使用[["$strxx"==""]],则无法区分是否存在变量是没有定义还是内容为空,在某些情况下需要区分两者。%(($+strxx))&&echogood%strxx=""%(($+strxx))&&echogoodgood(($+var))也可以用来判断其他类型的变量,如果变量存在则返回真(0),否则返回假(1)。字符串匹配判断判断是否包含一个字符串。%str1=abcd%str2=bc%[[$str1==*$str2*]]&&echogoodgood正则表达式匹配。%str=abc55def#对于少量字符串,尽量不要用grep#本文不讲正则表达式格式相关内容#另外,zsh有专门的正则表达式模块%[[$str=~"c[0-9]{2}\de"]]&&echoaacaseconversion%str="ABCDEabcde"#转为大写,(U)和:u效果一样%echo${(U)str}---${str:u}ABCDEABCDE---ABCDEABCDE#转为小写,(L)和:l效果相同%echo${(L)str}---${str:l}abcdeabcde---abcdeabcde#将首字母转为大写%echo${(C)str}abcdeabcde目录文件名截取%filepath=/a/b/c.x#:h为目录名,即前面的部分最后一个/,如果没有/那么就是.%echo${filepath:h}/a/b#:t就是取文件名,也就是最后一个/之后的部分,如果没有/,是字符串本身%echo${filepath:t}c.x#:e是文件扩展名,即文件名中最后一个点之后的部分,如果没有点则为空%echo${filepath:e}x#:r是没有结尾扩展名的路径%echo${filepath:r}/a/b/cstringdelimiter#使用空格作为分隔符,多个空格只算一个分隔符%str='aabbccdd'%echo${str[(w)2]}bb%echo${str[(w)3]}cc#指定分隔符%str='aa--bb--cc'#如果delimiter是:使用其他字符作为左右边界,比如ws.:.%echo${str[(ws:--:)3]}cc多行字符串字符串定义可以跨行。%str="line1>line2"%echo$strline1line2将文件内容读入字符串#比使用str=$(catfilename)性能好得多str=$(
