9.3. 参数替换

处理或/并扩展变量

${parameter}

和$parameter是相同的,都是表示变量parameter的值。在一些环境中,使用${parameter}比较不会引起误解.

可以把变量和字符串连接.

   1 your_id=${USER}-on-${HOSTNAME}
   2 echo "$your_id"
   3 #
   4 echo "Old \$PATH = $PATH"
   5 PATH=${PATH}:/opt/bin  #在脚本的生存期内,能额外增加路径/opt/bin到环境变量$PATH中去.
   6 echo "New \$PATH = $PATH"

${parameter-default}, ${parameter:-default}

如果变量没有被设置,使用默认值。

   1 echo ${username-`whoami`}
   2 # 如果变量$username还没有被设置,则把命令`whoami`的结果赋给该变量.

${parameter-default}和${parameter:-default}几乎是相等的。它们之间的差别是:当一个参数已被声明,但是值是NULL的时候两者不同.

   1 #!/bin/bash
   2 # param-sub.sh
   3 
   4 #  变量是否被声明,
   5 #+ 即使它的值是空的(null)
   6 #+ 也会影响是否使用默认值.
   7 
   8 username0=
   9 echo "username0 has been declared, but is set to null."
  10 echo "username0 = ${username0-`whoami`}"
  11 # 不会有输出.
  12 
  13 echo
  14 
  15 echo username1 has not been declared.
  16 echo "username1 = ${username1-`whoami`}"
  17 # 会输出默认值.
  18 
  19 username2=
  20 echo "username2 has been declared, but is set to null."
  21 echo "username2 = ${username2:-`whoami`}"
  22 #                            ^
  23 # 和上面一个实例比较.
  24 # 有输出是因为:-比-多一个测试条件.
  25 
  26 
  27 #
  28 
  29 # 再来一次:
  30 
  31 variable=
  32 # 变量已声明,但被设置了空值(null).
  33 
  34 echo "${variable-0}"    # (没有输出)
  35 echo "${variable:-1}"   # 1
  36 #               ^
  37 
  38 unset variable
  39 
  40 echo "${variable-2}"    # 2
  41 echo "${variable:-3}"   # 3
  42 
  43 exit 0

默认值结构可以在脚本中提供一个没有传递命令行参数时的默认值.

   1 DEFAULT_FILENAME=generic.data
   2 filename=${1:-$DEFAULT_FILENAME}
   3 #  如果没有指定值,下面的代码会使用filename变量的默认值"generic.data".
   4 #
   5 #
   6 #  其他的代码...

参考例子 3-4, 例子 28-2,和例子 A-6.

使用一个and list来提供一个默认的命令行参数作比较.

${parameter=default}, ${parameter:=default}

如果变量parameter没有设置,把它设置成默认值.

两种形式几乎相同,只是和上面的一样[1]:只有当$parameter变量被声明且被设置成null值时不同.

   1 echo ${username=`whoami`}
   2 # 变量"username"现在已经被设置成`whoami`的输出.

${parameter+alt_value}, ${parameter:+alt_value}

如果变量parameter设置,使用alt_value作为新值,否则使用空字符串。

除了引起的当变量被声明且值是空值时有些不同外,两种形式几乎相等。请看下面的例子.

   1 echo "###### \${parameter+alt_value} ########"
   2 echo
   3 
   4 a=${param1+xyz}
   5 echo "a = $a"      # a =
   6 
   7 param2=
   8 a=${param2+xyz}
   9 echo "a = $a"      # a = xyz
  10 
  11 param3=123
  12 a=${param3+xyz}
  13 echo "a = $a"      # a = xyz
  14 
  15 echo
  16 echo "###### \${parameter:+alt_value} ########"
  17 echo
  18 
  19 a=${param4:+xyz}
  20 echo "a = $a"      # a =
  21 
  22 param5=
  23 a=${param5:+xyz}
  24 echo "a = $a"      # a =
  25 # 产生与a=${param5+xyz}不同。
  26 
  27 param6=123
  28 a=${param6+xyz}
  29 echo "a = $a"      # a = xyz

${parameter?err_msg}, ${parameter:?err_msg}

如果变量parameter已经设置,则使用该值,否则打印err_msg错误信息。

这两种形式几乎相同,仅有和上面所说的一点不同:带有:使当变量已声明但值是空值时不同.


例子 9-14. 使用参数替换和错误信息

   1 #!/bin/bash
   2 
   3 #  检查一些系统环境变量
   4 #  这是很好的预防性防护.
   5 #  例如,如果$USER(用户在控制台的名字)没有被设置,
   6 #+ 系统将不接受你.
   7 
   8 : ${HOSTNAME?} ${USER?} ${HOME?} ${MAIL?}
   9   echo
  10   echo "Name of the machine is $HOSTNAME."
  11   echo "You are $USER."
  12   echo "Your home directory is $HOME."
  13   echo "Your mail INBOX is located in $MAIL."
  14   echo
  15   echo "If you are reading this message,"
  16   echo "critical environmental variables have been set."
  17   echo
  18   echo
  19 
  20 # ------------------------------------------------------
  21 
  22 #  ${variablename?}结构也能检查一个脚本中变量的设置情况
  23 #
  24 
  25 ThisVariable=Value-of-ThisVariable
  26 #  注意,顺便说一下字符串变量可能被设置成不允许的字符
  27 #
  28 : ${ThisVariable?}
  29 echo "Value of ThisVariable is $ThisVariable".
  30 echo
  31 echo
  32 
  33 
  34 : ${ZZXy23AB?"ZZXy23AB has not been set."}
  35 #  如果变量ZZXy23AB没有被设置,
  36 #+ 则脚本会打印一个错误信息而结束。
  37 
  38 # 你可以指定错误信息.
  39 # : ${variablename?"ERROR MESSAGE"}
  40 
  41 
  42 # 等同于:              dummy_variable=${ZZXy23AB?}
  43 #                      dummy_variable=${ZZXy23AB?"ZXy23AB has not been set."}
  44 #
  45 #                      echo ${ZZXy23AB?} >/dev/null
  46 
  47 #  使用命令"set -u"后,再比较这些检查变量是否被设置的方法的不同...
  48 #
  49 
  50 
  51 
  52 echo "You will not see this message, because script already terminated."
  53 
  54 HERE=0
  55 exit $HERE   # 不会执行到这儿.
  56 
  57 # 实际上,这个脚本会返回退出状态码为1(echo $?).


例子 9-15. 变量替换和"usage"信息[译者注:通常就是帮助信息]

   1 #!/bin/bash
   2 # usage-message.sh
   3 
   4 : ${1?"Usage: $0 ARGUMENT"}
   5 #  如果没有提供命令行参数则脚本在这儿就退出了,
   6 #+ 并打印了错误信息.
   7 #    usage-message.sh: 1: Usage: usage-message.sh ARGUMENT
   8 
   9 echo "These two lines echo only if command-line parameter given."
  10 echo "command line parameter = \"$1\""
  11 
  12 exit 0  # 仅在命令行参数提供时,才会在这儿退出.
  13 
  14 # 分别检查有命令行参数和没有命令行参数时的退出状态。
  15 # 如果有命令行参数,则"$?"为0.
  16 # 否则, "$?"为1.

变量替换与/或扩展. 下面的各种表达式是expr字符串操作中match的补充(参考Example 12-9). 它们通常都用来解析文件路径名.

变量长度/子串提取

${#var}

字符串长度(即变量$var的字符个数)。对于数组来说,${#array}是数组的第一个元素的升序.

例外情况:

  • ${#*}${#@} 表示位置参数的个数.

  • 对于一个数组来说,${#array[*]}${#array[@]}表示数组中元素的个数.


例子 9-16. 变量值的长度

   1 #!/bin/bash
   2 # length.sh
   3 
   4 E_NO_ARGS=65
   5 
   6 if [ $# -eq 0 ]  # 必须要有命令行参数给这个演示程序.
   7 then
   8   echo "Please invoke this script with one or more command-line arguments."
   9   exit $E_NO_ARGS
  10 fi  
  11 
  12 var01=abcdEFGH28ij
  13 echo "var01 = ${var01}"
  14 echo "Length of var01 = ${#var01}"
  15 # 现在,让我们在变量值内嵌入一个空格.
  16 var02="abcd EFGH28ij"
  17 echo "var02 = ${var02}"
  18 echo "Length of var02 = ${#var02}"
  19 
  20 echo "Number of command-line arguments passed to script = ${#@}"
  21 echo "Number of command-line arguments passed to script = ${#*}"
  22 
  23 exit 0

${var#Pattern}, ${var##Pattern}

删除从$var前端开始的最短或最长匹配$Pattern的字符串。

例子 A-7中的用法示例:
   1 # 摘自"days-between.sh"例子的函数
   2 # 剥去传递来的参数的前引0.
   3 
   4 strip_leading_zero () #  剥去传递的参数中可能存在的前导0
   5 {                     #
   6   return=${1#0}       #  "1"表示变量"$1" -- 传给函数的参数.
   7 }                     #  而"0"表示要从"$1"前端剥掉的字符 -- 剥掉0

Manfred Schwarb给出了比上面更多的细节的例子:
   1 strip_leading_zero2 () # 剥除前导的0字符,因为不这样
   2 {                      # Bash会把这串字符当成八进制值.
   3   shopt -s extglob     # 激活扩展匹配.
   4   local val=${1##+(0)} # 使用局部变量,匹配最长的0.
   5   shopt -u extglob     # 禁用扩展匹配.
   6   _strip_leading_zero2=${val:-0}
   7                        # 如果输入是0,返回0来替换返回"".
   8 }

另外一个usage(帮助信息)示例:
   1 echo `basename $PWD`        # 当前工作目录的基本名字.
   2 echo "${PWD##*/}"           # 当前工作目录的基本名字.
   3 echo
   4 echo `basename $0`          # 脚本名.
   5 echo $0                     # 脚本名.
   6 echo "${0##*/}"             # 脚本名.
   7 echo
   8 filename=test.data
   9 echo "${filename##*.}"      # data
  10                             # 文件的扩展名.

${var%Pattern}, ${var%%Pattern}

删除从$var前端开始的最短或最长匹配$Pattern的字符串。

Bash的版本2增加了附加的选项。

例子 9-17. 在参数替换中的模式匹配

   1 #!/bin/bash
   2 # patt-matching.sh
   3 
   4 # 使用# ## % %%的参数替换操作的模式匹配.
   5 
   6 var1=abcd12345abc6789
   7 pattern1=a*c  # * (星号)匹配a - c的所有字符.
   8 
   9 echo
  10 echo "var1 = $var1"           # abcd12345abc6789
  11 echo "var1 = ${var1}"         # abcd12345abc6789
  12                               # (另一种形式)
  13 echo "Number of characters in ${var1} = ${#var1}"
  14 echo
  15 
  16 echo "pattern1 = $pattern1"   # a*c  (此表达式匹配'a'与'c'之间的所有字符)
  17 echo "--------------"
  18 echo '${var1#$pattern1}  =' "${var1#$pattern1}"    #         d12345abc6789
  19 # 匹配开头的最短字串                    abcd12345abc6789
  20 #     ^^^^^                                               |-|
  21 echo '${var1##$pattern1} =' "${var1##$pattern1}"   #                  6789      
  22 # 最长匹配,剥掉开头的12个字符                abcd12345abc6789
  23 #              ^^^^^                                      |----------|
  24 
  25 echo; echo; echo
  26 
  27 pattern2=b*9            # 匹配'b'和'9'之间的所有字符
  28 echo "var1 = $var1"     # 仍然是  abcd12345abc6789
  29 echo
  30 echo "pattern2 = $pattern2"
  31 echo "--------------"
  32 echo '${var1%pattern2}  =' "${var1%$pattern2}"     #     abcd12345a
  33 # 最短匹配, 剥去后面的6个字符      abcd12345abc6789
  34 #               ^^^^                         |----|
  35 echo '${var1%%pattern2} =' "${var1%%$pattern2}"    #     a
  36 # 最长匹配, 剥去后面的12个字符               abcd12345abc6789
  37 #               ^^^^                                      |-------------|
  38 
  39 # 记住, #和##表示在字符串的左边(即开头)操作,
  40 #       %和%%表示在字符串的右边(即结尾)操作.
  41 
  42 echo
  43 
  44 exit 0


例子 9-18. 更改文件扩展名(后缀):

   1 #!/bin/bash
   2 # rfe.sh: 更改文件扩展名.
   3 #
   4 #         rfe old_extension new_extension
   5 #
   6 # 例如:
   7 # T为了把当前目录下所有的*.gif文件改成*.jpg,如下执行:
   8 #          rfe gif jpg
   9 
  10 
  11 E_BADARGS=65
  12 
  13 case $# in
  14   0|1)             # 在这里,竖线(|)意味着"或"。
  15   echo "Usage: `basename $0` old_file_suffix new_file_suffix"
  16   exit $E_BADARGS  # 如果是0或1,就退出脚本
  17   ;;
  18 esac
  19 
  20 
  21 for filename in *.$1
  22 # 把文件名以第一个参数为后缀的文件全部列举出来
  23 do
  24   mv $filename ${filename%$1}$2
  25   #  剥去文件名中匹配第一个参数的部分,
  26   #+ 然后加上第二个参数.
  27 done
  28 
  29 exit 0

变量扩展/子串替换

这些结构从ksh中学习而来.

${var:pos}

变量var被展开成从位移pos个字符往后的值.

${var:pos:len}

从变量var中展开成从位移pos的字符往后最长为len的字符串。参考例子 A-14中这个操作符的创造性用法。

${var/Pattern/Replacement}

在变量var第一个匹配Pattern的字符串用Replacement代替.

如果省略了Replacement ,则第一个匹配Pattern的字符串会被删除.

${var//Pattern/Replacement}

全局替换Global replacement.所有在变量var中被Pattern匹配到的都由Replacement代替.

和上面的一样,如果Replacement被省略,则所有的匹配Pattern的字符串都会被删除.


例子 9-19. 用模式匹配来解析任意的字符串

   1 #!/bin/bash
   2 
   3 var1=abcd-1234-defg
   4 echo "var1 = $var1"
   5 
   6 t=${var1#*-*}
   7 echo "var1 (with everything, up to and including first - stripped out) = $t"
   8 #  t=${var1#*-}  也一样,
   9 #+ 因为 # 匹配最短的字符串,
  10 #+ 并且 * 匹配前面所说的所有字符,也包括空字符串.
  11 # (多谢Stephane Chazelas指出这一点.)
  12 
  13 t=${var1##*-*}
  14 echo "If var1 contains a \"-\", returns empty string...   var1 = $t"
  15 
  16 
  17 t=${var1%*-*}
  18 echo "var1 (with everything from the last - on stripped out) = $t"
  19 
  20 echo
  21 
  22 # -------------------------------------------
  23 path_name=/home/bozo/ideas/thoughts.for.today
  24 # -------------------------------------------
  25 echo "path_name = $path_name"
  26 t=${path_name##/*/}
  27 echo "path_name, stripped of prefixes = $t"
  28 # 在这个特例中,和   t=`basename $path_name`  效果一样.
  29 #  t=${path_name%/}; t=${t##*/}   是更一般的解决办法,
  30 #+ 但有时还是会失败.
  31 #  如果$path_name 以一个新行符结尾,那么 `basename $path_name` 就不能正常工作了,
  32 #+ 但上面的表达式可以正常工作.
  33 # (多谢, S.C.)
  34 
  35 t=${path_name%/*.*}
  36 # 效果和   t=`dirname $path_name`   一样
  37 echo "path_name, stripped of suffixes = $t"
  38 # 有时这会失败,比如"../", "/foo////", # "foo/", "/".
  39 #  移除前缀,特别当basename没有前缀时,
  40 #+ 但目录名可以,但也使问题也更复杂了.
  41 # (多谢, S.C.)
  42 
  43 echo
  44 
  45 t=${path_name:11}
  46 echo "$path_name, with first 11 chars stripped off = $t"
  47 t=${path_name:11:5}
  48 echo "$path_name, with first 11 chars stripped off, length 5 = $t"
  49 
  50 echo
  51 
  52 t=${path_name/bozo/clown}
  53 echo "$path_name with \"bozo\" replaced  by \"clown\" = $t"
  54 t=${path_name/today/}
  55 echo "$path_name with \"today\" deleted = $t"
  56 t=${path_name//o/O}
  57 echo "$path_name with all o's capitalized = $t"
  58 t=${path_name//o/}
  59 echo "$path_name with all o's deleted = $t"
  60 
  61 exit 0

${var/#Pattern/Replacement}

如果变量var前缀匹配模式Pattern,则用Replacement代替匹配模式的字符串.

${var/%Pattern/Replacement}

如果变量var后缀匹配模式Pattern,则用Replacement代替匹配模式的字符串.


例子 9-20. 匹配字符串前缀和后缀的模式

   1 #!/bin/bash
   2 # var-match.sh:
   3 # Demo of pattern replacement at prefix / suffix of string.
   4 
   5 v0=abc1234zip1234abc    # 初值.
   6 echo "v0 = $v0"         # abc1234zip1234abc
   7 echo
   8 
   9 # 匹配字符串的前缀(开头).
  10 v1=${v0/#abc/ABCDEF}    # abc1234zip1234abc
  11                         # |-|
  12 echo "v1 = $v1"         # ABCDEF1234zip1234abc
  13                         # |----|
  14 
  15 # 匹配字符串的后缀(尾部).
  16 v2=${v0/%abc/ABCDEF}    # abc1234zip123abc
  17                         #              |-|
  18 echo "v2 = $v2"         # abc1234zip1234ABCDEF
  19                         #               |----|
  20 
  21 echo
  22 
  23 #  ----------------------------------------------------
  24 #  必须匹配字符串的开头/尾部,
  25 #+ 否则不会发生替换.
  26 #  ----------------------------------------------------
  27 v3=${v0/#123/000}       # 匹配,但不是在开头.
  28 echo "v3 = $v3"         # abc1234zip1234abc
  29                         # 不会被替换.
  30 v4=${v0/%123/000}       # 匹配,但不是尾部.
  31 echo "v4 = $v4"         # abc1234zip1234abc
  32                         # 不会被替换.
  33 
  34 exit 0			

${!varprefix*}, ${!varprefix@}

匹配所有前面声明过的变量,并且变量名以varprefix开头.
   1 xyz23=whatever
   2 xyz24=
   3 
   4 a=${!xyz*}      # 展开为声明过的并以"xyz".
   5 echo "a = $a"   # a = xyz23 xyz24
   6 a=${!xyz@}      # Same as above.
   7 echo "a = $a"   # a = xyz23 xyz24
   8 
   9 # Bash, 版本2.04,增加了这个属性.

[1]

如果变量$parameter在一个非交互的脚本中为null,它会以127 退出状态码终结(Bash错误代码表示"command not found"(命令没有发现)).