$ 字符表示参数扩展、命令替换或算术扩展。要扩展的参数名称或符号可以用括号括起来,括号是可选的,但建议使用,这样可以保护要扩展的变量,防止紧跟其后的字符被解释为名称的一部分。
更多信息请参阅《Bash 用户手册》 https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html.
Section 15.1: 修改字母字符的大小写
Version ≥ 4.0
改大写
$ v="hello"
# Just the first character
$ printf '%s\n' "${v^}"
Hello
# All characters
$ printf '%s\n' "${v^^}"
HELLO
# Alternative
$ v="hello world"
$ declare -u string="$v"
$ echo "$string"
HELLO WORLD
改小写
$ v="BYE"
# Just the first character
$ printf '%s\n' "${v,}"
bYE
# All characters
$ printf '%s\n' "${v,,}"
bye
# Alternative
$ v="HELLO WORLD"
$ declare -l string="$v"
$ echo "$string"
hello world
大小写转换
$ v="Hello World"
# All chars
$ echo "${v~~}"
hELLO wORLD
$ echo "${v~}"
# Just the first char
hello World
Section 15.2: 参数长度
# Length of a string
$ var='12345'
$ echo "${#var}"
5
请注意,以字符数表示的长度并不一定等同于字节数(如在 UTF-8 中,大多数字符都是用多个字节编码的),也不等同于字形/音调数(其中有些是字符的组合),也不一定等同于显示宽度。
# Number of array elements
$ myarr=(1 2 3)
$ echo "${#myarr[@]}"
3
# Works for positional parameters as well
$ set -- 1 2 3 4
$ echo "${#@}"
4
# But more commonly (and portably to other shells), one would use
$ echo "$#"
4
Section 15.3: 字符串中的替换模式
匹配第一个:
$ a='I am a string'
$ echo "${a/a/A}"
I Am a string
匹配全部:
$ echo "${a//a/A}"
I Am A string
匹配开头:
$ echo "${a/#I/y}"
y am a string
匹配结尾:
$ echo "${a/%g/N}"
I am a strinN
将匹配的内容替换为空:
$ echo "${a/g/}"
I am a strin
为数组项添加前缀:
$ A=(hello world)
$ echo "${A[@]/#/R}"
Rhello Rworld
Section 15.4: 子字符串和子数组
var='0123456789abcdef'
# Define a zero-based offset
$ printf '%s\n' "${var:3}"
3456789abcdef
# Offset and length of substring
$ printf '%s\n' "${var:3:4}"
3456
Version ≥ 4.2
# Negative length counts from the end of the string
$ printf '%s\n' "${var:3:-5}"
3456789a
# Negative offset counts from the end
# Needs a space to avoid confusion with ${var:-6}
$ printf '%s\n' "${var: -6}"
abcdef
# Alternative: parentheses
$ printf '%s\n' "${var:(-6)}"
abcdef
# Negative offset and negative length
$ printf '%s\n' "${var: -6:-5}"
a
如果参数是位置参数或下标数组的元素,也适用同样的展开式:
# Set positional parameter $1
set -- 0123456789abcdef
# Define offset
$ printf '%s\n' "${1:5}"
56789abcdef
# Assign to array element
myarr[0]='0123456789abcdef'
# Define offset and length
$ printf '%s\n' "${myarr[0]:7:3}"
789
类似的扩展也适用于位置参数,位置参数的偏移量以 1 为基础:
# Set positional parameters $1, $2, ...
$ set -- 1 2 3 4 5 6 7 8 9 0 a b c d e f
# Define an offset (beware $0 (not a positional parameter)
# is being considered here as well)
$ printf '%s\n' "${@:10}"
0
a
b
c
d
e
f
# Define an offset and a length
$ printf '%s\n' "${@:10:3}"
0
a
b
# No negative lengths allowed for positional parameters
$ printf '%s\n' "${@:10:-2}"
bash: -2: substring expression < 0
# Negative offset counts from the end
# Needs a space to avoid confusion with ${@:-10:2}
$ printf '%s\n' "${@: -10:2}"
7
8
# ${@:0} is $0 which is not otherwise a positional parameters or part
# of $@
$ printf '%s\n' "${@:0:2}"
/usr/bin/bash
1
子字符串扩展可用于索引数组:
# Create array (zero-based indices)
$ myarr=(0 1 2 3 4 5 6 7 8 9 a b c d e f)
# Elements with index 5 and higher
$ printf '%s\n' "${myarr[@]:12}"
c
d
e
f
# 3 elements, starting with index 5
$ printf '%s\n' "${myarr[@]:5:3}"
5
6
7
# The last element of the array
$ printf '%s\n' "${myarr[@]: -1}"
f
Section 15.5: 删除字符串开头的模式
短匹配:
$ a='I am a string'
$ echo "${a#*a}"
m a string
长匹配:
$ echo "${a##*a}"
string
Section 15.6: 参数的间接扩展
Bash indirection 允许获取名称包含在另一个变量中的变量的值。变量示例
$ red="the color red"
$ green="the color green"
$ color=red
$ echo "${!color}"
the color red
$ color=green
$ echo "${!color}"
the color green
还有一些例子可以证明间接扩展的用法:
$ foo=10
$ x=foo
$ echo ${x} #Classic variable print
foo
$ foo=10
$ x=foo
$ echo ${!x} #Indirect expansion
10
再举一个例子:
$ argtester () { for (( i=1; i<="$#"; i++ )); do echo "${i}";done; }; argtester -ab -cd -ef
1 #i expanded to 1
2 #i expanded to 2
3 #i expanded to 3
$ argtester () { for (( i=1; i<="$#"; i++ )); do echo "${!i}";done; }; argtester -ab -cd -ef
-ab # i=1 --> expanded to $1 ---> expanded to first argument sent to function
-cd # i=2 --> expanded to $2 ---> expanded to second argument sent to function
-ef # i=3 --> expanded to $3 ---> expanded to third argument sent to function
Section 15.7: 参数扩展和文件名
您可以使用 Bash 参数扩展来模拟常见的文件名处理操作,如basename和dirname。
我们将以此作为示例路径:
FILENAME="/tmp/example/myfile.txt"
模拟 dirname 并返回文件路径的目录名:
echo "${FILENAME%/*}"
#Out: /tmp/example
模拟基名 $FILENAME,返回文件路径的文件名:
echo "${FILENAME##*/}"
#Out: myfile.txt
模拟基名 $FILENAME .txt 并返回不带 .txt 扩展名的文件名:
BASENAME="${FILENAME##*/}"
echo "${BASENAME%%.txt}"
#Out: myfile
Section 15.8: 默认值替换
${parameter:-word}如果参数未设置或为空,则替换 word 的扩展名。否则,将替换参数的值。
$ unset var
$ echo "${var:-XX}" # Parameter is unset -> expansion XX occurs
XX
$ var="" # Parameter is null -> expansion XX occurs
$ echo "${var:-XX}"
XX
$ var=23 # Parameter is not null -> original expansion occurs
$ echo "${var:-XX}"
23
${parameter:=word}如果参数未设置或为空,则会将 word 的扩展名分配给参数。然后替换参数的值。位置参数和特殊参数不能以这种方式赋值。
$ unset var
$ echo "${var:=XX}" # Parameter is unset -> word is assigned to XX
XX
$ echo "$var"
XX
$ var="" # Parameter is null -> word is assigned to XX
$ echo "${var:=XX}"
XX
$ echo "$var"
XX
$ var=23 # Parameter is not null -> no assignment occurs
$ echo "${var:=XX}"
23
$ echo "$var"
23
Section 15.9: 字符串末尾的删除模式
短匹配:
$ a='I am a string'
$ echo "${a%a*}"
I am
长匹配:
$ echo "${a%%a*}"
I
Section 15.10: Munging during expansion
变量不一定要扩展到其值--可以在扩展过程中提取子字符串,这对提取文件扩展名或路径的一部分很有用。全局变换字符保留其通常的含义,因此 .* 指的是一个字面意义上的点,后跟任意字符序列;它不是一个正则表达式。
$ v=foo-bar-baz
$ echo ${v%%-*}
foo
$ echo ${v%-*}
foo-bar
$ echo ${v##*-}
baz
$ echo ${v#*-}
bar-baz
还可以使用默认值来扩展变量。 --例如,我想调用用户的编辑器,但如果用户没有设置编辑器,我就给他们指定使用 vim。
$ EDITOR=nano
$ ${EDITOR:-vim} /tmp/some_file
\# opens nano
$ unset EDITOR
$ $ ${EDITOR:-vim} /tmp/some_file
\# opens vim
执行这种扩展有两种不同的方法,它们的区别在于相关变量是空还是未设置。使用 :- 时,如果变量未设置或为空,则使用默认值;而使用 - 时,如果变量未设置,则使用默认值,但如果变量设置为空字符串,则使用默认值:
$ a="set"
$ b=""
$ unset c
$ echo ${a:-default_a} ${b:-default_b} ${c:-default_c}
set default_b default_c
$ echo ${a-default_a} ${b-default_b} ${c-default_c}
set default_c
与默认值类似,也可以给出替代值;如果某个变量不可用,则使用默认值,如果该变量可用,则使用替代值。
$ a="set"
$ b=""
$ echo ${a:+alternative_a} ${b:+alternative_b}
alternative_a
你也许已经注意到这些扩展可以嵌套,在为命令行标志提供参数时,使用替代选项就变得特别有用;
$ output_file=/tmp/foo
$ wget ${output_file:+"-o ${output_file}"} www.three-corner.xyz
# expands to wget -o /tmp/foo www.three-corner.xyz
$ unset output_file
$ wget ${output_file:+"-o ${output_file}"} www.three-corner.xyz
# expands to wget www.three-corner.xyz
Section 15.11: 如果变量为空或未设置,则出错
其语义类似于默认值替换,但不是替换默认值,而是用提供的错误信息出错。形式有 ${VARNAME?ERRMSG} 和 ${VARNAME:?ERRMSG}。带 : 的表单会在变量为unset或empty时出错,而不带 : 的表单只会在变量为unset时出错。如果出现错误,将输出 ERRMSG,并将退出代码设为 1。
#!/bin/bash
FOO=
# ./script.sh: line 4: FOO: EMPTY
echo "FOO is ${FOO:?EMPTY}"
# FOO is
echo "FOO is ${FOO?UNSET}"
# ./script.sh: line 8: BAR: EMPTY
echo "BAR is ${BAR:?EMPTY}"
# ./script.sh: line 10: BAR: UNSET
echo "BAR is ${BAR?UNSET}"
运行上面的完整示例时,需要注释掉每个出错的 echo 语句才能继续。