Bash编程(6) String操作
1. 拼接
1) 简单的字符串拼接如:PATH=$PATH:$HOME/bin。如果拼接的字符串包含空格或特殊字符,需要使用双引号括起,如:
var=$HOME/bin # 注释并不是赋值的一部分 var="$HOME/bin # but this is" # bash 3.1后,可以使用+=拼接(+=也可用于数组相加) var=abc $ var=abc $ var+=xyz $ echo "$var" abcxyz
注意:+=的性能较直接拼接的效率高,测试如下:
$ var=; time for i in {1..10000}; do var=${var}foo; done; real 0m1.251s user 0m1.144s sys 0m0.104s $ var=; time for i in {1..10000}; do var+=foo; done; real 0m0.156s user 0m0.156s sys 0m0.000s
2) 重复字符到指定长度
_repeat(){ #@ 功能:重复字符串到指定长度 _REPEAT= while (( ${#_REPEAT} < $2)) do _REPEAT+=$1 done } $ _repeat % 40 $ printf "%s\n" "$_REPEAT" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
通过拼接在每个循环中拼接多个实例来提高函数速度:
_repeat(){ #@ 功能:重复字符串到指定长度 _REPEAT=$1 while (( ${#_REPEAT} < $2)) do _REPEAT=$_REPEAT$_REPEAT$_REPEAT done _REPEAT=${_REPEAT:-:$2} ## 裁剪指定长度字符串 } repeat(){ _repeat "$@" printf "%s\n" "$_REPEAT" } alert(){ #@ 功能: 打印包含边界及鸣响的警告信息 _repeat "${2:-#}" $(( ${#1} + 8 )) printf '\a%s\n' "$_REPEAT" ## \a = BEL printf '%2.2s %s %2.2s\n' "$_REPEAT" "$1" "$_REPEAT" printf '%s\n' "$_REPEAT" } $ alert "Do you really want to detele all your files?" ################################################################################# ## Do you really want to detele all your files? ## #################################################################################
2. 字符处理
没有直接的参数扩展来提供字符串的首字符或尾字符,可借助通配符问号?,以及字符串截取来抽取首字符或尾字符。
$ var=strip $ allbutfirst=${var#?} # 去除首字符 $ allbutlast=${var%?} # 去除尾字符 $ printf "%8s %8s\n" "$allbutfirst" "$allbutlast" trip stri $ first=${var%"$allbutfirst"} $ last=${var#"$allbutlast"} $ printf "%8s %8s\n" "$first" "$last" s p ## 小测试 $ while [ -n "$var" ]; do temp=${var#?}; char=${var%"$temp"}; printf "%s\n" "$char"; var=$temp; done ## 将字符串的每个字母按行打印 s t r i p $ while [ -n "$var" ]; do temp=${var%?}; char=${var#"$temp"}; printf "%s\n" "$char"; var=$temp; done ## 将字符串的每个字母反向打印 p i r t s
也可以通过"%c"获取字符串首字符
$ printf -v first "%c" "$var" # 将原本输出到标注输出的信息赋值给first $ echo $first s
3. 大小写转换
Bourne shell中,字符转换可以通过tr命令完成,tr作用:将第一个参数中的字符转换成对应的第二个参数中的字符
$ echo abcdefgh | tr ceh CEH ## c=>C,e=>E,h=>H abCdEfgH $ echo abcdefgh | tr ceh HEC ## c=>H,e=>E,h->C abHdEfgC $ echo touchdown | tr 'a-z' 'A-Z' ## 将小写转换为大写 TOUCHDOWN
POSIX shell中,可以通过参数扩展来完成。
to_upper(){ case $1 in a*) _UPR=A;; b*) _UPR=B;; c*) _UPR=C;; d*) _UPR=D;; e*) _UPR=E;; f*) _UPR=F;; g*) _UPR=G;; h*) _UPR=H;; i*) _UPR=I;; j*) _UPR=J;; k*) _UPR=K;; l*) _UPR=L;; m*) _UPR=M;; n*) _UPR=N;; o*) _UPR=O;; p*) _UPR=P;; q*) _UPR=Q;; r*) _UPR=R;; s*) _UPR=S;; t*) _UPR=T;; u*) _UPR=U;; v*) _UPR=V;; w*) _UPR=W;; x*) _UPR=X;; y*) _UPR=Y;; z*) _UPR=X;; *) _UPR=${1%${1#?}};; esac } $word=function $to_upper "$word" $printf "%c%s\n" "$_UPR" "${word#?}" Function ## 将所有字符转换为大写 _upword(){ local word=$1 while [ -n "$word" ] ## 循环直到$word为空 do to_upper "$word" _UPWORD=$_UPWORD$_UPR word=${word#?} ## 移除$word中的首字母 done } upword(){ _upword "$@" printf "%s\n" "$_UPWORD" }
4. 比较内容且不需考虑大小写
当输入为单个字母,例如请求Y或N,可以使用逻辑运算符或(|)或者方括号([])对大小写进行选择。
read ok case $ok in y|Y) echo "Great!" ;; n|N) echo Good-bye; exit 1 ;; *) echo Invalid entry ;; esac read ok case $ok in [yY]) echo "Great!" ;; [nN]) echo Good-bye; exit 1 ;; *) echo Invalid entry ;; esac
当输入较长时,以上方法需要将所有的可能组合进行展示,这样的方法较为繁琐。
## 针对于"|",需要列出所有组合 jan | jaN | jAn | jAN | Jan | JAn | JAN) echo "Great!" ;; ## 对于"[]",脚本不宜阅读 read monthname case $monthname in [Jj][Aa][Nn]*) month=1 ;; [Ff][Ee][Bb]*) month=2 ;; ## 输入剩余的月份 [Dd][Ee][Cc]*) month=12 ;; [1-9]1[0-2] month=$monthname ;; 考虑输入数字的情况 *) echo "Invalid month: $monthname" >&2 ;; esac
较好的方法是将输入统一转换为大写或小写再进行比较:
_upword "$monthname" case _UPWORD in JAN*) month=1 ;; FEB*) month=2 ;; ## 输入剩余的月份 DEC*) month=12 ;; [0-9]|1[0-2]) month=$monthname *) echo "Invalid month: $monthname" >&2 ;; esac
bash 4.*中字符转换为大写,也可采用${monthname^^}执行
5. 检查变量名的有效性
检查变量名是否满足仅包含字母、数字和下划线,且只能以字母和下划线开头。
validname(){ case $1 in [!a-zA-Z_]* | *[!a-zA-Z0-9_]*) return 1;; esac } for name in name1 2var first.name first_name last-name do validname "$name" && echo " valid: $name" || echo "invalid: $name" done valid: name1 invalid: 2var invalid: first.name valid: first_name invalid: last-name
6. 字符串插入
_insert_string(){ #@功能: 在字符串的指定位置插入字符串 local insert_string_dflt=2 ## 默认的插入位置 local string=$1 ## 被插入的字符串 local i_string=$2 ## 待插入字符串 local i_pos=${3:-${insert_string_dflt:-2}} ## 插入位置 local left right left=${string:0:$(( $i_pos -1 ))} right=${string:$(( $i_pos -1 ))} _insert_string=$left$i_string$right } insert_string(){ _insert_string "$@" && printf "%s\n" "$_insert_string" } $ insert_string poplar u 4 popular $ insert_string show ad 3 shadow $ insert_string tail ops ## 使用默认位置 topsail
7. 覆盖
在一个字符串上覆盖另一个字符串。
_overlay(){ local string=$1 local sub=$2 local start=$3 local left right left=${string:0:start-1} right=${string:start+${#sub}-1} _OVERLAY=$left$sub$right } overlay(){ _overlay "$@" && printf "%s\n" "$_OVERLAY" } $ { > overlay pony b 1 > overlay pony u 2 > overlay pony s 3 > overlay pony d 4 > } bony puny posy pond
8. 裁剪不想要的字符
字符串首尾的空格可以通过循环和条件判断完成。
var=" John " while : ## 无限循环 do case $var in ' '*) var=${var#?} ;; ## 如果字符串以空格开始,则移除 *' ') var=${var%?} ;; ## 如果字符串以空格结尾,则移除 *) break; ## 当字符串的头部或尾部均无空格,则退出循环 esac done
更有效的方法是找到首尾最长待删除的空格,然后从原始字符串中删除。
var=" John " printf "%s|%s\n" "$var" "${#var}" rightspaces=${var##*[! ]} ## 删除一切直到最后一个非空值 printf "%s|%s\n" "$rightspaces" ${#rightspaces} ## rightspaces为4个空格 var=${var%"$rightspaces"} ## var目前为"John " printf "%s|%s\n" "$var" "${#var}" leftspaces=${var%%[! ]*} ## 从第一个非空值删除直到结尾 printf "%s|%s\n" ${leftspaces} ${#leftspaces} var=${var#"$leftspaces"} printf "%s|%s\n" "$var" "${#var}"
进一步封装的方法如下:如果存在第二个参数,则从字符串中删除该参数对应的字符,如果为空,则默认删除空格。
_trim(){ #@ 从$1中删除空格(或$2中的字符) local trim_string _TRIM=$1 printf "%s|%s\n" "$_TRIM" ${#_TRIM} trim_string=${_TRIM##*[!${2:- }]} printf "%s|%s\n" "$trim_string" ${#trim_string} _TRIM=${_TRIM%"$trim_string"} printf "%s|%s\n" "$_TRIM" ${#_TRIM} trim_string=${_TRIM%%[!${2:- }]*} printf "%s|%s\n" "$trim_string" ${#trim_string} _TRIM=${_TRIM#"$trim_string"} printf "%s|%s\n" "$_TRIM" ${#_TRIM} } trim(){ _trim "$@" && printf "%s\n" "$_TRIM" } $ trim " S p a c e d o u t " S p a c e d o u t $ trim "0002367.45000" 0 2367.45
9. 索引
定位一个字符串在另一个字符串中的索引位置。
_index(){ #@ $2在$1中的位置保存在$_INDEX local idx case $1 in "") _INDEX=0; return 1 ;; *"$2"*) idx=${1%%"$2"*} ## 提取匹配位置的起始 _INDEX=$(( ${#idx} + 1 )) ;; *) _INDEX=0; return 1 ;; esac } index(){ _index "$@" && printf "%d\n" "$_INDEX" }
例:基于月份的前3个字母,打印出对应的数值
_month2num(){ local month=JAN.FEB.MAR.APR.MAY.JUN.JUL.AUG.SEP.OCT.NOV.DEC _upword "${1:0:3}" ## 提取$1中的前3个字母,并转换为大写 _index "$month" "$_UPWORD" || return 1 _MONTH2NUM=$(( $_INDEX / 4 + 1 )) } month2num(){ _month2num "$@" && printf "%s\n" "$_MONTH2NUM" }