Condizionali Bash a Confronto: Quando Usare test, [ e [[

Confronta test, parentesi singole e doppie per mantenere i tuoi condizionali Bash portabili, sicuri e leggibili.

Condizionali Bash a Confronto: Quando Usare test, [ e [[

Quando un condizionale Bash si comporta in modo strano, spesso il problema è la costruzione che hai scelto. test, [ ] e [[ ]] sembrano simili, ma gestiscono quoting, pattern, regex e portabilità in modo diverso.

Questa guida confronta le tre forme in modo che tu possa scrivere condizionali sicuri, leggibili e appropriati per la shell in cui il tuo script viene effettivamente eseguito.

Il Comando test: Le Fondamenta

Il comando test è uno dei modi più antichi e fondamentali per valutare condizioni negli script shell. È un comando built-in nella maggior parte delle shell moderne e fa parte dello standard POSIX, rendendolo altamente portabile. test valuta un'espressione e restituisce uno stato di uscita di 0 (vero) o 1 (falso).

Utilizzo Base

Il comando test accetta uno o più argomenti, che formano l'espressione da valutare. Controlla attributi di file, confronti tra stringhe e confronti tra interi.

# Verifica se un file esiste
if test -f "miofile.txt"; then
    echo "miofile.txt esiste ed è un file regolare."
fi

# Verifica se due stringhe sono uguali
NOME="Alice"
if test "$NOME" = "Alice"; then
    echo "Il nome è Alice."
fi

# Verifica se un numero è maggiore di un altro
CONTATORE=10
if test "$CONTATORE" -gt 5; then
    echo "Il contatore è maggiore di 5."
fi

Operatori Comuni di test

  • Operatori per File: -f (file regolare), -d (directory), -e (esiste), -s (non vuoto), -r (leggibile), -w (scrivibile), -x (eseguibile).
  • Operatori per Stringhe: = (uguale), != (diverso), -z (stringa vuota), -n (stringa non vuota).
  • Operatori per Interi: -eq (uguale), -ne (diverso), -gt (maggiore di), -ge (maggiore o uguale), -lt (minore di), -le (minore o uguale).

Consiglio: Racchiudi sempre tra virgolette le variabili usate con test (es. "$NOME") per evitare problemi di word splitting e pathname expansion se il valore della variabile contiene spazi o caratteri glob.

Parentesi Singole [ ]: La Forma di test

La costruzione a parentesi singola [ ] è una sintassi alternativa per il comando test. In molte shell, [ è un built-in della shell, e i sistemi spesso forniscono anche un /usr/bin/[ esterno. La differenza chiave è che [ richiede un ] di chiusura come ultimo argomento. Come test, è conforme a POSIX.

Sintassi e Semantica

# Equivalente a test -f "miofile.txt"
if [ -f "miofile.txt" ]; then
    echo "miofile.txt esiste ed è un file regolare usando [ ]."
fi

# Equivalente a test "$NOME" = "Alice"
NOME="Bob"
if [ "$NOME" != "Alice" ]; then
    echo "Il nome non è Alice."
fi

Nota lo spazio obbligatorio dopo [ e prima di ]. Questi vengono trattati come argomenti separati per il comando [.

Racchiudere le Variabili tra Virgolette: Un Dettaglio Critico

Poiché [ ] è fondamentalmente il comando test, eredita gli stessi comportamenti riguardo word splitting e pathname expansion. Ciò significa che le variabili non quotate possono portare a comportamenti imprevisti o vulnerabilità di sicurezza.

Considera questo esempio:

#!/bin/bash

INPUT="file con spazi.txt"

# PERICOLOSO: La variabile non quotata causerà problemi se INPUT contiene spazi
# La shell eseguirà il word splitting, trattando "file" e "con spazi.txt" come argomenti separati
# portando a un errore di sintassi o a una valutazione errata.
# if [ -f $INPUT ]; then echo "Trovato"; else echo "Non trovato"; fi 

# CORRETTO: Racchiudi la variabile tra virgolette per trattarla come un singolo argomento
if [ -f "$INPUT" ]; then
    echo "'file con spazi.txt' esiste."
else
    echo "'file con spazi.txt' non esiste o non è un file regolare."
fi

Senza virgolette, $INPUT si espanderebbe in file con spazi.txt, e [ -f file con spazi.txt ] verrebbe interpretato come un errore di sintassi dal comando [ perché -f si aspetta un solo operando. Racchiudere tra virgolette garantisce che $INPUT venga passato come un singolo argomento, "file con spazi.txt".

Pericoli di Word Splitting e Pathname Expansion

Sia test che [ sono soggetti ai comportamenti predefiniti della shell di word splitting e pathname expansion (globbing). Se una variabile contiene spazi o caratteri glob (*, ?, [ ]) e non è quotata, la shell la espanderà prima che test o [ vedano gli argomenti. Ciò può portare a errori di sintassi o confronti errati quando i caratteri glob corrispondono a file esistenti.

Doppie Parentesi [[ ]]: La Parola Chiave Moderna di Bash

La costruzione a doppie parentesi [[ ]] è una parola chiave di Bash (supportata anche da Ksh e Zsh), non un comando esterno o un alias. Questa distinzione è cruciale, poiché permette a [[ ]] di comportarsi diversamente e offrire funzionalità migliorate e una sicurezza superiore rispetto a test o [ ].

Funzionalità Avanzate

[[ ]] introduce diverse potenti funzionalità non disponibili con test o [:

  1. Nessun Word Splitting o Pathname Expansion: Le variabili all'interno di [[ ]] generalmente non hanno bisogno di essere quotate (anche se è spesso buona pratica farlo per chiarezza). La shell gestisce il contenuto di [[ ]] come un'unica unità, prevenendo word splitting e pathname expansion. Questo riduce significativamente errori comuni di scripting e rischi per la sicurezza.

    # Non c'è bisogno di quotare le variabili (anche se è comunque sicuro farlo)
    INPUT="file con spazi.txt"
    if [[ -f $INPUT ]]; then # $INPUT viene trattato come una singola stringa qui
        echo "'$INPUT' esiste."
    fi
    
  2. Globbing per il Confronto di Stringhe: Gli operatori == e != eseguono pattern matching (globbing) piuttosto che uguaglianza stretta di stringhe quando usati all'interno di [[ ]]. Ciò significa che puoi usare *, ? e [] come wildcard.

    NOME_FILE="mio_documento.txt"
    if [[ "$NOME_FILE" == *".txt" ]]; then # Verifica se NOME_FILE termina con .txt
        echo "È un file di testo!"
    fi
    
    # Nota: Per uguaglianza stretta di stringhe senza globbing, usa `test` o `[ ]` con `=`
    # o assicurati che non ci siano caratteri glob nel lato destro di `==` in [[ ]]
    # (o racchiudi tra virgolette il lato destro se contiene caratteri glob letterali che vuoi confrontare letteralmente).
    
  3. Matching con Espressioni Regolari: L'operatore =~ permette di eseguire matching con espressioni regolari.

    INDIRIZZO_IP="192.168.1.100"
    if [[ "$INDIRIZZO_IP" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
        echo "Formato IP valido."
    fi

    # Importante: Il pattern regex sul lato destro di =~ generalmente NON dovrebbe essere quotato
    # se contiene caratteri che altrimenti verrebbero trattati come pattern glob.
    # Se la regex è in una variabile, anche questa dovrebbe essere non quotata.
    # Esempio di pattern: ^[A-Za-z]+$
    ```

4.  **Operatori Logici `&&` e `||`**: `[[ ]]` supporta gli operatori logici in stile C più intuitivi `&&` (AND) e `||` (OR) per combinare più condizioni, insieme a `!` per la negazione. Questi operatori hanno una corretta valutazione short-circuit e precedenza, a differenza di `-a` e `-o` di `test`.

    ```bash
    ETA=25
    if [[ "$NOME" == "Alice" && "$ETA" -ge 18 ]]; then
        echo "Alice è maggiorenne."
    fi

    if [[ "$UTENTE" == "root" || -w /etc/fstab ]]; then
        echo "O è root o può scrivere su fstab."
    fi
    ```

### Natura Specifica di Bash

Sebbene `[[ ]]` offra vantaggi significativi, il suo principale svantaggio è che è un'estensione di Bash/Ksh/Zsh e non fa parte dello standard POSIX. Ciò significa che gli script che si basano su `[[ ]]` potrebbero non essere portabili su `sh`, `dash` o sistemi Unix-like più vecchi/minimalisti.

## Confronto Fianco a Fianco: `test` vs. `[` vs. `[[`

Ecco una tabella che riassume le differenze chiave:

| Caratteristica              | `test`                           | `[ ]`                               | `[[ ]]`                                   |
| :------------------------- | :------------------------------- | :---------------------------------- | :---------------------------------------- |
| **Tipo**                   | Comando built-in (o esterno)     | Comando built-in (alias di `test`)  | Parola chiave della shell (Bash, Ksh, Zsh)|
| **Conforme a POSIX**       | Sì                               | Sì                                 | No                                        |
| **Richiede `]` di Chiusura**| No                               | Sì (come ultimo argomento)          | Sì (come parte della parola chiave)       |
| **Word Splitting**         | Sì, su variabili non quotate     | Sì, su variabili non quotate        | No, le variabili sono trattate come stringhe singole |
| **Pathname Expansion**     | Sì, su variabili non quotate     | Sì, su variabili non quotate        | No |
| **Pattern Matching Glob**  | No per uguaglianza stringhe      | No per uguaglianza stringhe         | Sì con lato destro non quotato di `==` o `!=` |
| **Espressioni Regolari**   | No                               | No                                  | Sì con `=~` |
| **AND/OR Logici**          | `-a`, `-o` esistono ma sono facili da fraintendere | `-a`, `-o` esistono ma sono facili da fraintendere | `&&`, `||` con normale comportamento short-circuit |
| **Comandi Composti**       | Richiede chiamate `test` separate | Richiede chiamate `[` separate      | Può combinare espressioni direttamente (`&&`/`||`)|
| **Quotatura Variabili**    | **Obbligatoria** per sicurezza   | **Obbligatoria** per sicurezza      | Generalmente non richiesta, ma buona pratica |

## Quando Usare Cosa

Scegliere la costruzione condizionale giusta dipende principalmente dai requisiti di portabilità e dalla complessità della tua logica condizionale.

### Conformità POSIX vs. Funzionalità Moderne di Bash

-   **Usa `test` o `[ ]` quando...**
    -   **La portabilità è fondamentale**: Se il tuo script deve funzionare su qualsiasi shell conforme a POSIX (`sh`, `dash`, sistemi più vecchi, ecc.), `test` o `[ ]` sono le tue uniche opzioni affidabili.
    -   Le tue condizioni sono semplici (controlli su file, confronti base tra stringhe/interi).
    -   Ti senti a tuo agio con la quotatura attenta di tutte le variabili e l'uso di `&&`/`||` a livello di shell al di fuori delle parentesi quando hai bisogno di logica composta.

-   **Usa `[[ ]]` quando...**
    -   **Stai scrivendo esclusivamente per Bash** (o Ksh/Zsh) e non hai bisogno di portabilità POSIX.
    -   Hai bisogno di funzionalità avanzate come pattern matching glob, matching con espressioni regolari o operatori logici in stile C `&&`/`||`.
    -   Vuoi le funzionalità di sicurezza avanzate che prevengono word splitting e pathname expansion, portando a codice più robusto e meno soggetto a errori.
    -   Le tue condizioni coinvolgono logica complessa che sarebbe macchinosa con `test -a`/`-o`.

### Buone Pratiche e Raccomandazioni

1.  **Dai Priorità a `[[ ]]` per Script Bash**: Se il tuo script è destinato a Bash, `[[ ]]` è generalmente la scelta preferita grazie alla sua maggiore sicurezza, funzionalità estese e sintassi più intuitiva per condizioni complesse. Riduce drasticamente gli errori comuni di scripting legati alla quotatura e ai caratteri speciali.

2.  **Quota Sempre in `test` e `[ ]`**: Se *devi* usare `test` o `[ ]` per la conformità POSIX, abituati a **quotare sempre le tue variabili** per prevenire comportamenti imprevisti dovuti a word splitting e pathname expansion.

    ```bash
    # Buona pratica per [ ] e test
    VAR="una stringa con spazi"
    if [ -n "$VAR" ]; then echo "Non vuota"; fi
    ```

3.  **Fai Attenzione al Pattern Matching**: In `test` e `[ ]`, `=` è usato per l'uguaglianza di stringhe. In `[[ ]]`, `=` e `==` possono entrambi eseguire pattern matching quando il lato destro non è quotato. Racchiudi tra virgolette il lato destro quando vuoi un confronto letterale tra stringhe.

4.  **Espressioni Regolari con `=~`**: Quando usi `=~` in `[[ ]]`, il lato destro dovrebbe tipicamente essere non quotato per permettere alla shell di interpretarlo come un pattern di espressione regolare, non come una stringa letterale da confrontare.

    ```bash
    # Pattern regex non quotato è corretto per =~ in [[ ]]
    if [[ "$LINEA" =~ ^Errore: ]]; then echo "Errore trovato"; fi
    ```

## Conclusione

Usa `[ ]` o `test` quando il tuo script deve funzionare sotto POSIX `sh`. Usa `[[ ]]` quando il tuo shebang è Bash e vuoi una gestione più sicura delle variabili, matching glob, matching regex e condizioni composte più pulite. L'abitudine principale è semplice: abbina la sintassi condizionale alla shell e quota deliberatamente.