ChatGPT解决这个技术问题 Extra ChatGPT

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

我看过这个例子:

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

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

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

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

在这里找到一个相关的问题:stackoverflow.com/questions/5658085/…
仅供参考,\s 不是标准 POSIX 定义的正则表达式语法的一部分(BRE 或 ERE 都不是);它是一个 PCRE 扩展,并且大多数情况下不能从 shell 获得。 [[:space:]] 是更普遍的等价物。
\s 可以替换为 [[:space:]],顺便说一下,. 可以替换为 ?,并且基线 shell 模式语言的 extglob 扩展可用于可选子组、重复组等。
我在 Solaris 上的 bash 版本 4.1.11 中使用它... echo ${hello//[0-9]} 注意缺少最后的斜线。

C
Charles Duffy

使用 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 的完整语言支持来做你的竞标......


据我所知,这只进行了一次替换。有没有办法让它像我发布的代码一样替换所有出现的模式?
我已经更新了我的答案以演示多个替换以及全局模式匹配。让我知道这是否有帮助。
非常感谢!出于好奇,您为什么从单行版本(在您的原始答案中)切换到两行版本?
由于进程初始化时间,使用 sed 或其他外部工具的成本很高。我特别搜索了全 bash 解决方案,因为我发现使用 bash 替换比为循环中的每个项目调用 sed 快 3 倍以上。
@CiroSantilli六四事件法轮功纳米比亚威视,当然,这是普遍的智慧,但这并不明智。是的,无论如何 bash 都很慢——但是避免子 shell 的编写良好的 bash 实际上比为每个微小任务调用外部工具的 bash 快几个数量级。此外,编写良好的 shell 脚本将受益于更快的解释器(如 ksh93,它的性能与 awk 相当),而编写不佳的 shell 脚本则没有什么可做的。
C
Charles Duffy

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

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

...产量...

howareyoudoingtodday

有件事告诉我你会喜欢这些:stackoverflow.com/questions/5624969/… =)
=~ 是关键。但是考虑到循环中的重新分配,有点笨拙。两年前的@jheddings 解决方案是另一个不错的选择——调用 sed 或 perl)。
如果使用每次调用来处理多行输入,则调用 sedperl 是明智的。在循环内部调用这样的工具,而不是使用循环来处理其输出流,是鲁莽的。
仅供参考,在 zsh 中,它只是 $match 而不是 $BASH_REMATCH。 (您可以使用 setopt bash_rematch 使其表现得像 bash。)
这很奇怪——因为 zsh 并没有试图成为一个 POSIX shell,它可以说是遵循 POSIX 指南关于用于 POSIX 指定(shell 或系统相关)目的的全大写变量和保留小写变量的字母应用程序使用。但是由于 zsh 是运行应用程序的东西,而不是应用程序本身,因此使用应用程序变量命名空间而不是系统命名空间的决定似乎非常不合常理。
n
nickl-

这些示例也可以在 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

开心!


赦免? ${foo//$bar/$baz} 不是 POSIX.2 BRE 或 ERE 语法——它是 fnmatch() 样式的模式匹配。
...因此,虽然 ${hello//[[:digit:]]/} 有效,但如果我们只想过滤掉以字母 o 开头的数字,${hello//o[[:digit:]]*} 将具有与预期完全不同的行为(因为在 fnmatch 模式中,* 匹配所有字符,而不是将前一个项目修改为 0 个或多个)。
有关 fnmatch 的完整规范,请参阅 pubs.opengroup.org/onlinepubs/9699919799/utilities/…(以及它通过引用合并的所有内容)。
man bash:可以使用额外的二元运算符 =~,其优先级与 == 和 != 相同。使用时,运算符右侧的字符串被视为扩展正则表达式并进行相应匹配(如在 regex(3) 中)。
@aderchox 你是对的,对于数字你可以使用 [0-9][[:digit:]]
J
Josiah DeWitt

如果您正在重复调用并且关心性能,该测试表明 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]

如果您对减少分叉的方法感兴趣,请在 this answer to How to set a variable to the output of a command in Bash? 中搜索词 newConnector
C
Community

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

$ hello=ho02123ware38384you443d34o3434ingtod38384day
$ echo ${hello//[[:digit:]]/}
howareyoudoingtodday

只是想总结一下答案(尤其是@nickl-'s https://stackoverflow.com/a/22261334/2916086)。


D
Dabe Murphy

我知道这是一个古老的线程,但这是我在 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

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


T
Tono Nam

此示例在输入 hello ugly world 中搜索正则表达式 bad|ugly 并将其替换为 nice

#!/bin/bash

# THIS FUNCTION NEEDS THREE PARAMETERS
# arg1 = input              Example:  hello ugly world
# arg2 = search regex       Example:  bad|ugly
# arg3 = replace            Example:  nice
function regex_replace()
{
  # $1 = hello ugly world
  # $2 = bad|ugly
  # $3 = nice

  # REGEX
  re="(.*?)($2)(.*)"

  if [[ $1 =~ $re ]]; then
    # if there is a match
    
    # ${BASH_REMATCH[0]} = hello ugly world
    # ${BASH_REMATCH[1]} = hello 
    # ${BASH_REMATCH[2]} = ugly
    # ${BASH_REMATCH[3]} = world    

    # hello + nice + world
    echo ${BASH_REMATCH[1]}$3${BASH_REMATCH[3]}
  else    
    # if no match return original input  hello ugly world
    echo "$1"
  fi    
}

# prints 'hello nice world'
regex_replace 'hello ugly world' 'bad|ugly' 'nice'

# to save output to a variable
x=$(regex_replace 'hello ugly world' 'bad|ugly' 'nice')
echo "output of replacement is: $x"
exit

V
Vladimir Djuricic

设置变量

hello=ho02123ware38384you443d34o3434ingtod38384day

然后,在 var 上使用正则表达式替换回显

echo ${hello//[[:digit:]]/}

这将打印:

howareyoudoingtodday

额外 - 如果你想要相反的(获取数字字符)

echo ${hello//[![:digit:]]/}

这将打印:

021233838444334343438384

这与问题的代码几乎相同。您错过了有关“pattern 字段似乎不支持完整的正则表达式语法(例如,如果我使用 .\s,它会尝试匹配文字字符)”的部分。 – 例如,您不能执行 echo ${hello//[[:digit:]\s]/}
@AdamKatz 是的,没什么大不了的,它发生了。谢谢
A
Asclepius

你可以使用蟒蛇。这效率不高,但可以使用更灵活的语法完成工作。

申请备案

以下 pythonscript 将用“TO”替换“FROM”(但不是“notFrom”)。

正则表达式_replace.py

import sys
import re

for line in sys.stdin:
    line = re.sub(r'(?<!not)FROM', 'TO', line)
    sys.stdout.write(line)

您可以将其应用于文本文件,例如

$ cat test.txt
bla notFROM
FROM FROM
bla bla
FROM bla

bla  notFROM FROM

bla FROM
bla bla


$ cat test.txt | python regex_replace.py
bla notFROM
TO TO
bla bla
TO bla

bla  notFROM TO

bla TO
bla bla

应用于变量

#!/bin/bash

hello=ho02123ware38384you443d34o3434ingtod38384day
echo $hello

PYTHON_CODE=$(cat <<END
import sys
import re

for line in sys.stdin:
    line = re.sub(r'[0-9]', '', line)
    sys.stdout.write(line)
END
)
echo $hello | python -c "$PYTHON_CODE"

输出

ho02123ware38384you443d34o3434ingtod38384day
howareyoudoingtodday

我对此表示反对,因为我搜索了“在 Bash 中使用正则表达式”。 Python 不会帮助我设置我的 PS1 提示符(afaik)。