Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I am looking for a method to print the longest number in a string.

E.g.: If I have the string

212334123434test233

how can I print

212334123434

?

Note: I am looking for the longest continuous sequence of numbers, not for the numerically higher value.


Edit: Thanks for the answers, everyone. The response to this question has been quite overwhelming. I marked @HaukeLaging's post as the accepted answer because it suited my specific case very well but I'd like to point out that all answers are equally valid. It's always great to have several different options to solve a problem.

share|improve this question
    
What do you want the method to do when there are multiple equally long continuous sequences? Take the first? The last? A random one? –  Anthon Apr 29 at 4:37
    
@Anthon Huh, I hadn't thought of that. Luckily enough that's not an issue in my specific case. I guess any of the options would be fine. –  Glutanimate Apr 29 at 4:46
3  
Note that the answer you have accepted (and all others so far except one) won't deal with decimal numbers. I don't know if that's a problem for you. –  terdon Apr 29 at 10:21
    
@terdon: It's not an issue in my specific case because I am dealing with IDs rather than actual numbers but I'd like to thank you for your answer nonetheless! I am sure someone else will find it very useful in the future. –  Glutanimate Apr 29 at 10:35
    
Would you like the solution to be able to deal with negative numbers? And if so - does the minus sign count towards the length? –  Floris Apr 29 at 11:34

9 Answers 9

up vote 7 down vote accepted
echo 212334123434test233abc44 | 
awk '{gsub("[^0-9]+","\n"); print;}' | 
awk '{ if (length($0) > max) {max = length($0); maxline = $0} } 
  END { print maxline }'

212334123434
share|improve this answer

I believe you can do this with just grep, sort, and tail as well. Here are some example strings.

$ echo <str> | grep -oP "\d+" | sort -n | tail -1

Where <str> is our string under question.

Example

$ set -o posix; set | grep "str[0-9]"
str0=212334123434test233
str1=212334123434test233abc44
str2=233test212334123434
str3=a212334123434test233abc44
str4=a91234b212334123434abc

Now if I run these through my grep ... command in turn.

$ echo $str0 | grep -oP "\d+" | sort -n | tail -1
212334123434
$ echo $str1 | grep -oP "\d+" | sort -n | tail -1
212334123434
$ echo $str2 | grep -oP "\d+" | sort -n | tail -1
212334123434
$ echo $str3 | grep -oP "\d+" | sort -n | tail -1
212334123434
$ echo $str4 | grep -oP "\d+" | sort -n | tail -1
212334123434

This approach works by selecting all the substrings that are sequences of digits. We then sort this output numerically, sort -n, and then grab the last value in the list, using tail -1. This will be the longest substring.

You can see how it works by taking the tail -1 off and rerunning one of the examples:

$ echo $str4 | grep -oP "\d+" | sort -n
91234
212334123434

Strings that start with zeros

The above approach works for every situation I could conceive of except one. @terdon mentioned in chat this scenario which foils the above approach.

  • 0000000000001
  • 2

So to deal with this you'll need to change tactics slightly. The kernel of the above approach can still be leveraged, however we need to inject the number of characters into the results too. This gives sort the ability to sort the results by number of characters in the strings & their values.

$ for i in $(echo $str0 | grep -oP "\d+");do a=$(echo "$i" | wc -c); \
    echo "$a $i"; done | sort -n | tail -1 | cut -d" " -f2

Results:

$ echo $str0
0000000000001a2test

$ for i in $(echo $str0 | grep -oP "\d+");do a=$(echo "$i" | wc -c); \
    echo "$a $i"; done | sort -n | tail -1 | cut -d" " -f2
0000000000001

You can condense this a bit by making use of Bash's ability to determine a variable's length using ${#var}.

$ for i in $(echo $str0 | grep -oP "\d+");do echo "${#i} $i"; done | \
    sort -n | tail -1 | cut -d" " -f2
0000000000001

Using `grep -P

I've opted to use grep -P ... above because I, being a Perl developer, like the class syntax of saying all digits like so: \d+, instead of [[:digit:]]\+ or [0-9]\+. But for this particular problem it isn't really needed. You could just as easily swapped out the grep I've used like so:

$ .... grep -o "[0-9]\+" ....

For example:

$ for i in $(echo $str0 | grep -o "[0-9]\+");do echo "${#i} $i"; done | \
    sort -n | tail -1 | cut -d" " -f2
0000000000001
share|improve this answer
2  
Using ${#i} to get the string length can save you calling wc, if you want to go bash-specific –  glenn jackman Apr 29 at 17:04
    
@glennjackman - thanks added your improvement to my A 8-) –  slm Apr 29 at 17:16
    
GNU grep 2.16 (at least) says -P is "highly experimental". You can use grep -o "[0-9]\+" instead of grep -oP "\d+" –  David Conrad Apr 29 at 23:53
1  
@DavidConrad - added these details to the A as well, thanks! –  slm Apr 30 at 0:04

A solution in perl:

echo 212334123434test233abc44 |
perl -nle 'print ((
    map { $_->[0] }
    sort{ $a->[1] <=> $b->[1] }
    map { [$_,length] }
    split /\D+/, $_)[-1]
    )'
212334123434

References

share|improve this answer
2  
Love a nice Schwartzian Transform! –  glenn jackman Apr 29 at 17:17

Using python with the string passed on the commandline and assuming you want the first sequence of maximum length:

import sys

longest = current = ""
for x in sys.argv[1]:
    if current and not x.isdigit():
        if len(current) > len(longest):
            longest = current
        current = ""
    else:
        current += x 
print(longest)
share|improve this answer
2  
or tersely python -c "import re,sys; print max(re.split(r'\D+', sys.argv[1]), key=len)" –  1_CR Apr 29 at 13:22

Here's another Perl approach that can deal with decimals as well as integers:

echo "0.212334123434test233" | 
 perl -lne 'while(/([\d.]+)/g){$max=$1 if length($1) > length($max)} print $max'

Note that none of the answers so far posted will deal with decimals and since you specify that you want the longest and not the numerically largest number, I assume you actually need decimals.

Explanation

  • perl -lne : The -n means "read the input line by line, and run the script given by -e on it". The -l adds a newline to each print call (and other things not relevant here).
  • while(/([\d.]+)/g) : iterate through all numbers (\d means [0-9], so [\d.] will match digits and .. If you also want to find negative numbers, add -. The parentheses capture the matched string as $1 which is used in the next step.
  • $max=$1 if length($1) > length($max) : If the length of the current match is greater than the longest so far ($max) save the match as $max.
  • print $max : print the longest string of numbers found. This will be executed after the while loop finishes, so after all numbers have been found.
share|improve this answer
1  
+1 Your regex is a bit too generic, though. It would match IP addresses for example. I propose something like \D(\d+(?:\.\d+)?)\D instead. –  Joseph R. Apr 29 at 17:25
    
Should also work without the \D anchors... –  Joseph R. Apr 29 at 17:48
    
@JosephR. hmm, true, I had not considered consecutive . like in IP addresses. –  terdon Apr 29 at 21:57

Given

str="212334123434test233"

then in bash

max=""
while read num; do 
  (( ${#num} > ${#max} )) && max=$num
done < <(grep -Eo '[0-9]+' <<< "$str")
echo $max
212334123434

A possibly purer bash solution using an array constructed by replacing non-digit characters in the string with whitespace, in place of the grep

max=""
declare -a nums="${str//[^[:digit:]]/ }"
for num in ${nums[@]}; do 
  (( ${#num} > ${#max} )) && max=$num
done
echo $max
share|improve this answer

Building on the answer from @mikeserv, here is yet another alternative. It extracts the numbers (per mikeserv's method), then sorts them in numerical order and takes the last one. Barring leading zeros, this will give you the largest number (not taking account of sign):

echo 1111askdlfm2234 |  printf %s\\n $(tr -sc 0-9 \ ) | sort -n | tail -1
share|improve this answer
    
This one actually works - mine did not. I had the '\r' on the wrong side! I'm going to delete it. You can also just use the shell like - set -- $(echo $str | tr ... ) ; b=${#1} ; for d ; do [ ${#d} -gt $b ] && b=${#d} n=$d ; done ; echo $n –  mikeserv Apr 29 at 17:19
1  
I deleted my own awful post, and you dealt gently enough with me. Since you're already using tr anyway, I'd bear no grudge if you incorporated the above. Probably sort is faster, but, then again, it waits for the stream end the same as the $(subshell). I don't know. In any case, yours is already an excellent answer, but if you feel like adding in the above shell loop feel free is all I'm saying. And by the way - it's possible you could do without sort altogether with a little creative handling of wc -L and tee in stream... I'm done with this question though - I'm embarrassed. –  mikeserv Apr 29 at 17:42
    
One last thing though - you might as well pull tr out of the subshell and be rid of printf. Just do '0-9' '\n'. –  mikeserv Apr 29 at 18:04
    
@mikeserv - the good thing about this site is that we learn from each other. Thanks for your help; without your answer I would not even have started on my own... –  Floris Apr 29 at 18:42

bash and GNU sort

IFS=$'\0' read -r l _ < <(tr -cs '[:digit:]' '[\0*]' <<<'11abcde1234556ghijk22'| sort -znr)
echo $l
1234556
share|improve this answer

Use non-numeric characters to split the string, and find the longest sequence or largest numeric value (for equal-length numbers) with a ternary operator.

$ echo "212334123434test233" | awk -F'[^0-9]+' '{for(i=1;i<=NF;i++){m=length($i)>=length(m)||$i>m?$i:m}};END{print m}'
212334123434

You can also set awk's record separator (RS) to be any non-numeric character string:

$ echo "212334123434test233" \
    | awk -v RS='[^0-9]+' '
        length(longest) < length($0) {longest = $0};
        END{print longest}'
212334123434
share|improve this answer
2  
Why not just set RS = '[^0-9]+' and use Awk's inherent loop? echo "212334123434test233" | awk -v RS='[^0-9]+' 'length(longest) < length($0) {longest = $0};END{print longest}' 212334123434 –  user61786 Apr 29 at 6:23
    
@awk_FTW you should put that down as an answer too. :) Thanks for showing me the RS variable, I must admit this is the first time I'm seeing it. You have more tips to offer for awk than I do hahaha! –  h.j.k. Apr 29 at 7:01

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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