10.1 字符串处理

    ${#string}

    expr length $string

    上面两个表达式等价于C语言中的 strlen() 函数。

    expr "$string" : '.*'

    样例 10-1. 在文本的段落之间插入空行

    1. #!/bin/bash
    2. # paragraph-space.sh
    3. # 版本 2.1,发布日期 2012年7月29日
    4. # 在无空行的文本文件的段落之间插入空行。
    5. # 像这样使用: $0 <FILENAME
    6. MINLEN=60 # 可以试试修改这个值。它用来做判断。
    7. # 假设一行的字符数小于 $MINLEN,并且以句点结束段落。
    8. #+ 结尾部分有练习!
    9. while read line # 当文件有许多行的时候
    10. do
    11. echo "$line" # 输出行本身。
    12. len=${#line}
    13. if [[ "$len" -lt "$MINLEN" && "$line" =~ [*{\.}]$ ]]
    14. # if [[ "$len" -lt "$MINLEN" && "$line" =~ \[*\.\] ]]
    15. # 新版Bash将不能正常运行前一个版本的脚本。Ouch!
    16. # 感谢 Halim Srama 指出这点,并且给出了修正版本。
    17. then echo # 在该行以句点结束时,
    18. fi #+ 增加一行空行。
    19. done
    20. exit
    21. # 练习:
    22. # -----
    23. # 1) 该脚本通常会在文件的最后插入一个空行。
    24. #+ 尝试解决这个问题。
    25. # 2) 在第17行仅仅考虑到了以句点作为句子终止的情况。
    26. #+ 修改以满足其他的终止符,例如 ?, ! 和 "。

    起始部分字符串匹配长度

    expr match "$string" '$substring'

    其中,$substring 是一个。

    expr "$string" : '$substring'

    其中,$substring 是一个正则表达式。

    1. stringZ=abcABC123ABCabc
    2. # |------|
    3. # 12345678
    4. echo `expr match "$stringZ" 'abc[A-Z]*.2'` # 8
    5. echo `expr "$stringZ" : 'abc[A-Z]*.2'` # 8

    expr index $string $substring

    返回在 $string 中第一个出现的 $substring 字符所在的位置。

    1. stringZ=abcABC123ABCabc
    2. # 123456 ...
    3. echo `expr index "$stringZ" C12` # 6
    4. # C 的位置。
    5. echo `expr index "$stringZ" 1c` # 3
    6. # 'c' (第三号位) 较 '1' 出现的更早。

    几乎等价于C语言中的 strchr()

    截取字符串(字符串分片)

    ${string:position}

    $string 中截取自 $position 起的字符串。

    如果参数 $string 是 “*” 或者 “@”,那么将会截取自 $position 起的位置参数

    ${stringlength}

    $string 中截取自 $position 起,长度为 $length 的字符串。

    1. stringZ=abcABC123ABCabc
    2. # 0123456789.....
    3. # 索引位置从0开始。
    4. echo ${stringZ:0} # abcABC123ABCabc
    5. echo ${stringZ:1} # bcABC123ABCabc
    6. echo ${stringZ:7} # 23ABCabc
    7. echo ${stringz:7:3} # 23A
    8. # 三个字符的子字符串。
    9. # 从右至左进行截取可行么?
    10. echo ${stringZ:-4} # abcABC123ABCabc
    11. # ${parameter:-default} 将会得到整个字符串。
    12. # 但是……
    13. echo ${stringZ:(-4)} # Cabc
    14. echo ${stringZ: -4} # Cabc
    15. # 现在可以了。
    16. # 括号或者增加空格都可以"转义"位置参数。
    17. # 感谢 Dan Jacobson 指出这些。

    其中,参数 positionlength 可以传入一个变量而不一定需要传入常量。

    1. #!/bin/bash
    2. # rand-string.sh
    3. # 产生一个8个字符的随机字符串。
    4. if [ -n "$1" ] # 如果在命令行中已经传入了参数,
    5. then #+ 那么就以它作为起始字符串。
    6. str0="$1"
    7. else # 否则,就将脚本的进程标识符PID作为起始字符串。
    8. str0="$$"
    9. fi
    10. POS=2 # 从字符串的第二位开始。
    11. LEN=8 # 截取八个字符。
    12. str1=$( echo "$str0" | md5sum | md5sum )
    13. # ^^^^^^ ^^^^^^
    14. # 将字符串通过管道计算两次 md5 来进行两次混淆。
    15. randstring="${str1:$POS:$LEN}"
    16. # ^^^^ ^^^^
    17. # 允许传入参数
    18. echo "$randstring"
    19. exit $?
    20. # bozo$ ./rand-string.sh my-password
    21. # 1bdd88c4

    如果参数 $string 是 “*” 或者 “@”,那么将会截取自 $position 起,最大个数为 $length 的位置参数。

    expr substr $string $position $length

    $string 中截取自 $position 起,长度为 $length 的字符串。

    1. stringZ=abcABC123ABCabc
    2. # 123456789......
    3. # 索引位置从1开始。
    4. echo `expr substr $stringZ 1 2` # ab
    5. echo `expr substr $stringZ 4 3` # ABC

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

    $string 中截取自 $position 起的字符串,其中 $substring 是。

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

    $string 中截取自 $position 起的字符串,其中 $substring 是正则表达式。

    1. stringZ=abcABC123ABCabc
    2. # =======
    3. echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1
    4. echo `expr "$stringZ" : '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1
    5. echo `expr "$stringZ" : '\(.......\)'` # abcABC1
    6. # 上面所有的形式都给出了相同的结果。

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

    $string 结尾部分截取 $substring 字符串,其中 $substring 是正则表达式。

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

    $string 结尾部分截取 $substring 字符串,其中 $substring 是正则表达式。

    1. stringZ=abcABC123ABCabc
    2. # ======
    3. echo `expr match "$stringZ" '.*\([A-C][A-C][A-C][a-c]*\)'` # ABCabc
    4. echo `expr "$stringZ" : '.*\(......\)'` # ABCabc

    ${string#substring}

    删除从 $string 起始部分起,匹配到的最短的 $substring

    ${string##substring}

    删除从 $string 起始部分起,匹配到的最长的 $substring

    1. stringZ=abcABC123ABCabc
    2. # |----| 最长
    3. # |----------| 最短
    4. echo ${stringZ#a*C} # 123ABCabc
    5. # 删除 'a' 与 'c' 之间最短的匹配。
    6. echo ${stringZ##a*C} # abc
    7. # 删除 'a' 与 'c' 之间最长的匹配。
    8. # 你可以使用变量代替 substring。
    9. X='a*C'
    10. echo ${stringZ#$X} # 123ABCabc
    11. echo ${stringZ##$X} # abc
    12. # 同上。

    ${string%substring}

    删除从 $string 结尾部分起,匹配到的最短的 $substring

    例如:

    1. # 将当前目录下所有后缀名为 "TXT" 的文件改为 "txt" 后缀。
    2. # 例如 "file1.TXT" 改为 "file1.txt"。
    3. SUFF=TXT
    4. suff=txt
    5. for i in $(ls *.$SUFF)
    6. do
    7. mv -f $i $(i%.$SUFF).$suff
    8. # 除了从变量 $i 右侧匹配到的最短的字符串之外,
    9. #+ 其他一切都保持不变。
    10. done ### 如果需要,循环可以压缩成一行的形式。
    11. # 感谢 Rory Winston。

    ${string%%substring}

    这个操作对生成文件名非常有帮助。

    样例 10-3. 改变图像文件的格式及文件名

    1. #!/bin/bash
    2. # cvt.sh:
    3. # 将目录下所有的 MacPaint 文件转换为 "pbm" 格式。
    4. # 使用由 Brian Henderson (bryanh@giraffe-data.com) 维护的
    5. #+ "netpbm" 包下的 "macptobpm" 二进制工具。
    6. # Netpbm 是大多数 Linux 发行版的标准组成部分。
    7. OPERATION=macptopbm
    8. SUFFIX=pbm # 新的文件名后缀。
    9. if [ -n "$1" ]
    10. then
    11. directory=$1 # 如果已经通过脚本参数传入了目录名的情况……
    12. else
    13. directory=$PWD # 否则就使用当前工作目录。
    14. fi
    15. # 假设目标目录下的所有 MacPaint 图像文件都拥有
    16. #+ ".mac" 的文件后缀名。
    17. for file in $directory/* # 文件名匹配。
    18. do
    19. filename=${file%.*c} # 从文件名中删除 ".mac" 后缀
    20. #+ ('.*c' 匹配 '.' 与 'c' 之间的
    21. # 所有字符,包括其本身)。
    22. $OPERATION $file > "$filename.$SUFFIX"
    23. # 将转换结果重定向到新的文件。
    24. rm -f $file # 在转换后删除原文件。
    25. echo "$filename.$SUFFIX" # 将记录输出到 stdout 中。
    26. done
    27. exit 0
    28. # 练习:
    29. # -----
    30. # 这个脚本会将当前工作目录下的所有文件进行转换。
    31. # 修改脚本,使得它仅转换 ".mac" 后缀的文件。
    32. # *** 还可以使用另外一种方法。 *** #
    33. #!/bin/bash
    34. # 将图像批处理转换成不同的格式。
    35. # 假设已经安装了 imagemagick。(在大部分 Linux 发行版中都有)
    36. INFMT=png # 可以是 tif, jpg, gif 等等。
    37. OUTFMT=pdf # 可以是 tif, jpg, gif, pdf 等等。
    38. for pic in *"$INFMT"
    39. do
    40. p2=$(ls "$pic" | sed -e s/\.$INFMT//)
    41. # echo $p2
    42. convert "$pic" $p2.$OUTFMT
    43. done
    44. exit $?

    样例 10-4. 将流音频格式转换成 ogg 格式

    1. # ra2ogg.sh: 将流音频文件 (*.ra) 转换成 ogg 格式。
    2. # 使用 "mplayer" 媒体播放器程序:
    3. # http://www.mplayerhq.hu/homepage
    4. # 使用 "ogg" 库与 "oggenc":
    5. #
    6. # 脚本同时需要安装一些解码器,例如 sipr.so 等等一些。
    7. # 这些解码器可以在 compat-libstdc++ 包中找到。
    8. OFILEPREF=${1%%ra} # 删除 "ra" 后缀。
    9. OFILESUFF=wav # wav 文件后缀。
    10. OUTFILE="$OFILEPREF""$OFILESUFF"
    11. E_NOARGS=85
    12. if [ -z "$1" ] # 必须指定一个文件进行转换。
    13. then
    14. echo "Usage: `basename $0` [filename]"
    15. exit $E_NOAGRS
    16. fi
    17. ######################################################
    18. mplayer "$1" -ao pcm:file=$OUTFILE
    19. oggenc "$OUTFILE" # 由 oggenc 自动加上正确的文件后缀名。
    20. ######################################################
    21. rm "$OUTFILE" # 立即删除 *.wav 文件。
    22. # 如果你仍需保留原文件,注释掉上面这一行即可。
    23. exit $?
    24. # 注意:
    25. # -----
    26. # 在网站上,点击一个 *.ram 的流媒体音频文件
    27. #+ 通常只会下载到 *.ra 音频文件的 URL。
    28. # 你可以使用 "wget" 或者类似的工具下载 *.ra 文件本身。
    29. # 练习:
    30. # -----
    31. # 这个脚本仅仅转换 *.ra 文件。
    32. # 修改脚本增加适应性,使其可以转换 *.ram 或其他文件格式。
    33. #
    34. # 如果你非常有热情,你可以扩展这个脚本使其
    35. #+ 可以自动下载并且转换流媒体音频文件。
    36. # 给定一个 URL,自动下载流媒体音频文件 (使用 "wget"),
    37. #+ 然后转换它。

    下面是使用字符串截取结构对 的一个简单模拟。

    样例 10-5. 模拟 getopt

    1. #!/bin/bash
    2. # getopt-simple.sh
    3. # 作者: Chris Morgan
    4. # 允许在高级脚本编程指南中使用。
    5. getopt_simple()
    6. {
    7. echo "getopt_simple()"
    8. echo "Parameters are '$*'"
    9. until [ -z "$1" ]
    10. do
    11. echo "Processing parameter of: '$1'"
    12. if [ ${1:0:1} = '/' ]
    13. then
    14. tmp=${1:1} # 删除开头的 '/'
    15. parameter=${tmp%%=*} # 取出名称。
    16. value=${tmp##*=} # 取出值。
    17. echo "Parameter: '$parameter', value: '$value'"
    18. eval $parameter=$value
    19. fi
    20. shift
    21. done
    22. }
    23. # 将所有参数传递给 getopt_simple()。
    24. getopt_simple $*
    25. echo "test is '$test'"
    26. echo "test2 is '$test2'"
    27. exit 0 # 可以查看该脚本的修改版 UseGetOpt.sh。
    28. ---
    29. sh getopt_example.sh /test=value1 /test2=value2
    30. Parameters are '/test=value1 /test2=value2'
    31. Processing parameter of: '/test=value1'
    32. Parameter: 'test', value: 'value1'
    33. Processing parameter of: '/test2=value2'
    34. Parameter: 'test2', value: 'value2'
    35. test is 'value1'
    36. test2 is 'value2'

    子串替换

    ${string/substring/replacement}

    替换匹配到的第一个 $substring$replacement。[^2]

    ${string//substring/replacement}

    替换匹配到的所有 $substring$replacement

    1. stringZ=abcABC123ABCabc
    2. echo ${stringZ/abc/xyz} # xyzABC123ABCabc
    3. # 将匹配到的第一个 'abc' 替换为 'xyz'。
    4. echo ${stringZ//abc/xyz} # xyzABC123ABCxyz
    5. # 将匹配到的所有 'abc' 替换为 'xyz'。
    6. echo ---------------
    7. echo "$stringZ" # abcABC123ABCabc
    8. echo ---------------
    9. # 字符串本身并不会被修改!
    10. # 匹配以及替换的字符串可以是参数么?
    11. match=abc
    12. repl=000
    13. echo ${stringZ/$match/$repl} # 000ABC123ABCabc
    14. # ^ ^ ^^^
    15. echo ${stringZ//$match/$repl} # 000ABC123ABC000
    16. # Yes! ^ ^ ^^^ ^^^
    17. echo
    18. # 如果没有给定 $replacement 字符串会怎样?
    19. echo ${stringZ/abc} # ABC123ABCabc
    20. echo ${stringZ//abc} # ABC123ABC
    21. # 仅仅是将其删除而已。

    ${string/#substring/replacement}

    替换 $string 中最前端匹配到的 $substring$replacement

    ${string/%substring/replacement}

    替换 $string 中最末端匹配到的 $substring$replacement

    1. stringZ=abcABC123ABCabc
    2. echo ${stringZ/#abc/XYZ} # XYZABC123ABCabc
    3. # 将前端的 'abc' 替换为 'XYZ'
    4. echo ${stringZ/%abc/XYZ} # abcABC123ABCXYZ
    5. # 将末端的 'abc' 替换为 'XYZ'

    [^2]: 注意根据使用时上下文的不同,$substring 和 可以是文本字符串也可以是变量。可以参考第一个样例。