0

This is a follow-up question to this question, regarding how to know the number of grouped digits in string.

In bash,

How can I find the last occurrence of a group of digits in a string? So, if I have

string="123 abc 456"

I would get

456

And if I had

string="123 123 456"

I would still get

456
1
  • 3
    Look up some awk examples. Commented Aug 20, 2018 at 15:29

7 Answers 7

2

Without external utilities (such as sed, awk, ...):

$ s="123 abc 456"
$ [[ $s =~ ([0-9]+)[^0-9]*$ ]] && echo "${BASH_REMATCH[1]}"
456

BASH_REMATCH is a special array where the matches from [[ ... =~ ... ]] are assigned to.


Test code:

str=("123 abc 456" "123 123 456" "123 456 abc def" "123 abc" "abc 123" "123abc456def")

for s in "${str[@]}"; do
    [[ $s =~ ([0-9]+)[^0-9]*$ ]] && echo "$s -> ${BASH_REMATCH[1]}"
done

Output:

123 abc 456 -> 456
123 123 456 -> 456
123 456 abc def -> 456
123 abc -> 123
abc 123 -> 123
123abc456def -> 456
Sign up to request clarification or add additional context in comments.

Comments

2

You can use a regex in Bash:

$ echo "$string"
123 abc 456
$ [[ $string =~ (^.*[ ]+|^)([[:digit:]]+) ]] && echo "${BASH_REMATCH[2]}"
456

If you want to capture undelimited strings like 456 or abc123def456 you can do:

$ echo "$string"
test456text
$ [[ $string =~ ([[:digit:]]+)[^[:digit:]]*$ ]] && echo "${BASH_REMATCH[1]}"
456

But if you are going to use an external tool, use awk.

Here is a demo of Bash vs Awk to get the last field of digits in a string. These are for digits with ' ' delimiters or at the end or start of a string.

Given:

$ cat file
456
123 abc 456
123 123 456
abc 456
456 abc
123 456 foo bar
abc123def456

Here is a test script:

while IFS= read -r line || [[ -n $line ]]; do
    bv=""
    av=""
    [[ $line =~ (^.*[ ]+|^)([[:digit:]]+) ]] && bv="${BASH_REMATCH[2]}"
    av=$(awk  '{for (i=1;i<=NF;i++) if (match($i, /^[[:digit:]]+$/)) last=$i; print last}' <<< "$line")
    printf "line=%22s bash=\"%s\" awk=\"%s\"\n" "\"$line\"" "$bv" "$av"
done <file

Prints:

line=                 "456" bash="456" awk="456"
line=         "123 abc 456" bash="456" awk="456"
line=         "123 123 456" bash="456" awk="456"
line=             "abc 456" bash="456" awk="456"
line=             "456 abc" bash="456" awk="456"
line=     "123 456 foo bar" bash="456" awk="456"
line=        "abc123def456" bash="" awk=""

3 Comments

Works perfectly on Bash 3.2.57 (OS X) What version of Bash do you have?
I am running GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu). Hm, it's hard to believe since you are saying there must be [[:space:]] with your regex. So if there is only one "number group" at the beginning of the string, this shouldn't match in my opinion.
@PesaThe: Sorry! I misread your example test string. You are right, my regex this morning would not work for that. Fixed.
1
 grep -o '[0-9]\+' file|tail -1
  • grep -o lists matched text only
  • tail -1 output only the last match

well, if you have string:

grep -o '[0-9]\+' <<< '123 foo 456 bar' |tail -1

3 Comments

The OP specifically said he’s using a string, not a file. I would update the answer to make use of the herestring operator.
@CJK default grep uses BRE. that's why we need the \+
\+ isn't part of BRE, it's a GNU extension to BRE. "Pure" BRE would be [0-9][0-9]*.
1

You may use this sed to extract last number in a line:

sed -E 's/(.*[^0-9]|^)([0-9]+).*/\2/'

Examples:

sed -E 's/(.*[^0-9]|^)([0-9]+).*/\2/' <<< '123 abc 456'
456

sed -E 's/(.*[^0-9]|^)([0-9]+).*/\2/' <<< '123 456 foo bar'
456

sed -E 's/(.*[^0-9]|^)([0-9]+).*/\2/' <<< '123 123 456'
456

sed -E 's/(.*[^0-9]|^)([0-9]+).*/\2/' <<< '123 x'
123

RegEx Details:

  • (.*[^0-9]|^): Match 0 or more characters at start followed by a non-digit OR line start.
  • ([0-9]+): Match 1+ digits and capture in group #2
  • .*: Match remaining characters till end of line
  • \2: Replace it with back-reference #2 (what we captured in group #2)

2 Comments

Hm, sed -E 's/(^|.*[^0-9])([0-9]+).*/\2/' <<< '123 abc 456' gives me 123 :O
Damn, that was a typo :), .Should have copy/pasted from my terminal. It is corrected now
1

Another way to do it with pure Bash:

shopt -s extglob            # enable extended globbing - for *(...)

tmp=${string%%*([^0-9])}    # remove non-digits at the end
last_digits=${tmp##*[^0-9]} # remove everything up to the last non-digit

printf '%s\n' "$last_digits"

1 Comment

I couldn't come up with parameter expansion alone to do this at the time. Nice one ++
0

This is a good job for parameter expansion:

$ string="123 abc 456"
$ echo ${string##* }
456

1 Comment

That will fine last word non necessarily last number.
0

A simple answer with gawk:

echo "$string" | gawk -v RS=" " '/^[[:digit:]]+$/ { N = $0 } ; END { print N }'

With RS=" ", we read each field as a separate record. Then we keep the last number found and print it.

$ string="123 abc 456 abc"
$ echo "$string" | gawk -v RS=" " '/^[[:digit:]]+$/ { N = $0 } ; END { print N }'
456

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.