![在 bash 中:变量替换中的尾随空格捕获](https://linux22.com/image/170107/%E5%9C%A8%20bash%20%E4%B8%AD%EF%BC%9A%E5%8F%98%E9%87%8F%E6%9B%BF%E6%8D%A2%E4%B8%AD%E7%9A%84%E5%B0%BE%E9%9A%8F%E7%A9%BA%E6%A0%BC%E6%8D%95%E8%8E%B7.png)
当尝试从变量值中删除多个尾随空格时,我在 BASH 4.3.48 (SLES12 SP4) 和 BASH 4.4.23 (OpenSUSE Leap 15.1) 中看到了这一点:
~> xxx="-O -Wall "
~> echo "X${xxx%% }X" # (1)
X-O -Wall X
~> echo "X${xxx%% *}X"
X-OX
~> echo "X${xxx% }X"
X-O -Wall X
~> echo "X${xxx% *}X" # (2)
X-O -Wall X
~> echo "X${xxx%% \*}X"
X-O -Wall X
我觉得要么 要么(1)
应该(2)
完成这项工作。
该手册规定${parameter%%word}
:
删除匹配的后缀模式。该单词被扩展以产生一个模式,就像路径名扩展一样。如果模式与参数扩展值的尾部部分匹配,则扩展的结果是具有最短匹配模式(“%”情况)或最长匹配模式(“%”)的参数扩展值。 %'' 情况)已删除。
由于它不像文档中那样工作(或者按照我对文档的理解),我怀疑这是BASH 中的一个错误(-Wall
在“”的情况下,不匹配的后缀(“”)被删除)。%% *
我对吗?
答案1
在 中echo "X${xxx%% }X"
,模式是单个空格:。最长的匹配部分就是:一个空格。最短的匹配部分也只是:一个空格。
对于更多的事情,您需要通配符运算符*
。但这将匹配任何内容,删除-Wall
. Bash globbing 不支持直接拥有正则表达式的等效项a*
。你需要扩展的通配符:
$ shopt -s extglob
$ echo "X${xxx%%+( )}X"
X-O -WallX
答案2
在后缀删除中使用前缀删除:
$ xxx="-O -Wall "
$ echo "X${xxx%"${xxx##*[! ]}"}X"
X-O -WallX
- 删除直到最后一个非空格字符的所有内容 - 只留下尾随空格
- 使用这些空格作为后缀删除的模式
- 内部参数扩展应该被引用以防止它被解释为模式(上面不是必需的,但在其他情况下可能有用):
$ bash -c 'xxx="-O -Wall* "; echo "X${xxx%%"${xxx##*[! *]}"}X"'
X-O -WallX
$ bash -c 'xxx="-O -Wall* "; echo "X${xxx%%${xxx##*[! *]}}X"'
XX
这是一个人为的例子,但如果内部扩展没有被引用,它包含的星号将被外部扩展视为 shell 模式。引用后,它就变成了字面上的星号。
您观察到的行为不是错误,这只是简单的 shell 模式的工作方式:
${xxx%% }
- 一个空间就是一个空间
- 最长出现一次的单个空格是单个空格
${xxx%% *}
- 出现时间最长的单个空格,后跟任何内容/无任何内容
- 任何内容/什么都不包含
-Wall
${xxx% }
- 单个空格的最短出现次数是单个空格
${xxx% *}
- 单个空格后跟任何内容/无任何内容的最短出现次数是单个空格
${xxx%% \*}
\*
是反斜杠转义的星号,将被解释为字面星号- 变量中星号后面没有空格,不会删除后缀
答案3
read
也可能有效(假设IFS
包含“空格”):
xxx="-O -Wall "
read -r xxx <<EOF
$xxx
EOF
echo "X${xxx}X"
输出:
X-O -WallX
read
根据以下条件将输入拆分为字段IFS
IFS
默认情况下是空格/制表符/换行符,因此这将删除所有前导和尾随空格- 适用于变量的第一行(可能不适合多行变量,
bash
可以使用read -d ''
)
答案4
简单的参数扩展在它可以匹配和删除的模式方面非常有限。要从字符串末尾删除几个(重复的)字符,通常的解决方案是首先删除所有字符不是有问题的字符${xxx##*[! ]}
(所有尾随空格)。然后,作为第二步,从末尾删除该扩展所产生的所有内容(所有尾随空格)将为您提供所需的内容(删除尾随空格)。
$ xxx="-O -Wall "
$ echo "<${xxx%"${xxx##*[! ]}"}>"
<-O -Wall>
作为替代方案,在 bash 中,您可以使用扩展通配符:
$ shopt -s extglob
$ echo "<${xxx%%+( )}>"
<-O -Wall>
或者,作为更高级别的替代方案,您可以使用正则表达式匹配您想要的内容:
$ regex='(.*[^ ]) +$';
$ [[ $xxx =~ $regex ]] && echo "<${BASH_REMATCH[1]}>" || echo "<$xxx>"
<-O -Wall>
或者,作为脚本:
#!/bin/bash
xxx=${1:-"-O -Wall "}
regex='(.*[^ ]) +$'
if [[ $xxx =~ $regex ]] # if there are trailing spaces
then
echo "<${BASH_REMATCH[1]}>" # Print the string without spaces
else
echo "<$xxx>" # if there are no trailing spaces.
fi