15.5. Comandi inerenti ai file e all'archiviazione

Archiviazione

tar

È l'utility standard di archiviazione UNIX. [1] Dall'originale programma per il salvataggio su nastro (Tape ARchiving), si è trasformata in un pacchetto con funzionalità più generali che può gestire ogni genere di archiviazione con qualsiasi tipo di dispositivo di destinazione, dai dispositivi a nastro ai file regolari fino allo stdout (vedi Esempio 3-4). Tar GNU è stato implementato per accettare vari filtri di compressione, ad esempio tar czvf nome_archivio.tar.gz * che archivia ricorsivamente e comprime con gzip tutti i file, tranne quelli il cui nome inizia con un punto (dotfile), della directory di lavoro corrente ($PWD). [2]

Alcune utili opzioni di tar:

  1. -c crea (un nuovo archivio)

  2. -x estrae (file da un archivio esistente)

  3. --delete cancella (file da un archivio esistente)

    Attenzione

    Questa opzione non funziona sui dispositivi a nastro magnetico.

  4. -r accoda (file ad un archivio esistente)

  5. -A accoda (file tar ad un archivio esistente)

  6. -t elenca (il contenuto di un archivio esistente)

  7. -u aggiorna l'archivio

  8. -d confronta l'archivio con un filesystem specificato

  9. -z usa gzip sull'archivio

    (lo comprime o lo decomprime in base all'abbinamento con l'opzione -c o -x)

  10. -j comprime l'archivio con bzip2

Attenzione

Poiché potrebbe essere difficile ripristinare dati da un archivio tar compresso con gzip è consigliabile, per l'archiviazione di file importanti, eseguire salvataggi (backup) multipli.

shar

Utility di archiviazione shell. I file di un archivio shell vengono concatenati senza compressione. Quello che risulta è essenzialmente uno script di shell, completo di intestazione #!/bin/sh e contenente tutti i necessari comandi di ripristino. Gli archivi shar fanno ancora la loro comparsa solo nei newsgroup Internet, dal momento che shar è stata sostituita molto bene da tar/gzip. Il comando unshar ripristina gli archivi shar.

ar

Utility per la creazione e la manipolazione di archivi, usata principalmente per le librerie di file oggetto binari.

rpm

Il Red Hat Package Manager, o utility rpm, è un gestore per archivi binari o sorgenti. Tra gli altri, comprende comandi per l'installazione e la verifica dell'integrità dei pacchetti.

Un semplice rpm -i nome_pacchetto.rpm è di solito sufficiente per installare un pacchetto, sebbene siano disponibili molte più opzioni.

Suggerimento

rpm -qf identifica il pacchetto che ha fornito un determinato file.

bash$ rpm -qf /bin/ls
coreutils-5.2.1-31
	      

Suggerimento

rpm -qa fornisce l'elenco completo dei pacchetti rpm installati su un sistema. rpm -qa nome_pacchetto elenca solo il pacchetto corrispondente a nome_pacchetto.

bash$ rpm -qa
redhat-logos-1.1.3-1
glibc-2.2.4-13
cracklib-2.7-12
dosfstools-2.7-1
gdbm-1.8.0-10
ksymoops-2.4.1-1
mktemp-1.5-11
perl-5.6.0-17
reiserfs-utils-3.x.0j-2
...


bash$ rpm -qa docbook-utils
docbook-utils-0.6.9-2


bash$ rpm -qa docbook | grep docbook
docbook-dtd31-sgml-1.0-10
docbook-style-dsssl-1.64-3
docbook-dtd30-sgml-1.0-10
docbook-dtd40-sgml-1.0-11
docbook-utils-pdf-0.6.9-2
docbook-dtd41-sgml-1.0-10
docbook-utils-0.6.9-2
	      

cpio

Comando specializzato per la copia di archivi (copy input and output), si incontra molto raramente, essendo stato soppiantato da tar/gzip. Le sue funzionalità, comunque, vengono ancora utilizzate, ad esempio per spostare una directory. Specificando (per la copia) un'appropriata dimensione del blocco (block size), diventa sensibilmente più veloce di tar.

Esempio 15-27. Utilizzo di cpio per spostare una directory

#!/bin/bash

# Copiare una directory usando cpio.

# Vantaggi dell'uso di 'cpio':
#   Velocità nella copia. Con le pipe è più veloce di 'tar'.
#   Adatto per la copia di file speciali (named pipe, ecc.)
#+  dove 'cp' potrebbe fallire.

ARG=2
E_ERR_ARG=65

if [ $# -ne "$ARG" ]
then
  echo "Utilizzo: `basename $0` directory_origine  directory_destinazione"
  exit $E_ERR_ARG
fi

origine=$1
destinazione=$2


find "$origine" -depth | cpio -admvp "$destinazione"
#                ^^^^^         ^^^^^
# Leggete le pagine di manuale di 'find' e 'cpio' per decifrare queste opzioni.


# Esercizio:
# ---------
#  Aggiungete del codice per verificare l'exit status ($?) della pipe 
#+ 'find | cpio' e che visualizzi degli appropriati messaggi d'errore nel caso
#+ qualcosa non abbia funzionato correttamente.

exit 0
rpm2cpio

Questo comando crea un archivio cpio da un archivio rpm.

Esempio 15-28. Decomprimere un archivio rpm

#!/bin/bash
# de-rpm.sh: Decomprime un archivio 'rpm'

: ${1?"Utilizzo: `basename $0` file_archivio"}
# Bisogna specificare come argomento un archivio 'rpm'.


TEMPFILE=$$.cpio               # File temporaneo con nome "univoco".
                               # $$ è l'ID di processo dello script.

rpm2cpio < $1 > $TEMPFILE                # Converte l'archivio rpm in 
                                         #+ un archivio cpio.
cpio --make-directories -F $TEMPFILE -i  # Decomprime l'archivio cpio.
rm -f $TEMPFILE                          # Cancella l'archivio cpio.

exit 0

#  Esercizio:
#  Aggiungete dei controlli per verificare se
#+ 1) "file_archivio" esiste e
#+ 2) è veramente un archivio rpm.
#  Suggerimento:  verificate l'output del comando 'file'.

Compressione

gzip

Utility di compressione standard GNU/UNIX che ha sostituito la meno potente e proprietaria compress. Il corrispondente comando di decompressione è gunzip, equivalente a gzip -d.

Nota

L'opzione -c invia l'output di gzip allo stdout. È utile quando la si deve collegare con una pipe ad altri comandi.

Il filtro zcat decomprime un file compresso con gzip allo stdout, come possibile input per una pipe o una redirezione. In effetti, è il comando cat che agisce sui file compressi (compresi quelli ottenuti con la vecchia utility compress). Il comando zcat equivale a gzip -dc.

Attenzione

Su alcuni sistemi commerciali UNIX, zcat è il sinonimo di uncompress -c, di conseguenza non funziona su file compressi con gzip.

Vedi anche Esempio 7-7.

bzip2

Utility di compressione alternativa, più efficiente (ma più lenta) di gzip, specialmente con file di ampie dimensioni. Il corrispondente comando di decompressione è bunzip2.

Nota

Le versioni più recenti di tar sono state aggiornate per supportare bzip2.

compress, uncompress

È la vecchia utility proprietaria di compressione presente nelle distribuzioni commerciali UNIX. È stata ampiamente sostituita dalla più efficiente gzip . Le distribuzioni Linux includono, di solito, compress per ragioni di compatibilità, sebbene gunzip possa decomprimere i file trattati con compress.

Suggerimento

Il comando znew trasforma i file dal formato compress al formato gzip.

sq

Altra utility di compressione. È un filtro che opera solo su elenchi di parole ASCII ordinate. Usa la sintassi standard dei filtri, sq < file-input > file-output. Veloce, ma non così efficiente come gzip. Il corrispondente filtro di decompressione è unsq, con la stessa sintassi di sq.

Suggerimento

L'output di sq può essere collegato per mezzo di una pipe a gzip per una ulteriore compressione.

zip, unzip

Utility di archiviazione e compressione multipiattaforma, compatibile con il programma DOS pkzip.exe. Gli archivi "zippati" sembrano rappresentare, su Internet, il mezzo di scambio più comune rispetto ai "tarball".

unarc, unarj, unrar

Queste utility Linux consentono di decomprimere archivi compressi con i programmi DOS arc.exe, arj.exe e rar.exe.

Informazioni sui file

file

Utility per identificare i tipi di file. Il comando file nome_file restituisce la specifica di nome_file, come ascii text o data. Fa riferimento ai magic number che si trovano in /usr/share/magic, /etc/magic o /usr/lib/magic, secondo le distribuzioni Linux/UNIX.

L'opzione -f esegue file in modalità batch, per leggere l'elenco dei file contenuto nel file indicato. L'opzione -z, se usata su un file compresso, tenta di analizzare il tipo del file non compresso.

bash$ file test.tar.gz
test.tar.gz: gzip compressed data, deflated, last modified: Sun Sep 16 13:34:51 2001, os: Unix

bash file -z test.tar.gz
test.tar.gz: GNU tar archive (gzip compressed data, deflated, last modified: Sun Sep 16 13:34:51 2001, os: Unix)
	      

# Ricerca gli script sh e Bash in una data directory:

DIRECTORY=/usr/local/bin
PAROLACHIAVE=Bourne
# Script di shell Bourne e Bourne-Again

file $DIRECTORY/* | fgrep $PAROLACHIAVE

# Risultato:

# /usr/local/bin/burn-cd:          Bourne-Again shell script text executable
# /usr/local/bin/burnit:           Bourne-Again shell script text executable
# /usr/local/bin/cassette.sh:      Bourne shell script text executable
# /usr/local/bin/copy-cd:          Bourne-Again shell script text executable
# . . .

Esempio 15-29. Togliere i commenti da sorgenti C

#!/bin/bash
# strip-comment.sh: Toglie i commenti (/* COMMENTO */) in un programma C.

E_NOARG=0
E_ERR_ARG=66
E_TIPO_FILE_ERRATO=67

if [ $# -eq "$E_NOARG" ]
then
  echo "Utilizzo: `basename $0` file-C" >&2 #  Messaggio d'errore allo stderr.
  exit $E_ERR_ARG
fi

# Verifica il corretto tipo di file.
tipo=`file $1 | awk '{ print $2, $3, $4, $5 }'`
# "file $1" restituisce nome e tipo di file . . .
# quindi awk rimuove il primo campo, il nome . . .
# Dopo di che il risultato è posto nella variabile "tipo".
tipo_corretto="ASCII C program text"

if [ "$tipo" != "$tipo_corretto" ]
then
  echo
  echo "Questo script funziona solo su file sorgenti C."
  echo
  exit $E_TIPO_FILE_ERRATO
fi


# Script sed piuttosto criptico:
#--------
sed '
/^\/\*/d
/.*\*\//d
' $1
#--------
# Facile da capire, se dedicate diverse ore ad imparare i fondamenti di sed.


#  È necessario aggiungere ancora una riga allo script sed per trattare
#+ quei casi in cui una riga di codice è seguita da un commento.
#  Questo viene lasciato come esercizio (niente affatto banale).

#  Ancora, il codice precedente cancella anche le righe con un "*/" o "/*" 
#+ che non sono commenti, il che non è un risultato desiderabile.

exit 0


# ----------------------------------------------------------------------------
#  Il codice oltre la linea non viene eseguito a causa del precedente 'exit 0'.

# Stephane Chazelas suggerisce la seguente alternativa:

utilizzo() {
  echo "Utilizzo: `basename $0` file-C" >&2
  exit 1
}

STRANO=`echo -n -e '\377'`   # oppure STRANO=$'\377'
[[ $# -eq 1 ]] || utilizzo
case `file "$1"` in
  *"C program text"*) sed -e "s%/\*%${STRANO}%g;s%\*/%${STRANO}%g" "$1" \
  | tr '\377\n' '\n\377' \
  | sed -ne 'p;n' \
  | tr -d '\n' | tr '\377' '\n';;
  *) utilizzo;;
esac

#  Questo può ancora essere ingannato da occorrenze come:
#  printf("/*");
#  o
#  /*  /* errato commento annidato */
#
#  Per poter gestire tutti i casi particolari (commenti in stringhe, commenti
#+ in una stringa in cui è presente \", \\" ...) l'unico modo è scrivere un 
#+ parser C (usando, forse, lex o yacc?).

exit 0
which

which comandoXXX restituisce il percorso assoluto di "comandoXXX". È utile per verificare se un particolare comando o utility è installato sul sistema.

$bash which rm

/usr/bin/rm

whereis

Simile al precedente which, whereis comandoXXX restituisce il percorso assoluto di "comandoXXX" ed anche della sua pagina di manuale.

$bash whereis rm

rm: /bin/rm /usr/share/man/man1/rm.1.bz2

whatis

whatis filexxx ricerca "filexxx" nel database whatis. È utile per identificare i comandi di sistema e i file di configurazione. Può essere considerato una semplificazione del comando man.

$bash whatis whatis

whatis               (1)  - search the whatis database for complete words

Esempio 15-30. Esplorare /usr/X11R6/bin

#!/bin/bash

# Cosa sono tutti quei misteriosi eseguibili in /usr/X11R6/bin?

DIRECTORY="/usr/X11R6/bin"
# Provate anche "/bin", "/usr/bin", "/usr/local/bin", ecc.

for file in $DIRECTORY/*
do
  whatis `basename $file` # Visualizza le informazione sugli eseguibili.
done

exit 0

# Potreste desiderare di redirigere l'output di questo script, così:
# ./what.sh >>whatis.db
# o visualizzarne una pagina alla volta allo stdout,
# ./what.sh | less

Vedi anche Esempio 10-3.

vdir

Visualizza l'elenco dettagliato della directory. L'effetto è simile a ls -lb.

Questa è una delle fileutils GNU.

bash$ vdir
total 10
-rw-r--r--    1 bozo  bozo      4034 Jul 18 22:04 data1.xrolo
-rw-r--r--    1 bozo  bozo      4602 May 25 13:58 data1.xrolo.bak
-rw-r--r--    1 bozo  bozo       877 Dec 17  2000 employment.xrolo

bash ls -l
total 10
-rw-r--r--    1 bozo  bozo      4034 Jul 18 22:04 data1.xrolo
-rw-r--r--    1 bozo  bozo      4602 May 25 13:58 data1.xrolo.bak
-rw-r--r--    1 bozo  bozo       877 Dec 17  2000 employment.xrolo
	      

locate, slocate

Il comando locate esegue la ricerca dei file usando un database apposito. Il comando slocate è la versione di sicurezza di locate (che può essere l'alias di slocate).

$bash locate hickson

/usr/lib/xephem/catalogs/hickson.edb

readlink

Rivela il file a cui punta un link simbolico.

bash$ readlink /usr/bin/awk
../../bin/gawk
	      

strings

Il comando strings viene usato per cercare le stringhe visualizzabili in un file dati o in un file binario. Elenca le sequenze di caratteri trovate nel file di riferimento. E' utile per un esame rapido e sommario di un file core di scarico della memoria o per dare un'occhiata ad un file di immagine sconosciuto (strings file-immagine | more potrebbe restituire qualcosa come JFIF che indica un file grafico jpeg). In uno script, si può controllare l'output di strings con grep o sed. Vedi Esempio 10-7 e Esempio 10-9.

Esempio 15-31. Un comando strings "migliorato"

#!/bin/bash
# wstrings.sh: "word-strings" (comando "strings" migliorato)
#
#  Questo script filtra l'output di "strings" confrontandolo
#+ con un file dizionario.
#  In questo modo viene eliminato efficacemente il superfluo,
#+ restituendo solamente le parole riconosciute.

# ====================================================================
#       Verifica standard del/degli argomento/i dello script
ARG=1
E_ERR_ARG=65
E_NOFILE=66

if [ $# -ne $ARG ]
then
  echo "Utilizzo: `basename $0` nomefile"
  exit $E_ERR_ARG
fi

if [ ! -f "$1" ]                       # Verifica l'esistenza del file.
then
    echo "Il file \"$1\" non esiste."
    exit $E_NOFILE
fi
# ====================================================================


LUNMINSTR=3                            #  Lunghezza minima della stringa.
DIZIONARIO=/usr/share/dict/linux.words #  File dizionario.
                                       #  Può essere specificato un file 
                                       #+ dizionario diverso purché
                                       #+ di una parola per riga.


elenco=`strings "$nome_file" | tr A-Z a-z | tr '[:space:]' Z | \
tr -cs '[:alpha:]' Z | tr -s '\173-\377' Z | tr Z ' '`

#  Modifica l'output del comando 'strings' mediante diversi passaggi a 'tr'.
#  "tr A-Z a-z"  trasforma le lettere maiuscole in minuscole.
#  "tr '[:space:]' Z"  trasforma gli spazi in Z.
#  "tr -cs '[:alpha:]' Z"  trasforma i caratteri non alfabetici in Z,
#+ riducendo ad una sola le Z multiple consecutive.
#  "tr -s '\173-\377' Z"  trasforma tutti i caratteri oltre la 'z' in Z,
#+ riducendo ad una sola le Z multiple consecutive, liberandoci così di tutti i
#+ caratteri strani che la precedente istruzione non è riuscita a trattare.
#  Infine, "tr Z ' '" trasforma tutte queste Z in spazi, che saranno 
#+ considerati separatori di parole dal ciclo che segue.

#  **********************************************************
#  Notate la tecnica di concatenare diversi 'tr',
#+ ma con argomenti e/o opzioni differenti ad ogni passaggio.
#  **********************************************************


for parola in $elenco                 #  Importante:
                                      #  non bisogna usare $elenco col quoting.
                                      #  "$elenco" non funziona.
                                      #  Perché no?
do

  lunstr=${#parola}                   #  Lunghezza della stringa.
  if [ "$lunstr" -lt "$LUNMINSTR" ]   #  Salta le stringhe con meno
                                      #+ di 3 caratteri.
  then
    continue
  fi

  grep -Fw $parola "$DIZIONARIO"      #  Cerca solo le parole complete.
       ^^^                            #  Opzioni "stringhe Fisse" e
                                      #+ "parole (words) intere".

done  


exit $?

Confronti

diff, patch

diff: flessibile utility per il confronto di file. Confronta i file di riferimento riga per riga, sequenzialmente. In alcune applicazioni, come nei confronti di dizionari, è vantaggioso filtrare i file di riferimento con sort e uniq prima di collegarli tramite una pipe a diff. diff file-1 file-2 visualizza le righe dei file che differiscono, con le parentesi acute ad indicare a quale file ogni particolare riga appartiene.

L'opzione --side-by-side di diff visualizza riga per riga, in colonne separate, ogni file confrontato con un segno indicante le righe non coincidenti. Le opzioni -c e -u, similmente, rendono più facile l'interpretazione dell'output del comando.

Sono disponibili diversi front-end per diff, quali sdiff, wdiff, xdiff e mgdiff.

Suggerimento

Il comando diff restituisce exit status 0 se i file confrontati sono identici, 1 in caso contrario. Questo consente di utilizzare diff per un costrutto di verifica in uno script di shell (vedi oltre).

L'uso più comune di diff è quello per creare file di differenze da utilizzare con patch. L'opzione -e produce file idonei all'utilizzo con script ed o ex.

patch: flessibile utility per gli aggiornamenti. Dato un file di differenze prodotto da diff, patch riesce ad aggiornare un pacchetto alla versione più recente. È molto più conveniente distribuire un file di "differenze", di dimensioni relativamente minori, che non l'intero pacchetto aggiornato. Il "patching" del kernel è diventato il metodo preferito per la distribuzione delle frequenti release del kernel Linux.

patch -p1 <file-patch
# Prende tutte le modifiche elencate in 'file-patch'
# e le applica ai file che sono specificati in "file-patch".
# Questo esegue l'aggiornamento del pacchetto alla versione più recente.

Patch del kernel:

cd /usr/src
gzip -cd patchXX.gz | patch -p0
# Aggiornamento dei sorgenti del kernel usando 'patch'.
# Dal file "README" della documentazione del kernel Linux,
# di autore anonimo (Alan Cox?).

Nota

Il comando diff riesce anche ad eseguire un confronto ricorsivo tra directory (sui file in esse contenuti).

bash$ diff -r ~/notes1 ~/notes2
Only in /home/bozo/notes1: file02
Only in /home/bozo/notes1: file03
Only in /home/bozo/notes2: file04
	      

Suggerimento

Si usa zdiff per confrontare file compressi con gzip.

Suggerimento

diffstat si usa per creare un istogramma (grafico di distribuzione) dell'output di diff.

diff3

Versione estesa di diff che confronta tre file alla volta. Questo comando restituisce, come exit status, 0 in caso di successo, ma sfortunatamente non fornisce alcuna informazione sui risultati del confronto.

bash$ diff3 file-1 file-2 file-3
====
 1:1c
    Questa è la riga 1 di "file-1".
 2:1c
    Questa è la riga 1 di "file-2".
 3:1c
    Questa è la riga 1 di "file-3"
	      

sdiff

Confronta e/o visualizza due file con lo scopo di unirli in un unico file. A causa della sua natura interattiva, è difficile che questo comando venga impiegato negli script.

cmp

Il comando cmp è la versione più semplice di diff. Mentre diff elenca le differenze tra i due file, cmp mostra semplicemente i punti in cui differiscono.

Nota

Come diff, cmp restituisce exit status 0 se i file confrontati sono identici, 1 in caso contrario. Questo ne consente l'impiego per un costrutto di verifica in uno script di shell.

Esempio 15-32. Utilizzare cmp in uno script per confrontare due file

#!/bin/bash

ARG=2  # Lo script si aspetta due argomenti.
E_ERR_ARG=65
E_NONLEGGIBILE=66

if [ $# -ne "$ARG" ]
then
  echo "Utilizzo: `basename $0` file1 file2"
  exit $E_ERR_ARG
fi

if [[ ! -r "$1" || ! -r "$2" ]]
then
  echo "Entrambi i file, per essere confrontati, devono esistere"
  echo "ed avere i permessi di lettura."
  exit $E_NONLEGGIBILE
fi

cmp $1 $2 &> /dev/null  #  /dev/null elimina la visualizzazione del
                        #+ risultato del comando"cmp".
#   cmp -s $1 $2  ottiene lo stesso risultato (opzione "-s" di "cmp")
#   Grazie  Anders Gustavsson per averlo evidenziato.
#
# Funziona anche con 'diff', vale a dire, diff $1 $2 &> /dev/null

if [ $? -eq 0 ]         # Verifica l'exit status del comando "cmp".
then
  echo "Il file \"$1\" è identico al file \"$2\"."
else
  echo "Il file \"$1\" è diverso dal file \"$2\"."
fi

exit 0

Suggerimento

Si usa zcmp per i file compressi con gzip.

comm

Versatile utility per il confronto di file. I file da confrontare devono essere ordinati.

comm -opzioni primo-file secondo-file

comm file-1 file-2 visualizza il risultato su tre colonne:

  • colonna 1 = righe uniche appartenenti a file-1

  • colonna 2 = righe uniche appartenenti a file-2

  • colonna 3 = righe comuni ad entrambi i file.

Alcune opzioni consentono la soppressione di una o più colonne di output.

  • -1 sopprime la colonna 1

  • -2 sopprime la colonna 2

  • -3 sopprime la colonna 3

  • -12 sopprime entrambe le colonne 1 e 2, ecc.

È un comando utile per confrontare "dizionari" o elenchi di parole -- file di testo ordinati, con una parola per riga.

Utility

basename

Elimina il percorso del file, visualizzando solamente il suo nome. Il costrutto basename $0 permette allo script di conoscere il proprio nome, vale a dire, il nome con cui è stato invocato. Si può usare per i messaggi di "utilizzo" se, per esempio, uno script viene eseguito senza argomenti:

echo "Utilizzo: `basename $0` arg1 arg2 ... argn"

dirname

Elimina basename, dal nome del file, visualizzando solamente il suo percorso.

Nota

basename e dirname possono operare su una stringa qualsiasi. Non è necessario che l'argomento si riferisca ad un file esistente e neanche essere il nome di un file (vedi Esempio A-7).

Esempio 15-33. basename e dirname

#!/bin/bash

a=/home/bozo/daily-journal.txt

echo "Basename di /home/bozo/daily-journal.txt = `basename $a`"
echo "Dirname di /home/bozo/daily-journal.txt = `dirname $a`"
echo
echo "La mia cartella personale è `basename ~/`." 
                                   # funziona anche `basename ~`.
echo "La directory della mia cartella personale è `dirname ~/`." 
                                   # funziona anche `dirname ~`.

exit 0
split, csplit

Utility per suddividere un file in porzioni di dimensioni minori. Sono solitamente impiegate per suddividere file di grandi dimensioni allo scopo di eseguirne il salvataggio su floppy disk, per l'invio tramite e-mail o per effettuarne l'upload su un server.

Il comando csplit suddivide il file in base ad un dato criterio. La suddivisione viene eseguita nei punti in cui i modelli sono verificati.

Codifica e Crittografia

sum, cksum, md5sum, sha1sum

Sono utility per creare le checksum. Una checksum è un numero ricavato con un calcolo matematico eseguito sul contenuto di un file, con lo scopo di verificarne l'integrità. Uno script potrebbe verificare un elenco di checksum a fini di sicurezza, per esempio per assicurarsi che il contenuto di indispensabili file di sistema non sia stato modificato o corrotto. Per applicazioni di sicurezza, si dovrebbe utilizzare il comando md5sum a 128-bit (message digest 5 checksum) o, ancor meglio, il nuovissimo sha1sum (Secure Hash Algorithm).

bash$ cksum /boot/vmlinuz
1670054224 804083 /boot/vmlinuz

bash$ echo -n "Top Secret" | cksum
3391003827 10



bash$ md5sum /boot/vmlinuz
0f43eccea8f09e0a0b2b5cf1dcf333ba /boot/vmlinuz

bash$ echo -n "Top Secret" | md5sum
8babc97a6f62a4649716f4df8d61728f  -
	      

Nota

Il comando cksum visualizza anche la dimensione, in byte, del suo riferimento. sia esso un file o lo stdout.

I comandi md5sum e sha1sum visualizzano un trattino quando l'input proviene dallo stdout.

Esempio 15-34. Verificare l'integrità dei file

#!/bin/bash
# file-integrity.sh: Verifica se i file di una data directory
#                    sono stati modificati senza autorizzazione.

E_DIR_ERRATA=70
E_ERR_DBFILE=71

dbfile=File_record.md5
# Nome del file che contiene le registrazioni (file database).


crea_database ()
{
  echo ""$directory"" > "$dbfile"
  # Scrive il nome della directory come prima riga di dbfile.
  md5sum "$directory"/* >> "$dbfile"
  # Accoda le checksum md5 e i nomi dei file.
}

verifica_database ()
{
  local n=0
  local nomefile
  local checksum

  # --------------------------------------------------------- #
  #  Questa verifica potrebbe anche non essere necessaria, ma è 
  #+ meglio essere pignoli che rischiare.

  if [ ! -r "$dbfile" ]
  then
      echo "Impossibile leggere il database delle checksum!"
      exit $E_ERR_DBFILE
  fi
  # --------------------------------------------------------- #

  while read record[n]
  do

    directory_verificata="${record[0]}"
    if [ "$directory_verificata" != "$directory" ]
    then
      echo "Le directory non corrispondono!"
      # E' stato indicato un nome di directory sbagliato.
      exit $E_DIR_ERRATA
    fi

    if [ "$n" -gt 0 ]   # Non è il nome della directory.
    then
      nomefile[n]=$( echo ${record[$n]} | awk '{ print $2 }' )
      #  md5sum scrive nel primo campo la checksum, nel
      #+ secondo il nome del file.
      checksum[n]=$( md5sum "${nomefile[n]}" )


      if [ "${record[n]}" = "${checksum[n]}" ]
      then
        echo "${nomefile[n]} non è stato modificato."

      elif [ "`basename ${nomefile[n]}`" != "$dbfile" ]
             #  Salta il database delle checksum,
             #+ perché cambia ad ogni invocazione dello script.
	     #  ---
	     #  Questo significa, purtroppo, che quando si esegue
	     #+ lo script su $PWD, la coincidenza con il
	     #+ file database delle checksum non viene rilevata.
	     #  Esercizio: Risolvete questo problema.
        then
          echo "${nomefile[n]} : CHECKSUM ERRATA!"
          # Il file è stato modificato dall'ultima verifica.
      fi

    fi



    let "n+=1"
  done <"$dbfile"   # Legge il database delle checksum.

}

# ============================================================= #
# main ()

if [ -z  "$1" ]
then
  directory="$PWD"      #  Se non altrimenti specificata, usa la
else                    #+ directory di lavoro corrente.
  directory="$1"
fi

clear                   # Pulisce lo schermo.
echo " In esecuzione il controllo dell'integrità dei file in $directory"
echo

# ------------------------------------------------------------------- #
  if [ ! -r "$dbfile" ] # Occorre creare il database?
  then
      echo "Sto creando il database, \""$directory"/"$dbfile"\"."; echo
      crea_database
  fi
# ------------------------------------------------------------------- #

verifica_database       # Esegue il lavoro di verifica.

echo

#  Sarebbe desiderabile redirigere lo stdout dello script in un file, 
#+ specialmente se la directory da verificare contiene molti file.

exit 0

#  Per una verifica d'integrità molto più approfondita, 
#+ considerate l'impiego del pacchetto "Tripwire",
#+ http://sourceforge.net/projects/tripwire/.

Vedi anche Esempio A-19 e Esempio 33-14 per un uso creativo del comando md5sum.

Nota

Essendoci state delle notizie che segnalano che la codifica md5sum a 128 bit è stata spezzata, non si può che dare il benvenuto, tra gli strumenti per il calcolo della checksum, al più sicuro sha1sum a 160 bit.

Alcuni consulenti per la sicurezza sono dell'opinione che anche sha1sum possa essere compromessa. Per cui, a quando la prossima utility a 512 bit?

bash$ md5sum filetesto
e181e2c8720c60522c4c4c981108e367  filetesto


bash$ sha1sum filetesto
5d7425a9c08a66c3177f1e31286fa40986ffc996  filetesto
	      
shred

Cancella in modo sicuro un file, sovrascrivendolo diverse volte con caratteri casuali prima di cancellarlo definitivamente. Questo comando ha lo stesso effetto di Esempio 15-55, ma esegue il compito in maniera più completa ed elegante.

Questa è una delle fileutils GNU.

Attenzione

Tecniche di indagine avanzate potrebbero essere ancora in grado di recuperare il contenuto di un file anche dopo l'uso di shred.

uuencode

Questa utility codifica i file binari (immagini, file musicali, file compressi, ecc.) in caratteri ASCII, rendendoli disponibili per la trasmissione nel corpo di un messaggio e-mail o in un post di newsgroup. È particolarmente utile nei casi in cui non è disponibile la codifica MIME (multimedia).

uudecode

Inverte la codifica, ripristinando i file binari, codificati con uuencode, al loro stato originario.

Esempio 15-35. Decodificare file

#!/bin/bash
#  Decodifica con uudecode tutti i file della directory di lavoro corrente
#+ cifrati con uuencode.

righe=35        # 35 righe di intestazione (molto generoso).

for File in *   # Verifica tutti i file presenti in $PWD.
do
  ricerca1=`head -n $righe $File | grep begin | wc -w`
  ricerca2=`tail -n $righe $File | grep end | wc -w`
  #  Decodifica i file che hanno un "begin" nella parte iniziale e un "end" 
  #+ in quella finale.
  if [ "$ricerca1" -gt 0 ]
  then
      if [ "$ricerca2" -gt 0 ]
      then
            echo "Decodifico con uudecode - $File -"
            uudecode $File
    fi
  fi
done  

#  Notate che se si invoca questo script su se stesso, l'esecuzione è ingannata
#+ perché pensa di trovarsi in presenza di un file codificato con uuencode,
#+ poiché contiene sia "begin" che "end".

#  Esercizio:
#  Modificate lo script per verificare in ogni file l'intestazione di un
#+ newsgroup, saltando al file successivo nel caso non venga trovata.

exit 0

Suggerimento

Il comando fold -s può essere utile (possibilmente in una pipe) per elaborare messaggi di testo di grandi dimensioni, decodificati con uudecode, scaricati dai newsgroup Usenet.

mimencode, mmencode

I comandi mimencode e mmencode elaborano gli allegati e-mail nei formati di codifica MIME. Sebbene i gestori di e-mail (mail user agents come pine o kmail) siano normalmente in grado di gestirli automaticamente, queste particolari utility consentono di manipolare tali allegati manualmente, da riga di comando o in modalità batch per mezzo di uno script di shell.

crypt

Una volta questa era l'utility standard UNIX per la cifratura di file. [3] Regolamenti governativi (USA N.d.T.), attuati per ragioni politiche, che proibiscono l'esportazione di software crittografico, hanno portato alla scomparsa di crypt da gran parte del mondo UNIX, nonché dalla maggioranza delle distribuzioni Linux. Per fortuna i programmatori hanno prodotto molte alternative decenti, tra le quali cruft realizzata proprio dall'autore del libro (vedi Esempio A-4).

Miscellanea

mktemp

Crea un file temporaneo [4] con nome "univoco". Invocata da riga di comando senza alcun argomento, crea un file vuoto (lunghezza zero) nella directory /tmp.

bash$ mktemp
/tmp/tmp.zzsvql3154
	      

PREFISSO=nomefile
tempfile=`mktemp $PREFISSO.XXXXXX`
#                          ^^^^^^ Occorrono almeno 6 posti per
#+                                il suffisso del nome del file.
#   Se non viene indicato nessun nome di file,
#+  viene usato "tmp.XXXXXXXXXX" come nome di default.

echo "nome del file temporaneo = $tempfile"
# nome del file temporaneo = nomefile.QA2ZpY
#                            o qualcosa del genere...

#  Crea un file con quel nome nella directory di lavoro corrente
#+ con impostazione dei permessi a 600.
#  "umask 177" diventa, quindi, inutile,
#  sebbene il suo uso sia sempre una buona pratica di programmazione.

make

Utility per costruire e compilare pacchetti binari. Può anche essere usata per una qualsiasi serie di operazioni che devono essere eseguite a seguito di successive modifiche nei file sorgenti.

Il comando make verifica Makefile, che è un elenco di dipendenze ed operazioni che devono essere svolte.

install

Comando speciale per la copia di file. È simile a cp, ma in grado di impostare i permessi e gli attributi dei file copiati. Questo comando sembra fatto su misura per l'installazione di pacchetti software e come tale appare frequentemente nei Makefile (nella sezione make install). Potrebbe essere usato allo stesso modo in script d'installazione.

dos2unix

Questa utility, scritta da Benjamin Lin e collaboratori, converte i file di testo in formato DOS (righe che terminano con CR-LF) nel formato UNIX (righe che terminano con il solo LF), e viceversa.

ptx

Il comando ptx [file-indicato] produce un indice permutato (elenco a riferimento incrociato) del file. Questo, se necessario, può essere successivamente filtrato e ordinato in una pipe.

more, less

Comandi per visualizzare un file, o un flusso, di testo allo stdout, una schermata alla volta. Possono essere usati per filtrare l'output dello stdout . . . o di uno script.

Un'applicazione interessante di more è la "verifica preventiva" di una sequenza di comandi, per prevenire conseguenze potenzialmente spiacevoli.

ls /home/bozo | awk '{print "rm -rf " $1}' | more
#                                            ^^^^
		 
# Verifica l'effetto della seguente (disastrosa) riga di comando:
#      ls /home/bozo | awk '{print "rm -rf " $1}' | sh
#      Evita l'esecuzione da parte della shell . . .^^

Note

[1]

Nel significato qui inteso, un archivio è semplicemente una serie di file correlati registrati in una singola locazione.

[2]

tar czvf nome_archivio.tar.gz * include i dotfile presenti nelle directory che si trovano al di sotto della directory di lavoro corrente. Questa è una "funzionalità" non documentata del tar GNU.

[3]

Cifratura di tipo simmetrico, usata per i file su un sistema singolo o su una rete locale, contrapposta a quella a "chiave pubblica", di cui pgp è il ben noto esempio.

[4]

Crea una directory temporanea se richiamato con l'opzione -d.