3

I have the following bash script:

#!/bin/bash

encl0=( 0,0 0,1 0,2 0,3 0,4 0,5 0,7 0,8 0,9 0,10 0,11 0,12 0,13 0,14 0,15 )
MISSING_DISKS=()
OLDIFS=$IFS
IFS=$'\n'
MISSING_DISKS+=($({ printf '0 %s\n' {0..15}; printf '0 %s\n' "${encl0[@]#0,}"; } | sort | uniq -u))
IFS=$OLDIFS
echo "$({ printf '0 %s\n' {0..15}; printf '0 %s\n' "${encl0[@]#0,}"; } | sort | uniq -u)"
echo "${MISSING_DISKS[@]}"
if ((${#MISSING_DISKS[@]}>1)); then
    echo "Greater than 1"
else
    echo "Success"
fi

When I run it with bash v4.4 it works as I expect:

$ /usr/local/bin/bash test.sh
0 6
0 6
Success

However when I run it with bash v3.2 it does not:

$ /bin/bash test.sh
0 6
0 0 0 0 1 2 3 4 5 7 8 9 10 11 12 13 14 15 0 1 0 10 0 11 0 12 0 13 0 14 0 15 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9
Greater than 1

I don't understand how MISSING_DISKS is being set to something different than the output of the command that is setting it though. Does anyone know what is causing this?

jesse_b
  • 37,005
  • printf '0 %s\n' "${encl0[@]#0,}" is not expanded in multiple words but one string only. Add -x to bash when you call script to see the different. I don't remember when that bug was fixed at all. – cuonglm May 13 '18 at 00:59

1 Answers1

7

Playing with spaces, tabs or newlines is always close to failure.

The core problem occurs here:

encl0=( 0,0 0,1 0,2 0,3 0,4 0,5 0,7 0,8 0,9 )
IFS=$'\n'
printf '<0 %s>\n' "${encl0[@]#0,}"

If executed on bash 3.2:

$ b32sh ./script
<0 0 1 2 3 4 5 7 8 9>

The expansion of "${encl0[@]#0,}" is processed as one string, not a list of values.

The problem doesn't manifest if either the IFS has an space or if the expansion does not edit each value of the array:

#!/bin/bash
encl0=( 0,0 0,1 0,2 0,3 0,4 0,5 0,7 0,8 0,9 )
IFS=$' \n'
printf '<0 %s>\n' "${encl0[@]#0,}"

Executed:

$ b32sh ./script
<0 0>
<0 1>
<0 2>
<0 3>
<0 4>
<0 5>
<0 7>
<0 8>
<0 9>

Or:

#!/bin/bash
encl0=( 0,0 0,1 0,2 0,3 0,4 0,5 0,7 0,8 0,9 )
IFS=$'\n'
printf '<0 %s>\n' "${encl0[@]}"

Executed:

b32sh ./so
<0 0,0>
<0 0,1>
<0 0,2>
<0 0,3>
<0 0,4>
<0 0,5>
<0 0,7>
<0 0,8>
<0 0,9>

The problem is hidden in your script because you restore IFS IFS=$OLDIFS before the testing echo line.

One way to avoid the issue is to not use an space in the printf:

#!/bin/bash
encl0=( 0,0 0,1 0,2 0,3 0,4 0,5 0,7 0,8 0,9 0,10 0,11 0,12 0,13 0,14 0,15 )
MISSING_DISKS=()
OLDIFS=$IFS
IFS=$' \n'
MISSING_DISKS+=($({ printf '0x%s\n' {0..15}; printf '0x%s\n' "${encl0[@]#0,}"; } | sort | uniq -u))
echo "test $({ printf '0x%s\n' {0..15}; printf '0x%s\n' "${encl0[@]#0,}"; } | sort | uniq -u)"
echo "var  ${MISSING_DISKS[@]}"
if ((${#MISSING_DISKS[@]}>1)); then
    echo "Greater than 1"
else
    echo "Success"
fi
IFS=$OLDIFS

The other alternative is to avoid the expansion with substitution after changing the IFS to a newline by using an alternate array:

#!/bin/bash
OLDIFS=$IFS
encl0=( 0,0 0,1 0,2 0,3 0,4 0,5 0,7 0,8 0,9 0,10 0,11 0,12 0,13 0,14 0,15 )
IFS=$' \n'; arr=("${encl0[@]#0,}")
MISSING_DISKS=()
IFS=$'\n'
MISSING_DISKS+=($({ printf '0 %s\n' {0..15}; printf '0 %s\n' "${arr[@]}"; } | sort | uniq -u))
echo "test $({ printf '0 %s\n' {0..15}; printf '0 %s\n' "${arr[@]}"; } | sort | uniq -u)"
echo "var  ${MISSING_DISKS[@]}"
if ((${#MISSING_DISKS[@]}>1)); then
    echo "Greater than 1"
else
    echo "Success"
fi
IFS=$OLDIFS

I recommend you that:

  • Follow the rule that variables in CAPS are environment variables.
  • There is no need to augment += an array that is empty at that point in the code MISSING_DISKS+=.
  • Use a non-space character in the printf to avoid the need to remove the space from IFS latter. That makes the script more robust.

If those changes are done, the script becomes:

#!/bin/bash
oldIFS=$IFS
encl0=( 0,0 0,1 0,2 0,3 0,4 0,5 0,7 0,8 0,9 0,10 0,11 0,12 0,13 0,14 0,15 )
unset missing_disks

IFS=' '
           arr=($(printf '0-%s\n' "${encl0[@]#0,}"))
           arr+=($(printf '0-%s\n' {0..15}))

missing_disks=($(printf '%s\n' "${arr[@]}" | sort | uniq -u))

           echo "test $(printf '0-%s\n' "${arr[@]}" | sort | uniq -u)"
           echo "var  ${missing_disks[@]}"

((${#missing_disks[@]}>1)) && echo "Greater than 1" || echo "Success"
IFS=$oldIFS
  • Thanks! This solved my issue! Also this is just a snippet of the main script to demonstrate the issue. This code is a function within a script I'm writing and there may be more than one disk enclosure which is why I'm using += to add to the array in case it's already been added to. – jesse_b May 13 '18 at 11:33