Check variable is an array in Bourne like shell?

93

Solution 1

I don't think you can, and I don't think it actually makes any difference.

unset a
a=x
echo "${a[0]-not array}"

x

That does the same thing in either of ksh93 and bash. It looks like possibly all variables are arrays in those shells, or at least any regular variable which has not been assigned special attributes, but I didn't check much of that.

The bash manual talks about different behaviors for an array versus a string variable when using += assignments, but it afterwards hedges and states that the the array only behaves differently in a compound assignment context.

It also states that a variable is considered an array if any subscript has been assigned a value - and explicitly includes the possibility of a null-string. Above you can see that a regular assignment definitely results in a subscript being assigned - and so I guess everything is an array.

Practically, possibly you can use:

[ 1 = "${a[0]+${#a[@]}}" ] && echo not array

...to clearly pinpoint set variables that have only been assigned a single subscript of value 0.

Solution 2

In zsh

zsh% a=(1 2 3) s=1
zsh% [[ ${(t)a} == *array* ]] && echo array
array
zsh% [[ ${(t)s} == *array* ]] && echo array
zsh%

Solution 3

So you effectively want just the middle part of declare -p without the junk around it?

You could write a macro such as:

readonly VARTYPE='{ read __; 
       case "`declare -p "$__"`" in
            "declare -a"*) echo array;; 
            "declare -A"*) echo hash;; 
            "declare -- "*) echo scalar;; 
       esac; 
         } <<<'

so that you can do:

a=scalar
b=( array ) 
declare -A c; c[hashKey]=hashValue;
######################################
eval "$VARTYPE" a #scalar
eval "$VARTYPE" b #array
eval "$VARTYPE" c #hash

(A mere function won't do if you'll want to use this on function-local variables).


With aliases

shopt -s expand_aliases
alias vartype='eval "$VARTYPE"'

vartype a #scalar
vartype b #array
vartype c #hash

Solution 4

For bash, it's a little bit of a hack (albeit documented): attempt to use typeset to remove the "array" attribute:

$ typeset +a BASH_VERSINFO
bash: typeset: BASH_VERSINFO: cannot destroy array variables in this way
echo $?
1

(You cannot do this in zsh, it allows you to convert an array to a scalar, in bash it's explicitly forbidden.)

So:

 typeset +A myvariable 2>/dev/null || echo is assoc-array
 typeset +a myvariable 2>/dev/null || echo is array

Or in a function, noting the caveats at the end:

function typeof() {
    local _myvar="$1"
    if ! typeset -p $_myvar 2>/dev/null ; then
        echo no-such
    elif ! typeset -g +A  $_myvar 2>/dev/null ; then
        echo is-assoc-array
    elif ! typeset -g +a  $_myvar 2>/dev/null; then
        echo is-array
    else
        echo scalar
    fi
}

Note the use of typeset -g (bash-4.2 or later), this is required within a function so that typeset (syn. declare) doesn't work like local and clobber the value you are trying to inspect. This also does not handle function "variable" types, you can add another branch test using typeset -f if needed.


Another (nearly complete) option is to use this:

    ${!name[*]}
          If name is an array variable, expands to  the  list
          of  array indices (keys) assigned in name.  If name
          is not an array, expands to 0 if name  is  set  and
          null  otherwise.   When @ is used and the expansion
          appears within double quotes, each key expands to a
          separate word.

There's one slight problem though, an array with a single subscript of 0 matches two of the above conditions. This is something that mikeserv also references, bash really doesn't have a hard distinction, and some of this (if you check the Changelog) can be blamed on ksh and compatibilty with how ${name[*]} or ${name[@]} behave on a non-array.

So a partial solution is:

if [[ ${!BASH_VERSINFO[*]} == '' ]]; then
    echo no-such
elif [[ ${!BASH_VERSINFO[*]} == '0' ]]; then 
    echo not-array
elif [[ ${!BASH_VERSINFO[*]} != '0' ]]; 
    echo is-array    
fi

I have used in the past a variation on this:

while read _line; do
   if [[ $_line =~ ^"declare -a" ]]; then 
     ...
   fi 
done < <( declare -p )

this too needs a subshell though.

One more possibly useful technique is compgen:

compgen -A arrayvar

This will list all indexed arrays, however associative arrays are not handled specially (up to bash-4.4) and appear as regular variables (compgen -A variable)

Solution 5

yash's array builtin has some options that only work with array variables. Example: the -d option will report an error on non-array variable:

$ a=123
$ array -d a
array: no such array $a

So we can do something like this:

is_array() (
  array -d -- "$1"
) >/dev/null 2>&1

a=(1 2 3)
if is_array a; then
  echo array
fi

b=123
if ! is_array b; then
  echo not array
fi

This approach won't work if array variable is readonly. Trying to modify a readonly variable leading to an error:

$ a=()
$ readonly a
$ array -d a
array: $a is read-only
Share:
93

Related videos on Youtube

user3717775
Author by

user3717775

Updated on September 18, 2022

Comments

  • user3717775
    user3717775 over 1 year

    I am trying to upload an image file with the code below, but the file is not being uploaded. The console still shows the message "1 Record Successfully Inserted."

    Create table image
    (
       name varchar2(20),
       photo blob
    );
    
    import java.sql.*;
    import java.io.*;
    
    public class ImageWriter {
    
        static Connection connection = null;
        static CallableStatement pstat = null;
        static String connectionURL = null;
    
        public static void main(String[] args) {
            try{
                Class.forName("oracle.jdbc.driver.OracleDriver");
                connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "SYSTEM", "SYSTEM");
    
                PreparedStatement pstat = connection.prepareStatement("insert into image(name,photo) values(?,?)");
    
                FileInputStream fin = new FileInputStream("E:\\test.jpg");
                pstat.setString(1, "ABC");
                pstat.setBinaryStream(2, fin,fin.available());
    
                int result = pstat.executeUpdate();
                System.out.println(result + " Record Successfully Inserted");
    
                connection.close();
    
            }
            catch(Exception e){
                e.printStackTrace();
            }
    
        }
    
    }
    
    • Raptor
      Raptor almost 10 years
      why do you want to save an image into DB? You can just save the image path and leave the image file in server...
    • sunleo
      sunleo almost 10 years
      You have done insertion then whats wrong.?
    • user3717775
      user3717775 almost 10 years
      insertion is done only for name field not for photo field.As I am seeing data in database for photo, it shows data not available.
    • Robby Cornelissen
      Robby Cornelissen almost 10 years
      Do you get the same problem if you just do pstat.setBinaryStream(2, fin);, i.e. without the fin.available()?
  • cuonglm
    cuonglm over 8 years
    So I guess checking if ${a[1]-not array} can do the task, can't it?
  • mikeserv
    mikeserv over 8 years
    @cuonglm - Well, not according to the bash manual: An array variable is considered set if a subscript has been assigned a value. The null string is a valid value. If any subscript is assigned its an array per spec. In practice, also no, because you can do a[5]=x. I guess [ 1 -eq "${#a[@]}" ] && [ -n "${a[0]+1}" ] could work.
  • PSkocik
    PSkocik over 8 years
    @mikeserv Good point. Aliases make it look prettier. +1
  • mikeserv
    mikeserv over 8 years
    i meant - alias vartype="$VARTYPE"... or just not defining the $VARTYPE at all - it should work, right? you only should need that shopt thing in bash because it breaks with the spec regarding alias expansion in scripts.
  • PSkocik
    PSkocik over 8 years
    @mikeserv I'm sure cuonglm is well capable of tweaking this approach to his needs and preferences. ;-)
  • PSkocik
    PSkocik over 8 years
    ... and security considerations.
  • mikeserv
    mikeserv over 8 years
    well, true, and it didn't matter when you just used eval, because you only targeted the one definition, but when you alias x='eval "$x"' it may not be immediately apparent to some that the alias and $x are connected over time. you know what I mean? i mean that a lot of people may think the alias definition sticks even if the variable doesn't - that's all i'm getting at... but you can't portably make functions readonly, and i've only now learned you can at all...
  • Admin
    Admin over 8 years
    The typeset +a also reports an error in ksh. Not in zsh, though.
  • Admin
    Admin over 8 years
    Maybe echo ${(t)var} is simpler. Thanks for this.
  • Admin
    Admin over 8 years
    Try: bash -c 'unset var; var=foo; typeset -p var'. Do bash answer report an array (needs an -a)?. Now compare with: bash -c 'unset var; var[12]=foo; typeset -p var'. Why is there a difference?. A: The shell maintains (for good or for bad) a notion of which vars are scalars or arrays. The shell ksh do mix both concepts into one.