# Replace all occurrences of a regular expression in a scalar variable. # The variable is modified directly. Respects the setting of the option # RE_MATCH_PCRE, but otherwise sets the zsh emulation mode. # # Arguments: # # 1. *name* (not contents) of variable or more generally any lvalue; # expected to be scalar. # # 2. regular expression # # 3. replacement string. This can contain all forms of # $ and backtick substitutions; in particular, $MATCH will be # replaced by the portion of the string matched by the regular # expression. Parsing errors are fatal to the shell process. if (( $# < 2 || $# > 3 )); then setopt localoptions functionargzero print -ru2 "Usage: $0 []" return 2 fi # ensure variable exists in the caller's scope before referencing it # to make sure we don't end up referencing one of our own. typeset -g -- "$1" || return 2 typeset -nu -- var=$1 || return 2 local -i use_pcre=0 [[ -o re_match_pcre ]] && use_pcre=1 emulate -L zsh local regexp=$2 replacement=$3 result MATCH MBEGIN MEND local -a match mbegin mend if (( use_pcre )); then # if using pcre, we're using pcre_match and a running offset # That's needed for ^, \A, \b, and look-behind operators to work # properly. zmodload zsh/pcre || return 2 pcre_compile -- "$regexp" && pcre_study || return 2 local -i offset=0 start stop local new ZPCRE_OP local -a finds while pcre_match -b -n $offset -- "$var"; do # we need to perform the evaluation in a scalar assignment so that # if it generates an array, the elements are converted to string (by # joining with the first chararacter of $IFS as usual) new=${(Xe)replacement} finds+=( ${(s[ ])ZPCRE_OP} "$new" ) # for 0-width matches, increase offset by 1 to avoid # infinite loop (( offset = finds[-2] + (finds[-3] == finds[-2]) )) done (( $#finds )) || return # no match unsetopt multibyte offset=1 for start stop new in "$finds[@]"; do result+=${var[offset,start]}$new (( offset = stop + 1 )) done result+=${var[offset,-1]} else # no PCRE # in ERE, we can't use an offset so ^, (and \<, \b, \B, [[:<:]] where # available) won't work properly. local subject=$var local -i ok while [[ $subject =~ $regexp ]]; do # append initial part and substituted match result+=$subject[1,MBEGIN-1]${(Xe)replacement} # truncate remaining string if (( MEND < MBEGIN )); then # zero-width match, skip one character for the next match (( MEND++ )) result+=$subject[MBEGIN] fi subject=$subject[MEND+1,-1] ok=1 [[ -z $subject ]] && break done (( ok )) || return result+=$subject fi var=$result