1

I am attempting to check if an argument is an array with the following code:

if [[ $(declare -p $1) ]] != *-a*;

Here $1 is a string with the value "123". I get the following error message from bash:

`arrays.bash: line 23: declare: 123: not found

This code works if I pass an array as an argument but not a string. I want to verify that the argument is either an array or an associative array. I have no concern with the contents at this point, I only want the type. Any ideas on how to do this?

Jonathan
  • 2,635
  • 3
  • 30
  • 49
  • 3
    `$1` is always a string. Always. It cannot be anything else. That also means it cannot be an array. – Charles Duffy Jan 29 '23 at 00:45
  • 1
    One could pass a string _with the name of an array_ in `$1`, but it's still a string. – Charles Duffy Jan 29 '23 at 00:46
  • 1
    Anyhow -- the code in your question checks what kind of variable the string in `$1` refers to, with the assumption that the string is in fact a variable name; it's falling when you aren't passing a variable name at all. This is to be expected. – Charles Duffy Jan 29 '23 at 00:47
  • Then why does it work when I pass an array? Interpreting the declare expression gives a string. if it is an array it contains the substring "-a" which is the flag that would have been used on the original declaration of the variable that references an array. A different string is produced for a string variable. I also tried it with a string variable with the same result. There is also the possibility of an integer being passed as an argument which would be incorrect. – Jonathan Jan 29 '23 at 00:54
  • How do you pass a variable name in Bash? – Jonathan Jan 29 '23 at 00:55
  • _How_ are you passing the array? The typical approach is `yourfunction "${arrayname[@]}"`, which makes the first element of the array be `$1`, the second be `$2`, etc -- they're still all strings, and it's one argument per element, not one argument for the whole array. – Charles Duffy Jan 29 '23 at 01:01
  • The other approach is `yourfunction arrayname`; at that point `$1` is a string, but you can use namevars or other indirect reference mechanisms to refer to the array the string names. – Charles Duffy Jan 29 '23 at 01:01
  • One of the things you _don't_ do is `yourfunction "$arrayname"`, which only passes the first element of the array and ignores all the others (in bash and ksh93; zsh is different, this is one of the reasons tagging for the correct shell is important). – Charles Duffy Jan 29 '23 at 01:02
  • 1
    ...regardless, you aren't providing a [mre] showing us how you're defining the array and how you're trying to pass it; from the question I don't even know that you're using a real array at all (we get a lot of n00bs using strings that contain whitespace and calling them arrays), much less how you're attempting to pass it. – Charles Duffy Jan 29 '23 at 01:04
  • Anyhow, one place you might start is [Update array passed by reference with bash](https://stackoverflow.com/questions/29473368/update-array-passed-by-reference-with-bash); your needs may be simpler, but going direct to the most complex case means you're covered for easier ones as well. – Charles Duffy Jan 29 '23 at 01:06
  • Also potentially of interest is [Passing multiple distinct arrays to a shell function](https://stackoverflow.com/questions/10953833/passing-multiple-distinct-arrays-to-a-shell-function). – Charles Duffy Jan 29 '23 at 01:07
  • ...anyhow, if you go the `yourfunction arrayname` approach, your `declare -p` code will work. – Charles Duffy Jan 29 '23 at 01:08
  • That said, note that there's a lot in common between how bash treats a string and how the shell treats a single-element array. After `stringvar=value`, running `"${stringvar[@]}"` evaluates to the exact same thing as `"${stringvar[0]}"` or `"$stringvar"`, similar to how `array=( "first element" "second element" )` sets up `"$array"` to be identical to `"${array[0]}"` (again, if your shell really is bash; zsh will differ). – Charles Duffy Jan 29 '23 at 01:09
  • Charles Duffy, if the array was named x I am simply passing x. At a later point in this function, I will become interested in the contents but not now. I am simply trying to tell the caller that the wrong type of data was passed as an argument. – Jonathan Jan 29 '23 at 01:11
  • 1
    Again, we need an actual [mre]. I don't know when you say "passing c" you mean `yourfunc c` or `yourfunc "$c"` or `yourfunc "${c[@]}"` or something else. Runnable code is far more precise than English-language descriptions of code. – Charles Duffy Jan 29 '23 at 01:12
  • c was a typo that I corrected. – Jonathan Jan 29 '23 at 01:13
  • In that case it sounds like you just need to check if the variable _exists_ before checking which type it is. `[[ -v "$1" ]] || { echo "ERROR: required a variable name as first argument" >&2; return 1; }` and _then_ when that's done do your `declare -p` check. – Charles Duffy Jan 29 '23 at 01:15
  • But you really should [edit] the question to make it more clear: it's not appropriate for folks to answer based on content that's only present in comments. – Charles Duffy Jan 29 '23 at 01:15
  • (or, as suggested in a comment on Cyrus's answer, just put a `2>/dev/null` on your `declare -p`s, and there you are -- no error) – Charles Duffy Jan 29 '23 at 01:16
  • 1
    The code shown won't work work; you have the `!= *-a*` *outside* the `[[ ... ]]` when it should be *inside*. As shown, you'll get a syntax error before you get any errors involving `declare`. – chepner Jan 29 '23 at 14:47

2 Answers2

1

After all, why worry about the types, if you are relying on it perhaps your approach is wrong or you may need a strong-typed language

% v=1
% declare -p v
declare -- v="1"
% echo $v
1
% echo ${v[@]}
1
% v[1]=2
% declare -p v
declare -a v=([0]="1" [1]="2")
% echo ${v[@]}
1 2
Diego Torres Milano
  • 65,697
  • 9
  • 111
  • 134
0

The Error In The Question

You called yourfunction "$a" instead of yourfunction a, when a=123. Don't do that: You need to pass the name of the variable, not its value.


General Solution: Bash 5.x+

Bash 5 has a new feature called parameter transformation, whereby ${parameter@operator} can perform a variety of actions; one of these is checking the type of the parameter.

myfunc() {
  [[ -v "$1" ]] || { echo "No variable named $1 exists" >&2; return 1; }
  case ${!1@a} in
    *a*) echo "Array";;
    *A*) echo "Associative array";;
    *i*) echo "Integer";;
    "")  echo "Default string";;
    *)   echo "Other/unknown flag set: ${!1@a}";;
  esac
}

Older Solution

myfunc() {
  local typedesc
  typedesc=$(declare -p "$1" 2>/dev/null) || {
    echo "No variable named $1 is set" >&2
    return 1
  }
  case $typedesc in
    "declare -a"*) echo "Array";;
    "declare -A"*) echo "Associative array";;
    "declare -i"*) echo "Integer";;
    "declare --"*) echo "Regular (default) string variable";;
    *)             echo "Other/unrecognized type";;
  esac
}
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441