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.

IQAndreas
  • 8,060
  • 8
  • 39
  • 74
  • 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 Aug 11 '14 at 10:46
  • Related: http://stackoverflow.com/questions/1891797/capturing-groups-from-a-grep-regex – fedorqui Aug 11 '14 at 11:15

5 Answers5

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
John B
  • 3,566
  • 1
  • 16
  • 20
  • 1
    Wow, +1, impressive tiny geeky feature of Bash, not so well documented...?! :) – jaybee Aug 11 '14 at 11:39
  • 1
    Documentation: http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion and http://www.gnu.org/software/bash/manual/bashref.html#Pattern-Matching – glenn jackman Aug 11 '14 at 17:56
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
perreal
  • 94,503
  • 21
  • 155
  • 181
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
konsolebox
  • 72,135
  • 12
  • 99
  • 105
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.

Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
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
Ed Morton
  • 188,023
  • 17
  • 78
  • 185