如何替换字符串中的字符,但仅限于它出现在分隔的子字符串中?

时间:2016-04-11 18:34:22

标签: string macos bash awk sed

我想用字符串中的另一个字符替换字符,但仅当字符出现在字符串的分隔子字符串中时。例如,对于字符串:

  

B [B] ABC [ABC] BBB [BBB]

我想将“b”更改为“x”,但前提是它在方括号内“[...]”。因此,所需的结果是字符串:

  

B [X] ABC [AXC] BBB [XXX]

我的偏好是sed或bash解决方案因为它们在我的舒适区域,但任何适用于Mac OS X的解决方案都没问题。从搜索来看,似乎这可以通过使用负向前瞻和负面后瞻的sed来完成,但我不相信这些功能在Mac版本的sed上可用。

7 个答案:

答案 0 :(得分:2)

使用GNU sed:

$ sed -r ':a;s/(\[[^]]*)b/\1x/;ta' <<< "b[b]abc[abc]bbb[bbb]"
b[x]abc[axc]bbb[xxx]
  • :a为即将推出的循环添加标签
  • s:替换命令
  • (\[[^]]*):搜索并捕获[后跟任何非]字符
  • 直到找到b
  • 匹配字符串将替换为最初捕获的字符串和x
  • ta:如果之前的替换成功,则循环标记:a(替换b的任何其他匹配项)

对于OS X上的GNU sed:

brew uninstall gnu-sed

更多信息:How to use GNU sed on Mac OS X

答案 1 :(得分:1)

这是一种(相当强力)纯粹的Bash解决方案:

raw='b[b]abc[abc]bbb[bbb]'
cooked=

declare -r delimited_rx='^(.*)\[([^][]*)\](.*)$'

while [[ $raw =~ $delimited_rx ]] ; do
    raw=${BASH_REMATCH[1]}
    printf -v cooked '[%s]%s%s' \
        "${BASH_REMATCH[2]//b/x}" \
        "${BASH_REMATCH[3]}" \
        "$cooked"
done

cooked=$raw$cooked

printf '%s\n' "$cooked"

答案 2 :(得分:1)

因为&#34;任何适用于Mac OS X的解决方案都很好&#34;,请考虑Perl:

perl -ple 's{\[([^][]*)\]}{ ($m=$1)=~s/b/x/g; "[$m]" }eg' <<< 'b[b]abc[abc]bbb[bbb]'

答案 3 :(得分:0)

使用gnu-awk:

s='b[b]abc[abc]bbb[bbb]'
awk -v OFS= -v FPAT='\\[[^]]+\\]|[^[]*' '{
   for (i=1; i<=NF; i++) if ($i ~ /\[.*\]/) gsub(/b/, "x", $i)} 1' <<< "$s"

<强>输出:

b[x]abc[axc]bbb[xxx]

在OSX上我使用home brew安装了gnu-awk。

答案 4 :(得分:0)

$ awk '{ while(match($0,/\[[^][]*b[^][]*\]/)) { tgt=substr($0,RSTART,RLENGTH); gsub(/b/,"x",tgt); $0=substr($0,1,RSTART-1) tgt substr($0,RSTART+RLENGTH) } } 1' file
b[x]abc[axc]bbb[xxx]

答案 5 :(得分:0)

感谢您提供的精彩解决方案!所有解决方案(sed,awk和bash)都完美地在我的系统上运行。由于我对sed有点偏爱,我发现使用t命令和循环的sed解决方案非常好。需要稍加修改,即更换;使用换行符,并将-r选项替换为-E,以使其在我的OS X系统上运行:

var noteStr = "1-415-655-0001 US TOLL\n\nAccess code: 197 703 792"

/* since we're using native pattern matching, let's use a native method
   also when extracting the first row (even if this is somewhat simpler
   if using Foundation bridged NSString methods)                            */
if let firstRowChars = noteStr.characters.split("\n").first,
    case let firstRow = String(firstRowChars) {

    // pattern matching for number characters
    let pattern = UnicodeScalar("0")..."9"
    let numbers = firstRow.unicodeScalars
        .filter { pattern ~= $0 }
        .reduce("") { String($0) + String($1) }

    print(numbers) // 14156550001

    /* Alternatively use .reduce with an inline if clause directly:
    let numbers = firstRow.unicodeScalars
        .reduce("") { pattern ~= $1 ? String($0) + String($1) : String($0)} */
}

我做了另一个修改,以确保只有当一个右方括号伴随一个开口方括号时才进行替换:

sed -E '
:a
s/(\[[^]]*)b/\1x/
ta
' <<< "b[b]abc[abc]bbb[bbb]"

b[x]abc[axc]bbb[xxx]

答案 6 :(得分:0)

echo 'b[b]abc[abc]bbb[bbb]' | awk -vRS='[][]' 'NR%2==0{gsub("b","x")}{printf $0 RT}'
b[x]abc[axc]bbb[xxx]