3 581
contributi
m (→Espansione di percorso: tag non chiuso) |
(sostituito echo con printf, più modifiche minori, ma resta da dividere in più guide) |
||
Riga 1: | Riga 1: | ||
{{Versioni_compatibili}} | {{Versioni_compatibili}} | ||
=Introduzione= | =Introduzione= | ||
Questa non è una guida completa, per la vastità dell'argomento trattato, e | Questa non è una guida completa, per la vastità dell'argomento trattato, e per il momento non è indicata nemmeno come prima guida a chi voglia approcciarsi a Bash. | ||
Infatti è | Infatti allo stato attuale è necessaria almeno la conoscenza dei comandi: | ||
* di | * di condizione (<code>test/[</code>); | ||
* di | * di esecuzione condizionata (<code>if</code>); | ||
* di | * di ciclo (<code>for</code>). | ||
* di | |||
* di | Mentre non sono trattati altri comandi importanti: | ||
* per definire funzioni (<code>... ()</code>); | * di input (<code>read</code>); | ||
* di condizione avanzata (<code>[[</code>, <code>((</code>); | |||
* di esecuzione condizionata (<code>case</code>); | |||
* di ciclo (<code>while</code>); | |||
* per definire funzioni (<code>... ()</code> / <code>function ...</code>); | |||
* per effettuare il parsing degli argomenti (<code>getopts</code>); | * per effettuare il parsing degli argomenti (<code>getopts</code>); | ||
* modificatori di variabili (<code>readonly</code>, <code>local</code>, <code>declare</code>); | * modificatori di variabili (<code>readonly</code>, <code>local</code>, <code>declare</code>); | ||
* ecc... | * ecc... | ||
Al momento lo scopo della guida è invece, per chi abbia già appreso i concetti più basilari, di evidenziare i comportamenti più distintivi e facili da sbagliare di Bash, con enfasi particolare sulle espansioni di stringhe, estremamente diverse da altri linguaggi di programmazione. Così da passare poi a guide più avanzate per completare l'apprendimento dello scripting in Bash. | |||
Per l'uso interattivo della shell si rimanda a [[Bash tips]]. Si noti che l'espansione della history, che qui non è trattata, è attiva soltanto in modalità interattiva. | Per l'uso interattivo della shell si rimanda a [[Bash tips]]. Si noti che l'espansione della history, che qui non è trattata, è attiva soltanto in modalità interattiva. | ||
==Comandi di output: echo e printf== | |||
Il comando <code>echo</code> è largamente diffuso in Bash per stampare delle stringhe su schermo, perché ha una sintassi più semplice di <code>printf</code> e non risente delle stesse limitazioni della shell POSIX, che interpreta ed espande i caratteri di escape (si legga la sezione dedicata) senza che ci sia un modo di stampare letteralmente una stringa. | |||
Tuttavia negli script l'uso di <code>echo</code> non è sempre possibile, rendendo necessaria la conoscenza almeno basilare di <code>printf</code>. In particolare, se si vuole stampare il contenuto di $var, '''non''' è sempre corretto scrivere: | |||
<pre> | |||
echo -n "$var" # stampa senza a capo finale | |||
echo "$var" # stampa con a capo finale | |||
</pre> | |||
perché $var potrebbe iniziare con il carattere <code>-</code> ed essere una combinazione delle opzioni: -e, -E, -n. | |||
Con <code>echo</code> non esiste un modo che assicuri la stampa del contenuto di una variabile in ogni situazione possibile. E non sempre il contenuto è noto a priori: in presenza di espansioni, come vedremo poi, o di input dell'utente. Per non incorrere in errori difficili da riconoscere, <code>echo</code> andrebbe usato soltanto nella shell interattiva, dove l'uso è più comodo, e <code>printf</code> andrebbe preferito anche in Bash per gli script, perché più robusto. | |||
In questa guida d'ora in poi si farà riferimento soltanto a <code>printf</code>. | |||
===Uso di printf=== | |||
Sintassi base: | |||
<pre>printf formato [ "stringa" ]</pre> | |||
Gli usi più avanzati non sono trattati in questa guida. | |||
Stampa senza a capo finale: | |||
<pre>printf %s "$var"</pre> | |||
Caratteri speciali (senza %s non serve una stringa): | |||
<pre> | |||
printf \\n # nuova riga | |||
printf \\t # tabulazione | |||
printf \\r # ritorno a inizio riga | |||
printf \\888 # stampa il carattere ascii con codice in base 8 | |||
</pre> | |||
Stampa con a capo finale: | |||
<pre>printf %s\\n "$var"</pre> | |||
Stampa con a capo prima e dopo: | |||
<pre>printf \\n%s\\n "$var" </pre> | |||
Per usi più complessi, anziché rendere più complicato il formato, è preferibile utilizzare più comandi <code>printf</code>: | |||
<pre> | |||
# funziona, ma è poco chiaro per chi non ne conosce la sintassi | |||
printf '%s\n\t%s %s\n' "Sintassi:" "$0" "[ arg ]" # più stringhe | |||
# equivalente, ma più leggibile: | |||
printf %s\\n "Sintassi:" | |||
printf \\t%s\\n "$0 [ arg ]" | |||
</pre> | |||
=Variabili (stringhe)= | =Variabili (stringhe)= | ||
Riga 40: | Riga 90: | ||
</pre> | </pre> | ||
==Espansione== | ==Espansione di variabile== | ||
Con l'unica eccezione dell'assegnazione, quando si accede al contenuto di una variabile senza quotarla, questa può essere trasformata in più di una singola stringa ('''esplosione''') in base agli spazi (e tabulazioni e "a capo") contenuti, e perfino in "niente" se è vuota. "Niente" proprio come se non presente nel codice. | Con l'unica eccezione dell'assegnazione, quando si accede al contenuto di una variabile senza quotarla, questa può essere trasformata in più di una singola stringa ('''esplosione''') in base agli spazi (e tabulazioni e "a capo") contenuti, e perfino in "niente" se è vuota. "Niente" proprio come se non presente nel codice. | ||
Riga 63: | Riga 113: | ||
L'opzione deve essere supportata dal comando esterno, non è trattata specialmente dalla shell.}} | L'opzione deve essere supportata dal comando esterno, non è trattata specialmente dalla shell.}} | ||
D'altra parte accedere una variabile senza quotarla permette di assegnare alla variabile tutte le opzioni da passare a un comando, se sono stringhe senza spazi e caratteri speciali, per poi accederle in una volta sola: | D'altra parte accedere una variabile senza quotarla permette di assegnare alla variabile tutte le opzioni da passare a un comando, se sono stringhe senza spazi e caratteri speciali che potrebbero essere espansi nuovamente (<code>* ? [ ]</code>), per poi accederle in una volta sola: | ||
<pre>ARGUMENTS="--arg1 --arg2 ..." | <pre>ARGUMENTS="--arg1 --arg2 ..." | ||
Riga 86: | Riga 136: | ||
<pre> | <pre> | ||
for file in "$@"; do | for file in "$@"; do | ||
# | # fare quello che si vuole con "$file" | ||
# ... | # ... | ||
done | done | ||
Riga 115: | Riga 164: | ||
Per esempio: | Per esempio: | ||
<pre> | <pre> | ||
printf %s\\n parola1; parola2 # ERRORE: parola2 è considerata un altro comando! | |||
printf %s\\n "parola1; parola2" # corretto | |||
printf %s\\n parola1 parola2 # stampa parola1 parola2 (senza caratteri speciali) con un singolo spazio | |||
printf %s\\n "parola1 parola2" # stampa mantenendo gli spazi tra le due parole | |||
# con una variabile | # con una variabile | ||
var="parola1 parola2" | var="parola1 parola2" # assegno la stringa alla variabile | ||
printf %s\\n $var # la stampo con un singolo spazio tra le parole | |||
printf %s\\n "$var" # la stampo così com'è scritta | |||
</pre> | </pre> | ||
Riga 128: | Riga 178: | ||
<pre> | <pre> | ||
# stampa tutto su una riga | # stampa tutto su una riga | ||
printf %s\\n "testo su \ | |||
più \ | più \ | ||
righe" | righe" | ||
# stampa su più righe | # stampa su più righe | ||
printf %s\\n "testo su | |||
più | più | ||
righe" | righe" | ||
Riga 141: | Riga 191: | ||
Sintassi: <code>$'stringa'</code> | Sintassi: <code>$'stringa'</code> | ||
Se l'espansione non è quotata o preceduta da escape, e la stringa non è una variabile, ne espande i caratteri di escape (utilizzabili anche con ''printf | Se l'espansione non è quotata o preceduta da escape, e la stringa non è una variabile, ne espande i caratteri di escape (utilizzabili anche con ''printf''): | ||
* '''\n''', nuova riga; | * '''\n''', nuova riga; | ||
* '''\b''', backspace (cancella un carattere); | * '''\b''', backspace (cancella un carattere); | ||
Riga 151: | Riga 201: | ||
==Racchiudere tra apici== | ==Racchiudere tra apici== | ||
Con gli apici (apostrofi) si riducono i caratteri speciali a uno soltanto, lo stesso apice, rappresentando la stringa per il suo solo valore letterale e impedendo tutte le espansioni: | Con gli apici (apostrofi) si riducono i caratteri speciali a uno soltanto, lo stesso apice, rappresentando la stringa per il suo solo valore letterale e impedendo tutte le espansioni: | ||
<pre> | <pre>printf %s\\n '$PATH "" \ `ls ..` \$HOME ~ * .[a-z]*' # stampa la stringa tra apici, letteralmente</pre> | ||
Lo svantaggio è che non esiste un carattere di escape: | Lo svantaggio è che non esiste un carattere di escape: | ||
<pre> | <pre>printf %s\\n 'L'\''albero di... ' # stampa "L'albero di..." (l'accento non può essere racchiuso tra apici)</pre> | ||
==Quotare (tra virgolette)== | ==Quotare (tra virgolette)== | ||
Racchiudere tra virgolette ogni stringa è raccomandabile, anche se non sempre necessario, così da ridurre il numero di caratteri speciali a cui pensare, permettendo allo stesso tempo l'espansione sicura delle variabili e dei comandi. I soli caratteri speciali rimasti sono <code>$</code>, <code>`</code> (ma non l'apice), <code>"</code> e <code>\</code>, che devono essere preceduti dal carattere di escape <code>\</code>. | Racchiudere tra virgolette ogni stringa è raccomandabile, anche se non sempre necessario, così da ridurre il numero di caratteri speciali a cui pensare, permettendo allo stesso tempo l'espansione sicura delle variabili e dei comandi. I soli caratteri speciali rimasti sono <code>$</code>, <code>`</code> (ma non l'apice), <code>"</code> e <code>\</code>, che devono essere preceduti dal carattere di escape <code>\</code>. | ||
All'interno di una stringa quotata l'espansione <code>$'stringa'</code> e tutte quelle che '''non''' sono attivabili da <code>$</code> non sono permesse. Sono possibili soltanto le espansioni di variabile/parametro, di comando e aritmetiche. | |||
Per esempio: | Per esempio: | ||
<pre> | <pre> | ||
printf %s\\n "$PATH" # espande la variabile PATH e ne stampa il contenuto | |||
printf %s\\n "\$HOME" # stampa letteralmente $HOME, senza espanderla (è equivalente a '$HOME') | |||
printf %s\\n "\"\" \\" # è equivalente a '"" \' | |||
printf %s\\n "$(ls ..)" # Esegue il comando "ls .." e ne stampa l'output | |||
printf %s\\n "$((2*2))" # Esegue l'espressione aritmetica e stampa 4 | |||
printf %s\\n "~ * .[a-z]*" # non effettua le espansioni di tilda e percorso, ma stampa letteralmente | |||
printf %s\\n "{a,b} $'\n'" # stampa letteralmente, senza espansioni | |||
</pre> | </pre> | ||
=Espansioni in stringhe quotate= | =Espansioni in stringhe quotate= | ||
Le espansione attivate da <code>$</code> avvengono con la stessa priorità, e in una stringa quotata sono le uniche permesse, quindi il risultato di un'espansione non può essere espanso | Le espansione attivate da <code>$</code> avvengono con la stessa priorità, e in una stringa quotata con l'eccezione di <code>$'stringa'</code> sono le uniche permesse, quindi il risultato di un'espansione non può mai essere espanso un'altra volta all'interno di una stringa quotata. Sono permesse le sole espansioni di variabile, già vista, e di parametro, di comando e aritmetica (intera), che saranno trattate in seguito. | ||
==Espansione di parametro (stringa)== | ==Espansione di parametro (stringa)== | ||
È una forma modificata dell'espansione di variabile, che permette di operare sulla stringa contenuta con un modificatore. | È una forma modificata dell'espansione di variabile, che permette di operare sulla stringa contenuta con un modificatore. | ||
L'espansione di parametro è utilizzabile ogni volta che lo è quella di variabile, con un'unica differenza: l'espansione di variabile può essere contenuta in | L'espansione di parametro è utilizzabile ogni volta che lo è quella di variabile, con un'unica differenza: l'espansione di variabile può essere contenuta in alcune espansioni di parametro, purché non al posto del nome della variabile, mentre non è mai possibile annidare più espansioni di parametro. | ||
Modificatori: | Modificatori: | ||
Riga 188: | Riga 240: | ||
<pre> | <pre> | ||
var="stringa" | var="stringa" | ||
rif="var" # rif contiene il nome (senza $) di $var | |||
var2=${! | var2=${!rif} # equivalente a: var2=$var | ||
printf %s\\n "$var2" # stampa: stringa | |||
printf %s\\n ${#var2} # stampa: 7 (la lunghezza di "stringa") | |||
</pre> | </pre> | ||
Riga 202: | Riga 254: | ||
<pre> | <pre> | ||
VAR="stringa-di-esempio" | VAR="stringa-di-esempio" | ||
printf %s\\n "${VAR#stringa-}" | |||
</pre> | </pre> | ||
ritorna il contenuto della variable VAR senza il prefisso "stringa-". VAR non viene modificata, salvo una nuova assegnazione: | ritorna il contenuto della variable VAR senza il prefisso "stringa-". VAR non viene modificata, salvo una nuova assegnazione: | ||
Riga 239: | Riga 291: | ||
VAR="questa.sarebbe.una.stringa.di.esempio" | VAR="questa.sarebbe.una.stringa.di.esempio" | ||
# Risultato: | |||
printf %s\\n "${VAR#*.}" # --> sarebbe.una.stringa.di.esempio | |||
printf %s\\n "${VAR##*.}" # --> esempio | |||
printf %s\\n "${VAR%.*}" # --> questa.sarebbe.una.stringa.di | |||
printf %s\\n "${VAR%%.*}" # --> questa | |||
printf %s\\n "${VAR/st/ST}" # --> queSTa.sarebbe.una.stringa.di.esempio | |||
printf %s\\n "${VAR//st/ST}" # --> queSTa.sarebbe.una.STringa.di.esempio | |||
</pre> | </pre> | ||
Riga 255: | Riga 307: | ||
<pre> | <pre> | ||
usage () { | usage () { | ||
printf %s\\n "Usage: ${0##*/}" | |||
} | } | ||
</pre> | </pre> | ||
Riga 272: | Riga 324: | ||
<pre> | <pre> | ||
# assegna alla variabile $oggi la data in formato YYYY_MM_DD | # assegna alla variabile $oggi la data in formato YYYY_MM_DD | ||
oggi=$(date +%F) # senza virgolette | oggi=$(date '+%F') # senza virgolette | ||
oggi="$(date +%F)" # equivalente a sopra (non servono nelle assegnazioni) | oggi="$(date '+%F')" # equivalente a sopra (non servono nelle assegnazioni) | ||
# associo a testo il contenuto del file indicato da $file | # associo a testo il contenuto del file indicato da $file | ||
Riga 284: | Riga 336: | ||
<pre> | <pre> | ||
# stampa stati | # stampa stati | ||
printf %s\\n "Login name: $(logname); Name: $(whoami); UID: $(id -ur); EUID: $(id -u); Groups: $(groups)" | |||
printf %s\\n "OS: Debian GNU/Linux $(cat /etc/debian_version) ($(lsb_release -sc))" # uguale a $(lsb_release -sd) | |||
printf %s\\n "Kernel: $(uname) $(uname -r) ($(uname -v))" | |||
</pre> | </pre> | ||
Riga 292: | Riga 344: | ||
Si noti che l'espansione di comando, come anche in '''sh''' (''POSIX''), non espande il comando a tutto l'output prodotto, ma omette sempre gli "a capo" finali. Se da una parte è utile nella maggior parte delle situazioni, può talvolta avere effetti collaterali difficili da prevedere. Si consideri per esempio: | Si noti che l'espansione di comando, come anche in '''sh''' (''POSIX''), non espande il comando a tutto l'output prodotto, ma omette sempre gli "a capo" finali. Se da una parte è utile nella maggior parte delle situazioni, può talvolta avere effetti collaterali difficili da prevedere. Si consideri per esempio: | ||
<pre> | <pre> | ||
printf %s\\n ciao > ./file_prova # scrive ciao e un "a capo" nel file_prova | |||
testo=$(cat file_prova) # associa il contenuto (SENZA "a capo") a $testo | testo=$(cat ./file_prova) # associa il contenuto (SENZA "a capo") a $testo | ||
printf %s\\n "Bytes: ${#testo}" # NON è la dimensione esatta del file! | |||
printf %s\\n "$testo" | # invia la stringa a cmp (più un "a capo") per un confronto | |||
cmp - ./file_prova # nessun errore!!! | |||
cmp - file_prova | printf %s\\n $? # infatti stampa 0 | ||
</pre> | </pre> | ||
Riga 312: | Riga 363: | ||
# a titolo esemplicativo per l'espansione di comando | # a titolo esemplicativo per l'espansione di comando | ||
nr=$( | nr=$(printf \\n) # SBAGLIATO, $nr è vuota | ||
nr=" | nr="$(printf \\n)" # SBAGLIATO, $nr è sempre vuota | ||
# una possibile soluzione... | # una possibile soluzione... | ||
nr=$(printf | nr=$(printf \\nX) # $nr contiene "a capo" seguito da X | ||
nr=${nr%X} # $nr contiene "a capo" (la X è rimossa) | nr=${nr%X} # $nr contiene "a capo" (la X è rimossa) | ||
# Attenzione che il carattere aggiunto dev'essere nell'output del comando | # Attenzione che il carattere aggiunto dev'essere nell'output del comando | ||
nr=$(printf | nr=$(printf \\n)X # SBAGLIATO, $nr contiene solo X | ||
</pre> | </pre> | ||
Riga 340: | Riga 390: | ||
altezza=5 | altezza=5 | ||
area=$(($base * $altezza)) | area=$(($base * $altezza)) | ||
printf %s\\n "Area rettangolo: ${area}" # Stampa 45 | |||
printf %s\\n "Area triangolo: $(($area / 2))" # Stampa 22 (RICORDA: solo interi) | |||
</pre> | </pre> | ||
=Espansioni non quotabili= | =Espansioni non quotabili= | ||
Le | Le espansioni trattate nelle sezioni successive avvengono unicamente in stringhe non quotate, almeno limitatamente ai caratteri speciali che le attivano. L'espansione <code>$'stringa'</code> non è quotabile, ma è stata già trattata con i caratteri di escape. | ||
Le loro priorità sono tutte diverse: l'espansione di parentesi è quella maggiore, seguita dalla tilda, da tutte le espansioni attivabili con '''$''' (quelle quotabili e <code>$'stringa'</code>) e infine dall'espansione di percorso. | |||
Inoltre le espansioni di parentesi e di percorso, che potrebbero essere espanse a più di una singola stringa, non sono possibili direttamente in un'assegnazione. | |||
==Espansione di tilda== | ==Espansione di tilda== | ||
Riga 361: | Riga 413: | ||
Esempi: | Esempi: | ||
<pre> | <pre> | ||
var=~ | var=~ # assegno la home dell'utente a $var | ||
var=$HOME | var=$HOME # equivalente (ma più chiaro) | ||
var="~" | var="~" # assegno ~ a $var | ||
printf %s\\n "$var" # stampo ~ | |||
printf %s\\n $var # equivalente (nessuna espansione) | |||
var=~root | var=~root # assegno a var la home di root | ||
printf %s\\n ~ # stampo la home dell'utente | |||
printf %s\\n "$HOME" # equivalente | |||
printf %s\\n "~" # stampo ~ | |||
printf %s\\n ~root # stampo la home di root | |||
printf %s\\n ~fdsfd # stampo ~fdsfd (l'utente fdsfd non esiste) | |||
</pre> | </pre> | ||
Riga 381: | Riga 433: | ||
L'espansione non è possibile, direttamente, in un'assegnazione. Avendo la priorità più bassa, contrariamente all'espansione di tilda può avvenire anche in seguito all'espansione di una variabile (e con ogni altra espansione), se non è quotata: | L'espansione non è possibile, direttamente, in un'assegnazione. Avendo la priorità più bassa, contrariamente all'espansione di tilda può avvenire anche in seguito all'espansione di una variabile (e con ogni altra espansione), se non è quotata: | ||
<pre> | <pre> | ||
var="./*" | var="./*" # assegno ./* a $var | ||
var=./* | var=./* # come sopra (nessuna espansione in un'assegnazione) | ||
printf %s\\n "$var" # stampa letteralmente ./* | |||
printf %s\\n $var # stampa la lista di tutti i file non nascosti | |||
# nella directory corrente, oppure ./* se è vuota | |||
</pre> | </pre> | ||
Riga 489: | Riga 541: | ||
Esempi: | Esempi: | ||
<pre> | <pre> | ||
comando &> /dev/null # non stampa niente a schermo | comando &> /dev/null # non stampa niente a schermo, neanche gli errori | ||
comando > /dev/null 2>&1 # equivalente (POSIX) | comando > /dev/null 2>&1 # equivalente (POSIX) | ||
Riga 501: | Riga 553: | ||
Infatti non esiste un modo di contenere il carattere ASCII n. 0 in nessuna posizione: | Infatti non esiste un modo di contenere il carattere ASCII n. 0 in nessuna posizione: | ||
<pre> | <pre> | ||
var=$(printf | var=$(printf \\000) # SBAGLIATO: $var è vuota | ||
var=$(printf | var=$(printf \\000X) # SBAGLIATO: $var contiene solo X | ||
var=$(printf | var=$(printf X\\000X) # SBAGLIATO: $var contiene XX | ||
var="$(printf | var="$(printf X\\000X)" # SBAGLIATO: equivalente a sopra | ||
var=$'\000' | var=$'\000' # SBAGLIATO anche così! | ||
</pre> | </pre> | ||
Riga 522: | Riga 574: | ||
wc -l) # conto il numero di righe | wc -l) # conto il numero di righe | ||
printf %s\\n "La directory prova contiene ${num_file} file" # restituisce 2 invece di 1 | |||
# forma corretta | # forma corretta | ||
Riga 529: | Riga 581: | ||
wc -c) # conto il numero di caratteri | wc -c) # conto il numero di caratteri | ||
printf %s\\n "La directory prova contiene ${num_file} file" # restituisce 1 | |||
# pulizia | # pulizia | ||
Riga 571: | Riga 623: | ||
# il blocco è eseguito solo se un comando fallisce | # il blocco è eseguito solo se un comando fallisce | ||
retval=$? | retval=$? | ||
printf %s\\n "ERRORE (exit status: $retval)" >&2 | |||
exit $retval | exit $retval | ||
} | } | ||
Riga 609: | Riga 661: | ||
|Verificata_da= | |Verificata_da= | ||
:[[Utente:S3v|S3v]] (in Bash tips) | :[[Utente:S3v|S3v]] (in Bash tips) | ||
:[[Utente:HAL 9000|HAL 9000]] | :[[Utente:HAL 9000|HAL 9000]] 12:00, 14 lug 2014 (CEST) | ||
|Estesa_da= | |Estesa_da= | ||
:[[Utente:S3v|S3v]] (in Bash tips) | :[[Utente:S3v|S3v]] (in Bash tips) |
contributi