Check Variable ist ein Array in Bourne wie Shell?

14

In einer Bourne-ähnlichen Shell, die Array-Variablen unterstützt, können wir mithilfe von Parsing prüfen, ob es sich bei der Variablen um ein Array handelt.

Alle folgenden Befehle wurden nach dem Ausführen ausgeführt a=(1 2 3).

zsh:

$ declare -p a
typeset -a a
a=( 1 2 3 )

bash:

$ declare -p a
declare -a a='([0]="1" [1]="2" [2]="3")'

ksh93:

$ typeset -p a
typeset -a a=(1 2 3)

pdksh und seine Ableitung:

$ typeset -p a
set -A a
typeset a[0]=1
typeset a[1]=2
typeset a[2]=3

yash:

$ typeset -p a
a=('1' '2' '3')
typeset a

Ein Beispiel in bash:

if declare -p var 2>/dev/null | grep -q 'declare -a'; then
  echo array variable
fi

Dieser Ansatz ist zu aufwendig und muss eine Subshell erzeugen. Die Verwendung einer anderen Shell, die wie =~in [[ ... ]]eingebaut ist, benötigt keine Subshell, ist aber immer noch zu kompliziert.

Gibt es einen einfacheren Weg, um diese Aufgabe zu erfüllen?

cuonglm
quelle
Unter welchen Umständen müssen Sie überprüfen, ob Ihre Variablen Arrays sind oder nicht?
Kusalananda

Antworten:

10

Ich glaube nicht, dass Sie das können, und ich glaube nicht, dass es tatsächlich einen Unterschied macht.

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

x

Das macht das gleiche in einem von ksh93und bash. Möglicherweise sind alle Variablen Arrays in diesen Shells oder zumindest reguläre Variablen, denen keine speziellen Attribute zugewiesen wurden, aber ich habe nicht viel davon überprüft.

In diesem bashHandbuch wird das unterschiedliche Verhalten eines Arrays im Vergleich zu einer Zeichenfolgenvariablen bei der Verwendung von +=Zuweisungen erläutert. Anschließend wird jedoch abgesichert und angegeben, dass sich das Array nur in einem zusammengesetzten Zuweisungskontext anders verhält .

Es wird auch angegeben, dass eine Variable als Array betrachtet wird, wenn einem Index ein Wert zugewiesen wurde - und explizit die Möglichkeit einer Nullzeichenfolge einschließt. Oben sehen Sie, dass eine reguläre Zuweisung definitiv dazu führt, dass ein Index zugewiesen wird - und ich denke, dass alles ein Array ist.

In der Praxis können Sie möglicherweise Folgendes verwenden:

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

... um gesetzte Variablen, denen nur ein einziger Index mit dem Wert 0 zugewiesen wurde, eindeutig zu lokalisieren.

mikeserv
quelle
Ich schätze mal, ob ${a[1]-not array}ich die Aufgabe erledigen kann, oder?
Dienstag,
@cuonglm - Nun, nicht gemäß bashHandbuch: Eine Array-Variable wird als gesetzt betrachtet, wenn einem Index ein Wert zugewiesen wurde. Die Nullzeichenfolge ist ein gültiger Wert. Wenn ein Index zugewiesen ist, wird ihm ein Array pro Spezifikation zugewiesen. In der Praxis auch nein, weil man das kann a[5]=x. Ich denke, [ 1 -eq "${#a[@]}" ] && [ -n "${a[0]+1}" ]könnte funktionieren.
mikeserv
6

Sie wollen also effektiv nur den mittleren Teil declare -pohne den Müll drum herum?

Sie könnten ein Makro schreiben wie:

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

damit Sie tun können:

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

(Eine bloße Funktion reicht nicht aus, wenn Sie dies für funktionslokale Variablen verwenden möchten.)


Mit Aliasen

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

vartype a #scalar
vartype b #array
vartype c #hash
PSkocik
quelle
@ MikeServ Guter Punkt. Aliase machen es schöner. +1
PSkocik
ich meinte - alias vartype="$VARTYPE"... oder definiere das $VARTYPEüberhaupt nicht - es sollte funktionieren, oder? Sie sollten das shoptDing nur brauchen, bashweil es mit der Spezifikation bezüglich der aliasErweiterung von Skripten bricht .
mikeserv
1
@mikeserv Ich bin sicher, dass cuonglm in der Lage ist, diesen Ansatz an seine Bedürfnisse und Vorlieben anzupassen. ;-)
PSkocik
... und Sicherheitsüberlegungen.
PSkocik
Zu keinem Zeitpunkt hat der obige Code einen vom Benutzer bereitgestellten Text ausgewertet. Es ist nicht weniger sicher als eine Funktion. Ich habe noch nie gesehen, dass Sie Probleme damit haben, Funktionen schreibgeschützt zu machen, aber OK, ich kann die Variable nur schreibgeschützt markieren.
PSkocik
6

In zsh

zsh% a=(1 2 3) s=1
zsh% [[ ${(t)a} == *array* ]] && echo array
array
zsh% [[ ${(t)s} == *array* ]] && echo array
zsh%
llua
quelle
Vielleicht echo ${(t)var}ist es einfacher. Danke dafür.
4

Um die Variable var zu testen, mit

b=("${!var[@]}")
c="${#b[@]}"

Es ist möglich zu testen, ob es mehr als einen Array-Index gibt:

[[ $c > 1 ]] && echo "Var is an array"

Wenn der erste Indexwert nicht Null ist:

[[ ${b[0]} -eq 0 ]] && echo "Var is an array"      ## should be 1 for zsh.

Die einzige schwere Verwirrung ist, wenn es nur einen Indexwert gibt und dieser Wert null (oder eins) ist.

In diesem Fall ist es möglich, einen Nebeneffekt zu verwenden, bei dem versucht wird, ein Array-Element aus einer Variablen zu entfernen, die kein Array ist:

**bash** reports an error with             unset var[0]
bash: unset: var: not an array variable

**zsh** also reports an error with         $ var[1]=()
attempt to assign array value to non-array

Dies funktioniert korrekt für Bash:

# Test if the value at index 0 could be unset.
# If it fails, the variable is not an array.
( unset "var[0]" 2>/dev/null; ) && echo "var is an array."

Für zsh muss der Index möglicherweise 1 sein (es sei denn, ein kompatibler Modus ist aktiv).

Die Unterschale wird benötigt, um den Nebeneffekt des Löschens des Index 0 von var zu vermeiden.

Ich habe keine Möglichkeit gefunden, es in ksh zum Laufen zu bringen.

Bearbeiten 1

Diese Funktion funktioniert nur in bash4.2 +

getVarType(){
    varname=$1;
    case "$(typeset -p "$varname")" in
        "declare -a"*|"typeset -a"*)    echo array; ;;
        "declare -A"*|"typeset -A"*)    echo hash; ;;
        "declare -- "*|"typeset "$varname*| $varname=*) echo scalar; ;;
    esac;
}

var=( foo bar );  getVarType var

Bearbeiten 2

Dies funktioniert auch nur für bash4.2 +

{ typeset -p var | grep -qP '(declare|typeset) -a'; } && echo "var is an array"

Hinweis: Dies führt zu falsch positiven Ergebnissen, wenn var die getesteten Zeichenfolgen enthält.


quelle
Wie wäre es mit einem Array mit null Elementen?
Dienstag,
1
Dat bearbeiten, tho. Sieht sehr originell aus. : D
PSkocik
@cuonglm Bei der Überprüfung wird ( unset "var[0]" 2>/dev/null; ) && echo "var is an array."korrekt gemeldet , dass var ein Array ist, wenn var auf var=()ein Array mit null Elementen festgelegt wurde. Es wirkt genau gleich zu deklarieren.
Der Skalartest funktioniert nicht, wenn der Skalar exportiert oder als Ganzzahl / Kleinbuchstabe / schreibgeschützt markiert ist. Sie können wahrscheinlich sicher sagen, dass jede andere nicht leere Ausgabe eine Skalarvariable bedeutet. Ich würde verwenden, grep -Eanstatt grep -Pdie Abhängigkeit von GNU grep zu vermeiden.
Stéphane Chazelas
@ StéphaneChazelas Der Test (in bash) für skalare mit Integer- und / oder Klein und / oder Nur - Lese beginnen immer mit -a, wie folgt aus : declare -airl var='()'. Daher wird der grep-Test funktionieren .
3

Bei Bash handelt es sich um einen kleinen Hack (wenn auch dokumentiert): Versuchen Sie typeset, das Attribut "array" zu entfernen:

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

(Dies ist nicht möglich. Sie können zshein Array in einen Skalar konvertieren, da bashdies ausdrücklich verboten ist.)

So:

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

Oder in einer Funktion, die Vorbehalte am Ende beachten:

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
}

Beachten Sie die Verwendung von typeset -g(bash-4.2 oder höher). Dies ist in einer Funktion erforderlich, damit typeset(syn. declare) Nicht wie localder Wert funktioniert und den Sie untersuchen möchten. Dies gilt auch nicht für Funktionstypen mit "Variablen". Sie können typeset -fbei Bedarf einen weiteren Verzweigungstest hinzufügen .


Eine weitere (fast vollständige) Option besteht darin, Folgendes zu verwenden:

    ${!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.

Es gibt jedoch ein kleines Problem: Ein Array mit einem einzelnen Index von 0 entspricht zwei der oben genannten Bedingungen. Dies ist etwas, worauf mikeserv auch hinweist, bash hat wirklich keine harte Unterscheidung, und einige davon (wenn Sie das Changelog überprüfen) können auf ksh und die Kompatibilität mit dem Verhalten ${name[*]}oder ${name[@]}Verhalten auf einem Nicht-Array zurückgeführt werden.

Also a Teillösung ist also:

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

Ich habe in der Vergangenheit eine Variation davon verwendet:

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

Auch das braucht eine Unterschale.

Eine weitere möglicherweise nützliche Technik ist compgen:

compgen -A arrayvar

Dadurch werden alle indizierten Arrays aufgelistet. Assoziative Arrays werden jedoch nicht speziell behandelt (bis zu bash-4.4) und erscheinen als reguläre Variablen ( compgen -A variable).

mr.spuratic
quelle
Der typeset +ameldet auch einen Fehler in ksh. Allerdings nicht in zsh.
1

Kurze Antwort:

Für die beiden Shells, die diese Notation ( bashund ksh93) eingeführt haben, ist eine skalare Variable nur ein Array mit einem einzelnen Element .

Für die Erstellung eines Arrays ist keine spezielle Deklaration erforderlich . Nur die Zuordnung ist ausreichend und eine einfache Zuordnung var=valueist identisch mit var[0]=value.

Henk Langeveld
quelle
Versuchen: bash -c 'unset var; var=foo; typeset -p var'. Meldet bash answer ein Array (benötigt ein -a)? Jetzt vergleichen mit: bash -c 'unset var; var[12]=foo; typeset -p var'. Warum gibt es einen Unterschied? A: Die Shell behält (für gut oder für schlecht) eine Vorstellung davon bei, welche Variablen Skalare oder Arrays sind. Die Shell ksh mischt beide Konzepte in einem.
1

yashs arrayBuiltin hat einige Optionen, die nur mit Array-Variablen funktionieren. Beispiel: Die -dOption meldet einen Fehler bei Nicht-Array-Variablen:

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

Also können wir so etwas machen:

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

Dieser Ansatz funktioniert nicht, wenn die Array-Variable schreibgeschützt ist . Der Versuch, eine schreibgeschützte Variable zu ändern , führt zu einem Fehler:

$ a=()
$ readonly a
$ array -d a
array: $a is read-only
cuonglm
quelle
0
#!/bin/bash

var=BASH_SOURCE

[[ "$(declare -pa)" =~ [^[:alpha:]]$var= ]]

case "$?" in 
  0)
      echo "$var is an array variable"
      ;;
  1)
      echo "$var is not an array variable"
      ;;
  *)
      echo "Unknown exit code"
      ;;
esac
Fólkvangr
quelle