2016-07-11 34 views
5

Poiché Bash, quando è in esecuzione nella modalità set -o nounset (ovvero set -u), può considerare gli array vuoti come non impostati indipendentemente dal fatto che siano stati effettivamente assegnati un valore vuoto, è necessario prestare attenzione quando si tenta di espandere un array: una delle soluzioni alternative consiste nel verificare se la lunghezza dell'array è zero. Per non parlare del fatto che ottenere il numero di elementi in un array è un'operazione comune di per sé.Ottieni la lunghezza di un array vuoto o non impostato quando è attiva l'opzione "nounset"

Durante lo sviluppo con Bash 4.2.47 (1) -release in openSUSE 42.1, sono abituati a quella dimensione dell'array ottenendo con ${#ARRAY_NAME[@]} riesce quando array è vuoto o disinserito. Tuttavia, mentre controllava il mio script con Bash 4.3.46 (1) -release in FreeBSD 10.3, si è scoperto che questa operazione potrebbe fallire con il messaggio di errore generico "variabile non associata". Fornire il valore predefinito per l'espansione non sembra funzionare per la lunghezza dell'array. Fornire catene di comando alternative sembra funzionare, ma non all'interno di una funzione chiamata attraverso un'espansione subshell - le funzioni si chiudono appena dopo il primo errore. Cos'altro può esserci di aiuto qui?

consideri il seguente esempio:

function Size() 
{ 
    declare VAR="$1" 
    declare REF="\${#${VAR}[@]}" 
    eval "echo \"${REF}\" || echo 0" 2>/dev/null || echo 0 
} 

set -u 
declare -a MYARRAY 

echo "size: ${#MYARRAY[@]}" 
echo "size: ${#MYARRAY[@]-0}" 
echo "Size: $(Size 'MYARRAY')" 
echo -n "Size: "; Size 'MYARRAY' 

Nell'ambiente openSUSE, tutte echo linee di uscita 0, come previsto. In FreeBSD, lo stesso risultato è possibile solo quando all'array viene assegnato esplicitamente un valore vuoto: MYARRAY=(); in caso contrario, entrambe le query inline nelle prime due righe falliscono, la terza riga viene emessa solo Size: (il che significa che il risultato dell'espansione è vuoto) e solo l'ultima riga viene completata completamente grazie all'outdoor || echo 0 - tuttavia il passaggio attraverso lo schermo è non è ciò che è solitamente inteso quando si cerca di ottenere la lunghezza dell'array.

Ecco la sintesi delle mie osservazioni:

        Bash 4.2 Bash 4.3 
            openSUSE FreeBSD 

counting elements of unset array  OK  FAILED 
counting elements of empty array  OK  OK 

content expansion of unset array  FAILED FAILED 
content expansion of unset array(*) OK  OK 
content expansion of empty array  FAILED FAILED 
content expansion of empty array(*) OK  OK 
    (* with fallback value supplied) 

Per me, che sembra piuttosto inconsistente. Esiste una vera soluzione a prova di futuro e multipiattaforma per questo?

+0

Aggiungi output di echo $ -' alla tua domanda. – Cyrus

+0

@Cyrus È 'huB' in entrambi i sistemi quando si esegue uno script,' himBH' quando si è in riga di comando. –

+1

Perché non fare semplicemente 'set + u', quindi fare i controlli necessari, quindi' set -u'? –

risposta

1

Come soluzione temporanea, ho seguito il percorso suggerito da @ William-Pursell e appena impostata l'opzione nounset durante la query:

function GetArrayLength() 
{ 
    declare ARRAY_NAME="$1" 
    declare INDIRECT_REFERENCE="\${#${ARRAY_NAME}[@]}" 
    case "$-" in 
    *'u'*) 
     set +u 
     eval "echo \"${INDIRECT_REFERENCE}\"" 
     set -u 
     ;; 
    *) 
     eval "echo \"${INDIRECT_REFERENCE}\"" 
     ;; 
    esac 
} 

(Usando if invece di case porta esecuzione di trascurabile più lento sul mio test Inoltre, case consente di associare facilmente le opzioni aggiuntive se ciò sarebbe necessario a volte.)

Ho provato anche sfruttando il fatto che il contenuto di espansione (con fallback o valore di sostituzione) di solito esito positivo anche per gli array unset:

function GetArrayLength() 
{ 
    declare ARRAY_NAME="$1" 
    declare INDIRECT_REFERENCE="${ARRAY_NAME}[@]" 
    if [[ -z "${!INDIRECT_REFERENCE+isset}" ]]; then 
     echo 0 
    else 
     INDIRECT_REFERENCE="\${#${ARRAY_NAME}[@]}" 
     eval "echo \"${INDIRECT_REFERENCE}\"" 
    fi 
} 

Tuttavia, si scopre che Bash non ottimizza ${a[@]+b} espansione, come esecuzione il tempo aumenta chiaramente per gli array più grandi, sebbene sia il più piccolo per gli array vuoti o non impostati.

Tuttavia, se qualcuno ha una soluzione migliore, è stato libero di inviare altre risposte.

1

Esistono differenze (documentate) tra i sapori di Linux e BSD di bash. Suggerirei di scrivere il codice secondo lo standard POSIX. Puoi iniziare qui per maggiori informazioni -> www2.opengroup.org.

Con questo in mente, si può iniziare bash con l'opzione della riga di comando --posix oppure è possibile eseguire il comando set -o posix mentre bash è in esecuzione. O farà sì che lo standard bash sia conforme allo standard POSIX.

Il suggerimento sopra riportato aumenta la probabilità di coerenza tra piattaforme diverse.

+1

Bel punto. Tuttavia, in questo caso particolare, non vedo alcuna differenza tra 'posix off' e' posix on'. Lo stesso vale per altre discrepanze che ho notato, come la corrispondenza delle espressioni regolari. –

Problemi correlati