在 bash 中:变量替换中的尾随空格捕获

在 bash 中:变量替换中的尾随空格捕获

当尝试从变量值中删除多个尾随空格时,我在 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

相关内容