Controllare che la variabile sia un array in Bourne come shell?


14

In Bourne come shell che supporta la variabile array, possiamo usare alcune analisi per verificare se la variabile è un array.

Tutti i comandi seguenti sono stati eseguiti dopo l'esecuzione 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 e il suo derivato:

$ 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

Un esempio in bash:

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

Questo approccio richiede troppo lavoro e deve generare una subshell. L'uso di altri shell incorporati come =~in [[ ... ]]non necessita di una subshell, ma è ancora troppo complicato.

C'è un modo più semplice per svolgere questo compito?


In quali circostanze dovresti verificare se le tue variabili sono array o no?
Kusalananda

Risposte:


10

Non penso che tu possa, e non penso che in realtà faccia alcuna differenza.

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

x

Che fa la stessa cosa in uno di ksh93e bash. Sembra che probabilmente tutte le variabili siano array in quelle shell, o almeno qualsiasi variabile normale a cui non sono stati assegnati attributi speciali, ma non ho verificato molto di ciò.

Il bash manuale parla di comportamenti diversi per un array rispetto a una variabile di stringa quando si usano le +=assegnazioni, ma successivamente copre e afferma che l'array si comporta diversamente solo in un contesto di assegnazione composto .

Indica inoltre che una variabile è considerata un array se a qualsiasi pedice è stato assegnato un valore e include esplicitamente la possibilità di una stringa nulla. Sopra puoi vedere che un'assegnazione regolare comporta sicuramente l'assegnazione di un pedice - e quindi immagino che tutto sia un array.

Praticamente, possibilmente puoi usare:

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

... per individuare chiaramente le variabili impostate a cui è stato assegnato un solo pedice di valore 0.


Quindi immagino di ${a[1]-not array}poter verificare se è possibile svolgere l'attività, vero?
cuonglm,

@cuonglm - Beh, non secondo il bashmanuale: una variabile di array è considerata impostata se a un pedice è stato assegnato un valore. La stringa null è un valore valido. Se viene assegnato un pedice è un array per specifica. In pratica, anche no, perché puoi farlo a[5]=x. Immagino che [ 1 -eq "${#a[@]}" ] && [ -n "${a[0]+1}" ]potrebbe funzionare.
Mikeserv,

6

Quindi vuoi effettivamente solo la parte centrale di declare -p senza la spazzatura intorno?

Puoi scrivere una macro come:

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

in modo che tu possa fare:

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

(Una semplice funzione non funzionerà se si desidera utilizzarla su variabili funzione-locali).


Con alias

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

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

@mikeserv Ottimo punto. Gli alias lo fanno sembrare più bello. +1
PSkocik,

volevo dire - alias vartype="$VARTYPE"... o semplicemente non definire $VARTYPEaffatto - dovrebbe funzionare, giusto? dovresti solo aver bisogno di quella shoptcosa bashperché si rompe con le specifiche riguardanti l' aliasespansione negli script.
Mikeserv,

1
@mikeserv Sono sicuro che cuonglm è in grado di modificare questo approccio in base alle sue esigenze e preferenze. ;-)
PSkocik,

... e considerazioni sulla sicurezza.
PSkocik,

In nessun caso il codice sopra riportato valuta l'utente fornito testo. Non è meno sicuro di una funzione. Non ti ho mai visto preoccuparti di rendere le funzioni di sola lettura, ma OK, posso contrassegnare la variabile in sola lettura.
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%

Forse echo ${(t)var}è più semplice. Grazie per questo.

4

Per testare la variabile var, con

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

È possibile verificare se esistono più di un indice di array:

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

Se il primo valore dell'indice non è zero:

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

L'unica confusione difficile è quando esiste un solo valore di indice e quel valore è zero (o uno).

Per tale condizione, è possibile utilizzare un effetto collaterale del tentativo di rimuovere un elemento array da una variabile che non è un array:

**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

Questo funziona correttamente per 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."

Per zsh l'indice potrebbe dover essere 1 (a meno che non sia attiva una modalità compatibile).

La sotto-shell è necessaria per evitare l'effetto collaterale della cancellazione dell'indice 0 di var.

Non ho trovato alcun modo per farlo funzionare in ksh.

Modifica 1

Questa funzione funziona solo 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

Modifica 2

Questo funziona anche solo per bash4.2 +

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

Nota: questo darà falsi positivi se var contiene le stringhe testate.


Che ne dici di array con zero elementi?
cuonglm,

1
Dat edit, tho. Sembra molto originale : D
PSkocik,

@cuonglm Il controllo ( unset "var[0]" 2>/dev/null; ) && echo "var is an array."riporta correttamente var è un array quando var è stato impostato su var=()un array con zero elementi. Agisce esattamente uguale a dichiarare.

Il test per scalare non funzionerà se lo scalare viene esportato o contrassegnato come intero / minuscolo / di sola lettura ... Probabilmente si può fare in modo sicuro che qualsiasi altro output non vuoto significhi variabile scalare. Userei grep -Einvece di grep -Pevitare la dipendenza da GNU grep.
Stéphane Chazelas,

@ StéphaneChazelas La prova (in bash) per scalare con interi e / o minuscole e / o di sola lettura iniziano sempre con -a, in questo modo: declare -airl var='()'. Pertanto il test grep funzionerà .

3

Per bash , è un po 'un trucco (sebbene documentato): tenta di utilizzare typesetper rimuovere l'attributo "array":

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

(Non puoi farlo in zsh, ti permette di convertire un array in uno scalare, in bashè esplicitamente vietato.)

Così:

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

O in una funzione, notando le avvertenze alla fine:

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
}

Nota l'uso di typeset -g(bash-4.2 o successivo), questo è richiesto all'interno di una funzione in modo che typeset(syn. declare) Non funzioni come locale ostruisca il valore che stai cercando di ispezionare. Anche questo non gestisce i tipi di "variabili" di funzione, è possibile aggiungere un altro test di diramazione usando typeset -fse necessario.


Un'altra opzione (quasi completa) è di usare questo:

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

Tuttavia, c'è un piccolo problema, un array con un singolo pedice di 0 corrisponde a due delle condizioni precedenti. Questo è qualcosa a cui mikeserv fa riferimento, bash in realtà non ha una netta distinzione e alcuni di questi (se si controlla il log delle modifiche) possono essere biasimati su ksh e sulla compatibilità con come ${name[*]}o ${name[@]}comportarsi su un non-array.

Quindi una soluzione parziale è:

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

In passato ho usato una variazione su questo:

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

anche questo ha bisogno di una subshell.

Un'altra tecnica forse utile è compgen:

compgen -A arrayvar

Verranno elencati tutti gli array indicizzati, tuttavia gli array associativi non vengono gestiti in modo speciale (fino a bash-4.4) e vengono visualizzati come variabili regolari ( compgen -A variable)


Il typeset +ariporta anche un errore nel ksh. Non in zsh, però.

1

Risposta breve:

Per le due shell che hanno introdotto questa notazione ( bashe ksh93) una variabile scalare è solo una matrice con un singolo elemento .

Né è necessaria una dichiarazione speciale per creare un array. È sufficiente solo il compito e un compito semplice var=valueè identico a var[0]=value.


Prova: bash -c 'unset var; var=foo; typeset -p var'. Bash answer riporta un array (ha bisogno di un -a) ?. Ora confrontate con: bash -c 'unset var; var[12]=foo; typeset -p var'. Perché c'è una differenza?. A: La shell mantiene (nel bene o nel male) la nozione di quali var sono scalari o array. La shell ksh mescola entrambi i concetti in uno solo.

1

Il arraybuiltin di yash ha alcune opzioni che funzionano solo con variabili array. Esempio: l' -dopzione segnalerà un errore sulla variabile non array:

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

Quindi possiamo fare qualcosa del genere:

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

Questo approccio non funzionerà se la variabile array è di sola lettura . Tentativo di modificare una variabile di sola lettura che porta a un errore:

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

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.