使用正则表达式搜索并替换bash


161

我看过这个例子:

hello=ho02123ware38384you443d34o3434ingtod38384day
echo ${hello//[0-9]/}

遵循以下语法: ${variable//pattern/replacement}

不幸的是,该pattern字段似乎不支持完整的正则表达式语法(例如,如果我使用.\s,它将尝试匹配文字字符)。

如何使用完整的正则表达式语法搜索/替换字符串?


:在这里找到一个相关的问题stackoverflow.com/questions/5658085/...
jheddings

2
仅供参考,\s它不是标准POSIX定义的正则表达式语法的一部分(无论是BRE还是ERE);这是PCRE扩展,大多数情况下无法从Shell获得。[[:space:]]是更通用的等效项。
查尔斯·达菲

1
\s可以用[[:space:]],顺便.?和替换,并且基线shell模式语言的extglob扩展可以用于诸如可选子组,重复组之类的事情。
Charles Duffy 2015年


我在Solaris的bash版本4.1.11中使用了此命令... echo $ {hello // [0-9]}请注意,缺少最后的斜杠。
丹尼尔·利斯顿

Answers:


175

使用sed

MYVAR=ho02123ware38384you443d34o3434ingtod38384day
echo "$MYVAR" | sed -e 's/[a-zA-Z]/X/g' -e 's/[0-9]/N/g'
# prints XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

请注意,后续-e的按顺序处理。同样,g表达式的标志将匹配输入中的所有匹配项。

您也可以使用此方法选择自己喜欢的工具,即perl,awk,例如:

echo "$MYVAR" | perl -pe 's/[a-zA-Z]/X/g and s/[0-9]/N/g'

这可能使您可以进行更多有创意的匹配...例如,在上面的代码段中,除非第一个表达式上存在匹配(由于延迟计算and),否则将不使用数字替换。当然,您有Perl的全部语言支持来进行出价...


据我所知,这只能进行一次替换。有没有办法像我发布的代码一样替换模式的所有出现?
拉纳鲁(Lanaru)2012年

我已经更新了答案,以演示多个替换以及全局模式匹配。让我知道是否有帮助。
jheddings 2012年

非常感谢!出于好奇,您为什么从单行版本(在原始答案中)切换为两行版本?
拉纳鲁(Lanaru)2012年

9
sed由于过程初始化时间的原因,使用或其他外部工具很昂贵。我特别搜索了全bash解决方案,因为我发现使用bash替换比sed在循环中调用每个项目快3倍以上。
rr-

6
@CiroSantilli六四事件法轮功纳米比亚威视,这是常识,但这并不明智。是的,bash不管如何都很慢-但是编写良好的bash避免了子shell的速度实际上要比为每个微小任务调用外部工具的bash快几个数量级。同样,编写精良的Shell脚本将受益于更快的解释器(如ksh93,其性能与awk相当),而编写拙劣的脚本则无济于事。
Charles Duffy 2015年

133

实际上,这可以通过纯bash来完成:

hello=ho02123ware38384you443d34o3434ingtod38384day
re='(.*)[0-9]+(.*)'
while [[ $hello =~ $re ]]; do
  hello=${BASH_REMATCH[1]}${BASH_REMATCH[2]}
done
echo "$hello"

产量

howareyoudoingtodday

2
有人

=~是关键。但是考虑到循环中的重新分配,这有点笨拙。两年前使用@jheddings解决方案是另一个不错的选择-调用sed或perl)。
布伦特·浮士德·浮士德2015年

3
如果使用每个调用来处理多个输入行,则调用sedperl明智。与使用循环来处理其输出流相反,在循环内部调用此类工具非常困难。
Charles Duffy 2015年

2
仅供参考,用zsh $match代替$BASH_REMATCH。(您可以使用使其表现得像bash一样setopt bash_rematch。)
Marian

这很奇怪-因为zsh并不是要成为POSIX shell,因此可以说是遵循POSIX指南的信,即所有大写变量用于POSIX指定的(shell或与系统相关的)用途,而小写变量被保留用于应用程序使用。但是,由于zsh是运行应用程序而不是应用程序本身的东西,因此使用应用程序变量名称空间而不是系统名称空间的决定似乎是错误的。
查尔斯·达菲

95

这些示例也可以在bash中使用,而无需使用sed:

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day
MYVAR=${MYVAR//[a-zA-Z]/X} 
echo ${MYVAR//[0-9]/N}

您还可以使用字符类括号表达式

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day
MYVAR=${MYVAR//[[:alpha:]]/X} 
echo ${MYVAR//[[:digit:]]/N}

输出

XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

但是,如果我正确理解问题,@ Lanaru想要知道的是为什么“完整”或PCRE扩展名\s\S\w\W\d\D等无法在php ruby​​ python等中支持。这些扩展名来自与Perl兼容的正则表达式(PCRE)和可能与其他形式的基于Shell的正则表达式不兼容。

这些不起作用:

#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo ${hello//\d/}


#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo $hello | sed 's/\d//g'

删除所有文字“ d”字符的输出

ho02123ware38384you44334o3434ingto38384ay

但是以下确实可以正常工作

#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo $hello | perl -pe 's/\d//g'

输出

howareyoudoingtodday

希望能进一步澄清问题,但是如果您还不困惑,为什么不在启用了REG_ENHANCED标志的Mac OS X上尝试一下:

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day;
echo $MYVAR | grep -o -E '\d'

在大多数* nix版本中,您只会看到以下输出:

d
d
d

欢乐!


6
赦免?${foo//$bar/$baz}不是 POSIX.2 BRE和ERE语法-它的的fnmatch() -风格的模式匹配。
查尔斯·达菲

8
...因此,尽管${hello//[[:digit:]]/}可行,但是,如果我们只想过滤出字母前面的数字o${hello//o[[:digit:]]*}则其行为将与预期的行为完全不同(因为在fnmatch模式中,*匹配所有字符,而不是将前一项修改为0或更多)。
2014年

1
有关fnmatch的完整说明,请参见pubs.opengroup.org/onlinepubs/9699919799/utilities/…(以及所有通过引用合并的内容)。
Charles Duffy 2014年

1
man bash:可以使用附加的二进制运算符=〜,其优先级与==和!=相同。使用它时,运算符右边的字符串被视为扩展的正则表达式,并进行了相应的匹配(如regex(3)中一样)。
尼克

1
@aderchox您是正确的,您可以使用数字[0-9][[:digit:]]
-nickl

13

如果您重复通话并且关心性能,则此测试表明BASH方法比分叉sed以及可能进行任何其他外部处理的速度快约15倍。

hello=123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X

P1=$(date +%s)

for i in {1..10000}
do
   echo $hello | sed s/X//g > /dev/null
done

P2=$(date +%s)
echo $[$P2-$P1]

for i in {1..10000}
do
   echo ${hello//X/} > /dev/null
done

P3=$(date +%s)
echo $[$P3-$P2]

1
如果您对减少分叉的方式感兴趣,如何在Bash中为命令的输出设置变量的此答案中
F.豪里

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.