3

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?

1
  • 4
    What if array=(1 2 7 6 7)? What is the 2nd largest value, 6 or 7? Commented Jan 23, 2019 at 15:38

3 Answers 3

4
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)
1
  • 5
    You could save one pipe if you reverse sort, so that the answer is always in second place e.g. 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 Commented Jan 23, 2019 at 14:03
4

A 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).

0

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'

You must log in to answer this question.