I have an array like this:
array=(1 2 7 6)
and would like to search for the second largest value, with the output being
secondGreatest=6
Is there any way to do this in bash?
printf '%s\n' "${array[@]}" | sort -n | tail -2 | head -1
Print each value of the array on it's own line, sort it, get the last 2 values, remove the last value
secondGreatest=$(printf '%s\n' "${array[@]}" | sort -n | tail -2 | head -1)
Set that value to the secondGreatest
variable.
Glenn Jackman had an excellent point about duplicate numbers that I didn't consider. If you only care about unique values you can use the -u
flag of sort:
secondGreatest=$(printf '%s\n' "${array[@]}" | sort -nu | tail -2 | head -1)
printf '%s\n' "${array[@]}" | sort -rn | awk NR==2
(likely head
+ tail
is more efficient though) or (at least with GNU Coreutils, which support the null delimiters) printf '%s\0' "${array[@]}" | sort -rzn | cut -d '' -f2
CommentedJan 23, 2019 at 14:03A bash-specific loop through the array could do it; you have to keep track of the largest and second-largest. The only other tricky part is to be careful about initializing those values; the largest value is initialized to the first element; the second-largest value is initialized the first time we see a value that's smaller than the largest value. Subsequently for the second-largest value, we only update it if it's strictly less than the current largest value:
#!/bin/bash array=(7 7 6 2 1) if [ "${#array[@]}" -lt 2 ] then echo Incoming array is not large enough >&2 exit 1 fi largest=${array[0]} secondGreatest='unset' for((i=1; i < ${#array[@]}; i++)) do if [[ ${array[i]} > $largest ]] then secondGreatest=$largest largest=${array[i]} elif (( ${array[i]} != $largest )) && { [[ "$secondGreatest" = "unset" ]] || [[ ${array[i]} > $secondGreatest ]]; } then secondGreatest=${array[i]} fi done echo "secondGreatest = $secondGreatest"
It's still slower than calling out to sort
, but it has the added benefit of picking the strictly-smaller second-largest value in the face of multiple high values (such as 7
and 7
above).
It's a good job for dc :
array=(1 2 7 6) echo ${array[*]} | dc -f - -e ' [lasbdsa]sB [dla!>Bsc1z>A]sA lAx [secondGreatest=]nlbp'
array=(1 2 7 6 7)
? What is the 2nd largest value, 6 or 7?