Skip to content

Instantly share code, notes, and snippets.

@x-yuri
Last active December 9, 2024 05:47
Show Gist options
  • Save x-yuri/61174f7f7cfb8291c62be560b123f3b3 to your computer and use it in GitHub Desktop.
Save x-yuri/61174f7f7cfb8291c62be560b123f3b3 to your computer and use it in GitHub Desktop.
bash: arrays and set -u

bash: arrays and set -u

In bash < 4.4 "${a[@]}" fails (unbound variable) if a is unset (nothing was assigned to a) or a=(). Also "${a[@]:+${a[@]}}" and "${a[@]+${a[@]}}" produce empty arrays if a=('').

What works before, after and in 4.4 is ${a[*]+"${a[@]}"} and ${a[@]+"${a[@]}"}.

It should also be mentioned that ${#a[@]} fails if a is unset (regardless of the version). And ${a[i]} fails if the specified element doesn't exist.

a.bats:

setup() {
    [ "$BATS_LIB_PATH" = /usr/lib/bats ] && BATS_LIB_PATH=$HOME/.bats/lib:$BATS_LIB_PATH
    bats_load_library bats-support
    bats_load_library bats-assert
    bats_require_minimum_version 1.5.0
    set -u
}

function args2str { local IFS=\|; echo "$#:($*)"; }

version_gte() {
    [ "`printf '%s\n%s\n' "$1" "$2" | sort -V | head -1`" != "$1" ]
}

bash_version() {
    echo "${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}.${BASH_VERSINFO[2]}"
}

bash_gte() { version_gte "`bash_version`" "$1"; }

bash_lt() { ! version_gte "`bash_version`" "$1"; }

# "${a[@]}"

@test "\"\${a[@]}\" fails if a is unset (unbound variable)" {
    bash_gte 4.4 && skip
    run -127 bash -uc ': ${a[@]}'
    assert_output --partial 'unbound variable'
}

@test "\"\${a[@]}\" produces 0:() if a is unset" {
    bash_lt 4.4 && skip
    assert_equal "`args2str "${a[@]}"`" "0:()"
}

@test "\"\${a[@]}\" fails if a=() (unbound variable)" {
    bash_gte 4.4 && skip
    run -127 bash -uc 'a=(); : ${a[@]}'
    assert_output --partial 'unbound variable'
}

@test "\"\${a[@]}\" produces 0:() if a=()" {
    bash_lt 4.4 && skip
    a=()
    assert_equal "`args2str "${a[@]}"`" "0:()"
}

# "${a[@]:+${a[@]}}"

@test "\"\${a[@]:+\${a[@]}}\" produces 0:() if a=('')" {
    bash_gte 4.4 && skip
    a=('')
    assert_equal "`args2str "${a[@]:+${a[@]}}"`" "0:()"
}

@test "\"\${a[@]:+\${a[@]}}\" produces 1:() if a=('')" {
    bash_lt 4.4 && skip
    a=('')
    assert_equal "`args2str "${a[@]:+${a[@]}}"`" "1:()"
}

# "${a[@]+${a[@]}}"

@test "\"\${a[@]+\${a[@]}}\" produces 0:() if a=('')" {
    bash_gte 4.4 && skip
    a=('')
    assert_equal "`args2str "${a[@]+${a[@]}}"`" "0:()"
}

@test "\"\${a[@]+\${a[@]}}\" produces 1:() if a=('')" {
    bash_lt 4.4 && skip
    a=('')
    assert_equal "`args2str "${a[@]+${a[@]}}"`" "1:()"
}

# ${a[*]:+"${a[@]}"}

@test "\${a[*]:+\"\${a[@]}\"} produces 0:() if a=('')" {
    a=('')
    assert_equal "`args2str ${a[*]:+"${a[@]}"}`" "0:()"
}

# ${a[@]:+"${a[@]}"}

@test "\${a[@]:+\"\${a[@]}\"} produces 0:() if a=('')" {
    a=('')
    assert_equal "`args2str ${a[@]:+"${a[@]}"}`" "0:()"
}

# ${a[*]+"${a[@]}"}

@test "\${a[*]+\"\${a[@]}\"} produces 0:() if a is unset" {
    assert_equal "`args2str ${a[*]+"${a[@]}"}`" "0:()"
}

@test "\${a[*]+\"\${a[@]}\"} produces 0:() if a=()" {
    a=()
    assert_equal "`args2str ${a[*]+"${a[@]}"}`" "0:()"
}

@test "\${a[*]+\"\${a[@]}\"} produces 1:() if a=('')" {
    a=('')
    assert_equal "`args2str ${a[*]+"${a[@]}"}`" "1:()"
}

@test "\${a[*]+\"\${a[@]}\"} produces 1:(a) if a=(a)" {
    a=(a)
    assert_equal "`args2str ${a[*]+"${a[@]}"}`" "1:(a)"
}

@test "\${a[*]+\"\${a[@]}\"} produces 2:(a|b) if a=(a b)" {
    a=(a b)
    assert_equal "`args2str ${a[*]+"${a[@]}"}`" "2:(a|b)"
}

# ${a[@]+"${a[@]}"}

@test "\${a[@]+\"\${a[@]}\"} produces 0:() if a is unset" {
    assert_equal "`args2str ${a[@]+"${a[@]}"}`" "0:()"
}

@test "\${a[@]+\"\${a[@]}\"} produces 0:() if a=()" {
    a=()
    assert_equal "`args2str ${a[@]+"${a[@]}"}`" "0:()"
}

@test "\${a[@]+\"\${a[@]}\"} produces 1:() if a=('')" {
    a=('')
    assert_equal "`args2str ${a[@]+"${a[@]}"}`" "1:()"
}

@test "\${a[@]+\"\${a[@]}\"} produces 1:(a) if a=(a)" {
    a=(a)
    assert_equal "`args2str ${a[@]+"${a[@]}"}`" "1:(a)"
}

@test "\${a[@]+\"\${a[@]}\"} produces 2:(a|b) if a=(a b)" {
    a=(a b)
    assert_equal "`args2str ${a[@]+"${a[@]}"}`" "2:(a|b)"
}

# ${#a[@]}

@test "\${#a[@]} fails if a is unset (unbound variable)" {
    run -1 bash -uc ': ${#a[@]}'
    assert_output --partial 'unbound variable'
}

# ${a[0]}

@test "\${a[0]} fails if a is unset (unbound variable)" {
    run -127 bash -uc ': ${a[0]}'
    assert_output --partial 'unbound variable'
}

@test "\${a[0]} fails if a=() (unbound variable)" {
    run -127 bash -uc 'a=(); : ${a[0]}'
    assert_output --partial 'unbound variable'
}
$ git clone https://github.com/bats-core/bats-core ~/.bats
$ git clone https://github.com/bats-core/bats-support ~/.bats/lib/bats-support
$ git clone https://github.com/bats-core/bats-assert ~/.bats/lib/bats-assert
$ ~/.bats/bin/bats a.bats
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment