Caro lettore, permettimi di iniziare con il ricordo di un compleanno. L'articolo che stai leggendo è stato terminato il 9 ottobre[1], giorno in cui nel 1940 nacque John Winston (poi Ono) Lennon. Magari a te non interessa per nulla, ma a me sì, e comunque prometto di terminare qui le commemorazioni.
Ti parlerò invece di sort, come certamente hai intuito dal titolo. A dire il vero, la cosa più buffa del comando è che esso ha subito una metamorfosi (o dovremmo dire una plastica facciale) per diventare per quanto possiblile normale. Quella che era una sua caratteristica, l'avere un'opzione preceduta da + invece che da -, é stata così bollata come obsolescente, chi la usa ancora viene guardato di traverso, e il signor POSIX ha persino deciso di modificare il modo in cui si indicano le chiavi di ordinamento, per essere sicuro che si perda ogni traccia del vecchio sistema. Cercherò comunque di dare anche le vecchie opzioni quando me le ricordo, in modo da perpetuarne il ricordo[2].
Bando alle ciance, ed eccoti la sintassi completa del comando, nella versione GNU e SVR4:
sort [-cmus] [-t separatore] [-o output-file] [-T tempdir] [-bdfiMnr] [+POS1 [-POS2]] [-k POS1[,POS2]] [file...]Per i curiosoni, aggiungo che la versione Solaris aggiunge il campo -y [kmem], che specifica quanti KB di memoria allocare inizialmente - tutta quella possibile, se non si specifica kmem - e che quella GNU permette anche un'altra sintassi:
sort {--help,--version}per avere rispettivamente una breve descrizione delle opzioni e il numero della versione, cose sicuramente utili ma che non sono certo degli ordinamenti.
Ah, dimenticavo: sort è fondamentalmente un filtro, quindi scrive il suo risultato sullo standard output, e, se si omette file, legge perciò da standard input.
Anche se sort ha come scopo principale quello di ordinare, già che c'erano i progettisti gli hanno aggiunto alcune funzionalità similari, chiamandole pomposamente "modi di operazione". Bontà loro, hanno fatto in modo che l'ordinamento fosse l'operazione di default, ma hanno aggiunto altre due operazioni: merge e controllo di ordinatezza[3]. Il modo di operare viene cambiato utilizzando il primo gruppo di parametri:
Un altro "aiuto" del GNU sort consiste nell'aggiungere un newline all'ultima riga, se per caso le mancasse. In compenso, afferma di non avere limiti sulla lunghezza delle righe di input, o restrizioni sui byte ammessi all'interno di queste righe. Io mi fido sulla parola.
Passiamo ora alle altre opzioni del comando: ce ne sono di due tipi diversi. Le prime due si applicano globalmente a tutto il file:
A proposito del separatore, ricordati che se lo specifichi, e non usi il default, può capitare di trovarsi dei campi senza nulla dentro: se il separatore è $, $$ delimita appunto un campo nullo.
Le altre opzioni modificano l'ordine delle righe di output, e possono invece essere globali o locali, Se le si mette vicino a una chiave di ordinamento, valgono solo per quella chiave; altrimenti valgono per tutte le chiavi che non sono state modificate "privatamente".
Tieni conto che, almeno su Solaris, conta anche il famigerato "Locale", che non è una stanza abitata oppure un bar, ma il tipo di lingua che si sta utilizzando. Se quindi hai il locale italiano, l'opzione -M userà GEN, FEB, ..., DIC. Così sembra dire la pagina di manuale, ma io non mi fido più di tanto e, visto che ovviamente il mio locale è C e non it, non mi preoccupo nemmeno.
Dulcis in fundo[8], ecco l'opzione che specifica i campi. Anzi le opzioni, perché c'è la nuova e la vecchia forma. Cominciamo con quella nuova:-k POS1[,POS2].
Innanzitutto, possiamo avere fino ad almeno nove campi di questo tipo, che saranno provate da sinistra a destra per ordinare. Se non ce ne fosse nessuna, si usa come chiave di ordinamento tutta la riga. I parametri che ho indicato banalmente come POS1 e POS2 sono in realtà delle "posizioni", e si possono suddividere a loro volta in più parti: il numero del campo da considerare, la posizione (come numero di caratteri) all'interno del campo, e le opzioni locali di cui parlavo prima. In pratica, lo vediamo come c.pM, tenendo sempre presente che solo la parte del numero di campo è obbligatoria.
I campi si numerano da 1 in poi; le posizioni all'interno pure, ma nel caso di POS2 si può specificare anche .0 (il default) per dire che si prende tutta la chiave. Insomma: mettere POS1 a 1.3 fa partire la chiave di ordinamento dal terzo carattere del primo campo, mettere POS2 a 2.4 la fa terminare col quarto carattere del secondo campo. Sono opzioni generalmente non utilizzate, ma occorre saperle. Poi si scriverà sempre -k 2,2 se interessa solo il secondo campo, o addirittura -k 2 se non interessa il primo campo. Meno caratteri da digitare[9].
La vecchia opzione +POS1 -POS2 è praticamente identica, se non fosse che cambia tutti gli offset di POS1. Il primo campo è infatti 0 e non 1, così come il primo carattere da considerare nel campo. Ma ripeto: questo vale solo per POS1, non su POS2, tanto per complicare la vita. POS2 funziona esattamente come nel nuovo sistema. Anzi no (non sparatemi!). Se in POS2 si specifica una posizione all'interno maggiore di zero, il numero del campo parte anch'esso da zero, e non da 1. Chiaro? no? Non l'ho compreso bene nemmeno io. Se ami gli specchietti, posso dirti che +w.xT -y.zU equivale a
non_definito (z==0, U contiene il flag b, c'è l'opzione -t) -k w+1.x+1T,y.0U (z==0 altrimenti) -k w+1.x+1T,y+1.zU (z > 0)
Se sei sopravvissuto fin qua, posso anche farti vedere qualche esempio, che magari ti mette un po' di gioia. La pagina di manuale che ho io su Linux infatti li evita, probabilmente perché vuole dare all'utente l'ebbrezza di fare le prove per conto suo. Userò sempre entrambi i metodi, il nuovo e il vecchio, per fare vedere che non sono tanto diversi.
% sort -k 2,2 file_in % sort +1 -2 file_ina seconda del sistema che preferiamo per indicare le chiavi. Fin qui nulla di difficile.
% sort -r -o file_out -k 2.2,2.2 file1 file2 % sort -r -o file_out +1.1 -1.2 file1 file2
% sort -t: -k 3,3n /etc/passwd % sort -t: +2 -3n /etc/passwd
% sort -um -k 3.1,3.0 file_in % sort -um +2.0 -3.0 file_in