0

There are multiple related Questions, seems they don't use awk to solve the problem.

echo "blah foo123bar234blah" | egrep -o '([0-9]+)' 

returns

123
234 

But

echo "blah foo123bar234blah" | 
    awk '{ match($0,/([0-9]+)/,m); print m[0], m[1],m[2]}'    

returns 123 123 and

echo "blah foo123bar234blah" | 
    awk '{ match($0,/([0-9]+).+([0-9]+)/,m); print m[0], m[1],m[2]}'    

returns 123bar234 123 4

In the manual, in the section: match(string, regexp [, array]), the example is:

echo foooobazbarrrrr |
    gawk '{ match($0, /(fo+).+(bar*)/, arr); print arr[1], arr[2]}'

Which returns foooo barrrrr.

So how can I extract multiple numbers from a string using awk (equivalent of grep -o)?

1
  • The problem is that .+ between the two numeric matches. It soaks up digits too. Personally, I would: tmp = $0; gsub(/[^0-9]/, " ", tmp); split(tmp, m);. Jan 14 at 22:18

7 Answers 7

5

With GNU awk for multi-char RS and RT:

$ echo "blah foo123bar234blah" |
    awk -v RS='[0-9]+' '$0=RT'
123
234

With any awk (and retaining the original regexp instead of negating it as that's only easy with a simple bracket expression and not a robust general approach):

$ echo "blah foo123bar234blah" |
    awk -v FS='\n' '{gsub(/[0-9]+/,FS"&"FS); for (i=2;i<=NF;i+=2) print $i}'
123
234

or:

$ echo "blah foo123bar234blah" |
    awk '{ while (match($0,/[0-9]+/) ) {print substr($0,RSTART,RLENGTH); $0=substr($0,RSTART+RLENGTH)} }'
123
234
6
  • 1
    great. When RS is a single character, RT contains the same single character. However, when RS is a regular expression, RT contains the actual input text that matched the regular expression. gnu.org/software/gawk/manual/html_node/gawk-split-records.html
    – jian
    Jan 14 at 15:36
  • @EdMorton. echo "blah foo123bar234blah" | awk -v FS='\n' '{gsub(/[0-9]+/,FS); for (i=1;i<=NF;i+=1) print i,$i}' I figured how gsub do. But I am not sure what does FS"&"FS do.
    – jian
    Jan 14 at 15:47
  • 1
    @Mark What I'm doing with gsub(/[0-9]+/,FS"&"FS) puts a newline before and after each number. Since I'm using newline (which can't be present in the current input record since I'm also using newline as the RS) as the FS, doing that means that every even numbered field is then a number.
    – Ed Morton
    Jan 14 at 15:49
  • 1
    now I get it. . An & in the replacement text is replaced with the text that was actually matched. Use \& to get a literal &. (This must be typed as "\\&"; see GAWK: Effective AWK Programming for a fuller discussion of the rules for ampersands and backslashes in the replacement text of sub(), gsub(), and gensub().)
    – jian
    Jan 14 at 15:58
  • No, not "any awk": support gsub. Oct 5 at 18:11
4

The match() function performs a single match of your regular expression. To find each run of digits using regular expression matching with match() in GNU awk, you have to loop.

{
    str = $0
    while (match(str,"[0-9]+",a)) {
        print a[0]
        str = substr(str,RSTART+RLENGTH)
    }
}

We are only interested in a[0] here, as we don't use parentheses in our regular expression. We don't use parentheses in our expression because we don't need them. We had possibly needed parentheses if we had wanted to match a known number of integers with a single expression (e.g. ([0-9]+)[^0-9]+([0-9]+), etc.), but in this exercise we don't really know how many integers there might be.

Or, with standard awk,

{
    str = $0
    while (match(str,"[0-9]+")) {
        print substr(str,RSTART,RLENGTH)
        str = substr(str,RSTART+RLENGTH)
    }
}

This matches runs of consecutive digits in the string in str. For each match, the matching string is printed and the part of str that is no longer interesting to look at is removed using substr().

Testing:

$ echo 'blah foo123bar234blah' | gawk '{ str = $0; while (match(str,"[0-9]+",a)) { print a[0]; str = substr(str,RSTART+RLENGTH) } }'
123
234
3

You could use the gsub() function of awk and transform every occurence of "substrings that to not consist of digits" into single spaces, and then use the split() function to split the resulting string at the whitespace. This behaves just as field splitting on the default FS variable and discards leading and trailing "empty fields":

awk '{gsub(/[^0-9]+/," ");n=split($0,a);for (i=1;i<=n;i++) print a[i]}'

So, for your example:

~$ echo "blah foo123bar234blah" | awk '{gsub(/[^0-9]+/," ");n=split($0,a);for (i=1;i<=n;i++) print a[i]}'
123
234
2

To extracts English 0-9 digits, using GNU awk for the FPAT:

awk -v FPAT='[0-9]+' -v OFS='\n' '$1=$1""'

or with any awk:

awk -F'[^0-9]+' '{ for(i=1; i<=NF; i++) if($i+0==$i) print $i }' infile

or specifically using the match() function (no benefit BTW, since we know we filter out only digits in [^0-9]+, so everything else are just digits but yes, in order to avoid printing empty fields it's useful but not etter than $i+0==$i as above):

awk -F'[^0-9]+' '{
    for(i=1; i<=NF; i++) {
        if (match($i, /[0-9]+/)) print $i
    }
}' infile
1

If you really want to just extract numbers (natural numbers and zero in the following example!), you may define anything else as field separators:

awk 'BEGIN {FS="[^0-9]+"}
     {printf $1 ; for (i=2 ; i<=NF ; i++) { printf " "$i} ; printf "\n"}'

(added some formatting so it returns space separated entries for on one line per record)

2
  • Why printf $1 is null/empty? this part I am not sure. I understand αғsнιη's solution.
    – jian
    Jan 14 at 16:16
  • 1
    @Mark Anything but a digit is field separator. If your string is foo123bar the first separator is foo and thus the first field empty (no entry before foo). For 123foo456bar, the first field would be 123.
    – FelixJN
    Jan 14 at 16:36
1

And also another option using GNU awk for patsplit():

echo "blah foo123bar234blah" | awk 'patsplit($0, a, /[[:digit:]]+/) { for (i in a) printf "%d\n", a[i]}'
123
234
0

why make it so complex with all that FPAT and patsplit and while(match()) -

just this suffices :

 echo 'blah foo123bar234blah' \
 \
 | mawk2 'BEGIN { FS="[^0-9]+"; OFS=ORS; ORS="" } NF=NF'

123
234

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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