3

I'd like to pass an array to a bash function but I get a bad substitution error

Example


mapfile -t ray < <(parallel -j 0 echo ::: {1..10})

declare -p ray

declare -a ray=([0]="2" [1]="1" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9" [9]="10")

arrLen() { echo "${#$1[@]}" }

arrLen ray

-bash: ${#$1[@]}: bad substitution

So is it impossible to pass params to bash arrays?

Nickotine
  • 467

1 Answers1

7

With recent versions of bash, you could use namerefs:

arrLen() {
  typeset -n __Var="$1"
  echo "${#__Var[@]}"
}

Here we choose __Var as the nameref variable name, as one that is unlikely to be used otherwise within your script. arrLen __Var fails with circular name reference errors.

Namerefs (like typeset, and bash's array design in general) is a feature that bash borrowed from the Korn shell. In ksh (ksh93 where namerefs were introduced), you'd write:

function arrLen {
  typeset -n var="$1"
  echo "${#var[@]}"
}

(ksh namerefs are able to reference a variable with the same name from the caller's scope (or the global scope), but scoping (static scoping) is only done in functions declared with the Korn syntax, not with the Bourne syntax)

Or you can always use eval to construct code dynamically.

arrLen() {
  eval 'echo "${#'"$1"'[@]}"'
}

With zsh:

arrLen() echo ${(P)#1}

bash Nameref resolution, zsh's P parameter expansion flag also do some form of eval (dynamic code evaluation) under the hood, so all those approaches are equally unsafe if the argument passed to arrLen is not guaranteed to be a valid variable name, but equally safe if they are.

  • Thanks a lot @StéphaneChazelas works perfectly – Nickotine Aug 15 '22 at 10:39
  • @StéphaneChazelas are the underscores necessary? This worked fine for me arrLen() { typeset -n Var="$1"; echo "${#Var[@]}"; } – Nickotine Aug 15 '22 at 10:42
  • Any reason, why use typeset instead of declare? – annahri Aug 15 '22 at 10:47
  • @Nickotine, see edit. – Stéphane Chazelas Aug 15 '22 at 10:51
  • 2
    @annahri, typeset is the name used by the Korn shell from the earlier 80s (where bash copied that feature) and most other shells, so I prefer that one. I also prefer readarray over mapfile as that's not doing a mapping, only a reading (and zsh had a (real) mapfile feature long before bash introduced its mapfile / readarray builtin) – Stéphane Chazelas Aug 15 '22 at 10:53
  • @StéphaneChazelas do you agree that this is a duplicate? in my opinion the other question is asking multiple things and is much harder to read, the accepted answer for that question doesn’t even answer this question, your answer is very clear, unlike the one in the other question. – Nickotine Aug 17 '22 at 07:13