删除前导空格在 Bash 中不起作用

Deleting leading spaces is not working in Bash

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

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

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

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

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

如果str有一个或多个前导space,那么它将return我想要的结果,即_bar baz(下划线 = 前导 space)。如果字符串有 没有 前导 space,它不会做任何事情并且会 return foo bar baz。 'echoes' return 这里的结果相同。

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

编辑

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

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

无论是否有尾随space,这三种解决方案都不会删除'foo'。唯一可行的解​​决方案是我发布的带有星号的解决方案 - 当有尾随 space 时它将删除 'foo',但没有尾随时则不会。

最好的办法是使用参数扩展(带有扩展的 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 的扩展包含 glob 字符,则必须引用它。例如当 pattern="foo[1]".

My understanding is that using * after [[:space:]] should match zero or more instances of [[:space:]], not one or more

错了。

What am I missing

那个glob is not 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]}"

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

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

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

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

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

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

它也可能导致其他类型的破坏,但在适当的时候了解情况下的技巧是很好的。