Bash – does this code work in bash v4.4 but not bash v3.2

arraybashshell-script

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?

Best Answer

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