首页 > 科技 > Bash技巧:使用参数扩展去掉字符串变量值的前缀或者后缀

Bash技巧:使用参数扩展去掉字符串变量值的前缀或者后缀


在 Linux bash shell 中,通常使用 ${parameter} 表达式来获取 parameter 变量的值,这是一种参数扩展 (parameter expansion)。
Bash 还提供了其他形式的参数扩展,可以对变量值做一些处理,起到操作字符串的效果。例如:

  • ${parameter#word} 从 parameter 变量值的开头往后删除匹配 word 的部分,保留后面剩余内容。
  • ${parameter%word} 从 parameter 变量值的末尾往前删除匹配 word 的部分,保留前面剩余内容。

参考 man bash 的 “Parameter Expansion” 小节的说明,具体举例说明如下。

${parameter#word} 和 ${parameter##word}

查看 man bash 对 ${parameter#word}${parameter##word} 的说明如下:

Romove matching prefix pattern.
The work is expanded to produce a pattern just as in pathname expansion. If the pattern matches the beginning of the value of parameter, then the result of the expansion is the expanded value of parameter with the shortest matching pattern (the '#' case) or the longest matching pattern (the '##' case) deleted.
If parameter is @ or *, the pattern removal operation is applied to each positional parameter in turn, and the expansion is the resultant list.
If parameter is an array variable subscripted with @ or *, the pattern removal operation is applied to each member of the array in turn, and the expansion is the resultant list.

即,${parameter#word} 在 parameter 变量值中,从头开始匹配 word 模式对应的内容。
如果匹配,则删除最短匹配部分并返回变量剩余部分内容。
后面会具体举例说明,方便理解。

${parameter##word} 是在 parameter 变量值中,从头开始匹配 word 模式对应的内容。
如果匹配,则删除最长匹配部分并返回变量剩余部分内容。

上面所说的 "最短匹配"、"最长匹配" 主要是针对有多个匹配的情况。
如果 parameter 变量值中有多个地方匹配 word 模式,则 "最短匹配" 是指在第一次匹配时就停止匹配,并返回剩余的内容。
而 "最长匹配" 会一直匹配到最后一次匹配为止,才返回剩余的内容。

这里的 word 模式可以使用通配符进行扩展,注意不是用正则表达式。

注意:上面所说的 “从头开始匹配” 是指把 parameter 变量值跟 word 模式从头开始比较,而不是在 parameter 变量值中任意匹配 word 模式。

具体举例说明如下:

$ value="This/is/a/test/string"$ echo ${value#This}/is/a/test/string$ echo ${value#test}This/is/a/test/string$ echo ${value#*test}/string

上面先定义了一个 value 变量,然后获取 ${value#This} 的值。
这个参数扩展表示在 value 变量值中从头开始匹配 "This" 字符串。
如果匹配,则删除 "This" 字符串,返回 value 变量值剩余部分内容。
这里能够匹配,所以去掉了 "This" 字符串,打印 "/is/a/test/string"。

但是 echo ${value#test} 的打印结果跟 value 变量值完全一样。
即没有匹配到中间的 "test" 字符串,最短匹配为空,没有删除任何字符串。

使用 ${value#*test} 才能匹配到 value 变量值中间的 "test" 字符串,并删除所有匹配的内容,打印 "/string"。
这里用 * 通配符匹配在 "test" 前面的任意字符,从而匹配 value 变量值开头的部分。

注意${parameter##word} 是操作 parameter 变量值,而不是操作 "parameter" 字符串
所以想要过滤某个字符串的内容,需要先把字符串赋值给某个变量,再用变量名来进行参数扩展。
直接把字符串内容写在大括号 {} 里面不起作用,即使用引号把字符串括起来也不行。

具体举例说明如下:

$ echo ${This/is/a/test/string#This}    # 执行不会报错,但是输出为空$ echo ${"This/is/a/test/string"#This}  # 添加引号会执行报错-bash: ${"This/is/a/test/string"#This}: bad substitution$ echo ${'This/is/a/test/string'#This}-bash: ${'This/is/a/test/string'#This}: bad substitution

可以看到,这里的四个表达式都不能直接处理字符串自身的内容。

${parameter##word} 的用法跟 ${parameter#word} 类似,也是删除匹配的内容,返回剩余的内容。
区别在于,${parameter#word} 是匹配到第一个就停止。
而 ${parameter##word} 是匹配到最后一个才停止。

以上面的 value 变量为例,具体说明如下:

$ echo ${value#*is}/is/a/test/string$ echo ${value##*is}/a/test/string

可以看到,echo ${value##*is} 打印的是 "/is/a/test/string"。
所给的 *is 匹配到 "This" 这个字符串,没有再匹配后面的 "is" 字符串,所以只删除了 "This" 字符串。

而 echo ${value##*is} 打印的是 "/a/test/string"。
它先匹配到 "This",但还是继续往后匹配,最后一个匹配是 "is" 字符串,所以删掉了 "This/is" 字符串。

再次强调,上面说的 "匹配" 是从头开始匹配,而不是部分匹配。
它是要求 word 模式扩展之后得到的字符串从头开始符合 parameter 变量值的内容,而不是在 parameter 变量值里面查找 word 模式。

例如下面的例子:

$ value="This/is/a/test/string./This/is/a/new/test"$ echo ${value##*This}/is/a/new/test.$ echo ${value##This}/is/a/test/string./This/is/a/new/test

可以看到,value 变量值有两个 "This" 字符串。
echo ${value##*This} 匹配到了最后一个 "This" 字符串。
但是 echo ${value##This} 还是只匹配到开头的 "This" 字符串。
它不是在 value 变量值里面查找 "This" 这个子字符串,而是把 "This" 字符串和 value 变量值从头开始、逐个字符进行匹配。

而在 echo ${value##*This} 表达式中,*This 经过通配符扩展后,在 value 变量值中有几种形式的匹配。
例如从头匹配到 "This" 字符串、匹配到 "This/is/a/test/string./This" 字符串。
去掉最长匹配后,打印为 "/is/a/new/test."。
注意体会其中的区别。

注意:在 bash 的参数扩展中,数字属于 positional parameters,可以用数字来引用传入脚本或者函数的参数。
用在当前表达式时,就表示对传入的参数值进行处理。
例如 $1 对应传入的第一个参数,那么 ${1##pattern} 表示在传入第一个参数值中匹配 pattern 模式,并进行处理。

具体举例如下:

$ function param_tail() { echo ${1##*test}; }$ param_tail "This is a test string."string.

可以看到,param_tail 函数使用 ${1##*test} 在传入的第一个参数中匹配 "test" 字符串、以及它前面的任意字符。
如果能够匹配,去掉匹配的内容,只保留后面的部分。

如果所给的 parameter 是一个数组变量且所给下标是 @ 或者 *,那么会对每一个数组元素都进行匹配删除操作,最后得到的扩展结果是合成后的列表。
当需要对多个字符串进行相同处理时,就可以把它们放到一个数组里面,然后使用这两个表达式来进行处理。

下面的例子从多个文件路径中过滤出各自的文件名,去掉了目录路径部分

$ arrays=("src/lib/utils.c" "src/main/main.c" "include/lib/utils.h")$ echo ${arrays[@]##*/}utils.c main.c utils.h

可以看到,${arrays[@]##*/} 表达式使用 arrays[@] 来指定操作 arrays 数组的每一个元素。
用 */ 来匹配目录路径的 / 字符,且在该字符前面可以有任意多的其他字符。
用 ## 指定进行最长匹配,从而会匹配到最后一个 / 字符为止。
最终,该表达式删除匹配的部分,返回剩余的内容,也就是返回文件名自身。

${parameter%word}, ${parameter%%word}

查看 man bash 对 ${parameter%word} ${parameter%%word} 的说明如下:

Remove matching suffix pattern.
The word is expanded to produce a pattern just as in pathname expansion. If the pattern matches a trailing portion of the expanded value of parameter, then the result of the expansion is the expanded value of parameter with the shortest matching pattern (the '%' case) or the longest matching pattern (the '%%' case) deleted.
If parameter is @ or *, the pattern removal operation is applied to each positional parameter in turn, and the expansion is the resultant list.
If parameter is an array variable subscripted with @ or *, the pattern removal operation is applied to each member of the array in turn, and the expansion is the resultant list.

即,${parameter%word} 匹配 parameter 变量值的后缀部分,从后往前匹配 word 模式对应的内容。
如果匹配,则删除最短匹配部分并返回变量剩余部分内容。
所谓的 "最短匹配" 是指从 parameter 变量值的末尾开始,从后往前匹配,一匹配到就结束匹配。
后面会举例说明,方便理解。

${parameter%%word} 匹配 parameter 变量值的后缀部分,从后往前匹配 word 模式对应的内容。
如果匹配,则删除最长匹配部分并返回变量剩余部分内容。
所谓的 "最长匹配" 是指从 parameter 变量值的末尾开始,从后往前匹配,一直匹配到最后一次匹配为止。

这里的 word 模式可以使用通配符进行扩展,注意不是用正则表达式。

前面说明的 ${parameter#word} 是从变量值中删除匹配的前缀部分,保留后面剩余的内容。
而 ${parameter%word} 相反,是从变量值中删除匹配的后缀部分,保留后面剩余的内容。

具体举例说明如下:

$ value="This/is/a/test/string./This/is/a/new/test"$ echo ${value%test}This/is/a/test/string./This/is/a/new/$ echo ${value%%test}This/is/a/test/string./This/is/a/new/$ echo ${value%%test*}This/is/a/$ echo ${value%is*}This/is/a/test/string./This/$ echo ${value#*is}/is/a/test/string./This/is/a/new/test

可以看到,${value%test} 在 value 变量值中,从后往前匹配到 "test" 字符串,删除匹配的内容,获取到前面剩余的部分。
而 ${value%%test} 获取到的值跟 ${value%test} 一样。
因为该表达式是从末尾开始逐字匹配,所以不会往前匹配到第二个 "test" 字符串,只匹配了末尾的 "test" 字符串。

如果要往前匹配到第二个 "test" 字符串,可以使用 * 通配符。
${value%%test*} 表示从后往前匹配,直到最后一个 "test" 模式才停止。

由于 ${parameter%word} 表达式是从后往前匹配,所以 * 通配符要写在 word 模式的后面,才能匹配到中间的内容。
如 ${value%is*} 的输出结果所示。
而 ${parameter#word} 是从前往后匹配,所以 * 通配符要写在 word 模式的前面,才能匹配到中间的内容。
如 ${value#*is} 的输出结果所示。

本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.souzhinan.com/kj/257755.html