Comandi Unix buffi: dd

Bene, ragazzi, siamo al numero estivo, e mi prendo un po' di vaccanze pure io, come potete vedere dalla brevità di questo articolo. D'accordo, sarebbe stato più logico che io avessi scritto di più, visto che da un lato voi avete maggior tempo per leggere, e d'altro canto le linee dovrebbero essere meno cariche e lasciarvi scaricare questo numero di BETA più velocemente. Fa lo stesso. Sono pigro, la scadenza è vicina, e non me la sento proprio di mettermi a sbrodolare sulla tastiera[1] tra l'afa e le zanzare.

In compenso, penso che l'argomento di questo numero sia abbastanza interessante: tratterò infatti di dd, il data duplicator, un programma relativamente poco conosciuto, ma che trovo utilissimo per mille cose che nessuno sospetterebbe a prima vista. Prima di presentare il comando in tutte le sfaccettature, voglio però narrare una leggenda che spiega perché la sua sintassi sia così buffa - altro che find!. Purtroppo non ricordo la fonte di quanto dirò[2], il che però nulla toglie al soffio di antichità che pervade la storia, e che ci fa sentire così piccoli...

La leggenda narra che nella notte dei tempi, quando nacque il primo UNIX, i suoi creatori ebbero bisogno a un certo punto di un comando a basso livello per copiare dati da un device all'altro. Visto però che avevano fretta, non si misero a studiare la sintassi del comando, e copiarono di peso quella usata nei mainframe IBM-360; tanto ci sarebbe stato tempo di definire un comando dall'interfaccia consistente con gli altri. Il tempo passò, e tutti si erano così abituati alle idiosincrasie di dd, che a nessuno venne in mente di riscrivere un comando simile. Sarà vero? Non lo so, ma ribadisco che sarebbe comunque bene inventato. Ma vediamo in dettaglio come esso funziona.

Opzioni

A dire il vero, dd non è completamente diverso dagli altri comandi Unix: anch'esso è infatti un filtro, che cioè legge per default dallo standard input e scrive sullo standard output. Insomma, se si scrive semplicemente "dd" e si preme return, il terminale rimane tranquillo[3]. Si può scrivere quello che si vuole, e terminare con ctl-D; a questo punto verrà riscritto quanto digitato, e poi dd aggiungerà la sua firma, del tipo "0+10 records in <CR> 0+1 records out", tanto per divertirsi un po'[4].

Bando alle ciance: la sintassi completa del comando è la seguente.

    dd [if=file] [of=file] [ibs=bytes] [obs=bytes] 
       [bs=bytes] [cbs=bytes] [skip=blocks] [seek=blocks] 
       [count=blocks] [conv={ascii,ebcdic,ibm,block,
           unblock,lcase,ucase,swab,noerror,notrunc,sync}]

Insomma, tutte le opzioni sono della forma variabile=valore. Attenzione: non si può mettere nessuno spazio né prima né dopo il segno di uguale. Questo rompeva un po' le scatole un tempo, visto che la shell non espandeva i nomi di file in questa situazione e occorreva digitarselo tutto; fortunatamente la bash di Linux è abbastanza intelligente da accorgersi del contesto e fare ugualmente funzionare l'espansione. In pratica, nicc problema!

Altra cosa importante da ricordare è che tutti i valori che accettano bytes possono essere seguiti da un moltiplicatore. La versione Linux di dd riconosce b per blocco (512), k per kilobyte (1024), w per word (2), e xm per moltiplicare per un numero m; questo diventa poi il valore di block.

Qui sotto è data una spiegazione più completa delle varie opzioni.

if=filein e of=fileout
dicono a dd rispettivamente di leggere da filein e di scrivere su fileout. Nella versione Linux, in quest'ultimo caso il file di output viene troncato al valore dato da seek, o a 0 nel caso tale parola chiave non sia presente. Ma attenti all'opzione notrunc!
ibs=nn e obs=nn
specificano quanti byte debbano essere letti o scritti per record, cioè per operazione di scrittura. Direi che il default è di 512 byte, vale a dire un blocco: almeno così capita nei file normali. Naturalmente non è che l'eventuale resto sia buttato via: quando sopra mostravo l'output di dd che diceva "0+10 records", avevo scritto dieci righe, tutte minori di 512 caratteri, e che quindi venivano trattate come "resti". Questi parametri sono molto importanti quando si usano device speciali come input oppure output: leggere in rete dovrebbe avere ad esempio ibs settato a 20k, e un floppino ha come blocco "naturale" 18k. Evitare di settare questi valori può risultare non solo in maggior tempo per eseguire il comando, ma anche in errori di timeout, se non peggio. Utente avvisato...
bs=nn
è un'abbreviazione per indicare contemporaneamente quanti byte vengono letti e scritti per volta.
cbs=nn
setta i buffer di conversione a nnbyte. Secondo il manuale, ciò serve quando si traduce da ASCII a EBCDIC, o da un device non a blocchi a uno a blocchi: usato insieme alle conversioni block e unblock. Non so. Non ho mai avuto la sfortuna di dovere usare tale opzione, e vivo ancora felice :-)
skip=nbl e seek=nbl
dicono al programma di saltare nblblocchi rispettivamente all'inizio dell'input e a quello dell'output. Ovviamente il secondo caso ha senso solo se si dà anche la conversione notrunc: vedi sotto. La dimensione del blocco è data dal valore di ibs (obs). Un'utile osservazione: in realtà si può tranquillamente scrivere skip=1b per indicare 512 "qualcosa". Ma se non si setta ibs a 1, il "qualcosa" è un blocco: in pratica, ciò significa 512*512 byte, o 256K. Era quello che volevate?
count=nbl
fa copiare solo nbl blocchi dall'input, ciascuno della dimensione indicata da ibs. Questa opzione, assieme alle precedenti, torna utile quando si ha un file corrotto e si vuole recuperarne il più possibile; si salta la parte incriminata e si ottiene il resto.
conv=conversion[,conversion...]
converte il file come specificato dal suo argomento. Possibili conversioni, almeno nella versione Linux, sono ascii, che converte da EBCDIC a ASCII; ebcdic e ibm, che fanno entrambe una conversione inversa[5]; block, che allunga tutti i record terminati da un newline alla lunghezza di cbs, sostituendo al newline degli spazi; unblock, che fa l'opposto (toglie gli spazi finali, e aggiunge un newline); lcase e ucase, per convertire il testo in minuscole o maiuscole; swab, che scambia tra loro a due a due i byte di input (ad esempio, se si è scritto un file di interi a 2 byte su una macchina basata su 680x0 e occorre leggerli su un PC); noerror, per continuare la conversione anche in caso di errori di input; sync, che allunga i blocchi di input alla dimensione di ibs aggiungendo dei NUL.

Esempi

L'esempio canonico di uso di dd è quello in cui vi sarete certamente imbattuti quando avete creato il vostro primo dischetto Linux: come scrivere su un floppino senza mettere su il filesystem MS-DOS. La soluzione è semplice:

% dd if=disk.img of=/dev/fd0 obs=18k count=80
Ho deciso di non usare ibs perché non so quale sia il miglior valore per un hard disk; potevo comunque usare bs al posto di obs e sarebbe andato tutto perfettamente. Da notare il fatto che per sicurezza ho anche indicato il numero di settori da scrivere (80), e che ho scritto direttamente sul nome a basso livello del floppy.

Un'altra utile applicazione di dd capita nel caso di backup in rete. Supponiamo di essere sulla macchina alpha, e che sulla macchina beta ci sia un nastro /dev/rst0 con un file tar che ci interessa, abbastanza grande da non potere essere salvato su beta e poi copiato. In questo caso, si può scrivere

% rsh beta 'dd if=/dev/rst0 ibs=8k obs=20k' | tar xvBf - 
per fare l'operazione in un singolo passo. In questo caso, abbiamo sfruttato rsh per leggere dal nastro: le dimensioni di input e di output sono messe al default per queste operazioni, cioè 8KB per leggere da nastro e 20KB per scrivere sulla ethernet. Dal punto di vista del nostro tar locale, i byte che arrivano sono esattamente quelli che avrebbe visto da nastro, a parte il modo un po' strano con cui arrivano, ragion per cui ho aggiunto l'opzione B al comando[6].

Infine, un rapido comando che può tornare utile quando arriva un file Word da Mac, e si scopre che nonostante tutte le controconversioni questo rimane illeggibile perché scritto in MacBinary: basta togliere i primi 128 byte.

% dd if=file.bad of=file.doc ibs=128 skip=1

Insomma, fa sempre comodo avere sotto mano il nostro duplicatore di dati![7]

Note & Chiose

[1]
Qualcuno potrebbe fornirmi un termine per la "sbrodolatura densa" rappresentata da quest'articolo?
[2]
Però garantisco che non l'ho inventata io.
[3]
Ma poi dovreste già cominciare a conoscere Unix: chi ve lo fa fare, a scrivere qualcosa che - come tutte le combinazioni di due lettere minuscole - è molto probabilmente un comando?
[4]
Però se provate a scrivere abbastanza a lungo, vi accorgerete che quello che ho scritto sopra è parzialmente falso. Lascio al lettore astuto il trovare la spiegazione.
[5]
Sì: non esiste un'unica caonversione da EBCDIC ad ASCII. La prima è quella standard, ma la seconda funziona meglio quando si stampa. Fortuna che non si dovrebbe mai avere bisogno di queste conversioni!
[6]
Tranquilli: tar è l'argomento del prossimo articolo.
[7]
Dimenticavo: non penso affatto che dd sia un acronimo per "data duplicator". Me lo sono inventato io, perché il nome mi piaceva. E poi dovevo pure scrivere qualcosa, no?
.mau.
Copyright © 1996 Maurizio Codogno e BETA. Questo testo può essere liberamente distribuito purché non a fini di lucro. Per ogni altro utilizzo, si prega contattare l'autore.