La shell, oltre ad essere una comoda interfaccia per il sistema, è un potente linguaggio di scripting. Le sequenze di comandi possono infatti essere salvate in un file e successivamente richiamate come comandi.

Il presente documento è un estratto delle innumerevoli funzioni della shell BASH, a titolo di introduzione alla programmazione ed all'uso. Per una trattazione completa si veda il manuale GNU. ogni osservazione o correzione è benvenuta (matteochiocciolamatteolucarellipuntonet)

Importante: lo shell-scripting, come molti altri linguaggi di script, è case-sensitive (cioè distingue tra maiuscole e minuscole) e NON IGNORA I BLANK, attenzione quindi a spazi, tab, return, ecc.

Indice
  1. Formato del file di programma
  2. Il segno $ e le variabili
  3. Altri caratteri speciali
  4. Raggruppamento dei comandi
  5. Parametri posizionali
  6. Definizione di funzioni
      esempi
  7. Principali variabili predefinite
  8. Altre variabili automatiche
  9. Principali comandi interni
  10. Principali comandi esterni
  11. Controllo dei jobs e dei processi
  12. Test
  13. Cicli e strutture condizionali
      esempi
  14. Pattern Matching
      esempi
  15. Particolari espansioni
      esempi
  16. Stringhe
      esempi
  17. Aritmetica
      esempi
  18. Array
  19. Trucchi e consigli

Formato del file di programma

Un programma per shell è un semplice file di testo (eventualmente con estensione .sh). E' utilizzabile come un qualsiasi altro comando se reso eseguibile

	chmod 755 ./mioscript
	./mioscript

Altrimenti può venire invocato passandolo come argomento alla shell (in tal caso non deve necessariamente essere eseguibile).

	sh mioscript

La prima riga deve contenere il percorso dell'interprete, quindi ogni script inizierà con la dicitura:

	#!/bin/sh

Alcuni Unix richiedono anche uno spazio dopo il "!".

Il segno $ e le variabili

Il segno "$" fa sì che il valore seguente venga "espanso" (valutato). Per le variabili questo significa che al momento dell'assegnazione utilizzeremo il solo nome mentre al momento dell'utilizzo dovremo far precedere il nome dal segno di dollaro. La valutazione delle variabili avviene anche nelle stringhe tra virgolette. L'espansione ha molti altri effetti, che si apprenderanno gradualmente.

	VALORE=3
	echo $VALORE

Va notato che le variabili di shell non richiedono dichiarazione di tipo. Ad una stessa variabile è possibile assegnare successivamente valori di ogni tipo (numerico, stringa, ecc). E' quindi possibile realizzare array misti.

Nei nomi delle variabili sono ammessi tutti i caratteri alfanumerici e l'underscore. Il primo carattere deve essere alfabetico. Per convenzione si utilizzano normalmente caratteri maiuscoli.

Per evitare ambiguità la variabile può essere delimitata dalle parentesi graffe

	VALORE=3
	echo ${VALORE}alpha

NOTA: Non vanno lasciati spazi intorno al segno "=" di un assegnamento.

Altri caratteri speciali

Raggruppamento dei comandi

I comandi possono essere raggruppati tramite le parentesi tonde () o tramite le parentesi graffe {}. I comandi raggruppati sono eseguiti come se fossero un singolo comando (quindi, ad esempio possono essere redirezionati in blocco). Il raggruppamento nelle parentesi tonde fa si che i comandi vengano eseguiti da una nuova shell figlia (subshell).
Nota: le parentesi graffe si ottengono dalla tastiera italiana premendo AltGr+7 e AltGr+0.

Parametri posizionali

I parametri posizionali sono gli argomenti passati allo script o alla funzione al momento della chiamata. Quindi se lo script "ordina.sh" viene chiamato:

	ordina.sh /home/user/documenti

All'interno dello script il primo parametro posizionale ($0) varrà "ordina.sh" il secondo ($1) varrà "home/user/documenti".

Definizione di funzioni

All'interno di uno script possono essere definite delle funzioni tramite una sintassi analoga al linguaggio C:

	[ function ] name ()
	{
		command-list;
	}

Gli argomenti della chiamata saranno i parametri posizionali all'interno della funzione. Non vanno quindi specificati nella definizione della funzione, e il loro numero può essere variabile. Le variabili all'interno della funzione hanno scope locale (cioè non sono definite all'esterno).

ES: tipica verifica di inzio script: se non sono presenti due parametri, oppure i due parametri non sono quelli previsti, stampa una breve nota di utilizzo ed esce.

	print_usage() {
		echo "Questo script va chiamato con 2 parametri"
		echo "Che possono essere \"-c path\""
		echo "            oppure \"-l path\""
	}

	[ $# != 2 ] && print_usage && exit 1
	[ $1 != "-l" ] && [ $1 != "-c" ] && print_usage && exit 2

Principali variabili predefinite

Altre variabili automatiche

Principali comandi interni

I comandi che seguono sono un estratto dei comandi built-in della shell. Per ottenere maggiori informazioni utilizzare man bash.

Principali comandi esterni

I comandi che seguono sono eseguibili indipendenti dalla shell. Vengono generalmente considerati presenti in ogni sistema, e quindi comunemente utilizzati all'interno degli script. Ognuno dei comandi presenta molte opzioni, per approfondire si vedano le corrispondenti pagine di manuale. I comandi illustrati risiedono generalmente in /bin e /usr/bin. Nella maggioranza dei sistemi esiste una pagina di manuale specifica per ogni comando visibile con man comando.

Controllo dei jobs e dei processi

Test

Un test si può realizzare con una delle seguenti forme (gli spazi sono necessari!):

	[ test ]
	test test

Alcuni dei test possibili sono:

espressioni su file (es: -a filename): confronto tra file (es: file1 -nt file2): espressioni su stringhe (es: -Z string): confronto tra stringhe (es: str1 == str2): Note:

Es: controlla il corretto numero di argomenti:

	[ $# != 2 ] && echo "sintassi errata" && exit 1

Es: se non esiste l'eseguibile mysqld termina:

	test -x /usr/sbin/mysqld || exit 0

Cicli e strutture condizionali

In tutte le strutture esposte i ritorni a capo sono necessari, nel caso in cui si voglia autilizzare una sola riga, vanno sostituiti con ; (puntoevirgola).

Es: se /bin/comando è eseguibile eseguilo, altrimenti stampa un errore

    if [ -x /bin/comando ]; then
      /bin/comando
    else
      echo "ERRORE: non posso eseguire /bin/comando"
    fi

Es: stampa numeri crescenti all'infinito

    COUNT=0
    while [ 0 ]; do
      echo $COUNT
      (( ++COUNT ))
    done

Es: attesa di 5 sec con (semplice) progress-bar

    for loop in 3 2 1 0 ; do sleep 1; echo -n "$loop"; done

Es: memorizza il nome dei file presenti nella directory config nell'array TPCONF

    TPCOUNT = 0
    declare -a TPCONF
    for CONFFILE in ./config/*
    do
        TPCONF[$TPCOUNT]=$CONFFILE
        (( TPCOUNT++ ))
    done

Es: countdown

    COUNT=20
    until [  $COUNT -lt 10 ]; do
         echo count: $COUNT
         let COUNT-=1
    done

Es: stampa una stringa descrittiva dell'architettura di processore

    case $( uname -m ) in
      i[3456]86 ) echo "architettura Intel o simili";;
      alpha     ) echo "architettura Alpha";;
      arm       ) echo "architettura Arm";;
      *         ) echo "Altro";;
    esac

Es: Selezione di un file con menù

    select SCELTA in ./*
    do
      [ -z $SCELTA  ] || break
    done
    echo Hai scelto $SCELTA
    

Pattern Matching

Una delle funzionalità più potenti della shell è nella ricerca di file e directory. E' possibile utilizzare nei path alcuni caratteri speciali:

ES: conteggia e memorizza in un array i file presenti nella directory ./conf/

	declare -a TPCONF
	TPCOUNT=0
	for CONFFILE in ./conf/*
	do
			TPCONF[$TPCOUNT]=$CONFFILE
			(( TPCOUNT++ ))
	done

Particolari espansioni

Alcune delle espansioni illustrate hanno una sintassi particolarmente criptica. Permettono, con un po' di pratica, sostituzioni quasi impossibili con altri linguaggi. Per una migliore comprensione si vedano gli esempi. NOTA: Le espansioni sono quasi sempre annidate, quindi i parametri vengono a loro volta espansi.

	Es: echo a{d,c,b}e  =output=> ade ace abe

Es: sostituisce gli spazi con underscore all'interno dei nomi dei file nella directory corrente (rinominandoli)

	for FILE in *
	do
		NUOVONOME=${FILE// /_}
		mv "$FILE" $NUOVONOME
	done

Es: suddivide un path nei suoi componenti e li assegna ad un array: (l'espressione sembra complessa, ma è veramente potente)

	PATH="/home/utente/doc/documento.pdf"
	declare -a COMP=( "\""${PATH//\//"\" \""}"\"" )

	# ciclo di stampa dell'array ottenuto
	for i in `/usr/bin/seq 1 $((${#COMP[*]}-1))`
	do
		echo ${COMP[$i]}
	done

Stringhe

La Bash incorpora molte funzioni di parsing delle stringhe, anche se con una certa mancanza di omogeneità. Alcune delle seguenti funzioni sono utilizzate anche in altri contesti (la prima ad esempio, applicata ad un array ne restituisce il numero di elementi). Molte altre operazioni sono possibili tramite i comandi tr ed expr.

	foo=/tmp/my.dir/filename.tar.gz
	
	len = ${#foo}         # ritorna la lunghezza della stringa
	path = ${foo%/*}      # ritorna /tmp/my.dir
	file = ${foo##*/}     # ritorna filename.tar.gz 
	base = ${file%%.*}    # ritorna filename 
	ext = ${file#*.}      # ritorna tar.gz
	
	${file/%.tar.gz/.tgz} # ritorna filename.tgz
	
	$(echo $file | tr '[A-Z]' '[a-z]') # ritorna FILENAME.TAR.GZ
	

Aritmetica

NOTA IMPORTANTE: la bash esegue calcoli solo su numeri interi, quindi ad esempio 5/2=2. Per avere calcoli in virgola mobile è necessario utilizzare un calcolatore esterno (dc, bc, ecc).

Un'espressione aritmetica si può valutare utilizzando la seguente forma:

	(( expr ))

Esistono inoltre i due comandi expr e bc, per il cui approfondimento si rimanda ai rispettivi manuali.

Le operazioni possibili sono:

Per parentesi annidate si utilizzano le parentesi semplici:

	(( 5 * ( 2 - $NUM ) ))

il $ è necessario, come sempre, per le assegnazioni e per l'output:

	SUM=$(( $A + $B ))
	echo $(( 5+3 )) 

mentre non è necessario per incremento e decremento:

	(( COUNTER++ ))

La doppia parentesi tonda va utilizzata anche per test di tipo numerico:

	(( $A >= $B )) && echo "B non è più grande di A"

Anche se per i test di confronto funziona anche la parentesi quadra (che però, in realtà, effettua un confronto tra stringhe):

	[ $A == $B ] && echo "A è uguale a B"

Array

La bash permette l'uso di array monodimensionali, che possono essere anche misti (cioè contenere valori di tipo differente).

	# definizione
	ARY=( uno due tre quattro )

	# stampa il numero di elementi di un array
	echo "${#ARY[*]}"
	
	# stampa due elementi dopo il terzo
	echo "${ARY[@]:3:2}"

	#stampa il 2 elemento
	echo "${ARY[1]}"

	# stampa l'intero array
	echo "${ARY[*]}"

	# ciclo di stampa sugli elementi di un array
	for (( I=0 ; I<${#ARY[*]} ; I++ ))
	do
		echo "elemento[$I]: ${ARY[$I]}"
	done

Trucchi e consigli