Your three examples are not all exactly alike.
In the last two:
if [ "${a}" == 0 ]; then
ffmpeg -framerate 2 -pattern_type glob -i "*.png" -pix_fmt yuv420p output.mp4
If $a was not quoted, and its value contained characters of $IFS (by default space, tab and newline) or wildcard characters, this would
cause [ to receive more than three arguments (beside the [ and ] ones), resulting in an error;
similarly, if the value of $a was the empty string, this would cause
[ to receive too few arguments:
$ (a=0; [ $a == 0 ] && echo OK)
OK
(but only if $IFS currently happens not to contain 0)
$ (a='foo bar'; [ $a == 0 ] && echo OK)
bash: [: too many arguments
(with the default value of $IFS)
$ (a=; [ $a == 0 ] && echo OK)
bash: [: ==: unary operator expected
(even with an empty $IFS or with zsh (that otherwise doesn't implement that implicit split+glob operator upon unquoted expansions))
$ (a='*'; [ $a == 0 ] && echo OK)
bash: [: too many arguments
(when run in a directory that contain at least 2 non-hidden files).
With quoting, no error:
$ (a='foo bar'; [ "$a" == 0 ] && echo OK)
$ (a=; [ "$a" == 0 ] && echo OK)
Your first example is different. The rules about expansion within
double quotes are special when arrays are involved; if a denotes an
array, then:
$a is the first element of the array (sticktly speaking it's ${a[0]} even if the element at indice 0 is not defined);
${a[*]} or ${a[@]} are the elements of the array, additionally split at $IFS (space, tab, newline by default);
"${a[@]}" is the elements of the array, not split at $IFS.
So your loop for i in "${indices[@]}"; do ... does not actually work
the same, depending on the contents of the array. For example:
$ (declare -a a=(a b c); printf '%s\n' $a)
a
$ (declare -a a=(a b c); printf '%s\n' ${a[*]})
a
b
c
$ (declare -a a=(a 'b c'); printf '%s\n' ${a[*]})
a
b
c
$ (declare -a a=(a 'b c'); printf '%s\n' ${a[@]})
a
b
c
$ (declare -a a=(a 'b c'); printf '%s\n' "${a[*]}")
a b c
$ (declare -a a=(a 'b c'); printf '%s\n' "${a[@]}")
a
b c