How to subtract 1 from numbers matched by regular expression?

awk substitution capabilities are quite limited. gawk has gensub() that can at least include parts of the matched portion in the replacement, but no operation can be done on those.

It's possible with awk, but you need to take a different approach:

awk '{
  text = $0
  $0 = ""
  while (match(text, /[0-9]+/)) {
    $0 = $0 substr(text, 1, RSTART-1) \
         (substr(text, RSTART, RLENGTH) - 1)
    text = substr(text, RSTART+RLENGTH)
  }
  $0 = $0 text
  print}'

Or with GNU awk as a variation on @jofel's approach:

gawk -v 'RS=[0-9]+' '{printf "%s", $0 (RT==""?"":RT-1)}'

or

gawk -v 'RS=[^0-9]+' '{printf "%s",($0==""?"":$0 - 1)RT}'

However, here it's a lot easier with perl:

perl -pe 's/\d+/$&-1/ge'

perl can use capture groups (as $1, $2... and $& for the whole matched portion) and with the e flag can run arbitrary perl expressions with those.


Your awk solution matches only the first number and then replaces all other number with the first number reduced by one.

Taking your program, you can use with GNU's awk (gawk):

awk 'BEGIN { RS="[^0-9]"; OFS=""; ORS=""; } {a=gensub(/([0-9]+)/,"\\1","g",$0);if(a~/[0-9]+/) {gsub(/[0-9]+/,(a-1),$0);} print $0,RT}'

But this can be simplified to

awk 'BEGIN { RS="[^0-9]"; OFS=""; ORS=""; } {if(length($0)) {print ($0-1);}print RT}' 

Or with comments:

awk '
  BEGIN { 
    RS="[^0-9]";  # set the record separator to a regexp matching all 
    OFS="";  # no output field separator
    ORS="";  # no output record separator (we use RT)
 } 
 {
     if(length($0)) { # if number found
       print ($0-1); # print it decreased by one
     }
     print RT # output current field separator (=non-digit). 
 }'

Each non-digit is used as record separator and re-inserted with the print statement.

Here a solution in python:

python -c 'import re,sys; print re.compile("\d+").sub(lambda i: str(int(i.group())-1),sys.stdin.read()),'