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?

k-a-v
  • 202
  • 2
  • 9

3 Answers3

5
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)
jesse_b
  • 35,934
  • 12
  • 91
  • 140
  • 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` – steeldriver Jan 23 '19 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).

Jeff Schaller
  • 66,199
  • 35
  • 114
  • 250
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'
ctac_
  • 1,960
  • 1
  • 6
  • 14