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
  • 364
  • 1
  • 11
  • I don't understand that *"pass params to bash arrays"* wording in that context. – Stéphane Chazelas Aug 15 '22 at 10:28
  • see [Can you pass an array to a function, but only make one parameter of the function receive the entire array?](https://unix.stackexchange.com/questions/418800/can-you-pass-an-array-to-a-function-but-only-make-one-parameter-of-the-function/418838) [What is the most correct way to pass an array to a function?](https://unix.stackexchange.com/questions/41357/what-is-the-most-correct-way-to-pass-an-array-to-a-function) [How to pass an array as function argument but with other extra parameters?](https://unix.stackexchange.com/q/690603/170373) – ilkkachu Aug 15 '22 at 12:21
  • @ilkkachu purpose? – Nickotine Aug 15 '22 at 12:24
  • 1
    @Nickotine, purpose of what? Of linking earlier posts on the same subject? To let you find possible solutions that might work for you without the need for rewriting the same things again. Though I'm not exactly sure what it is exactly that you want to do: Are you just looking for some way to pass values in an array to the function (which would be enough for printing), or do you specifically want to pass it by name and make modifications to it from the array (you're not showing that here, though), or are you just asking why that `${#$1[@]}` doesn't work? – ilkkachu Aug 15 '22 at 12:27
  • that question you linked is completely different to what I asked, the accepted answer had no issues understanding what I meant, thanks for your input. – Nickotine Aug 15 '22 at 12:53
  • 1
    @Nickotine no, your question is doing pretty much the same thing as https://unix.stackexchange.com/q/41357/70524, and it's second answer https://unix.stackexchange.com/a/162880/70524 is pretty much what the accepted answer here does too. – muru Aug 16 '22 at 00:40
  • @Nickotine, I also tried to ask what it was you exactly you needed, but you didn't answer that. – ilkkachu Aug 16 '22 at 05:58
  • @muru the only problem is that I may not have found my answer using that question as the answer would require me trawling through all the replies. The other question is asking multiple things which are different from what I was asking, my question is focused and clear, plus the use case is different he asks about the array index whereas my question is about the array name, the second answer is much less clear than the one I got here, so I think this should be reopened as it’s much clearer others will have difficulty getting the answer from the other question. – Nickotine Aug 17 '22 at 07:10

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.

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
  • 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