Nathan Pk
Nathan Pk

Reputation: 749

Is there a way to do multiple substitutions using regsub?

Is it possible to have do different substitutions in an expression using regsub?

example:

set a ".a/b.c..d/e/f//g"

Now, in this expression, is it possible to substitute "." as "yes" ".." as "no" "/" as "true" "//" as "false" in a single regsub command?

Upvotes: 1

Views: 4797

Answers (2)

Donal Fellows
Donal Fellows

Reputation: 137787

With a regsub, no. There's a long-standing feature request for this sort of thing (which requires substitution with the result of evaluating a command on the match information) but it's not been acted on to date.

But you can use string map to do what you want in this case:

set a ".a/b.c..d/e/f//g"
set b [string map {".." "no" "." "yes" "//" "false" "/" "true"} $a]
puts "changed $a to $b"
# changed .a/b.c..d/e/f//g to yesatruebyescnodtrueetrueffalseg

Note that when building the map, if any from-value is a prefix of another, the longer from-value should be put first. (This is because the string map implementation checks which change to make in the order you list them in…)


It's possible to use regsub and subst to do multiple-target replacements in a two-step process, but I don't advise it for anything other than very complex cases! A nice string map is far easier to work with.


EDIT: In Tcl 9.0, you can do this with regsub -command:

regsub -all -command  {\.\.?|//?} $a {
    dict get {. yes .. no / true // false}
}

The inner selection of what to replace with is via dict get as the neatest way to do this, but in general you're more likely to use apply and a lambda expression like this:

regsub -all -command  {\.\.?|//?} $a {apply {{substring} {
    const MAPPING {. yes .. no / true // false}
    return [dict get $MAPPING $substring]
}}}

Using a lambda lets you do much more complex transformations.

The argument passed to the inner command call is the substring that is matched (plus any capturing sub-REs as extra arguments), and the result of the inner command call is the substitution to use for the match.

Upvotes: 10

Marco Pallante
Marco Pallante

Reputation: 4043

You may also try to do it yourself. This is a draft proc which you could use as a starting point. It is not production ready, and you must be carefull because substitutions after the first one work on already substituted string.

These are the parameters:

  • options is a list of options that will be passed to every call to regsub
  • resubList is a list of key/value pairs, where the key is a regular expression and the value is a substitution
  • string is the string you want to substitute

This is the procedure, and it simply calls regsub multiple times, once for every element in resubList and, at the end, it returns the final string.

proc multiregsub {options resubList string} {
    foreach {re sub} $resubList {
        set string [regsub {*}$options -- $re $string $sub]
    }
    return $string
}

Upvotes: 2

Related Questions