14

The following array represented the numbers of disks on each linux machines

Each single array includes the number of disks on a linux machine.

echo ${ARRAY_DISK_Quantity[*]}
4 4 4 4 2 4 4 4

what is the simple way to identify that all array's values are equal?

Good status:

4 4 4 4 4 4 4 4

Bad status:

4 4 4 4 4 4 2 4

Bad status:

6 6 6 6 6 6 6 6 6 6 2 6 2
jesse_b
  • 37,005
yael
  • 13,106

8 Answers8

12

bash + GNU sort + GNU grep solution:

if [ "${#array[@]}" -gt 0 ] && [ $(printf "%s\000" "${array[@]}" | 
       LC_ALL=C sort -z -u |
       grep -z -c .) -eq 1 ] ; then
  echo ok
else
  echo bad
fi

English explanation: if unique-sorting the elements of the array results in only one element, then print "ok". Otherwise print "bad".

The array is printed with NUL bytes separating each element, piped into GNU sort (relying on the -z aka --zero-terminated and -u aka --unique options), and then into grep (using options -z aka --null-data and -c aka --count) to count the output lines.

Unlike my previous version, I can't use wc here because it requires input lines terminated with a newline...and using sed or tr to convert NULs to newlines after the sort would defeat the purpose of using NUL separators. grep -c makes a reasonable substitute.


Here's the same thing rewritten as a function:

function count_unique() {
  local LC_ALL=C

  if [ "$#" -eq 0 ] ; then 
    echo 0
  else
    echo "$(printf "%s\000" "$@" |
              sort --zero-terminated --unique |
              grep --null-data --count .)"
  fi
}



ARRAY_DISK_Quantity=(4 4 4 4 2 4 4 4)

if [ "$(count_unique "${ARRAY_DISK_Quantity[@]}")" -eq 1 ] ; then
  echo "ok"
else
  echo "bad"
fi
cas
  • 78,579
  • 1
    Note that sort -u doesn't return unique elements but one of each set of elements that sort the same. For instance, it would say "ok" on ARRAY_DISK_Quantity=(① ②) on a GNU systems where locales typically decide those 2 characters sort the same. You'd want LC_ALL=C sort -u for byte-to-byte uniqueness. – Stéphane Chazelas Dec 25 '17 at 11:58
  • just another note it will be fail also in case no additional disks are appears from CLI so need also to add this syntax – yael Dec 25 '17 at 12:22
  • [[ printf "%s\n" "${ARRAY_DISK_Quantity[@]}" | wc -l -eq printf "%s\n" "${ARRAY_DISK_Quantity[@]}" | grep -c "0" ]] && echo fail – yael Dec 25 '17 at 12:22
  • @StéphaneChazelas the locale issue is worth dealing with, as is the IFS issue. Testing for an empty list is, IMO, best done separately - there's no need to check for non-unique elements in an empty set. – cas Dec 25 '17 at 12:53
  • Hi Cas I prefer your previous answer – yael Dec 25 '17 at 13:55
  • feel free to use my original answer if it works for you, but the issues @StéphaneChazelas raised aren't just pedantry - they can cause weird (and serious) problems in some circumstances. If you're 100% confident that they don't apply to your array (and with the sample you've shown, there should be no problem), then it'll be fine....but I prefer to post a generic answer that is safe to use for many similar tasks than one that has lots of hidden assumptions that could result in breakage. – cas Dec 25 '17 at 14:07
  • @StéphaneChazelas: I am sure you know this, however it deserves pointing out explicitly, that that is the correct result. When asking whether all elements of a collection have a certain property, then the answer is always "yes" for the empty collection. This is basically a vacuous truth. – Jörg W Mittag Dec 25 '17 at 21:55
  • @JörgWMittag, I'd expect more people, when asked are all your antlers soft? (to keep the Christmassy note) would answer I don't have any antler. Few would say yes (I'd expect a few would say no). IOW, an empty set is a special case, there is no correct or incorrect outcome. I was just pointing it out. – Stéphane Chazelas Dec 26 '17 at 21:00
9

With zsh:

if ((${#${(u)ARRAY_DISK_Quantity[@]}} == 1)); then
  echo OK
else
  echo not OK
fi

Where (u) is a parameter expansion flag to expand unique values. So we're getting a count of the unique values in the array.

Replace == 1 with <= 1 is you want to consider an empty array is OK.

With ksh93, you could sort the array and check that the first element is the same as the last:

set -s -- "${ARRAY_DISK_Quantity[@]}"
if [ "$1" = "${@: -1}" ]; then
  echo OK
else
  echo not OK
fi

With ksh88 or pdksh/mksh:

set -s -- "${ARRAY_DISK_Quantity[@]}"
if eval '[ "$1" = "${'"$#"'}" ]'; then
  echo OK
else
  echo not OK
fi

With bash, you'd probably need a loop:

unique_values() {
  typeset i
  for i do
    [ "$1" = "$i" ] || return 1
  done
  return 0
}
if unique_values "${ARRAY_DISK_Quantity[@]}"; then
  echo OK
else
  echo not OK
fi

(would work with all the Bourne-like shells with array support (ksh, zsh, bash, yash)).

Note that it returns OK for an empty array. Add a [ "$#" -gt 0 ] || return at the start of the function if you don't want that.

  • all these answers not seems to support bash ? – yael Dec 25 '17 at 12:04
  • @yael, see edit for a bash solution. But why would you use bash? – Stéphane Chazelas Dec 25 '17 at 12:08
  • In Bash, the help page for typeset says Obsolete. See `help declare'. Is there a reason you're using it instead of local or declare? – wjandrea Dec 26 '17 at 02:52
  • 1
    @wjandrea typeset is the one that works in all 4 shells. It's also the original one from ksh in the early 80s ( bash mostly copied ksh88 when it comes to variable scoping type setting and declaration but decided to rename typeset declare and make typeset an alias to declare). – Stéphane Chazelas Dec 26 '17 at 07:32
4

bash + awk soltion:

function get_status() {
    arr=("$@")    # get the array passed as argument
    if awk 'v && $1!=v{ exit 1 }{ v=$1 }' <(printf "%d\n" "${arr[@]}"); then 
        echo "status: Ok"
    else 
        echo "status: Bad"
    fi
}

Test case #1:

ARRAY_DISK_Quantity=(4 4 4 4 4 2 4 4)
get_status "${ARRAY_DISK_Quantity[@]}"
status: Bad

Test case #2:

ARRAY_DISK_Quantity=(4 4 4 4 4 4 4 4)
get_status "${ARRAY_DISK_Quantity[@]}"
status: Ok
4

I have another bash only solution that should work with strings as well:

isarray.equal () {
    local placeholder="$1"
    local num=0
    while (( $# )); do
        if [[ "$1" != "$placeholder" ]]; then
            num=1
            echo 'Bad' && break
        fi
        shift
    done
    [[ "$num" -ne 1 ]] && echo 'Okay'
}

Demonstration:

[root@JBSTEST001 ~]# ARRAY_DISK_Quantity=(4 4 4 4 2 4 4 4)
[root@JBSTEST001 ~]# isarray.equal "${ARRAY_DISK_Quantity[@]}"
Bad
[root@JBSTEST001 ~]# ARRAY_DISK_Quantity=(4 4 4 4 4 4 4 4)
[root@JBSTEST001 ~]# isarray.equal "${ARRAY_DISK_Quantity[@]}"
Okay
[root@JBSTEST001 ~]# ARRAY_DISK_Quantity=(four four four four two four four four)
[root@JBSTEST001 ~]# isarray.equal "${ARRAY_DISK_Quantity[@]}"
Bad
[root@JBSTEST001 ~]# ARRAY_DISK_Quantity=(four four four four four four four four)
[root@JBSTEST001 ~]# isarray.equal "${ARRAY_DISK_Quantity[@]}"
Okay
jesse_b
  • 37,005
2

With bash and GNU grep:

if grep -qE '^([0-9]+)( \1)*$' <<< "${ARRAY_DISK_Quantity[@]}"; then 
  echo "okay"
else
  echo "not okay"
fi
Cyrus
  • 12,309
0

bash only solution (assuming a is ARRAY_DISK_Quantity)

ttt=${a[0]}
res=0
for i in "${a[@]}"
do 
    let res+=$(if [ "$ttt" -ne "$i" ]; then echo 1; else echo 0; fi);  
done
if [ "$res" -eq 0 ]
then 
    echo "ok"
else
    echo "bad"
fi
  • Works, but counts all errors when just one is enough: if [ "$ttt" -ne "$i" ]; then res=1; break; fi; – Joe Dec 30 '17 at 06:30
0

Here is POSIX Awk:

awk 'BEGIN {while (++z < ARGC) if (ARGV[z] != ARGV[1]) exit 1}' "${ARRAY_DISK_Quantity[@]}"
Zombo
  • 1
  • 5
  • 44
  • 63
0

Use a for loop to compare each array element to the next. End the loop one iteration less than the length of the array to avoid comparing the last element to nothing at the end.

for (( i=0; i<((${#array[@]}-1)); i++ )); do
    [ "${array[$i]}" != "${array[(($i+1))]}" ] && echo "Mismatch"
done
echo "Match"
  • Welcome on U&L and thank you for your contribution! This code will print "Match" even if a mismatch is found... is it intended? – fra-san Feb 21 '19 at 15:10