Bash - getting MAX value from a list of integers
Asked Answered
F

6

13

This script breaks up the cvs list into three columns. we are focusing on the "name" column. I want to discover the name that has the most characters. Once I find the name with the most characters, I want to assign that to a variable.

#!/bin/bash
for i in $(cat homeaway.txt )
do
echo $i | while IFS=, read -r area name host
do
        maxLength=0
        length=${#name}
        if [ $length -gt $maxLength ] ; then
            maxLength=$length
        else
           :
        fi
        printf "%s\n" $maxLength

      done
done

The script says - in English - If the length is greater than maxlength, set length to maxLength, if not, do nothing. The area string with the most characters in it is "script_name_12345678999999" which has 26. When the script reads through all the characters, $maxLength should return 26.

__DATA__
HOME,script_name_12345,USAhost.com   
AWAY,script_name_123,USAhost.com
HOME,script_name_1,EUROhost.com
AWAY,script_name_123,USAhost.com
HOME,script_name_123456,EUROhost.com
AWAY,script_name_12345678999999,USAhost.com
HOME,script_name_1234,USAhost.com
AWAY,script_name_1234578,USAhost.com
HOME,script_name_12,EUROhost.com
AWAY,script_name_123456789,USAhost.com

Once the script reaches the area value with 26 characters in it, it should stop assigning anything to $maxLength. Instead it returns a list of each strings length, and I have no idea how the zero gets in here

[email protected] $ ./length_test.sh
17
0   ### how does the zero get in here ?
15
13
15
18
26  ###script_name_12345678999999
16
19
14
21
Flinn answered 3/11, 2017 at 1:36 Comment(2)
maxLength=0 every loop?Dyane
it is not the intention to have the maxLength rest to zero every iteration in the loop. I thought I was just initializing it.Flinn
D
3

Your loops are a bit wonky (technical term) and you are resetting maxLength to zero on every iteration of your loop. You want something a bit more like:

#!/bin/bash

fn="${1:-/dev/stdin}"   ## read from file given as 1st argument (default stdin)

test -r "$fn" || {      ## validate file is readable 
    printf "error: file not readable '%s'.\n" "$fn"
    exit 1
}

declare -i maxlength=0  ## set maxlength before loop
maxname=

while IFS=, read -r area name host
do
    test -n "$name" || continue            ## if name not set get next line
    len=${#name}
    if [ "$len" -gt "$maxlength" ]; then   ## test length against max
        maxlength=$len                     ## update max if greater
        maxname="$name"                    ## save name in maxname
    fi
done <"$fn"                                ## feed loop by redirecting file

printf "maxname: %s  (len: %d)\n" "$maxname" "$maxlength"

Example Use/Output

$ bash maxnm.sh <dat/maxnm.txt
maxname: script_name_12345678999999  (len: 26)

Look things over and let me know if you have further questions.

Dyane answered 3/11, 2017 at 1:55 Comment(0)
A
32

my_command | sort -n | tail -1

Sort the output of the command numeric and ascending. Get the last element in the resulting list.

Acidosis answered 30/7, 2020 at 8:34 Comment(0)
F
7

On GNU/Linux, you can also do this in one shot. If the file data has your records:

cut --delimiter=, --fields=2 < data | wc --max-line-length

In English:

  • Pluck the 2nd column, using "," as the field separator
  • Print the length of the longest line
Floating answered 3/11, 2017 at 2:7 Comment(7)
this is very compelling piece of programmingFlinn
I think that this demonstrates the elegance and power of the shellFlinn
I thought we want to find the maximum value from a list of integers? If all values have the same number of digits, this does not work, right?Acidosis
Also, I think wc -L prints the maximum line length. So it will only give you the number of digits in the biggest number (i.e. if 400 is your biggest num, wc -L gives you 3)Acidosis
@Acidosis The question reads "I want to discover the name that has the most characters." So most characters, not greatest numeric value.Floating
Ah, okay. I was focusing on the "I want the max integer in a list of integers"Acidosis
cut --delimiter=, --fields=2 < data | sort | tail -n 1 - max cut --delimiter=, --fields=2 < data | sort | head -n 1 - minHexose
D
3

Your loops are a bit wonky (technical term) and you are resetting maxLength to zero on every iteration of your loop. You want something a bit more like:

#!/bin/bash

fn="${1:-/dev/stdin}"   ## read from file given as 1st argument (default stdin)

test -r "$fn" || {      ## validate file is readable 
    printf "error: file not readable '%s'.\n" "$fn"
    exit 1
}

declare -i maxlength=0  ## set maxlength before loop
maxname=

while IFS=, read -r area name host
do
    test -n "$name" || continue            ## if name not set get next line
    len=${#name}
    if [ "$len" -gt "$maxlength" ]; then   ## test length against max
        maxlength=$len                     ## update max if greater
        maxname="$name"                    ## save name in maxname
    fi
done <"$fn"                                ## feed loop by redirecting file

printf "maxname: %s  (len: %d)\n" "$maxname" "$maxlength"

Example Use/Output

$ bash maxnm.sh <dat/maxnm.txt
maxname: script_name_12345678999999  (len: 26)

Look things over and let me know if you have further questions.

Dyane answered 3/11, 2017 at 1:55 Comment(0)
F
2

It easy if you can use awk

since you said :

This script breaks up the cvs list into three columns. we are focusing on the "name" column. I want to discover the name that has the most characters

awk -F, '{l=length($2)}l>max{max=l; name=$2}END{print name, max}' infile

Here is Test Results:

$ cat infile
HOME,script_name_12345,USAhost.com   
AWAY,script_name_123,USAhost.com
HOME,script_name_1,EUROhost.com
AWAY,script_name_123,USAhost.com
HOME,script_name_123456,EUROhost.com
AWAY,script_name_12345678999999,USAhost.com
HOME,script_name_1234,USAhost.com
AWAY,script_name_1234578,USAhost.com
HOME,script_name_12,EUROhost.com
AWAY,script_name_123456789,USAhost.com

$ awk -F, '{l=length($2)}l>max{max=l; name=$2}END{print name, max}' infile
script_name_12345678999999 26

If you just want to get script_name of maxlength to variable then, just print variable name, and wrap in inside $(....) like below

$ myvar=$( awk -F, '{l=length($2)}l>max{max=l; name=$2}END{print name}' infile )
$ echo "$myvar"
$ script_name_12345678999999
Franklinfranklinite answered 3/11, 2017 at 1:55 Comment(0)
F
0

Yet an other max() in bash

#!/usr/bin/env bash
set -u

# simple one liner returning max of two numbers
max2() { printf '%d' $(( $1 > $2 ? $1 : $2 )); }

# max of n numbers
max() {
  [[ $# > 0 ]] || {
    echo "Warning: max takes minimum one argument"
    return 1
  }

  local rs=0 # return status
  local max="$1"
  shift

  for n in "$@"; do
    max=$(( "$n" > "$max" ? "$n" : "$max" ))
    rs=$(( $? > $rs ? $? : $rs ))
  done

  printf '%d' $max
  return $(( $? > $rs ? $? : $rs ))
}

# max 4 5 2 -65 12 11
## output:12

str="4 15 2 -65 +17 11"
echo "max in '${str}' is $(max ${str% * })"
## output: max in '4 15 2 -65 +17 11' is 17
Friedafriedberg answered 19/9, 2022 at 8:31 Comment(0)
C
0

Here is another sorting method, using array:

declare    -a count
while read -r area name host; do
    count[${#name}]+=" $name" # if there are more then one equally big names
done < <(sed 's/,/ /g' data)

The last item will be the biggest:

$ for i in ${!count[@]}; { echo $i ${count[$i]}; }
13 script_name_1
14 script_name_12
15 script_name_123 script_name_123
16 script_name_1234
17 script_name_12345
18 script_name_123456
19 script_name_1234578
21 script_name_123456789
26 script_name_12345678999999

To get the last one:

$ echo ${count[@]:(-1)}
script_name_12345678999999
Catalyst answered 19/9, 2022 at 9:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.