3

How would I get Bash to match a regular expression, but rather than replace the value with a constant string, it will instead pass the matched value to a function, and then get the value to replace with from the return value of the function.

Something like the following pseudocode, which replaces every match of [a-d] with the same character, but uppercase:

function uppercase() { echo ${1^^}; }
string="abcdefgh123cbazyz"
echo ${string//[a-d]/uppercase()}
# output: ABCDef123CBAzyz

I'm not particular, any language that is typically installed on a Unix system (such as sed, awk, or even the limited regex support built into bash) can be used.

2
  • If I want the match to be global, it is not so simple as re-running the function several times, as the return value may contain values which in turn are matched by the regular expression.
    – IQAndreas
    Commented Aug 11, 2014 at 10:46
  • Related: stackoverflow.com/questions/1891797/…
    – fedorqui
    Commented Aug 11, 2014 at 11:15

5 Answers 5

4

Bash can't use user defined functions inside parameter expansion.

To accomplish what you want, use pattern matching with case modification:

string="abcdefgh123cbazyz"
echo ${string^^[a-d]}

Output:

ABCDefgh123CBAzyz
2
1

You can use Perl for this:

perl -lape 's/([a-d])/`uppercase $1`/eg' <<< "$string"

but this will require an executable script named uppercase. Perl e (eval) flag executed a command on the match when there is a match.

Another way via sed:

function uppercase() { 
    echo ${1^^} 
}
export -f uppercase
string="abcdefgh123cbazyz"
echo "echo $(sed 's/\([a-d]\)/$(uppercase \1)/g' <<< "$string")" | sh
0

Use tr:

echo abcdefgh123cbazyz | tr '[a-d]' '[A-D]'

Or sed:

echo abcdefgh123cbazyz | sed -r 's|[a-d]|\U&|g'

Output:

ABCDefgh123CBAzyz

Update

Callback with Ruby:

puts "abcdefgh123cbazyz".gsub(/[a-d]/){ |m| m.upcase }

Callbak with Python:

import re

s = "abcdefgh123cbazyz"

def repl(m):
    return m.upper()

print(re.sub('\[\[:(.+?):\]\]', repl, s))

Callback with Perl:

my $s = "abcdefgh123cbazyz";
$s =~ s/([a-d])/uc($1)/eg;
print "${s}\n";

Output:

ABCDefgh123CBAzyz
3
  • Can either of those point to a bash function instead? (changing characters to uppercase was just one limited use case)
    – IQAndreas
    Commented Aug 11, 2014 at 11:20
  • IQAndreas It's not possible with Bash. Perhaps it could work with other languages where callbacks are possible.
    – konsolebox
    Commented Aug 11, 2014 at 11:22
  • @IQAndreas See update for Python and Ruby callbacks.
    – konsolebox
    Commented Aug 11, 2014 at 11:28
0

In Perl you can capture the group and pass it to a subroutine like this, using the e flag:

perl -pe 'sub callback { return uc $_[0] } s/([a-d])/callback $1/eg' <<<"$string"

Output for your string:

ABCDefgh123CBAzyz

Here I've just provided my own wrapper around the existing function uc that returns converts letters to uppercase. You can change the body of the subroutine to do whatever you want.

0

FWIW here's how you'd do it in GNU awk (for the 4th arg to split()):

$ cat tst.awk                                                
function uppercase(str) { return toupper(str) }
{
    split($0,flds,/[a-d]/,seps)
    for (i=1;i in flds; i++) {
        printf "%s%s", flds[i], uppercase(seps[i])
    }
    print ""
}

$ echo "abcdefgh123cbazyz" | gawk -f tst.awk
ABCDefgh123CBAzyz

or with any awk:

$ cat tst.awk                                                
function uppercase(str) { return toupper(str) }
{
    while ( match($0,/[a-d]/) ) {
        printf "%s%s", substr($0,1,RSTART-1), uppercase(substr($0,RSTART,RLENGTH))
        $0 = substr($0,RSTART+RLENGTH)
    }
    print
}

$ echo "abcdefgh123cbazyz" | awk -f tst.awk
ABCDefgh123CBAzyz

Not the answer you're looking for? Browse other questions tagged or ask your own question.