How to extract multiple strings with single regex expression in Awk

Viewed 176

I have the following strings:

Mike has XXX cats and XXXXX dogs.
MikehasXXXcatsandXXXXXdogs

I would like to replace Xs with the digits corresponding to the number of Xs:

I tried:

awk '{ match($0, /[X]+/);
  a = length(substr($0, RSTART, RLENGTH));
  gsub(/[X]+/, a) }1'

But it captures only the first match.

Expected output:

Mike has 3 cats and 5 dogs.
Mikehas3catsand5dogs
5 Answers

With your shown samples, could you please try following. Written and tested in GNU awk(should work in any awk).

awk '{for(i=1;i<=NF;i++){if($i~/^X+$/){$i=gsub(/X/,"&",$i)}}} 1'  Input_file

Sample output will be:

Mike has 3 cats and 5 dogs.

Explanation: Going through all the fields(space delimited) and checking if field starts from X and has only X till end of current field, if yes then globally substituting it with its own value(to get the count) and saving into current field itself. Then mentioning 1 will print current line.



NOTE: As per Ed sir's comment(under question section), in case your fields may have values other X too then try(this will even cover XXX456 value in any column too):

awk '{for(i=1;i<=NF;i++){if($i~/X/){$i=gsub(/X/,"&",$i)}}} 1'  Input_file


EDIT: Since OP's samples are changed so adding this solution here, written and tested with GNU awk.

awk -v RS='X+' '{ORS=(RT ? gsub(/./,"",RT) : "")} 1' Input_file

OR

awk -v RS='X+' '{ORS=(RT ? length(RT) : "")} 1' Input_file

Output will be as follows for above code:

Mike has 3 cats and 5 dogs.
Mikehas3catsand5dogs

another awk

$ awk '{for(i=1;i<=NF;i++) if($i~/^X+$/) $i=length($i)}1' file

Mike has 3 cats and 5 dogs.
$ awk '{while( match($0,/X+/) ) $0=substr($0,1,RSTART-1) RLENGTH substr($0,RSTART+RLENGTH)} 1' file
Mike has 3 cats and 5 dogs.
Mikehas3catsand5dogs

If Perl is okay:

$ perl -pe 's/X+/length $&/ge' ip.txt
Mike has 3 cats and 5 dogs.
Mikehas3catsand5dogs

The e flag allows Perl code in replacement section. $& will have the matched portion.

Here's the cleanest awk-based solution i can think of

 {mawk/mawk2/gawk} 'BEGIN { FS = "^$" } /X/ { 

      while(match($0, /[X]+/)) { sub(/[X]+/, RLENGTH) } } 1'

downside of this is having to use regex engine twice for every replacmeent. upside is that it avoids a bunch of substr( ) ops.

Related