18.1 Eseguibili, interpretabili e automazione dell'interpretazione
18.10 Raccolta di funzioni per una shell POSIX
18.10.1 Estrapola da «/etc/passwd» le righe di un certo intervallo di numeri UID
18.10.2 Estrapola da «/etc/passwd» le righe di un certo intervallo di numeri GID
18.10.3 Estrapola da «/etc/group» le righe di un certo intervallo di numeri GID
18.10.5 Seleziona un campo di una certa riga da un file come «/etc/passwd», «/etc/group» e simili
18.10.7 Cambia la parola d'ordine a un utente Unix e Samba, simultaneamente
18.10.10 Verifica che l'utente proprietario di un file possa accedervi
18.10.11 Verifica che il gruppo proprietario di un file possa accedervi
18.10.12 Verifica che gli utenti diversi possano accedere a un file
18.10.13 Estrapola il nome dell'utente proprietario di un file
echo
18.6.1
expr
18.7.3
false
18.7.1
printf
18.6.1
sleep
18.9.1
tee
18.8.1
test
18.7.2
true
18.7.1
yes
18.6.4
[
18.7.2
In questo capitolo si tratta in generale il problema dell'interpretazione di programmi in un sistema GNU/Linux e in particolare degli strumenti di cui ci si può avvalere utilmente per la realizzazione di script per una shell standard, con esempi e funzioni per risolvere problematiche comuni.
Quando si utilizza un sistema operativo complesso come GNU/Linux, dove il kernel ha un ruolo così importante, è difficile stabilire una distinzione netta tra un programma eseguibile binario e un programma interpretato. A livello astratto si intende che il programma interpretato richiede un programma interprete che è di fatto il suo esecutore, ma anche l'interprete potrebbe a sua volta essere interpretato da un altro programma di livello inferiore. È un po' come quando per tradurre un testo dal cinese all'italiano, si preferisce partire dal lavoro di qualcun altro che l'ha già tradotto in inglese.
Evidentemente si pone il problema di stabilire il livello di astrazione a cui si vuole fare riferimento. Si potrebbe dire che un programma binario «normale» sia quello che viene eseguito direttamente dal kernel senza bisogno di altri sostegni da parte di programmi interpreti aggiuntivi. In questo senso, potrebbe accadere anche di avere un programma che nel sistema «A» è un binario normale, mentre nel sistema «B» potrebbe essere eseguito per opera di un interprete intermedio, diventando lì un programma interpretato.
Il classico tipo di programma interpretato è lo script che normalmente viene individuato dalla stessa shell attraverso cui viene avviato. Per questo è stata stabilita la convenzione per cui questi programmi sono contenuti in file di testo, in cui la prima riga deve indicare il percorso dell'interprete necessario.
|
Tale convenzione impone che, in questo tipo di script, il simbolo # rappresenti l'inizio di un commento e che comunque si tratti di un file di testo normale. Inoltre, è stabilito implicitamente che il programma interprete indicato riceva il nome dello script da interpretare come primo argomento.
È il kernel che deve interpretare la prima riga di uno script, per mettere in esecuzione l'interprete indicato.
Quando il file da interpretare non è così semplice come uno script, per esempio perché non si tratta di un file di testo, si pone il problema di stabilire un metodo per il suo riconoscimento, altrimenti si è costretti a usare sempre un comando che richiami esplicitamente il suo interprete. L'esempio più comune di questa situazione è il programma scritto per un'altra piattaforma che si vuole utilizzare attraverso un interprete (o un emulatore) adatto. Generalmente, questi programmi estranei sono riconoscibili in base a una stringa binaria tipica che si può trovare all'inizio del file che li contiene; in pratica, in base al magic number del file. In altre situazioni, si può essere costretti a definire un'estensione particolare per i nomi di questi file, come avviene nel Dos.
A partire dall'introduzione dell'interprete Java anche per GNU/Linux, si è sentito maggiormente il problema di organizzare in modo coerente la gestione dei programmi che per un motivo o per l'altro devono essere interpretati attraverso un programma esterno al kernel stesso. Il meccanismo attuale permette una configurazione molto semplice del sistema, attraverso la quale si può automatizzare l'interpretazione di ciò che si vuole (sezione 8.3.1).
Per verificare che il kernel sia in grado di gestire questa funzione, basta controllare che all'interno della directory /proc/sys/fs/binfmt_misc/
appaiano i file register
e status
; il secondo in particolare, dovrebbe contenere la parola enabled. Se non ci sono, ma il kernel incorpora la gestione di binari da interpretare, è necessario innestare il file system binfmt_misc:
#
mount -t binfmt_misc none /proc/sys/fs/binfmt_misc
[Invio]
Una volta che sono disponibili i file virtuali register
e status
, per attivare la funzionalità occorre intervenire con il comando seguente:
#
echo 1 > /proc/sys/fs/binfmt_misc/status
[Invio]
Per disattivarla, basta utilizzare il valore zero.
#
echo 0 > /proc/sys/fs/binfmt_misc/status
[Invio]
Quando la gestione è disattivata, la lettura del file /proc/sys/fs/binfmt_misc/status
restituisce la stringa disabled.
Trattandosi di un'attività che riguarda il kernel, non c'è un file di configurazione vero e proprio. Per informare il kernel della presenza di programmi da interpretare attraverso eseguibili esterni, occorre sovrascrivere un file virtuale del file system /proc/
. In generale, questo si ottiene utilizzando un comando echo, il cui standard output viene ridiretto nel file /proc/sys/fs/binfmt_misc/register
. Per definire il supporto a un tipo di programma interpretato, si utilizza una riga secondo la sintassi seguente:
:nome:tipo:[scostamento]:riconoscimento:[maschera]\ |
Allo stato attuale, dal momento che i due punti verticali separano i vari campi di questo record, tale simbolo non può apparire all'interno di questi.
nome
Il primo campo serve a dare un nome a questo tipo di programma da interpretare. Ciò si traduce nella creazione di un file virtuale con lo stesso nome, /proc/sys/fs/binfmt_misc/nome
, che poi permette di controllarne le funzionalità.
tipo
Il secondo campo definisce il tipo di riconoscimento che si vuole utilizzare. La lettera M indica l'utilizzo di un magic number, ovvero una stringa nella parte iniziale del file, oppure la lettera E specifica che viene presa in considerazione l'estensione nel nome. Ciò serve a definire in che modo interpretare il quarto campo di questo record.
scostamento
Nel caso in cui si utilizzi un riconoscimento basato su una stringa iniziale, questa deve essere contenuta nei primi 128 byte, anche se non è detto che inizi dal primo. L'inizio della stringa di riconoscimento può essere indicato espressamente con un numero intero posto all'interno di questo campo: zero rappresenta il primo byte.
riconoscimento
Il quarto campo consente di inserire la stringa di riconoscimento o l'estensione del file. La stringa, ovvero il magic number, può essere specificata utilizzando delle sequenze di escape che consentono l'indicazione di valori esadecimali. Per questo si usa il prefisso \x, seguito da due cifre esadecimali che rappresentano un byte alla volta. A questo proposito, è bene ricordare che se il record viene definito in una riga di comando di una shell, è molto probabile che la barra obliqua inversa debba essere raddoppiata.
La stringa di riconoscimento può essere applicata a ciò che resta dopo il filtro con la maschera indicata nel campo successivo.
Nel caso si specifichi l'uso dell'estensione per riconoscere il tipo di file, questa non deve contenere il punto iniziale, che così è sottinteso.
maschera
Il quinto campo serve a indicare una maschera da utilizzare per filtrare i bit che compongono la parte di file che deve essere utilizzata per il riconoscimento attraverso il magic number. In pratica, di solito non si utilizza e si ottiene l'applicazione della maschera predefinita: \ff. La maschera viene applicata attraverso un AND con i byte corrispondenti del file; quello che ne deriva viene usato per il paragone con il modello specificato nel quarto campo.
La maschera predefinita, evidentemente, non provoca alcuna modifica.
programma_interprete
L'ultimo campo serve a indicare il percorso assoluto dell'interprete da utilizzare per mettere in esecuzione il programma identificato attraverso questo record. Evidentemente, si presume che questo programma possa essere avviato indicando il file da interpretare come primo argomento. Se necessario, l'interprete può essere uno script predisposto opportunamente per avviare il vero interprete nel modo richiesto.
Attualmente, si pongono delle limitazioni a cui è già stato accennato in parte:
il record che definisce un tipo di eseguibile da interpretare non può superare i 255 caratteri;
la stringa binaria di riconoscimento, ovvero il magic number, deve trovarsi all'intero dei primi 128 byte del file, ovvero dal byte zero al byte 127, e lo scostamento non può modificare questo limite;
il contenuto dell'ultimo campo, quello del percorso di avvio dell'interprete, non può superare i 127 caratteri.
Segue la descrizione di alcuni esempi.
#
echo ':Java:M::\xca\xfe\xba\xbe::/usr/bin/java:'
\
\ > /proc/sys/fs/binfmt_misc/register
[Invio]
Definisce il binario Java, riconoscibile dalla sequenza esadecimale CAFEBABE16, a partire dall'inizio del file. Per la sua interpretazione viene specificato il programma /usr/bin/java
, il quale potrebbe essere uno script che si occupa di avviare correttamente l'interprete giusto.
#
echo ':Java:E::class::/usr/bin/java:'
\
\ > /proc/sys/fs/binfmt_misc/register
[Invio]
Come nell'esempio precedente, con la differenza che l'eseguibile Java viene identificato solo per la presenza dell'estensione .class
.
#
echo ':acqua:M::#acqua::/bin/acqua:'
\
\ > /proc/sys/fs/binfmt_misc/register
[Invio]
Definisce un eseguibile di tipo «acqua», riconoscibile dalla stringa iniziale #acqua, a partire dall'inizio del file. Per la sua interpretazione viene specificato il programma /bin/acqua
.
#
echo ':acqua:E::acqua::/bin/acqua:'
\
\ > /proc/sys/fs/binfmt_misc/register
[Invio]
Definisce un eseguibile di tipo «acqua», riconoscibile dall'estensione .acqua
. Per la sua interpretazione viene specificato il programma /bin/acqua
.
Si osservi che i file eseguibili, anche se di fatto devono essere soltanto interpretati (quindi richiedono solo la lettura), devono avere i permessi di esecuzione. |
Non si può pensare che ogni volta che si vuole utilizzare un binario estraneo da interpretare, si debba dare il comando apposito, come negli esempi mostrati nella sezione precedente. Evidentemente, si tratta di inserire queste dichiarazioni in uno script della procedura di inizializzazione del sistema (in mancanza d'altro si potrebbe usare il solito rc.local
, se predisposto, contenuto nella directory /etc/rc.d/
o /etc/init.d/
, oppure in altra simile).
Una volta definito un tipo di eseguibile da interpretare, nella directory /proc/sys/fs/binfmt_misc/
viene creato un file virtuale con il nome corrispondente a quanto indicato nel primo campo del record di definizione. Se questo file viene sovrascritto con il valore -1, si ottiene l'eliminazione del tipo corrispondente. Se si fa la stessa cosa con il file status
, si elimina la gestione di tutti i binari specificati precedentemente. Seguono due esempi.
#
echo -1 > /proc/sys/fs/binfmt_misc/Java
[Invio]
Elimina la gestione del tipo di binario Java.
#
echo -1 > /proc/sys/fs/binfmt_misc/status
[Invio]
Elimina la gestione di tutti i tipi di binari da interpretare.
Capita spesso la necessità di scandire un elenco di parole allo scopo di eseguire uno o più comandi nell'ambito di tale scansione, utilizzando le parole stesse come argomenti dei comandi. Per fare un esempio di cosa si intende con questo, si pensi a un elenco di nomi di file, a partire dal quale si vuole ottenere l'esecuzione di un comando, una volta per ognuno di questi nomi. Questo tipo di scansione si esegue facilmente realizzando dei piccoli programmi, in forma di script, oppure si utilizza il programma xargs quando ciò che si intende fare non è troppo complesso.
Il comando for di una shell standard scandisce degli elementi, in corrispondenza dei quali esegue una lista di comandi.
for variabile in valore... do lista_di_comandi done |
L'elenco di parole che segue in viene espanso, generando una lista di elementi. La variabile indicata dopo for viene posta ogni volta al valore di ciascun elemento di questa lista e la lista di comandi che segue do viene eseguita ogni volta di conseguenza.
Per esempio, il comando seguente aggiunge a ogni file l'estensione .txt
, senza nemmeno bisogno di realizzare uno script:
$
for a in * ; do mv $a $a.txt ; done
[Invio]
Volendo vedere meglio questo esempio in uno script, basta trasformarlo nel modo seguente:
|
L'elenco di parole che segue in, può anche essere contenuto in una variabile di ambiente. Per esempio, si osservi lo script seguente, che in pratica svolge la stessa operazione già descritta, ma raccogliendo prima l'elenco dei nomi attraverso il comando ls:
|
L'esempio seguente è più complesso, perché consente di cambiare l'estensione dei file, non solo di aggiungerne una. In particolare, si vuole sostituire l'estensione .testo
con .txt
:
|
In pratica, sed riceve dallo standard input il nome del file, nel quale trova la stringa finale .testo e la sostituisce con .txt (SED è descritto nella sezione 23.5).
L'utilità del programma di servizio xargs(1) non si comprende immediatamente. Volendo sintetizzare, xargs estrae dallo standard input delle «parole», intese come sequenze di caratteri e simboli separati da spazi o da interruzioni di riga, per utilizzarle come argomenti di un comando che viene avviato più volte, in base alla quantità di queste parole disponibili.
La situazione più banale è quella rappresentata dal comando seguente:
$
xargs echo
[Invio]
Volendo ottenere lo stesso risultato con uno script di shell, si potrebbe tradurre nel codice seguente:
|
In pratica, viene letto tutto lo standard input (salvo limiti di memoria); quanto inserito viene scandito e si aggiunge ogni elemento a una lista separata solo da uno spazio singolo; alla fine viene emessa questa lista attraverso lo standard output.
Si osservi che utilizzando xargs senza alcun argomento, si ottiene comunque questo risultato in modo predefinito. |
$
xargs echo
[Invio]
uno due tre quattro
[Invio]
cinque sei
[Invio]
sette
[Invio]
[Ctrl d]
uno due tre quattro cinque sei sette |
Quello che si vede sopra è un esempio di come può comportarsi xargs usato in questo modo. Benché sia questo il comportamento predefinito, di solito occorre specificare in che modo xargs debba leggere lo standard input:
xargs [opzioni] [comando [argomenti_del_comando]]
|
L'opzione che definisce in che modo debba comportarsi xargs nei confronti dello standard input va scelta tra quelle descritte nell'elenco che segue. È importante comprendere che non serve a nulla indicare più di una di queste opzioni, dal momento che solo l'ultima viene presa in considerazione.
-I stringa_da_rimpiazzare
|
In questo modo, si stabilisce che deve essere presa in considerazione una riga alla volta di ciò che proviene dallo standard input; tuttavia, questa riga non viene fornita automaticamente come argomento finale del comando, al contrario va utilizzato un simbolo per collocare tali informazioni. Si osservi l'esempio seguente:
$
xargs -I ciao echo ciao ciao
[Invio]
uno due tre quattro
[Invio]
uno due tre quattro uno due tre quattro |
cinque sei
[Invio]
cinque sei cinque sei |
sette
[Invio]
sette sette |
[Ctrl d]
Come si vede, viene stabilito che la stringa ciao serve a indicare in che punto deve essere collocato ciò che si estrae dallo standard input.
-L n_righe
|
Si stabilisce di utilizzare la quantità di righe non vuote indicata come argomento per ogni avvio del comando. Tuttavia, se una riga termina con uno spazio orizzontale (uno spazio vero e proprio o un carattere di tabulazione), si intende che questa non sia terminata e continui nella riga successiva. Si osservi l'esempio seguente che mostra proprio questo particolare:
$
xargs -L 2 echo
[Invio]
uno due
[Invio]
tre quattro cinque
[Invio]
uno due tre quattro cinque |
sei sette
[Invio]
otto
[Invio]
nove dieci
[Invio]
sei sette otto nove dieci |
undici
[Invio]
[Ctrl d]
undici |
Si può osservare che dopo la parola «sette» c'è uno spazio che crea una continuazione con la riga successiva.
--max-args=n_parole | -n n_parole
|
Si stabilisce di utilizzare la quantità di parole indicata come argomento per ogni avvio del comando. Si osservi l'esempio seguente:
$
xargs -n2 echo
[Invio]
uno due tre
[Invio]
uno due |
quattro cinque
[Invio]
tre quattro |
[Ctrl d]
cinque |
Quelle che qui sono state chiamate «parole», ovvero le sequenze di caratteri che vengono prese in considerazione come elementi singoli, sono distinte in base all'uso di spazi, caratteri di tabulazione e interruzioni di riga. Queste parole possono anche essere delimitate tra apici singoli o doppi (' e "), per esempio per includere degli spazi di qualche tipo; inoltre è possibile utilizzare il carattere \ per confermare il valore letterale del carattere successivo.
Oltre all'opzione che specifica la modalità di lettura e scansione dello standard input se ne possono usare altre, tra le quali quelle descritte nel seguito.
-E stringa_di_fine_file
|
In condizioni normali, la fine del file viene riconosciuta al termine dello stesso. Negli esempi mostrati in precedenza, veniva terminato l'inserimento dei dati attraverso la combinazione [Ctrl d], proprio in questo senso. Tuttavia, attraverso questa opzione, xargs riconosce una stringa particolare, individuata come parola isolata, con lo scopo di indicare la fine dei dati da prendere in considerazione. In mancanza dell'uso di questa opzione, potrebbe trattarsi in modo predefinito della stringa _, ma ciò non è assicurato dallo standard. Pertanto, se si deve usare la stringa _ senza il significato di terminazione che potrebbe avere, diventa necessario l'uso dell'opzione -E con qualcosa di diverso, eventualmente anche solo la stringa nulla: -E "".
--interactive | -p
|
Con questa opzione, si fa in modo che xargs chieda conferma prima di eseguire qualsiasi comando (prompt). La conferma avviene inserendo la lettera y e premendo successivamente [Invio]. Qualunque altra cosa annulla il comando proposto.
--verbose | -t
|
Mostra i comandi prima di eseguirli.
--max-chars=n_max_caratteri | -s n_max_caratteri
|
Permette di stabilire un limite massimo per la lunghezza dei comandi generati da xargs (si include tutto il comando generato, non solo gli argomenti che gli vengono forniti).
--exit | -x
|
In presenza di un comando che eccede la quantità di caratteri massima predefinita, o fissata con l'opzione -s, termina il funzionamento di xargs.
Se si realizza uno script che deve essere richiamato fornendogli degli argomenti (dei parametri) sotto forma di opzioni, come si è abituati con i programmi di servizio comuni, può essere conveniente l'utilizzo di programmi o comandi appositi. Se si dispone di una shell POSIX, si può utilizzare il comando getopts, come descritto nella sezione 17.3.4.1. Esiste anche un programma di servizio denominato getopt (senza la «s» finale), in grado di svolgere un compito equivalente (benché in modo differente), quando non ci si può avvalere del comando di shell. Va però osservato che il programma di servizio getopt non è previsto dallo standard POSIX. Quando nella documentazione di getopt si fa riferimento allo standard POSIX, ciò che può essere standard è il comportamento della funzione getopt(), usata eventualmente da getopt stesso.
Il programma di servizio getopt tradizionale ha la sintassi seguente:
getopt stringa_di_opzioni parametro... |
La stringa di opzioni è un elenco di lettere che rappresentano le opzioni ammissibili; se ci sono opzioni che richiedono un argomento, le lettere corrispondenti di questa stringa devono essere seguite dal simbolo due punti (:). Gli argomenti successivi sono i valori dei parametri da analizzare. Lo scopo del programma è solo quello di controllare che tutto sia in ordine e di mettere a posto ciò che è possibile sistemare, emettendo l'elenco delle opzioni, nel modo «corretto». Per esempio:
$
getopt ab:c -a uno -b due -c tre quattro
[Invio]
Potrebbe restituire il testo seguente:
-a -b due -c -- uno tre quattro |
Infatti, avendo utilizzato la definizione ab:c, è stato stabilito che solo l'opzione -b ha un argomento, per cui, l'argomento uno è stato spostato alla fine delle opzioni, dopo il trattino doppio (--).
Se il programma getopt di cui si dispone è aderente strettamente alle specifiche POSIX (in quanto si utilizza una funzione getopt() limitata alle specifiche dello standard POSIX), il risultato che si ottiene è diverso, dal momento che la scansione termina nel momento in cui si trova il primo argomento che non riguarda le opzioni:
-a -- uno -b due -c tre quattro |
L'esempio seguente dovrebbe chiarire in che modo si può utilizzare getopt per scandire gli argomenti della riga di comando:
|
In pratica, si comprende che lo scopo di getopt è solo quello di fare un po' di ordine tra le opzioni e di distinguere le opzioni dal resto. Supponendo che il nome dello script sia scansione_1.sh, se si utilizza come nell'esempio già visto, si dovrebbe ottenere il risultato seguente:
$
./scansione_1.sh -a uno -b due -c tre quattro
[Invio]
Opzione a Opzione b, argomento «due» Opzione c Argomenti rimanenti: uno tre quattro |
Se invece getopt si deve adeguare alla funzione getopt() dello standard POSIX, il risultato cambia come segue:
Opzione a Argomenti rimanenti: uno -b due -c tre quattro |
I programmi di servizio Linux si compongono anche di una versione di getopt(2) un po' più evoluta dello standard, ma ugualmente compatibile con le versioni «normali». È ammissibile l'uso della stessa sintassi vista nella sezione precedente e in particolare si può anche forzare l'aderenza alle specifiche POSIX definendo la variabile di ambiente POSIXLY_CORRECT. Questa edizione di getopt è in grado di identificare anche le opzioni «lunghe». Oltre allo schema sintattico già visto, si può utilizzare in particolare quello seguente:
getopt [opzioni_di_getopt] -o|--options stringa_di_opzioni_corte \ |
In pratica, questa versione di getopt può avere delle opzioni proprie che ne regolano il funzionamento, tra le quali -o è obbligatoria, dal momento che il suo argomento è proprio la stringa che definisce quali opzioni possono essere presenti nei parametri. Eventualmente, per indicare opzioni lunghe, si utilizza l'opzione -l.
La stringa che definisce le opzioni corte, si comporta fondamentalmente come già spiegato nella sezione precedente. In particolare, se si usano due volte i due punti (::), si specifica che l'opzione ha un argomento facoltativo e non obbligatorio. La stringa che definisce le opzioni lunghe è simile a quella delle opzioni corte, con la differenza che, dovendo indicare dei nomi e non solo delle lettere singole, questi sono separati attraverso una virgola; per quanto riguarda l'uso dei due punti, la modalità è la stessa.
Questa versione di getopt ha anche la particolarità di essere in grado di proteggere gli argomenti che ne hanno bisogno, ma per arrivare a questo deve sapere con quale shell si sta operando. Infatti, dal momento che getopt restituisce una stringa che poi deve essere scandita nuovamente, se un argomento contiene caratteri particolari che richiedono una qualche forma di protezione (come gli spazi), è necessario che venga fatta una trasformazione opportuna, la quale non può essere unica per tutte le situazioni. In condizioni normali, il risultato che si ottiene è adatto per Bash, altrimenti occorre utilizzare l'opzione -s.
|
Come esempio viene mostrata una variante dello script proposto nella sezione precedente, dove si scandiscono anche le opzioni lunghe e l'ultima ha un argomento facoltativo.
|
Supponendo che il nome dello script sia scansione_2.sh, se si utilizza come nell'esempio seguente,
$
./scansione_2.sh -auno -bdue -ctre quattro
[Invio]
oppure
$
./scansione_2.sh --a-lunga=uno --b-lunga=due
\
\ --c-lunga=tre quattro
[Invio]
si dovrebbe ottenere il risultato seguente:
Opzione a Opzione b, argomento «due» Opzione c, argomento «tre» Argomenti rimanenti: uno quattro |
Tuttavia, se utilizzando le opzioni corte, gli argomenti di queste non vengono attaccati alle lettere rispettive, come nell'esempio seguente,
$
./scansione_2.sh -a uno -b due -c tre quattro
[Invio]
gli argomenti facoltativi non vengono presi in considerazione:
Opzione a Opzione b, argomento «due» Opzione c, senza argomenti Argomenti rimanenti: uno tre quattro |
Quando si realizzano degli script, si ha spesso la necessità di realizzare dei file temporanei, magari solo per accumulare il risultato di un'elaborazione senza tentare di fare altri tipi di acrobazie. Il programma di servizio che si usa per queste cose è tempfile:(3)
tempfile [opzioni] |
Nella maggior parte dei casi, tempfile viene usato senza argomenti, ottenendo la creazione di un file vuoto nella directory temporanea (/tmp/
), con permessi normali (lettura e scrittura per tutti, meno quanto filtrato dalla maschera dei permessi), ottenendo il percorso assoluto di questo file dallo standard output.
|
Segue la descrizione di alcuni esempi.
$
tempfile
[Invio]
Crea un file temporaneo e ne restituisce il nome attraverso lo standard output.
|
Quello che si vede è l'esempio tipico di uno script, incompleto, in cui si crea un file temporaneo accumulandone il nome in una variabile di ambiente; quindi si fa qualcosa con quel file (in questo caso si inserisce il risultato del comando ls -l), infine si elimina il file, sempre utilizzando l'espansione della variabile che ne contiene il nome.
In situazioni determinate, può essere importante avviare un programma, o un altro script con un insieme di variabili di ambiente diverso da quello che si erediterebbe normalmente. Per questo si può usare il programma di servizio env:(4)
env [opzioni] [nome=valore]... [comando [argomenti_del_comando]]
|
Come si può intuire, le opzioni di env servono a eliminare o ad aggiungere delle variabili di ambiente, senza interferire con l'ambiente dello script.
|
A titolo di esempio, si supponga di avere due script: nel primo viene dichiarata la variabile di ambiente CIAO e viene chiamato il secondo eliminando questa variabile dall'ambiente; il secondo script si limita a mostrare il contenuto di questa variabile, se è disponibile.
|
|
Il risultato è che funziona solo la visualizzazione della variabile che avviene con il comando echo del primo script, perché nel secondo non è disponibile. Sarebbe stato diverso se il primo e unico script fosse stato quello seguente:
|
In questo caso, anche se il comando echo viene avviato senza la disponibilità della variabile CIAO, si otterrebbe ugualmente la sua visualizzazione, dal momento che l'espansione della stessa avviene prima della chiamata del programma env.
Spesso, la realizzazione di uno script di shell interattivo, è molto difficile; o meglio, è difficile realizzare qualcosa di pratico da usare. La shell offre il comando interno read, per leggere ciò che viene inserito attraverso la tastiera, ma questo permette di ottenere un'interazione molto banale, a livello di riga di comando. In alternativa si possono usare dei programmi realizzati appositamente per abbellire gli script, come nel caso di dialog.
Il programma di servizio echo(5) emette le stringhe indicate come argomento, separate da uno spazio e con l'aggiunta di un codice di interruzione di riga finale. Per usare echo nel modo più compatibile possibile, occorre limitarsi allo schema sintattico seguente, conforme allo standard, dove non è ammesso l'uso di alcuna opzione:
echo [stringa...]
|
Il programma potrebbe riconoscere alcune sequenze di escape, utili per comporre il testo da visualizzare. Tuttavia, la versione GNU del programma e il comando omonimo della shell Bash richiedono un'opzione per conformarsi a tali sequenze speciali. Pertanto, per poter usare il comando o il programma echo in modo uniforme tra i vari sistemi Unix, è necessario evitare le sequenze di escape e le opzioni.
|
Il testo visualizzato dal comando o dal programma echo è concluso normalmente da un codice di interruzione di riga. Un'estensione diffusa del programma echo consiste nella disponibilità dell'opzione -n (non standard), con cui si sopprime l'aggiunta di tale codice finale, consentendo di mantenere il cursore alla fine del testo visualizzato. Per esempio così:
$
echo -n "ciao " ; echo "a tutti"
[Invio]
ciao a tutti |
Per ovviare alle carenze di echo, occorre scegliere piuttosto il programma printf,(6) il quale emette attraverso lo standard output la stringa di composizione fornita, utilizzando gli argomenti, con regole analoghe a quelle della funzione printf() del linguaggio C:
printf composizione [argomento...]
|
Nella stringa di composizione, vanno usate opportunamente le sequenze di escape della tabella 18.31. Per esempio, per mandare a capo il testo dopo la visualizzazione della stringa, occorre concludere con la sequenza \n (cosa che per echo è invece implicita. Inoltre, se dopo la stringa di composizione ci sono degli argomenti, si possono inserire degli specificatori di conversione, caratterizzati dal fatto che iniziano con il simbolo di percentuale (%), dove ogni specificatore serve a rappresentare un argomento. Il modo in cui si esprime uno specificatore di conversione può essere complesso, pertanto viene mostrato un modello sintattico che descrive la sua struttura, limitatamente alle necessità del programma printf:
%[simbolo][n_ampiezza][.n_precisione]tipo |
La prima cosa da individuare in uno specificatore di conversione è il tipo di argomento che viene interpretato e, di conseguenza, il genere di rappresentazione che se ne vuole produrre. Il tipo viene espresso da una lettera alfabetica, alla fine dello specificatore di conversione. La tabella successiva riepiloga i tipi principali che dovrebbero essere accettabili in ogni realizzazione di printf.
|
Nel modello sintattico che descrive lo specificatore di conversione, si vede che subito dopo il segno di percentuale può apparire un simbolo (flag). I simboli principali che possono essere utilizzati sono descritti nella tabella successiva.
|
Tra il simbolo (flag) e il tipo può apparire un numero che rappresenta l'ampiezza da usare nella trasformazione ed eventualmente la precisione: ampiezza[.precisione]. Il concetto parte dalla rappresentazione dei valori in virgola mobile, dove l'ampiezza indica la quantità complessiva di caratteri da usare e la precisione indica quanti di quei caratteri usare per il punto decimale e le cifre successive, ma si applica anche alle stringhe.
In generale, per quanto riguarda la rappresentazione di valori numerici, la parte intera viene sempre espressa in modo completo, anche se l'ampiezza indicata è inferiore; ai numeri interi la precisione non si applica; per i numeri in virgola mobile con rappresentazione esponenziale, la precisione riguarda le cifre decimali che precedono l'esponente; per le stringhe la precisione specifica la quantità di caratteri da considerare, troncando il resto.
Segue la descrizione di alcuni esempi.
$
printf "ciao " ; printf "a tutti\n"
[Invio]
Questo esempio serve a dimostrare che printf non manda a capo il cursore, alla fine della visualizzazione, se non richiesto espressamente con la sequenza \n.
ciao a tutti |
$
printf "%02x %02x %02x %02x\n" 192 168 1 71
[Invio]
Converte in esadecimale, i valori forniti come argomento, facendo in modo che ogni numero occupi esattamente due cifre.
c0 a8 01 fe |
$
printf "%03o %03o %03o %03o\n" 192 168 1 71
[Invio]
Converte in ottale, i valori forniti come argomento, facendo in modo che ogni numero ottale occupi esattamente tre cifre.
300 250 001 376 |
|
Di norma, read è un comando interno delle shell POSIX, anche se potrebbe essere disponibile un programma di servizio equivalente, da utilizzare con una shell differente. Il modello sintattico seguente rappresenta una semplificazione che dovrebbe essere compatibile in generale con le shell POSIX:
read [-p invito] [variabile...] |
Il comando read potrebbe essere utilizzato da solo, senza argomenti; in questo caso servirebbe soltanto per attendere la pressione del tasto [Invio], permettendo all'utente di leggere un'informazione che appare sullo schermo, prima di proseguire con altre operazioni.
L'opzione -p dovrebbe essere abbastanza chiara: permette di definire una stringa di invito all'inserimento di qualcosa. Infine, i nomi che vengono collocati in coda alla riga di comando, rappresentano altrettante variabili di ambiente che vengono create appositamente, assegnando loro le parole inserite attraverso read; in particolare, l'ultima variabile dell'elenco raccoglie tutte le parole rimanenti.
|
L'esempio dovrebbe permettere di capire il funzionamento di read. Si osservi in particolare il fatto che l'invito viene ottenuto attraverso il comando printf, senza indicare alla fine il codice di interruzione di riga. Supponendo che si tratti dello script read.sh:
$
./read.sh
[Invio]
Inserisci una frase:
ciao come stai? io sto bene
[Invio]
La prima parola inserita è «ciao» La seconda parola inserita è «come» Il resto della frase è «stai? io sto bene» |
La shell Korn e la shell Bash offrono una struttura di controllo particolare, utile per la selezione interattiva di un elemento da un elenco. Si tratta di select, la cui sintassi si riassume nello schema sintattico seguente:
select variabile [in valore...] do lista_di_comandi done |
L'elenco di parole che segue in viene espanso, generando una lista di elementi. L'insieme delle parole espanse viene emesso attraverso lo standard error, ognuna preceduta da un numero. Se in (e i suoi argomenti) viene omesso, vengono utilizzati i parametri posizionali. In pratica è come se venisse usato in $@.
Dopo l'emissione dell'elenco, viene mostrato l'invito contenuto nella variabile PS3 e viene letta una riga dallo standard input. Se la riga consiste del numero corrispondente a una delle parole mostrate, allora viene assegnato alla variabile indicata dopo select la parola corrispondente. Se la riga è vuota (probabilmente è stato premuto soltanto [Invio]), l'elenco e l'invito vengono emessi nuovamente. Se viene letto il codice corrispondente a EOF ([Ctrl d]), il comando termina. Qualsiasi altro valore letto fa sì che la variabile sia posta al valore della stringa nulla. La riga letta viene salvata nella variabile REPLY. La lista di comandi che segue do viene eseguita dopo ciascuna selezione fino a che non viene incontrato un comando break o return.
Il valore restituito da select è quello dell'ultimo comando eseguito all'interno della lista do, oppure zero se nessun comando è stato eseguito.
Viene mostrato nuovamente lo stesso esempio già presentato in occasione della descrizione di select fatta nell'ambito dei capitoli dedicati a Bash: fa apparire un menù composto dagli argomenti fornitigli; a ogni selezione mostra quello scelto.
|
L'esempio seguente proviene dagli script di nanoLinux II 1998 e rappresenta la selezione del nome di un'interfaccia di rete, il quale viene accumulato nella variabile di ambiente INTERFACCIA:
|
Il programma di servizio yes(7) emette ripetitivamente senza fine, attraverso lo standard output, le stringhe indicate come argomento (separate da uno spazio l'una dall'altra), seguite dal codice di interruzione di riga.
yes [stringa...] |
Se non viene indicata alcuna stringa come argomento, emette la lettera «y». Il programma continua la sua esecuzione fino a che non viene interrotto. Segue la descrizione di alcuni esempi.
$
yes
[Invio]
y y y y y ... |
Senza argomenti, yes emette una serie indefinita di lettere «y» seguite dal codice di interruzione di riga.
$
yes n
[Invio]
n n n n n ... |
Se vengono specificate delle stringhe come argomento, queste stringhe vengono emesse ripetitivamente.
$
yes | mio_prog
[Invio]
Si invia una serie di lettere «y», seguite dal codice di interruzione di riga, al programma ipotetico mio_prog che probabilmente tende a fare delle domande alle quali si vuole rispondere sempre con una lettera «y».
Dialog e altri programmi più o meno compatibili, hanno lo scopo di gestire effetti più appariscenti in uno script di shell, interagendo con l'utilizzatore attraverso schermate colorate e finestre di dialogo, le quali, a seconda dei casi sono adatte allo schermo a caratteri, oppure richiedono la grafica.(8) (9) (10) (11)
dialog [opzioni_generali] [definizione_del_tipo_di_interazione] |
whiptail [opzioni_generali] [definizione_del_tipo_di_interazione] |
Xdialog [opzioni_generali] [definizione_del_tipo_di_interazione] |
gdialog [opzioni_generali] [definizione_del_tipo_di_interazione] |
Si può intuire che il programma «standard» sia Dialog, essendo fatto per i terminali a caratteri, utilizzando la libreria Ncurses.(12) Il programma Whiptail, è una rivisitazione, fatta sempre per i terminali senza grafica, ma usa la libreria Newt.(13) Gli altri programmi si usano con la grafica.
La riga di comando distingue due tipi di opzioni: quelle che hanno valore in senso generale influenzando il comportamento del programma e quelle che definiscono un tipo di interazione con l'utilizzatore. Nella documentazione originale, queste ultime sono definite box-options, perché si riferiscono ai riquadri che vengono mostrati sullo schermo. Evidentemente, si può utilizzare al massimo una sola opzione che definisca una finestra di dialogo.
Dovendo definire delle finestre su uno schermo a caratteri, le opzioni che permettono di descriverle, fanno riferimento a delle dimensioni in caratteri. Questi valori non possono essere omessi e in caso si voglia fare riferimento alle dimensioni ottimali, in base alla disponibilità dello schermo, basta indicare il valore zero, tenendo conto però che questa possibilità non funziona sempre.
La documentazione di Dialog in particolare è accompagnata da esempi di script più completi di quelli che si vedono qui. Vale la pena di studiarli per apprendere bene il funzionamento di questi programmi. In generale, dovrebbero trovarsi a partire dalla directory |
|
|
Gli esempi seguenti mostrano diverse situazioni, dove si mette anche a confronto il risultato che si ottiene tra i vari programmi.
|
In questo script, viene mostrata la finestra di dialogo che si vede nella figura 18.48; in base alla scelta affermativa o negativa, si ottiene la visualizzazione di un messaggio differente.
.------- Domanda -------. | Ti piace Dialog? | |-----------------------| | < Yes > < No > | `-----------------------' |
Lo script successivo mostrata la finestra di dialogo che si vede nella figura 18.51 e, in base alla scelta del colore, si ottiene il numero corrispondente.
|
Si osservi che è stato evitato l'uso di una lettera accentata, perché alcune delle varie interpretazioni del programma, non sono in grado di gestire altro che la codifica ASCII pura e semplice.
.------------- Menu' -------------. | Scegli il colore che preferisci | | .-----------------------------. | | | 0 nero | | | | 1 marrone | | | | 2 rosso | | | | 3 arancio | | | | 4 giallo | | | | 5 verde | | | | 6 blu | | | | 7 viola | | | | 8 grigio | | | | 9 bianco | | | `-----------------------------' | |---------------------------------| | < OK > <Cancel> | `---------------------------------' |
Lo script successivo è una variante di quello precedente in cui si possono selezionare più colori assieme. Nella figura 18.54 si vede la finestra di dialogo che si ottiene.
|
.------------- Menù -------------. | Scegli i colori che preferisci | | .----------------------------. | | | [X] 0 nero | | | | [ ] 1 marrone | | | | [ ] 2 rosso | | | | [ ] 3 arancio | | | | [ ] 4 giallo | | | | [ ] 5 verde | | | | [ ] 6 blu | | | | [ ] 7 viola | | | | [ ] 8 grigio | | | | [X] 9 bianco | | | `----------------------------' | |--------------------------------| | < OK > <Cancel> | `--------------------------------' |
|
Alcuni programmi sono particolarmente indicati per la costruzione di espressioni e, per questo motivo, il risultato della loro elaborazione si traduce essenzialmente nella restituzione di un valore (exit status).
Il programma di servizio false(15) si limita a restituire il valore uno, corrispondente in pratica a Falso nell'ambito dei comandi di shell:
false |
Il programma di servizio true(16) si limita a restituire il valore zero, corrispondente in pratica a Vero nell'ambito dei comandi di shell:
true |
Il programma di servizio test,(17) ovvero il comando interno di una shell standard, avente lo stesso nome, risolve (valuta) l'espressione indicata. Il valore restituito può essere Vero (corrispondente a zero) o Falso (corrispondente a uno) ed è pari al risultato della valutazione dell'espressione.
test espressione_condizionale
|
[ espressione_condizionale ]
|
Come si può osservare dai modelli mostrati, si può usare questo programma anche con il nome [, ovvero una parentesi quadra aperta, ma in tal caso, alla fine dell'espressione deve apparire un'altra parentesi quadra chiusa. Questo strattagemma consente di scrivere delle espressioni con una notazione simile a quella di un linguaggio di programmazione comune.
Bisogna ricordare che, sia test, sia [, rappresentano il nome di un programma, o al limite il nome di un comando interno di alcune shell, pertanto le parentesi quadre non possono essere attaccate all'espressione da valutare, così come il nome test non potrebbe esserlo. |
Le espressioni possono essere unarie o binarie. Le espressioni unarie sono usate spesso per esaminare lo stato di un file. Vi sono operatori su stringa e anche operatori di comparazione numerica. Ogni operatore e operando deve essere un argomento separato.
Per fare riferimento a un descrittore di I/O (per esempio uno dei flussi di dati standard), si può indicare un file nella forma /dev/fd/n
, dove il numero finale rappresenta l'n-esimo descrittore. In alternativa, si può fare riferimento direttamente ai file /proc/self/fd/n
, secondo lo standard del kernel Linux.
Nella tabella 18.57 e in quelle successive, vengono elencate le espressioni elementari che possono essere utilizzate in questo modo.
|
|
|
|
|
|
Seguono due esempi senza descrizione, mostrando il risultato ottenuto attraverso lo standard output:
$
test 1 -lt 2 && echo "ok"
[Invio]
ok |
$
[ "prova" = "pro" ]
\
\|| echo "le stringhe non combaciano"
[Invio]
le stringhe non combaciano |
$
test -d /bin && echo "/bin è una directory"
[Invio]
/bin è una directory |
$
[ -e /bin/sh && ] echo "/bin/sh è un file eseguibile"
[Invio]
/bin/sh è un file eseguibile |
Il programma di servizio expr(18) valuta un'espressione e ne emette il risultato attraverso lo standard output. Ogni elemento dell'espressione deve essere un argomento separato.
expr espressione
|
Gli operandi possono essere numeri o stringhe a seconda del tipo di operazione che si intende applicare. Se vengono usate le parentesi, è molto probabile che la shell utilizzata costringa a proteggerle attraverso le tecniche che la stessa mette a disposizione. Il valore restituito da expr dipende essenzialmente dal risultato dell'espressione nel modo seguente:
|
Non si deve confondere il valore restituito dal programma con il risultato delle espressioni: expr valuta le espressioni come farebbe un linguaggio di programmazione comune, attribuendo al valore uno il significato di Vero e a zero il valore Falso. |
Le espressioni possono essere concatenate attraverso degli operatori logici, come descritto nella tabella successiva.
|
Gli operatori di comparazione sono i soliti che si usano in matematica, come descritto nella tabella successiva.
|
Se la comparazione è corretta (Vero), genera il valore uno, altrimenti si ottiene zero.
expr tenta inizialmente di considerare gli operatori da confrontare come numerici; se in questo modo fallisce l'operazione, tenta quindi di eseguire una comparazione lessicografica.
Gli operatori numerici sono i soliti che si usano in matematica, come descritto nella tabella successiva.
|
|
Segue la descrizione di alcuni esempi.
$
export miavar=2
[Invio]
$
expr $miavar + 1
[Invio]
3 |
Viene creata la variabile miavar assegnandole il valore 2, quindi calcola la somma tra il suo contenuto e 1.
$
expr abc : 'a\(.\)c'
[Invio]
b |
Estrae dalla stringa la lettera centrale attraverso l'espressione regolare.
$
expr index ambaraba br
[Invio]
3 |
Cerca la prima posizione all'interno della stringa ambaraba che corrisponda alla lettera b, oppure alla lettera r.
La ridirezione dei flussi di input e di output dei programmi viene svolta dalle shell. Il programma tee è molto importante in queste situazioni perché permette di copiare in un file il flusso di dati che lo attraversa.
Il programma di servizio tee(19) emette attraverso lo standard output quanto ricevuto dallo standard input, facendone una copia anche nei file indicati come argomento. Si tratta quindi di un filtro che permette di copiare i dati in transito in un file.
tee [opzioni] [file...]
|
|
Nella scrittura di script ci sono situazioni in cui è necessario fare delle pause, per permettere il completamento di qualcosa che non può essere controllato in modo sequenziale.
Il programma di servizio sleep(20) attende per il tempo indicato come argomento, quindi termina la sua esecuzione. La durata si esprime attraverso un numero intero che rappresenta una quantità di secondi.
sleep durata
|
A seconda della realizzazione del programma, potrebbe essere consentito l'uso di numeri non interi, oppure l'aggiunta di un moltiplicatore, ma in generale, per ottenere la compatibilità massima, è meglio limitarsi all'uso di numeri interi che rappresentano secondi.
L'esempio seguente richiede una pausa di 10 s e quindi termina la sua esecuzione:
$
sleep 10
[Invio]
In questa sezione viene proposta una raccolta di funzioni per una shell POSIX comune, allo scopo di facilitare la gestione di un sistema GNU/Linux. Queste funzioni derivano dall'esperienza di NLNX (una distribuzione GNU/Linux per architettura x86, derivata da Debian.
Queste funzioni si avvalgono evidentemente di programmi di servizio comuni nei sistemi Unix; in particolare SED (sezione 23.5). Di conseguenza, la comprensione del funzionamento di queste funzioni richiede una buona conoscenza nell'uso di tali programmi.
password_records_by_uid_range uid_min uid_max \ |
|
L'esempio seguente utilizza la funzione per estrapolare le righe di /etc/passwd
associate a numeri UID tra 1 000 e 29 999. Il risultato viene emesso semplicemente attraverso lo standard output.
|
password_records_by_gid_range gid_min gid_max \ |
|
L'esempio seguente utilizza la funzione per estrapolare le righe di /etc/passwd
associate al numero GID zero.
|
group_records_by_gid_range gid_min gid_max \ |
|
L'esempio seguente utilizza la funzione per estrapolare le righe di /etc/group
associate ai numeri GID da 1 000 a 29 999.
|
Per selezionare interattivamente l'utente si usa Dialog e si preferisce depositare il nome scelto in un file:
select_user uid_min uid_max output_file < /etc/passwd |
|
|
L'esempio seguente utilizza la funzione per estrapolare un utente dal file /etc/passwd
, tra i numeri UID 1 000 e 29 999. Il risultato viene visualizzato attraverso lo standard output.
|
table_get_column_field file indice n_colonna |
|
L'esempio seguente utilizza la funzione per estrapolare la parola d'ordine cifrata dell'utente «tizio» dal file /etc/shadow
, visualizzando il risultato attraverso lo standard output.
|
user_add_unix_samba user passwd home full_name room work_ph \ |
|
user_passwd_unix_samba user passwd |
|
user_del_unix_samba user |
|
La funzione che viene proposta richiede due elenchi, contenuti in altrettanti file di testo, contenenti rispettivamente un insieme di voci e un sottoinsieme di voci da selezionare. Lo scopo è quello di cambiare il sottoinsieme selezionato e di aggiornare il contenuto del secondo file.
select_subset_save file_list_full file_list_selected\ |
La funzione si avvale di un'altra che deve produrre l'opposto del sottoinsieme selezionato:
|
|
Si osservi l'esempio seguente:
|
In questo c'è il file /etc/xxx/ELENCO_COMPLETO
, contenente un elenco di voci (ogni voce deve costituire una parola sola), quindi c'è il file /etc/xxx/ELENCO_SELEZIONATO
con un sottoinsieme delle voci del primo file. Attraverso la selezione che si esegue con la funzione, il file /etc/xxx/ELENCO_SELEZIONATO
viene aggiornato con il nuovo sottoinsieme, ma in ogni caso si genera il file /tmp/xxx.selezionati
con le voci selezionate e il file /tmp/xxx.non_selezionati
con le altre voci.
Si tratta di tre funzioni, le quali, rispettivamente, verificano se l'utente proprietario di un file o di una directory ha il permesso di accedervi in lettura, scrittura, esecuzione o attraversamento. Si osservi che si tratta di un controllo basato sul proprietario del file e non sui privilegi che l'interprete dello script si trova ad avere in un certo momento.
file_owner_access_r file |
file_owner_access_w file |
file_owner_access_x file |
Le tre funzioni restituiscono Vero nel caso il permesso richiesto sia disponibile; altrimenti Falso in caso contrario.
|
|
|
Si tratta di tre funzioni analoghe a quelle della sezione precedente, le quali verificano se il gruppo proprietario di un file o di una directory ha il permesso di accedervi in lettura, scrittura, esecuzione o attraversamento.
file_group_access_r file |
file_group_access_w file |
file_group_access_x file |
Le tre funzioni restituiscono Vero nel caso il permesso richiesto sia disponibile; altrimenti Falso in caso contrario.
|
|
|
Si tratta di tre funzioni analoghe a quelle della sezione precedente, le quali verificano se gli utenti diversi dal proprietario e dal gruppo del file o di una directory, hanno il permesso di accedervi in lettura, scrittura, esecuzione o attraversamento.
file_other_access_r file |
file_other_access_w file |
file_other_access_x file |
Le tre funzioni restituiscono Vero nel caso il permesso richiesto sia disponibile; altrimenti Falso in caso contrario.
|
|
|
file_owner_name file |
|
user_home_directory utente |
|
user_uid utente |
|
In questa sezione viene proposto uno script basato sull'uso di Dialog, per realizzare un piccolo programma frontale con uno scopo preciso: facilitare la scrittura di testi come lo si potrebbe fare con una macchina da scrivere. Questo tipo di lavoro, così come è impostato, è organizzato in modo da definire prima l'azione, poi gli oggetti coinvolti, come succedeva spesso nei programmi gestionali dei primi anni 1980. Lo script è disponibile qui: allegati/shell-testi.sh.
Lo script è pensato per l'uso da parte di chi non vuole sapere come si usa un sistema operativo, a costo di accontentarsi di poche cose. Per questa ragione, lo script è pensato per sostituire il file ~/.profile
, in modo che dopo l'identificazione dell'utente, ciò che appare sia un menù di funzioni.
Questo script deve organizzare la gestione di file di testo, cercando di evitare che l'utente incorra in errori gravi, dovuti all'assoluta ignoranza di questioni che generalmente sono ritenute banali. Per questo, all'avvio verifica l'esistenza di una directory che ha lo scopo di raccogliere i documenti testuali e di sottodirectory per la conservazione di diverse versioni precedenti. Se queste directory mancano, provvede a crearle.
.--------------------Menù principale-----------------------. | Funzioni disponibili: | | .------------------------------------------------------. | | | new crea un documento nuovo | | | | view visualizza un documento già esistente | | | | edit modifica un documento già esistente | | | | copy copia un documento | | | | rename cambia nome a un documento | | | | delete elimina un documento che non serve più | | | | list elenca il contenuto di un disco rimovibile | | | | view2 visualizza un documento contenuto in un dis| | | | erase cancella un disco rimovibile | | | | format inizializza un disco rimovibile | | | | export esporta dei documenti in un disco rimovibil| | | | import importa dei documenti da un disco rimovibil| | | | print stampa un documento | | | | startx avvia la grafica | | | | quit fine lavoro | | | `------------------------------------------------------' | |----------------------------------------------------------| | < OK > <Cancel> | `----------------------------------------------------------' |
Come si può vedere, le lettere accentate sono state realizzate con l'uso di un apostrofo inverso, per evitare qualunque problema di compatibilità con la configurazione della console.
Nella parte iniziale dello script vengono inseriti i comandi che dovrebbero trovarsi invece nel file ~/.profile
, quindi vengono dichiarate delle variabili di ambiente il cui valore iniziale può essere modificato:
|
La tabella successiva riepiloga il significato di queste variabili di ambiente:
|
Per la gestione dei file di testo, ci si avvale di un programma esterno, definito dalla variabile di ambiente DATA_EDITOR_COMMAND, usato solo per modificare un file, mentre il resto può essere gestito esternamente, attraverso le voci previste nel menù.
La creazione di un nuovo documento, si ottiene con la selezione della voce {new
}, a cui segue la richiesta di specificare il nome che si preferisce:
.--crea un documento nuovo---. | Inserisci il nome del | | documento da creare: | | .------------------------. | | |Nuovo-1 | | | `------------------------' | |----------------------------| | < OK > <Cancel> | `----------------------------' |
Inizialmente viene proposto un nome predefinito, in base all'indicazione della variabile di ambiente DATA_NAME_DEFAULT. Il nome può essere modificato e lo si può scrivere come si vuole: se il nome contiene simboli diversi dalle lettere alfabetiche latine, da numeri e da trattini (- o _), questo viene aggiustato in qualche modo; se il nome esiste già, viene modificato in modo da evitare il conflitto. Il file viene creato vuoto e viene avviato il programma di modifica per consentire la scrittura di ciò che si desidera.
Per visualizzare il contenuto di un documento, ovvero di un file di testo contenuto nella directory prevista, si può selezionare la voce {view
}, ottenendo così la richiesta di selezionare il nome di questo:
.-visualizza un documento gia` esistente-. | Seleziona il documento da | | visualizzare: | | .------------------------------------. | | | appunti-1 . | | | | appunti-2 . | | | | da-fare . | | | | cose-vecchie . | | | `------------------------------------' | |----------------------------------------| | < OK > <Cancel> | `----------------------------------------' |
Si osservi che dall'elenco sono esclusi volutamente i nomi che contengono una tilde (~), che normalmente rappresenta una copia di sicurezza generata dal programma usato per modificare i file.
La modifica si ottiene selezionando la voce {edit
} e il comportamento è simile a quanto già descritto a proposito della visualizzazione, con la differenza che viene avviato il programma per modificare i file di testo. Si presume che l'utente si limiti a salvare, senza modificare il nome, altrimenti questo script potrebbe essere inutile.
.-modifica un documento gia` esistente-. | Seleziona il documento da | | modificare: | | .----------------------------------. | | | appunti-1 . | | | | appunti-2 . | | | | da-fare . | | | | cose-vecchie . | | | `----------------------------------' | |--------------------------------------| | < OK > <Cancel> | `--------------------------------------' |
Selezionando dal menù la voce copy è possibile fare una copia di un file esistente, all'interno della stessa directory. In pratica, questa copia potrebbe servire per realizzare un documento a partire dai contenuti di un altro già scritto in precedenza. Viene richiesto di selezionare il file da copiare, quindi viene chiesto il nome da assegnare: se il nome nuovo va in conflitto con un altro file già esistente, la copia viene annullata.
.-------copia un documento-------. | Seleziona il documento da | | copiare: | | .----------------------------. | | | appunti-1 . | | | | appunti-2 . | | | | da-fare . | | | | cose-vecchie . | | | `----------------------------' | |--------------------------------| | < OK > <Cancel> | `--------------------------------' |
In questo esempio si suppone di avere selezionato il nome {appunti-1
}, perciò è lo stesso nome appunti-1 che viene proposto inizialmente per la copia, ma ovviamente deve essere modificato:
.-----copia un documento-----. | Inserisci il nome per la | | copia del documento: | | .------------------------. | | |appunti-1 | | | `------------------------' | |----------------------------| | < OK > <Cancel> | `----------------------------' |
Dal menù è possibile selezionare la voce {rename
} per essere guidati alla modifica del nome di uno dei file che compongono i documenti. Come già per la copia, viene richiesto di selezionare un nome esistente, quindi viene offerta una mascherina per inserire il nome nuovo. Il nome che si attribuisce non può essere uguale a uno già presente; in caso contrario, l'operazione non viene eseguita.
.---cambia nome a un documento---. | Seleziona il documento da | | rinominare: | | .----------------------------. | | | appunti-1 . | | | | appunti-2 . | | | | da-fare . | | | | cose-vecchie . | | | `----------------------------' | |--------------------------------| | < OK > <Cancel> | `--------------------------------' |
.-cambia nome a un documento-. | Inserisci il nome nuovo | | per il documento: | | .------------------------. | | |appunti-1 | | | `------------------------' | |----------------------------| | < OK > <Cancel> | `----------------------------' |
Per cancellare un file è possibile selezionare la voce {delete
}, a cui segue la richiesta di selezionare il nome da eliminare. Prima di procedere alla cancellazione, una copia del file viene conservata nelle directory usate per le versioni precedenti.
.-elimina un documento che non serve piu`-. | Seleziona il documento da cancellare | | .-------------------------------------. | | | appunti-1 . | | | | appunti-2 . | | | | da-fare . | | | | cose-vecchie . | | | `-------------------------------------' | |-----------------------------------------| | < OK > <Cancel> | `-----------------------------------------' |
.--elimina un documento che non serve piu`--. | Vuoi cancellare il documento "appunti-1"? | | | |-------------------------------------------| | < Yes > < No > | `-------------------------------------------' |
Lo script prevede che per alcune operazioni delicate venga fatta la copia dei documenti che devono essere modificati, cancellati o sostituiti. Questa copia viene eseguita all'interno di sottodirectory con il nome ~backup.n/
, dove la tilde (~) fa parte del nome e il numero finale rappresenta il livello della copia. In pratica, la sottodirectory ~backup.1/
contiene le copie più recenti.
Il numero di livelli di copia è definito dalla variable di ambiente DATA_BACKUP_LEVELS e può essere modificato liberamente: lo script, a ogni avvio, verifica la presenza di queste sottodirectory e in caso siano assenti le crea senza fare domande all'utente.
Lo script non prevede un sistema di recupero dei documenti nelle loro versioni precedenti. Eventualmente, per questo, l'utente deve chiedere aiuto a persona più preparata.
Pur trattandosi di un lavoro molto semplice, si presume che la persona che lo utilizza abbia la necessità di trasferire i propri documenti da e verso un disco rimovibile. Per poter fare questo, è necessario che sia stato previsto nel file /etc/fstab
il modo di accedere a dei dischi esterni anche a un utente comune. In base a questa configurazione è necessario modificare la variabile di ambiente IMPORT_EXPORT_MOUNT_POINTS_LIST.
La variabile di ambiente IMPORT_EXPORT_MOUNT_POINTS_LIST deve contenere un elenco di coppie di «parole», secondo la forma:
directory_innesto descrizione |
Per esempio, ammesso che la directory /mnt/fd0/
serva per innestare un dischetto e che la directory /mnt/sda1/
serva per innestare la prima partizione di un'unità USB, si potrebbe inizializzare la variabile nel modo seguente:
|
Si osservi che non si possono inserire spazi nella descrizione.
Sulla base di questo esempio, il file /etc/fstab
potrebbe contenere le righe seguenti, dove si dà per scontato che il file system sia di tipo Dos-FAT:
|
Oltre a questo è necessario che l'utente possa inizializzare i dischetti, pertanto deve avere i privilegi per poterlo fare. Nella variabile di ambiente IMPORT_EXPORT_DEVICES_LIST si mette l'elenco dei file di dispositivo che l'utente può inizializzare. Anche in questo caso i nomi sono associati a una descrizione. Supponendo che sia concesso di inizializzare solo il dischetto, la variabile può essere inizializzata così:
|
Anche per questa situazione, non si possono inserire spazi nella descrizione.
Attraverso le voci del menù è possibile: ottenere l'elenco del contenuto di un'unità di memorizzazione esterna; vedere il contenuto di un file di tale unità; cancellare il contenuto dell'unità esterna o inizializzarla. Inoltre, è possibile copiare file da o verso l'unità esterna.
Per tutte queste funzioni è richiesto di specificare l'unità esterna. In tutti i casi, escluso quello dell'inizializzazione, viene mostrato l'elenco contenuto nella variabile di ambiente IMPORT_EXPORT_MOUNT_POINTS_LIST, mentre per l'inizializzazione vale l'altra variabile (IMPORT_EXPORT_DEVICES_LIST).
Viene mostrato il caso dell'esportazione, che si ottiene con la voce {export
} del menù. Si comincia dalla selezione dell'unità che deve accogliere la copia:
.esporta dei documenti in un disco rimovibile--. | Seleziona l'unita` da usare per | | l'esportazione dei documenti: | | .------------------------------------------. | | | /mnt/a dischetto | | | | /mnt/d penna_USB | | | `------------------------------------------' | |----------------------------------------------| | < OK > <Cancel> | `----------------------------------------------' |
Si selezionano i documenti da esportare, con l'aiuto della barra spaziatrice:
.esporta dei documenti in un disco rimovibile--. | Seleziona o deseleziona i documenti da | | esportare, con l'aiuto della barra | | spaziatrice: | | .------------------------------------------. | | | [X] appunti-2 . | | | | [X] da-fare . | | | | [ ] cose-vecchie . | | | `----------------v(+)----------------------' | |----------------------------------------------| | < OK > <Cancel> | `----------------------------------------------' |
Alla conferma inizia la copia; se ci sono file con lo stesso nome, lo script verifica se il contenuto è lo stesso, altrimenti chiede conferma per procedere alla sovrascrittura.
L'importazione avviene nello stesso modo, con la differenza che se viene richiesta la sovrascrittura, i documenti precedenti vengono salvati attraverso il sistema di rotazione delle versioni.
La stampa si ottiene selezionando la voce {print
} dal menù principale. A questo segue la richiesta di selezionare un solo documento dall'elenco di quelli esistenti. La stampa avviene con i comandi indicati nelle variabili di ambiente PRINTER_FILTER e PRINTER_COMMAND.
Richard Günther, Kernel Support for miscellaneous (your favorite) Binary Formats, sorgenti_linux/Documentation/binfmt_misc.txt
Brian A. Lantz, Richard Günther, Java(tm) Binary Kernel Support for Linux, sorgenti_linux/Documentation/java.txt
1) GNU findutils GNU GPL
2) util-linux: getopt UCB BSD
3) Debianutils: tempfile UCB BSD
4) GNU core utilities GNU GPL
5) GNU core utilities GNU GPL
6) GNU core utilities GNU GPL
7) GNU core utilities GNU GPL
11) Gdialog (Zenity) GNU GPL
12) Ncurses software libero con licenza speciale FSF
15) GNU core utilities GNU GPL
16) GNU core utilities GNU GPL
17) GNU core utilities GNU GPL
18) GNU core utilities GNU GPL
19) GNU core utilities GNU GPL
20) GNU core utilities GNU GPL
«a2» 2013.11.11 --- Copyright © Daniele Giacomini -- appunti2@gmail.com http://informaticalibera.net