Bash empty array expansion with `set -u`

后端 未结 11 2199
不知归路
不知归路 2020-12-12 15:37

I\'m writing a bash script which has set -u, and I have a problem with empty array expansion: bash appears to treat an empty array as an unset variable during e

相关标签:
11条回答
  • 2020-12-12 15:51

    @ikegami's accepted answer is subtly wrong! The correct incantation is ${arr[@]+"${arr[@]}"}:

    $ countArgs () { echo "$#"; }
    $ arr=('')
    $ countArgs "${arr[@]:+${arr[@]}}"
    0   # WRONG
    $ countArgs ${arr[@]+"${arr[@]}"}
    1   # RIGHT
    $ arr=()
    $ countArgs ${arr[@]+"${arr[@]}"}
    0   # Let's make sure it still works for the other case...
    
    0 讨论(0)
  • 2020-12-12 15:51

    The most simple and compatible way seems to be:

    $ set -u
    $ arr=()
    $ echo "foo: '${arr[@]-}'"
    
    0 讨论(0)
  • 2020-12-12 15:52

    this may be another option for those who prefer not to duplicate arr[@] and are okay to have an empty string

    echo "foo: '${arr[@]:-}'"
    

    to test:

    set -u
    arr=()
    echo a "${arr[@]:-}" b # note two spaces between a and b
    for f in a "${arr[@]:-}" b; do echo $f; done # note blank line between a and b
    arr=(1 2)
    echo a "${arr[@]:-}" b
    for f in a "${arr[@]:-}" b; do echo $f; done
    
    0 讨论(0)
  • 2020-12-12 15:53

    Here are a couple of ways to do something like this, one using sentinels and another using conditional appends:

    #!/bin/bash
    set -o nounset -o errexit -o pipefail
    countArgs () { echo "$#"; }
    
    arrA=( sentinel )
    arrB=( sentinel "{1..5}" "./*" "with spaces" )
    arrC=( sentinel '$PWD' )
    cmnd=( countArgs "${arrA[@]:1}" "${arrB[@]:1}" "${arrC[@]:1}" )
    echo "${cmnd[@]}"
    "${cmnd[@]}"
    
    arrA=( )
    arrB=( "{1..5}" "./*"  "with spaces" )
    arrC=( '$PWD' )
    cmnd=( countArgs )
    # Checks expansion of indices.
    [[ ! ${!arrA[@]} ]] || cmnd+=( "${arrA[@]}" )
    [[ ! ${!arrB[@]} ]] || cmnd+=( "${arrB[@]}" )
    [[ ! ${!arrC[@]} ]] || cmnd+=( "${arrC[@]}" )
    echo "${cmnd[@]}"
    "${cmnd[@]}"
    
    0 讨论(0)
  • 2020-12-12 16:02

    @ikegami's answer is correct, but I consider the syntax ${arr[@]+"${arr[@]}"} dreadful. If you use long array variable names, it starts to looks spaghetti-ish quicker than usual.

    Try this instead:

    $ set -u
    
    $ count() { echo $# ; } ; count x y z
    3
    
    $ count() { echo $# ; } ; arr=() ; count "${arr[@]}"
    -bash: abc[@]: unbound variable
    
    $ count() { echo $# ; } ; arr=() ; count "${arr[@]:0}"
    0
    
    $ count() { echo $# ; } ; arr=(x y z) ; count "${arr[@]:0}"
    3
    

    It looks like the Bash array slice operator is very forgiving.

    So why did Bash make handling the edge case of arrays so difficult? Sigh. I cannot guarantee you version will allow such abuse of the array slice operator, but it works dandy for me.

    Caveat: I am using GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu) Your mileage may vary.

    0 讨论(0)
  • 2020-12-12 16:05

    Turns out array handling has been changed in recently released (2016/09/16) bash 4.4 (available in Debian stretch, for example).

    $ bash --version | head -n1
    bash --version | head -n1
    GNU bash, version 4.4.0(1)-release (x86_64-pc-linux-gnu)
    

    Now empty arrays expansion does not emits warning

    $ set -u
    $ arr=()
    $ echo "${arr[@]}"
    
    $ # everything is fine
    
    0 讨论(0)
提交回复
热议问题