【问题标题】:Deleting leading spaces is not working in Bash删除前导空格在 Bash 中不起作用
【发布时间】:2021-03-13 06:20:00
【问题描述】:

我在 Bash 中有一个字符串,它可能以也可能不以任意数量的前导空格开头,例如

"  foo bar baz"
" foo bar baz"
"foo bar baz"

我想从字符串中删除“foo”的第一个实例,以及任何前导空格(可能没有)。

根据this question 的建议,我尝试了以下方法:

str=" foo bar baz"
regex="[[:space:]]*foo"
echo "${str#$regex}"
echo "${str#[[:space:]]*foo}"

如果 str 有 一个或多个 前导空格,那么它将返回我想要的结果,即 _bar baz(下划线 = 前导空格)。如果字符串有 no 前导空格,它将不执行任何操作并返回 foo bar baz。两个“回声”在这里返回相同的结果。

我的理解是在[[:space:]] 之后使用* 应该匹配 个或多个[[:space:]] 实例,而不是一个或多个。我在这里遗漏了什么或做错了什么?

编辑

@Raman - 我尝试了以下方法,但它们也不起作用:

echo "${str#[[:space:]]?foo}"
echo "${str#?([[:space:]])foo}"
echo "${str#*([[:space:]])foo}"

无论是否有尾随空格,所有三个解决方案都不会删除 'foo'。唯一可行的解​​决方案是我用星号发布的解决方案 - 当有尾随空格时它将删除“foo”,但在没有尾随空格时不会。

【问题讨论】:

  • @RamanSailopal docs at GNU 表示? 匹配零个或一个匹配项,* 匹配零个或多个匹配项。无论如何我都试过了,但没有用 - 将更新问题。
  • and they also don't work: 启用 extglob...
  • ${str#*foo} 有什么问题?
  • @oguzismail 在 str=oguzfoo 的情况下,我猜 op 不想匹配。
  • @Lou:只是为了确保这不是 XY 问题:您是否要在空格处拆分字符串?如果是这种情况,您应该改用:read -ra ary -d '' < <(printf '%s\0' "$str"),并且您将拥有数组 ary 中的令牌。

标签: string bash removing-whitespace parameter-expansion


【解决方案1】:

最好的做法是使用参数扩展(带有扩展的 glob),如下所示:

# Make sure extglob is enabled
shopt -s extglob

str=" foo bar baz"
echo "${str##*([[:space:]])}"

这使用扩展 glob *([[:space:]])## 参数扩展(贪婪匹配)。

编辑。由于你的模式有后缀foo,你不需要使用贪心匹配:

echo "${str#*([[:space:]])foo}"

够了。

注意。您也可以将foo 放入变量中,但请注意,您必须引用它:

pattern=foo
echo "${str#*([[:space:]])"$pattern"}"

会起作用。如果pattern 的扩展包含全局字符,则必须引用它。例如当pattern="foo[1]".

【讨论】:

  • 谢谢,这行得通!为什么出于兴趣使用最长匹配而不是最短匹配?
  • @Lou:否则只会删除第一个空格。但现在我意识到你的模式中也有foo,所以echo "${str#*([[:space:]])foo}" 就足够了。我已经编辑了答案(并且还添加了关于将模式放入变量中的注释)。
  • @Lou: 1. 你不需要在re 中引用令牌foo:这就足够了:re="*([[:space:]])foo*([[:space:]])"。 2. 模式不能被引用! 实际上,引号是为了防止将模式解释为模式!因此:echo "${str#$re}"$re 不带引号)是正确的。
  • @Lou: 不完全是:) 模式变量应该在参数扩展中被引用只有当你不希望它被解释为模式时。 这里有一个非常简单的例子,你可以试试:str="foo bar"; pattern="*"。 (我们在这里使用引号的事实是无关紧要的)。没有引号:echo "${str#$pattern}" 你会得到oo bar,因为str 匹配模式 f*。但是加上引号:echo "${str#"$pattern"}",你会得到foo bar,因为strpattern逐字内容 不匹配,即f*
  • 啊,有道理!谢谢你解释得这么清楚:)
【解决方案2】:

我的理解是在 [[:space:]] 之后使用 * 应该匹配零个或多个 [[:space:]] 实例,而不是一个或多个

错了。

我错过了什么

glob 不是regex。在 regex 中,* 匹配零个或多个前面的字符或组。在 glob 中,* 匹配任何内容。这和文件名扩展是一样的,想想ls [[:space:]]*foo

您可以使用扩展的 bash glob 并执行以下操作:

shopt -s extglob
str=' foo bar baz'
echo "${str#*([[:space:]])foo}"

要做更复杂的事情,实际使用正则表达式。

str=' foo bar baz';
[[ $str =~ ^[[:space:]]*foo(.*) ]];
echo "${BASH_REMATCH[1]}"

【讨论】:

  • 啊,太棒了!我以前不知道正则表达式和 glob 之间的区别。现在它可以工作了,并且字符串正确修剪。干杯:)。
  • 出于兴趣,为什么要从一开始就获得最长的匹配 ##?最短匹配 # 也可以。
  • 哦,我想你也想删除 foo 后面的空格。
【解决方案3】:

如果您想要的是真正的正则表达式匹配,您应该使用真正的正则表达式匹配:

$: [[ "$str" =~ [[:space:]]*(.*) ]]
$: echo "[${BASH_REMATCH[1]}]"
[foo  bar       baz]

更简单的方法是跳过引号。

$: echo "[$str]"
[ foo bar baz]
$: new=$( echo $str )
$: echo "[$new]"
[foo bar baz]

请注意,在任何更复杂的情况下,这会让您面临各种混乱。 如果您想在值之间保留多个连续的空格,或者一个制表符而不是引号等,它会中断。

$: str=' foo  bar'$'\t''baz';
$: echo "[$str]"
[ foo  bar      baz]
$: new=$( echo $str )
$: echo "[$new]"
[foo bar baz]

它也可能造成其他类型的破坏,但最好在适当的时候了解这些情况的诀窍。

【讨论】:

  • 这不是我想要做的——我只想从字符串前面加上一个给定的单词,而不是从字符串中的所有单词中修剪前导空格。
  • 这就是我警告它的原因。第一个匹配解决方案是更好的方法
猜你喜欢
  • 1970-01-01
  • 2017-04-05
  • 1970-01-01
  • 2017-12-18
  • 1970-01-01
  • 2021-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多