Comandi Unix buffi: sort

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.

Modi di operare

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:

-c (controlla)
si limita a controllare se il file è davvero ordinato. Se sì, esce silenzioso: altrimenti, spernacchia un messaggio di errore (come "sort: disorder on pippo") ed esce con codice 1. La versione specifica Solaris[4], invece, non manda alcun messaggio sullo standard output e si limita a settare il codice di uscita.
-m (merge)
fa il merge dei file indicati. Essi devono già essere ordinati per conto loro: l'opzione serve semplicemente per accelerare l'ordinamento, invece che fare tutti i controlli tipici.
-u (unico)
stampa solo la prima di una successione di righe che sono uguali al confronto, a meno che non si usi anche -c, nel qual caso il programma si arrabbia se ci sono due righe consecutive uguali. Nota bene: le righe non devono essere uguali, basta che lo sia la parte che si confronta. E non confonderti con uniq, che è il comando apposito per trovare le righe doppie[5], e che chissà mai quando tratterò.
-s (stabile)
è infine un'opzione specifica al GNU sort, che pensa di fare un favore mettendo in ordine le righe byte per byte come extrema ratio[6], quando i confronti indicati danno un risultato di identità. Se la cosa non ti piace, e vuoi tenere il tuo ordine in questi casi, devi ricordarti di aggiungere anche questa opzione.

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.

Opzioni

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)

Esempi

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.

  1. Se vogliamo solo ordinare secondo il secondo campo, possiamo scrivere
       % sort -k 2,2 file_in
       % sort +1 -2 file_in
    
    a seconda del sistema che preferiamo per indicare le chiavi. Fin qui nulla di difficile.
  2. Supponiamo invece di avere due file di ingresso, file1 e file2; di volere file_out come uscita; di volere l'ordine inverso, e infine di usare come chiave di ordinamento il secondo carattere del secondo campo. Difficile? macché! Basta scrivere
       % sort -r -o file_out -k 2.2,2.2 file1 file2
       % sort -r -o file_out +1.1 -1.2 file1 file2
    
  3. Vogliamo ordinare /etc/passwd per numero di utente (il terzo campo, se non te lo ricordassi, e i campi sono separati da ":")? Detto fatto:
       % sort -t: -k 3,3n /etc/passwd
       % sort -t: +2 -3n /etc/passwd
    
  4. Se infine abbiamo un file già ordinato, e vogliamo mantenere solo una riga per tutti gli ingressi che hanno lo stesso terzo campo, possiamo usare il modo "merge", anche se non facciamo in realtà il merge di più file. Il comando diventa
       % sort -um -k 3.1,3.0 file_in
       % sort -um +2.0 -3.0 file_in
    
Penso che come oscurità della linea di comando, sort abbia ben pochi rivali: ma in fin dei conti, gli utilizzi pratici non sono difficili da ricordare. E poi vuoi mettere il DOS, che ti costringeva a pietire in giro un programma specifico per ordinare?

Note & Chiose

[1]
Ben dopo l'ultimo momento disponibile, da bravo articolista.
[2]
D'accordo che questo numero di BETA comprende anche novembre e che il tempo fuori non promette nulla di buono, ma forse sono davvero un po' troppo lugubre. Meglio darci un taglio.
[3]
Ordinatura? ordinaggine?? ordinità???
[4]
Solaris ha la versione standard (/usr/bin/sort) e quella speciale (/usr/xpg4/bin/sort), tanto per confondere le acque e costringere noi tapini a controllare tutte le volte il PATH.
[5]
Pensavi forse "uniche"? Beh, è più o meno la stessa cosa, a meno di una piccola opzione...
[6]
Last resort, nell'originale. Ma un po' di latino fa sempre bene.
[7]
Almeno per chi è stato scottato da cat.
[8]
Vedi chiosa [6].
[9]
Sono pigro, io.
.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.