如何匹配字符串中倒数第二个空白字符之前的所有字符?

如何匹配字符串中倒数第二个空白字符之前的所有字符?

我试图替换字符串“Abbey Street E.2 Buckfast Street”中的子字符串“Abbey Street E.2”以获得结果“Buckfast Street”。

我尝试过:s/[^ ]* [^ ]* //,但我得到的是“街道”。

基本上我试图使用替换命令删除倒数第二个空白字符之前的所有内容。

我的操作系统是MacOs,我使用的是vim。

另外,我对上述替换命令的理解是将任何非空白字符匹配到空白字符,然后将任何非空白字符匹配到空白字符。它是否正确?

答案1

在 sed 中(我更容易在其中进行测试),我们可以构建所需的正则表达式。
A[^ ]*应该匹配任何单词(如果没有标点符号)。所以:

$ a="Abbey Street E.2 Buckfast Street"
$ echo "$a" | sed 's/[^ ]*//'
 Street E.2 Buckfast Street

将删除第一个单词。请注意,输出中已留有空格。然后我们还需要删除空间。并重复相同的操作 3 次以删除 3 个前导单词(并保留最后两个单词):

$ echo "$a" | sed 's/\([^ ]* \)\{3\}//'
Buckfast Street

但在你的描述中你说:直到倒数第二个空白字符,那是不同的。从 6 个单词的句子中删除 3 个单词将留下 3 个单词,而不是最后两个

因此,我们需要向后工作,为了查看正则表达式的效果,我将捕获每个部分并用 分隔打印|==|

捕获单词的基本想法是使用[^ ]*,是的,它可以工作(有时)。使用 -E 来避免\'s:

$ echo "$a" | sed -E 's/([^ ]*)(.*)/\1|==|\2/'
Abbey|==| Street E.2 Buckfast Street

.*它捕获第一个括号中的第一个单词和第二个括号中的“所有其余”( )。但是,如果我们想反转正则表达式:

$ echo "$a" | sed -E 's/(.*)([^ ]*)/\1|==|\2/'
Abbey Street E.2 Buckfast Street|==|

这里发生的是.*捕获所有内容,下一部分捕获字符(这是 的有效结果*)。我们需要一些锚点或分隔符,强制正则表达式匹配特定点的某些字符或点。我们可以使用空格作为分隔符,使用$作为锚点来确保所选的单词实际上是最后的字符串的:

$ echo "$a" | sed -E 's/(.* )([^ ]*)$/\1|==|\2/'
Abbey Street E.2 Buckfast |==|Street

重复我们匹配的空格最后两个字

$ echo "$a" | sed -E 's/(.* )([^ ]* [^ ]*)$/\1|==|\2/'
Abbey Street E.2 |==|Buckfast Street

现在,选择您想要保留和/或删除的部分:

$ echo "$a" | sed -E 's/(.* )([^ ]* [^ ]*)$/\2/'
Buckfast Street

当然,此时无需捕获第一部分:

$ echo "$a" | sed -E 's/.* ([^ ]* [^ ]*)$/\1/'
Buckfast Street

此 ERE 的 BRE 等效项在 vim 中工作:

:s/.* \([^ ]* [^ ]*\)$/\1/

答案2

在 vim 中你可能需要转义().我有这样的事情:

:s/.* \(.\+ .\+$\)/\1/

它将在没有至少 2 个空格的行上中断。

答案3

我们还可以使用 awk 打印字符串的最后两个单词:

awk '{printf(NF>1)?$(NF-1)" "$NF"\n":(NF>0)?$NF"\n":""}'

例子:

$ echo ""|awk '{printf(NF>1)?$(NF-1)" "$NF"\n":(NF>0)?$NF"\n":""}'
$ echo "1"|awk '{printf(NF>1)?$(NF-1)" "$NF"\n":(NF>0)?$NF"\n":""}'
1
$ echo "1 22"|awk '{printf(NF>1)?$(NF-1)" "$NF"\n":(NF>0)?$NF"\n":""}'
1 22
$ echo "1 22 333"|awk '{printf(NF>1)?$(NF-1)" "$NF"\n":(NF>0)?$NF"\n":""}'
22 333
$ echo "1 22 333 4444"|awk '{printf(NF>1)?$(NF-1)" "$NF"\n":(NF>0)?$NF"\n":""}'
333 4444

如果是 sed,我会使用:

sed 's/^.*\s\([^ \t]\+\)\s\+\([^ \t]\+\)\s*$/\1 \2/g'

例子:

$ echo " 1  22   3333  4444   "|sed 's/^.*\s\([^ \t]\+\)\s\+\([^ \t]\+\)\s*$/\1 \2/g'
3333 4444

由于需要正确处理行中包含少量空格(或制表符符号)的行,并且行末尾也可能有额外的空格,在这种情况下的输出:两个由一个空格单词分隔的行,从而增加了复杂性。但是,这种情况不包括仅包含一个单词的行或仅包含空格的行,它将按原样打印它们。我们可以关心它,但它会使 sed 命令变得更加复杂,所以我在这里跳过它。


更新。

如果是 MacOS sed,它看起来像这样(我排除了制表符以使其更容易):

sed 's/^.* \([^ ][^ ]*\)  *\([^ ][^ ]*\) *$/\1 \2/g'

例子:

$ echo " 1  22   3333  4444   "|sed 's/^.* \([^ ][^ ]*\)  *\([^ ][^ ]*\) *$/\1 \2/g'
3333 4444

答案4

您正在寻找的是:
:s/^\S*\s\S*\s\S*\s//
其中 ^ 代表行的开头,
\s 代表“空白”(空格或制表符)
,\S 代表“无空白”

这可以“缩写”为:
:s/^\(\S*\s\)\{3\}//
代表出现 3 次“任意数量的非空白”,后跟一个空白

这应该匹配并删除“Abbey Street E.2”并保留“Buckfast Street”。

相关内容