Bash scripting - espansioni quotabili: differenze tra le versioni

aggiunti esempi più complessi
m (rimosso template autori)
(aggiunti esempi più complessi)
Riga 112: Riga 112:
Inoltre la prima forma può essere annidata facilmente, mentre la seconda richiederebbe un livello aggiuntivo di escape.
Inoltre la prima forma può essere annidata facilmente, mentre la seconda richiederebbe un livello aggiuntivo di escape.


L'output del comando consiste in zero, una o più stringhe: in base agli spazi presenti nell'output prodotto, e in maniera analoga all'espansione a cui sono soggette le variabili. Per trasformare l'output di un comando in una singola stringa è necessario che l'espansione di comando sia quotata, con l'eccezione dell'assegnazione a una variabile.
Il comando è eseguito in una subshell e non può modificare quindi le variabili in nessun caso. Il suo output consiste in zero, una o più stringhe: in base agli spazi presenti nell'output prodotto, e in maniera analoga all'espansione a cui sono soggette le variabili. Per trasformare l'output di un comando in una singola stringa è necessario che l'espansione di comando sia quotata, con l'eccezione dell'assegnazione a una variabile.


Esempi di assegnazione:
Esempi di assegnazione:
Riga 127: Riga 127:
bytes=${testo%% *}          # estraggo il primo argomento (espansione di parametro)
bytes=${testo%% *}          # estraggo il primo argomento (espansione di parametro)
</pre>
</pre>
Si noti che se il file non esiste, il comando restituirebbe un exit status diverso da zero, ma al solito l'esecuzione dello script non sarebbe interrotta. Andrebbe quindi sempre controllato ogni exit status dei comandi nel seguente modo:
<pre>
if ! testo=$(wc -c -- "$file"); then
    # eseguita se l'assegnazione fallisce (se il comando nell'espansione fallisce)
    printf %s\\n "ERRORE: file (${file}) non esistente!" >&2
    exit 1
fi
# la variabile testo è definita
bytes=${testo%% *}          # estraggo il primo argomento
</pre>
Mentre invece <code>date</code> non può fallire in condizioni normali, se non per errori di sintassi di chi ha scritto lo script, per cui non è necessario controllarne l'exit status. Implicitamente si assume che, se si utilizza '''bash''', allora tutti i comandi di sistema siano sufficientemente avanzati da supportare anche opzioni non previste da ''POSIX'' o di trovarsi su una distribuzione GNU/Linux. Si noti però che il comando <code>date</code> potrebbe fallire su altri sistemi, a seconda della versione installata, se non ci si limita alle opzioni previste da ''POSIX'' per il comando.


Passaggio dell'output dei comandi come argomento:
Passaggio dell'output dei comandi come argomento:
Riga 135: Riga 147:
printf %s\\n "Kernel: $(uname) $(uname -r) ($(uname -v))"
printf %s\\n "Kernel: $(uname) $(uname -r) ($(uname -v))"
</pre>
</pre>
Vale la stessa considerazione sugli exit status, ma alcuni comandi non possono fallire, se non in presenza di problemi del sistema operativo stesso, e il loro exit status non è generalmente controllato.
Per decidere se l'exit status di un comando sia da controllare o meno è in genere sufficiente chiedersi se può fallire per azioni che possono (potenzialmente) essere compiute dall'utente, come fornire un percorso di file non valido o inesistente. Altre possibilità sono in genere ignorate, perché se si volesse una compatibilità ancora maggiore probabilmente non si starebbe usando '''bash''' in primo luogo.


===Output con a capo finali===
===Output con a capo finali===
Riga 168: Riga 183:
nr="$(printf \\n)X"  # SBAGLIATO, $nr contiene solo X
nr="$(printf \\n)X"  # SBAGLIATO, $nr contiene solo X
</pre>
</pre>
====Output più complessi con a capo finale====
Proviamo ora con un esempio più complesso con il comando <code>readlink</code>, che stampa il percorso dato in forma assoluta, anche seguendo eventuali link simbolici.
Ovviamente è '''sbagliato''':
<pre>
absolute_path=$(readlink -f -- "$file")  # eventuali "a capo" sarebbero rimossi
</pre>
Proviamo ad aggiungere un carattere dentro l'espansione dopo l'output del comando. Si può fare con una concatenazione:
<pre>
absolute_path=$(readlink -f -- "$file" && printf %s X)
</pre>
Il significato di <code>&&</code> si vedrà poi, basti sapere che <code>printf</code> viene eseguito solo se il comando non fallisce, così da non alterarne l'exit status.
Con il delimitatore niente viene perso ma, provando a stampare la variabile:
* resta la nuova riga aggiunta dal comando, di solito rimossa dall'espansione di comando assieme a tutte quelle finali;
* resta il delimitatore.
Quindi basta rimuovere gli ultimi due caratteri con un'espansione di parametro. Ricapitolando:
<pre>
absolute_path=$(readlink -f -- "$file" &&
                printf %s X)
absolute_path=${absolute_path%??}        # ? sostituisce un carattere qualsiasi
</pre>
Ora è possibile usare <code>"$absolute_path"</code>.
Si ricordi che tutti i nomi di file, salvo quelli seguenti qualche convenzione (come quelli di sistema), possono contenere caratteri non usuali nel loro nome, e in particolare il carattere "a capo". Perciò, se non si controlla a priori ogni nome di file, cercando possibili caratteri non validi e interrompendo l'esecuzione con un errore, uno script robusto deve occuparsi anche di quella possibilità.
Possibilità alternativa, con controllo di errori e interruzione immediata:
<pre>
if ! absolute_path=$(readlink -f -- "$file"); then
    printf %s\\n "ERRORE: percorso non trovato o contenente caratteri non validi!" >&2
    exit 1
fi
</pre>
Si noti però che se esistessero multipli file, con lo stesso nome a parte la presenza degli "a capo" finali, non verrebbe mai restituito errore con nessuno di loro, ma ogni volta che si specifica uno dei file con "a capo" si finirebbe per usare sempre il file senza "a capo" finali senza accorgersene.


==Espansione aritmetica intera==
==Espansione aritmetica intera==
3 581

contributi