使用正则表达式在bash中搜索和替换

Lan*_*aru 142 regex bash

我见过这个例子:

hello=ho02123ware38384you443d34o3434ingtod38384day
echo ${hello//[0-9]/}
Run Code Online (Sandbox Code Playgroud)

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

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

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

jhe*_*ngs 151

使用sed:

MYVAR=ho02123ware38384you443d34o3434ingtod38384day
echo "$MYVAR" | sed -e 's/[a-zA-Z]/X/g' -e 's/[0-9]/N/g'
# prints XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX
Run Code Online (Sandbox Code Playgroud)

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

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

echo "$MYVAR" | perl -pe 's/[a-zA-Z]/X/g and s/[0-9]/N/g'
Run Code Online (Sandbox Code Playgroud)

这可能允许您进行更多创意匹配...例如,在上面的剪辑中,除非第一个表达式上存在匹配(由于延迟and评估),否则不会使用数字替换.当然,您可以获得Perl的完整语言支持来进行投标......

  • 由于进程初始化时间的原因,使用`sed`或其他外部工具很昂贵.我特别搜索了all-bash解决方案,因为我发现使用bash替换比我循环中每个项目调用`sed`快3倍以上. (9认同)
  • @CiroSantilli六四事件法轮功纳米比亚威视,被授予,这是共同的智慧,但这并不是明智之举.是的,bash在任何时候都很慢 - 但是精心编写的bash避免了子shell的速度比bash快几个数量级,它可以为每个微小的任务调用外部工具.另外,编写良好的shell脚本将受益于更快的解释器(如ksh93,其性能与awk相当),而写得不好的解释器则无需做任何事情. (5认同)
  • @ rr-如果您甚至担心速度,请不要使用bash :-) (3认同)

Cha*_*ffy 119

这实际上可以在纯粹的bash中完成:

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

... ...产量

howareyoudoingtodday
Run Code Online (Sandbox Code Playgroud)

  • 有人告诉我您会喜欢这些:http://stackoverflow.com/questions/5624969/how-to-reference-captures-in-bash-regex-replacement#answer-22261643 =) (2认同)
  • 如果使用每次调用来处理多个输入行,则调用sed或perl是明智的。与使用循环来处理其输出流相反,在循环内部调用此类工具非常困难。 (2认同)
  • 仅供参考,在zsh中,它只是`$ match`而不是`$ BASH_REMATCH`.(你可以使用`setopt bash_rematch`使其行为像bash一样.) (2认同)

nic*_*kl- 85

这些例子也适用于bash,不需要使用sed:

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day
MYVAR=${MYVAR//[a-zA-Z]/X} 
echo ${MYVAR//[0-9]/N}
Run Code Online (Sandbox Code Playgroud)

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

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day
MYVAR=${MYVAR//[[:alpha:]]/X} 
echo ${MYVAR//[[:digit:]]/N}
Run Code Online (Sandbox Code Playgroud)

产量

XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX
Run Code Online (Sandbox Code Playgroud)

@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'
Run Code Online (Sandbox Code Playgroud)

删除所有文字"d"字符的输出

ho02123ware38384you44334o3434ingto38384ay
Run Code Online (Sandbox Code Playgroud)

但以下确实按预期工作

#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo $hello | perl -pe 's/\d//g'
Run Code Online (Sandbox Code Playgroud)

产量

howareyoudoingtodday
Run Code Online (Sandbox Code Playgroud)

希望能更清楚地说明一些事情,但如果你不感到困惑,为什么不在启用了REG_ENHANCED标志的Mac OS X上尝试这个呢?

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day;
echo $MYVAR | grep -o -E '\d'
Run Code Online (Sandbox Code Playgroud)

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

d
d
d
Run Code Online (Sandbox Code Playgroud)

的nJoy!

  • ...所以,而`$ {hello // [[:digit:]] /}`有效,如果我们只想过滤掉前面带字母`o`的数字,`$ {hello // o [[:数字:]]*}`与预期的行为完全不同(因为在fnmatch模式中,`*`匹配所有字符,而不是将前一项修改为0或更多). (7认同)
  • 赦免?`$ {foo // $ bar/$ baz}`不是**POSIX.2 BRE或ERE语法 - 它是fnmatch() - 样式模式匹配. (6认同)
  • 有关 fnmatch 的完整规范,请参阅 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_03(以及它通过引用包含的所有内容)。 (2认同)

Jos*_*itt 12

如果你正在重复调用并关注性能,那么这个测试显示BASH方法比分叉和可能的任何其他外部进程快约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]
Run Code Online (Sandbox Code Playgroud)


yeg*_*niy 6

使用[[:digit:]](注意双括号)作为模式:

$ hello=ho02123ware38384you443d34o3434ingtod38384day
$ echo ${hello//[[:digit:]]/}
howareyoudoingtodday
Run Code Online (Sandbox Code Playgroud)

只是想总结答案(特别是@ nickl-的/sf/answers/1558293411/)。


Dab*_*phy 6

我知道这是一个古老的线程,但它是我在 Google 上的第一次点击,我想分享resub我整理的以下内容,这些内容增加了对多个 $1、$2 等反向引用的支持......

#!/usr/bin/env bash

############################################
###  resub - regex substitution in bash  ###
############################################

resub() {
    local match="$1" subst="$2" tmp

    if [[ -z $match ]]; then
        echo "Usage: echo \"some text\" | resub '(.*) (.*)' '\$2 me \${1}time'" >&2
        return 1
    fi

    ### First, convert "$1" to "$BASH_REMATCH[1]" and 'single-quote' for later eval-ing...

    ### Utility function to 'single-quote' a list of strings
    squot() { local a=(); for i in "$@"; do a+=( $(echo \'${i//\'/\'\"\'\"\'}\' )); done; echo "${a[@]}"; }

    tmp=""
    while [[ $subst =~ (.*)\${([0-9]+)}(.*) ]] || [[ $subst =~ (.*)\$([0-9]+)(.*) ]]; do
        tmp="\${BASH_REMATCH[${BASH_REMATCH[2]}]}$(squot "${BASH_REMATCH[3]}")${tmp}"
        subst="${BASH_REMATCH[1]}"
    done
    subst="$(squot "${subst}")${tmp}"

    ### Now start (globally) substituting

    tmp=""
    while read line; do
        counter=0
        while [[ $line =~ $match(.*) ]]; do
            eval tmp='"${tmp}${line%${BASH_REMATCH[0]}}"'"${subst}"
            line="${BASH_REMATCH[$(( ${#BASH_REMATCH[@]} - 1 ))]}"
        done
        echo "${tmp}${line}"
    done
}

resub "$@"

##################
###  EXAMPLES  ###
##################

###  % echo "The quick brown fox jumps quickly over the lazy dog" | resub quick slow
###    The slow brown fox jumps slowly over the lazy dog

###  % echo "The quick brown fox jumps quickly over the lazy dog" | resub 'quick ([^ ]+) fox' 'slow $1 sheep'
###    The slow brown sheep jumps quickly over the lazy dog

###  % animal="sheep"
###  % echo "The quick brown fox 'jumps' quickly over the \"lazy\" \$dog" | resub 'quick ([^ ]+) fox' "\"\$low\" \${1} '$animal'"
###    The "$low" brown 'sheep' 'jumps' quickly over the "lazy" $dog

###  % echo "one two three four five" | resub "one ([^ ]+) three ([^ ]+) five" 'one $2 three $1 five'
###    one four three two five

###  % echo "one two one four five" | resub "one ([^ ]+) " 'XXX $1 '
###    XXX two XXX four five

###  % echo "one two three four five one six three seven eight" | resub "one ([^ ]+) three ([^ ]+) " 'XXX $1 YYY $2 '
###    XXX two YYY four five XXX six YYY seven eight
Run Code Online (Sandbox Code Playgroud)

H/T回复@Charles Duffy(.*)$match(.*)