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 [:
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." fiGlobbing 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).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.