bash, o come parlare con Linux


Nota: ho scritto questo testo nel lontano 1993. Adesso l'ho velocemente convertito in HTML togliendo i riferimenti alla e-zine per cui l'avevo scritto (Telematicus), ma non aggiornando praticamente nulla. Ad ogni modo, dovrebbe essere sufficientemente utile... - .mau.

Caro lettore, cara lettrice,
se stai leggendo questo testo probabilmente vuoi avere delle informazioni a riguardo di bash, la shell standard di Linux. Bene: qui troverai piu che altro una serie di pettegolezzi... ma ben mischiato ci saranno anche delle informazioni spero interessanti e/o utili. In bocca al lupo, e spero che almeno la lettura sia divertente.

Indice:

O quante belle shell, madama Doré!
I caratteri speciali ("di espansione")
I parametri
Il prompt
I comandi passati: la history
La ridirezione
Il job control
I comandi di bash
L'ultima parola: test

** O quante belle shell, madama Doré!

Innanzitutto, perché si parla di shell? Devi sapere che, oltre che conchiglia (e bomba!), la parola shell significa più generalmente "involucro". La shell si può perciò considerare l'involucro che ricopre il sistema operativo e con cui l'utente si interfaccia per fare le sue richieste. Tali richieste sono soddisfatte una per volta: è un classico esempio di linguaggio interpretato anziché compilato. Se usi il DOS, gli esempi più noti di shell sono COMMAND.COM e 4DOS.COM (che in realtà è un EXE... ma non dire in giro che te l'ho detto). Se usi un Macintosh, non esiste nessuna shell, probabilmente perché l'Apple ritiene che sarebbe troppo pericolosa per l'utente medio, costringendolo a usare delle applicazioni (cioè programmi compilati da qualcun altro).

Quando è nato Unix, la shell doveva principalmente servire a richiamare tutti i piccoli programmi di utilità che erano stati creati e permettere di automatizzare alcuni stupidi compiti. In principio c'era la shell, che si scriveva sh e si pronunciava Bourne Shell, dato che era stata creata dal signor Steve Bourne. Molti Veri Programmatori Unix insistono che se una persona deve scrivere un file di comandi, l'unico modo corretto è usare sh: è però vero che essa è piuttosto spartana, e vi mancano molte cose, tipo la possibilità di potere richiamare i comandi già digitati (persino il COMMAND.COM originale teneva in memoria l'ultimo comando!) e fare ritornare interattivo un programma che si era lasciato girare in background - una delle cose che fanno capire la bellezza di un sistema multitasking. È così capitato che a Berkeley si siano messi di buzzo buono a scrivere una nuova shell, che potesse essere programmata più o meno come in C. Per mancanza di fantasia, essa si chiama csh - pronunciato come sea-shell - ed è probabilmente ancora oggi la più popolare per l'uso interattivo. È vero che esiste un articolo "Programmare in csh è dannoso" che spiega tutte le deficienze e i bachi che vi si possono trovare, come del resto capita in tutti i programmi scritti da studenti universitari. È pure vero che è sì possibile richiamare ed editare i comandi precedenti, ma la cosa è riservata ai solutori più che abili; ma cosa volete: bisogna sapersi accontentare.

Negli ultimi anni, ci sono stati due filoni di sviluppo per le shell; csh ha generato tcsh, che ha finalmente riconosciuto la possibilità di usare i tasti cursore per richiamare i comandi precedenti, mentre da sh si sono sviluppate moltissime shell che cercavano di ovviare alle limitazioni della loro progenitrice e si distinguevano per aggiungere al nome una lettera qualunque dell'alfabeto. Abbiamo così shell da ash a zsh, passando per jsh (che aggiunge semplicemente il controllo di più processi da linea di comando) a ksh (la Korn shell, scritta da David Korn, che è compatibile con sh ma ha molte features della csh e la possibilità di editare le vecchie linee di comando alla tcsh. Tra l'altro, chi usa Solaris oramai se la trova come default).

E poi sono arrivati gli amici del GNU Project. Fedeli ai loro sponsor (i produttori di hardware che sono sempre contenti quando qualcuno esce fuori con un programma enorme che occupa considerevoli porzioni dei dischi rigidi e consuma milioni di cicli di CPU), e filosoficamente amanti di sh, hanno deciso di incrementarla con tutte le idee che erano loro venute in mente. È nata così la Bourne Again SHell, o bash per gli amici. Nella migliore tradizione GNU, ovviamente, bash è anche un gioco di parole; la parola significa più o meno "spaccare tutto" e magari l'hai anche vista scritta qualche volta nei vecchi telefilm di Batman, quando c'erano le lotte tra il Dinamico Duo e quei poveretti che dovevano sempre riciclarsi come "banditi vulgares" di questo o quel criminale.

** I caratteri speciali ("di espansione")

Bene. Come ti ho detto prima, una shell legge i tuoi comandi uno per volta e li esegue. Però in realtà comincia ad eseguire alcuni comandi prima ancora che tu inizi a scrivere qualcosa. Come nel DOS viene letto il file AUTOEXEC.BAT - o anche, se hai il buon gusto di lavorare con 4dos, 4DOS.INI - bash legge due files. /etc/profile contiene tutte le definizioni "generali", quelle cioè che il system administrator nella sua infinita saggezza ha stabilito debbano essere condivise da tutti gli utenti; ~/.bashrc, o in sua mancanza ~/.profile, è invece il file dove puoi mettere i tuoi comandi personali di inizializzazione. Come dici? non trovi nessun file ~ nella tua directory? Naturale! La tilde (~) è un'abbreviazione usata in csh e bash per indicare la tua "home directory", quella cioè da cui inizi tutte le tue sessioni e che è scritta nel file di password. È anche possibile indicare la home directory di un altro utente per mezzo della tilde; quella dell'utente pippo è ad esempio ~pippo.

La tilde è il primo esempio di carattere speciale, un carattere cioè che viene trattato in maniera particolare quando viene letto il tuo comando. Se uno vuole usare a tutti i costi la tilde come prima lettera di un nome di un file, può sempre farlo, precedendola da un backslash \ che funziona come carattere di escape oppure mettendo il nome del file tra virgolette "", o ancora col classico trucchetto di fare precedere il nome da "./". Dimenticavo: se hai una tastiera italiana ti troverai un pochino male, visto che backslash e parentesi quadre e graffe sono usate piuttosto spesso.

Ma a che servono tutti questi caratteri speciali? Uno dei loro scopi principali è il servire per l'espansione dell'input. Già che sono qui, vedo di mostrarti quali sono i vari tipi di espansione, a quali caratteri speciali corrispondono e cosa fanno. Comincerò con quelli di espansione dei nomi di file, che più o meno avrai già visto con l'MSDOS, giusto per non farti sentire troppo a disagio. Il punto interrogativo ? corrisponde a uno e un solo carattere; quindi ad esempio se io sto listando dei file (il comando ls è più o meno l'equivalente di DIR in DOS) e scrivo

 
      ls a?e 
nell'output potrebbe comparire ape, a=e e persino a e (anche se ci vuole del coraggio a creare un file con uno spazio all'interno del suo nome), ma so per certo che non potrà comparire né aeasse. L'asterisco * invece corrisponde a 0 o più caratteri: se avessi scritto "ls a*e" sarebbero stati considerati anche gli ultimi due nomi dell'esempio precedente. Attenzione! A differenza del DOS, dove il punto separa brutalmente le due parti di un nome e quindi il comando DEL * cancella in realtà solo i file senza estensione, in Unix il punto è un carattere (quasi) come tutti gli altri, e l'equivalente comando rm * cancella tutti i file della directory, tranne quelli che cominciano per punto (per convenzione, sono i files ausiliari, che quindi ha senso nascondere generalmente alla vista). L'ultimo tipo di espansione possibile è quella tra parentesi quadre; ad esempio,
      ls pippo.[0123456789]
elenca tutti i file che della forma pippo.C, dove C è una cifra. È anche possibile specificare un range di caratteri scrivendo il primo e l'ultimo separati da un trattino, sicché l'esempio sopra sarebbe scritto come ls pippo.[0-9]; se poi il primo carattere tra le quadre è ! oppure ^, vengono scelti tutti i caratteri non compresi nell'elenco: quindi,
      ls pippo.[^o]
mostrerebbe ad esempio pippo.c e pippo.h, ma non pippo.o.

Come ti avevo detto, l'espansione dei nomi dei file non è certo l'unica possibile sotto bash. Ti ho già accennato all'espansione data dalla tilde; è interessante sapere che se dopo la tilde scrivo un +, a questo viene sostituito il nome della directory dove sono in questo momento, mentre se scrivo un - viene usato il nome dell'ultima directory in cui sono stato. Esempio:

      bash$ pwd
      /opt/GCN/htdocs
      bash$ cd
      bash$ pwd
      /mau
      bash$ echo ~-
      /opt/GCN/htdocs
      bash$ echo ~+
      /mau
La forma ~- è utile quando ad esempio si stia scompattando un file .tar.gz. Non c'è nulla di più scomodo che scoprire che il file è stato creato a partire dalla root, e quindi occorre posizionarsi là e ricopiare tutto il path per indirizzare il nostro file... Vuoi mettere la comodità di scrivere tar xvzf ~-/file.tar.gz ? (D'accordo, tcsh ti fa fare lo stesso con pushd e =1, ma non facciamo guerre di religione).

Molto più simpatica è l'espansione con le graffe, che non esisteva in sh ed è stata copiata dalla csh. In pratica, se io scrivo a{d,b,c}e, ottengo la stessa cosa che se avessi scritto

      ade abe ace
(nota l'ordine: è proprio quello che ho scritto io, e non quello alfabetico). E a che serve tutto ciò? A evitare di complicarsi la vita. Se ad esempio ho deciso di farmi una copia del file questo_e_un_file_dal_nome_inutilmente_lungo, basta che scriva
      cp questo_e_un_file_dal_nome_inutilmente_lung{o,o.bak}.

È poi possibile valutare una (semplice) espressione aritmetica con la forma $[espressione]; oppure sostituire il risultato di un comando con la forma $(comando) o la più nota `comando`. Posso ad esempio scrivere

      echo 'Data e ora in questo momento:' `date`
e ottenere a terminale
      Data e ora in questo momento: Mon Jan 20 17:05:23 MET 1997

(ah, non ti avevo detto che Unix sa esattamente quando c'è da cambiare l'ora legale e fa tutto lui? la prima volta ci sono persino rimasto un po' male... e la cosa più bella è stato vedere nel 1996 che Windows95 pensava di fare la stessa cosa, e ha ciccato clamorosamente il giorno di ritorno all'ora solare)

** I parametri

Il dollaro $ serve in genere per indicare l'espansione di qualcosa: $[] espande l'espressione aritmetica tra le quadre, $() espande il comando tra le tonde, e ${} espande il parametro tra le graffe. Innanzitutto, che cos'è un parametro? È l'equivalente di una variabile; gli si assegna un valore con l'istruzione

      par=valore
(di per sé si può anche evitare di assegnare un valore, se tutto quello che ci interessa è un toggle, cioè qualcosa di cui vogliamo semplicemente controllare l'esistenza) e lo si può utilizzare scrivendo ${par}, o semplicemente $par se subito dopo c'è uno spazio o un carattere di interpunzione: se insomma bash può capire dove finisce il nome della variabile.

Esistono alcuni parametri speciali, tipo $0 che sta per il nome del programma che si sta eseguendo, $1, $2, ... per i parametri del file, o $$ per il numero del PID della shell (vale a dire il numero del processo UNIX associato a questo lancio del programma - comodo per creare dei nomi di file temporaneo univoci, come /tmp/$$). Esistono alcune variabili che bash ha già settato automaticamente e che sono più o meno utili: PWD indica l'attuale directory, OLDPWD l'ultima che si è usata, PATH è più o meno come il PATH del dos, noclobber permette di non ricoprire inavvertitamente i file presenti sul sistema; ma esiste anche SECONDS che dice quanti secondi sono passati dall'attivazione della shell.

E in ogni caso si possono fare cose di alta scuola con i parametri, soprattutto se uno ama avere la rete di sicurezza di un valore di default da usare quando il parametro non esiste. Ad esempio, ${par:-word} significa usare il valore di par, se esiste, o altrimenti word; oppure ${par:?msg} scrive sullo standard error (sì, te lo spiego che cos'è lo standard error, stai tranquillo!) il messaggio msg se il parametro par non è stato settato.

Già che ci sono, una cosa che non c'entra nulla: perché i nomi delle variabili sono scritti generalmente in maiuscolo? Perché è una simpatica convenzione per indicare che stiano nell'environment (ambiente, per gli amici che non parlano l'inglese), e quindi passati ("ereditati") alle sottoshell, che li potranno utilizzare a piacere. Non però che ciò venga fatto automaticamente... occorre infatti scrivere il comando magico export PAR che in genere, per non sbagliarsi, viene messo nella stessa riga dell'assegnazione, qualcosa come

      EDITOR=/usr/ucb/vi ; export EDITOR

      export EDITOR=/usr/ucb/vi
(vi è il Vero Editor di chiunque usi un sistema operativo il cui nome termini in X; prima o poi te ne parlerò con voluttà). Le formule sopra sono equivalenti: la prima però si può anche usare con la semplice sh.

** Il prompt

Sono stato pesante, lo so. Per riposarci un pochino, adesso ti parlo del prompt, quella cosa che la shell si ostina a scrivere tutte le volte che termina di eseguire un comando. Come default, bash usa bash$, che non è proprio il massimo della vita. Come nel dos, si può personalizzare il prompt a piacere, usando la variabile di ambiente PS1; esistono anche PS2 che viene usata quando si sta facendo un ciclo for, PS3 usata durante una select, e PS4 usata durante un'esecuzione passo-passo, ma non complichiamoci la vota. All'interno di queste variabili, si possono anche usare delle combinazioni speciali di caratteri, che iniziano tutte con un backslash, che vengono interpretate ogni volta: abbiamo così

\t
l'ora (t sta per time)
\d
la data (date)
\n
un newline, cioè a capo
\s
il nome della shell (senza il path)
\w
l'attuale directory (working dir)
\W
l'ultimo elemento dell'attuale directory: ad esempio, se sei su /usr/local/lib/tex/inputs, \W vale inputs
\u
lo username
\h
l'hostname
\#
il numero progressivo del comando che verrà eseguito
\!
il numero del comando nel file di storia (utile per richiamarlo, vedi sotto)
\$
# se l'utente è root, $ altrimenti. In genere lo si usa come ultimo carattere del prompt.
\nnn
il carattere di valore ottale \nnn. Prova ad usare \007 !
\\
un backslash

Esempio rapido: un simpatico prompt potrebbe essere creato con

      PS1='\u /\W (\#) \$ '
che mi dà come risultato
      mau /cho (7) $
ma naturalmente se si hanno cicli di CPU da sprecare ci si può complicare la vita aggiungendo dei comandi all'interno della stringa di prompt. Mi sono divertito a provare
     PS1='(`echo $SECONDS/3600|bc`:`echo $SECONDS/60%60|bc`:
	     `echo $SECONDS%60|bc`) \$ '
che ovviamente è lentissimo, ma in un certo qual senso divertente. Hai scoperto qual è il risultato? Se proprio ti piace usarlo, però, meglio che te lo riscriva come
     PS1='($[SECONDS/3600]:$[SECONDS/60%60]:$[SECONDS%60])\$ '

** I comandi passati: la history

Avevo detto nell'introduzione che uno dei grandi vantaggi di bash rispetto alla Bourne Shell sta nel potere usare i tasti cursore (le freccine) per vedere i comandi passati ed eventualmente modificarli. Esiste però anche un altro modo per fare tutto ciò, usando la cosiddetta "history". In questa modalità, prima si sceglie il comando da usare e poi lo si modifica per i nostri scopi. Ad un primo approccio, la cosa può sembrare ben più complicata: ma una volta che ci prendi l'abitudine scoprirai che le modifiche più semplici - che poi sono quelle che fai più spesso - sono più veloci del dovere ricercare il comando con le freccette e modificarlo!

La history utilizza il carattere ! per indicare i comandi memorizzati nella sua storia. Questo ha importanza anche se non intendi affatto usare queste espressioni. Infatti, se ti serve scrivere davvero un punto esclamativo, devi farlo precedere da un backslash \, altrimenti verrà preso come indice di una sostituzione a partire dalla history.

Cominciamo a vedere come si ricerca un comando. Per indicare il comando numero n, si usa !n. Se si vuole partire dal fondo, si usa !-n. Per comodità, invece di !n-1, l'ultimo comando eseguito si può indicare con !!, o anche solo ! se vi si aggiungono altri modificatori. Infine !string si riferisce all'ultimo comando che iniziava con la i caratteri string (cosa molto utile se si vuole di nuovo lanciare il file /questo/e/un/path/lungo/con/nome.lunghetto), e !?string? all'ultimo comando che conteneva al suo interno i caratteri string.

Una volta trovato il comando, si può usarne una sola parola: basta aggiungere i caratteri :n, dove n è la posizione della parola nel comando. Il nome dell'ultimo comando è così !:0 e si hanno anche le abbreviazioni simil-vi ^ equivalente a :1, $ per l'ultima e * per tutte meno la zeresima, cioè per gli argomenti del comando precedente. L'abbreviazione di gran lunga più usata è !$: è comunissimo ad esempio scrivere una serie di comandi tipo

      bash$ grep pippo telem035.txt
          (output del comando)
      bash$ vi !$
per editare il file telem035.txt. Alle volte si va avanti per cinque o sei comandi a forza di !$ ...

Infine si può anche modificare la parola prescelta, con i modificatori seguenti, anche loro preceduti dal magico :. Per spiegare meglio cosa succede, immaginiamo di avere la "parola" /home/mau/testi/telem035.txt e vediamo cosa succede.

:h
lascia la "head", cioè tutto meno il nome del file. Otteniamo /home/mau/testi
:t
toglie il pathname, lasciando la "tail". Otteniamo telem035.txt
:r
rimuove il suffisso (la parte dopo il punto), lasciando la "root". Otteniamo /home/mau/testi/telem035
:e
rimuove tutto tranne il suffisso, lasciando l'"end". Otteniamo txt

Esiste anche il "modificatore" :p, che stampa semplicemente ("print") il comando, ma non lo esegue. E a che serve, allora? si può usare la freccia in su per richiamare il comando, e poi editarlo a manina... Ah già, non ho ancora detto come si fa. Non è difficile: le frecce spostano il cursore, e poi si scrive. Ci sono poi una trentina di sequenze di escape che permettono di fare praticamente tutto: io non le ricordo assolutamente. Le uniche più o meno utili, o almeno che io ho imparato, sono ctl-A per posizionarsi all'inizio della riga, ctl-E per posizionarsi alla fine, tab per cercare di completare la parola che si stava scrivendo. Bash cerca tutti i nomi che iniziano con quanto già scritto: i nomi sono scelti tra quelli nella directory indicata oppure tra gli eseguibili nel PATH, nel caso si stia scrivendo la prima parola del comando. Se non si può completare in maniera univoca il nome - ad esempio sto scrivendo vi b<TAB> e ho i file bash.txt, bash2.txt e jarg035.txt - bash completerà per quanto possibile scrivendo vi bash, e mi farà suonare il campanello. Premendo ancora una volta <TAB>, vedrò tutti i possibili completamenti.

In questo caso è utile settare la variabile FIGNORE, che conterrà una lista di suffissi di cui a noi non frega nulla. Ad esempio uno può avere

 FIGNORE=.o:.aux
Se ora abbiamo i files pippo.c e pippo.o, scrivere vi p<TAB> darà come risultato direttamente pippo.c, ignorando l'altro. Se però l'unica scelta possibile fosse stata pippo.o, allora il completamento sarebbe stato fatto ignorando FIGNORE!

** La ridirezione

Passiamo ora al "lavaggio del cervello"... scusa, la ridirezione dei file. Cominciamo dall'inizio. Un qualunque programma Unix parte sempre con tre "file" aperti. Questi sono lo standard input, cioè il posto in cui stanno i comandi da eseguire, lo standard output, dove viene scritto tutto quello che il programma dà come risultato, e lo standard error, che conterrà tutti gli errori incontrati nell'esecuzione del programma (sì, lo so che tu errori non ne fai mai, ma metti che un raggio cosmico abbia colpito la tua RAM mentre eseguivi il programma...)

Ti ho scritto "file" tra virgolette perché, se non te ne fossi ancora accorto, il termine sotto Unix ha un significato piuttosto ampio: esempio classico è /dev/null, che si mangia tranquillamente tutto l'output e ritorna dei bellissimi input vuoti di nulla. In ogni caso, all'inizio non ci sono mai problemi; tutti e tre i file sono associati al tuo terminale, e l'unico modo di distinguerli è dato dal loro descrittore, che almeno inizialmente vale 0 per lo stdin, 1 per lo stdout e 2 per lo stderr, nomi abbreviati in puro stile Unix.

Probabilmente non ti aspetterai che, se vuoi che il programma prog usi pippo come input file e pluto come standard output, basta scrivere

      bash$ prog < pippo > pluto
Ma è come il DOS! Beh, effettivamente quando il DOS 3.0 ha introdotto la ridirezione, ha allegramente scopiazzato da Unix. In effetti anche (anzi, già inizialmente!) in Unix esiste il carattere di pipe | , che immagino conoscerai benissimo e di cui non ti parlo in questa sede. Ma ovviamente abbiamo di più. Ad esempio, vogliamo ridirigere lo standard error sul file paperino? Immediato, basta modificare il comando in
      bash$ prog < pippo > pluto 2> paperino

Visto a cosa serve sapere qual è il numero descrittore del file? Tra l'altro, questo vale anche per l'input, anche se è difficile immaginare a cosa possa servire scrivere prog 5< mydata.txt, a meno che tu non abbia un programma in FORTRAN66. Nota: se la variabile noclobber è settata, bash si arrabbia nel caso che tu cerchi di ridirigere l'output su un file che esiste già. In questo caso, puoi utilizzare la forma >| che ti permette di scriverci sopra senza problemi esistenziali.

Altri casi utili sono quello dell'append, se si vuole aggiungere il nuovo testo al file già presente, e ottenuto con >>; e la ridirezione congiunta di stdout e stderr, utile quando si lancia il programma in batch e si vuole avere un controllo a posteriori di tutto quanto è successo. (Ah, non c'entra nulla ma magari ti può interessare. Hai mai provato a lanciare il comando tail -f nome_file per vedere in diretta cosa si sta scrivendo sul file?) In questo caso si hanno due forme equivalenti, >& e &>.

Una cosa non immediata, ma in realtà potentissima è poi il concetto di here-document, il documento "sul posto". Se uno sta scrivendo uno script file e deve lanciare un comando, può essere utile infilare l'input di un comando all'interno dello script stesso. Ma come fa allora la shell a capire dove finisce l'input e dove ricomincia lo script? Semplice. Si scrive a un certo punto

      bash$ prog << fine_input
Qualunque parola può andare bene al posto di "fine_input": in genere si scelgono scritte buffe tipo EnDoFfIlE_ per la ragione che si vedrà adesso. Da questo punto in poi, tutte le linee di testo sono considerate "appartenere" al programma prog, fino a che non se ne trova una che sia esattamente "fine_input". A questo punto la shell ritorna a leggersi lei le righe di comandi. Ecco perché la parola delimitante deve essere strana: non si vuole trovarsela per sbaglio all'interno dell'input del programma!

Due finezze possibili: se la parola di delimitazione è scritta tra virgolette, allora l'espansione dei parametri si blocca: quindi per esempio $1 rimane $1 e non viene espanso al primo parametro della linea di comando. Se poi invece di << usi <<-, allora tutti i tab iniziali vengono tolti. In questo modo tu puoi indentarti a piacere il tuo testo e ottenere un bell'aspetto grafico per il tuo script (e non ridere, è più utile di quanto sembri). Una tristezza: tutto questo non vale se il programma usa la rete, come telnet o ftp. Infatti in questo caso ci sono i socket che si mettono di mezzo, mi hanno detto, e l'input si perde da qualche parte. Confesso la mia ignoranza a proposito, dovrò studiare bene la faccenda.

L'ultima possibilità di ridirezione da considerare è <>, che serve per ridirigere sia input che output: se lo si precede da un numero, come in 5<>filename, si usa il file con quel descrittore, altrimenti gli standard input e output.

Abbiamo poi la possibilità di duplicare (e chiudere) i descrittori, usando gli operatori <& e >&. Più precisamente, newfd<&oldfd duplica il file descriptor di numero oldfd su quello numero newfd (oppure sullo 0, se newfd è omesso). Se invece di scrivere oldfd si usa il trattino - , allora il file di numero newfd viene chiuso. Similmente per >& dove l'unica differenza è che il valore di defaultin questo caso è 1.

Ricordati anche che le ridirezioni vengono lette da sinistra a destra. Perciò le due forme

      ls > dirlist 2>&1
      ls 2>&1 > dirlist
fanno due cose diverse. La prima manda sia stderr che stdout sul file dirlist, mentre la seconda manda solo lo stdout su dirlist, perché lo stderr è stato già ridiretto da un'altra parte.

** Il job control

In confronto a tutto quello sproloquio di ridirezioni, il job control (o controllo dei programmi) è di una facilità assurda. Innanzitutto sappiamo entrambi che Unix è un sistema multitask: un corollario è che dal tuo terminale puoi fare girare contemporaneamente più programmi. Ma naturalmente tu hai bisogno di stabilire a quale di questi programmi vada il tuo input, e quale possa scrivere l'output; e qui nasce il concetto di job control. In ogni istante tu hai un processo in foreground, cioè che interagisce con la tastiera, e zero o più processi in background, che se ne stanno a girare per conto loro. Ci sono tre sistemi per mettere un job in background: lanciarlo da linea di comando, oppure digitare ctl-Z o ctl-Y mentre questo gira. La differenza tra questi ultimi due modi è che nel primo caso il processo si blocca immediatamente, mentre nel secondo continua a girare fino a che non richiede dell'input. In ogni caso apparirà prima o poi una linea del tipo

       [1] 4576
Il numero tra parentesi quadre è quello che bash ha assegnato al comando per proprio uso, mentre l'altro è il numero del processo Unix. A questo punto, bash aspetta le nostre istruzioni: possiamo rimettere il processo in foreground scrivendo fg, oppure possiamo declassarlo a background scrivendo bg. Come possiamo sapere quali comandi stanno girando dal nostro terminale? Ma è banale: il comando jobs li mostra tutti, ciascuno con il proprio numero di comando. Inoltre, l'ultimo che è stato stoppato ha vicino il carattere + ed è quello di default; il penultimo ha il carattere -.

Questi numeri ci servono per indirizzare direttamente i comandi: basta precederli con un percento % . Quindi, kill %1 cancella il comando numero 1 della lista. Attento a non digitare kill 1, che se lanciato da root butta giù la macchina!

** I comandi di bash

Vediamo infine alcuni dei comandi che bash comprende direttamente. Non scrivo molto, perché il loro uso è abbastanza intuitivo: solo che trovare la loro descrizione non è così semplice.

:
non fa nulla: in realtà espande i parametri scritti dopo di esso, ma visto che non li usa, che te ne fai? Lo puoi vedere come altro carattere di commento, assieme a #.
. filename (oppure source filename)
legge i comandi contenuti nel file filename.
alias
se usato da solo elenca la lista degli alias presenti; seguito da un nome, (alias pippo) indica l'eventuale alias associato a quel nome; nella forma alias nome=valore, fa sì che tutte le volte che si scrive nome viene sostituito valore.
break [n]
esce da un loop for, while o until. Se n è specificato (deve essere >=1), si esce per n livelli di innestamento. Similmente, continue [n] smette di eseguire tutte le istruzioni fino al termine del primo (dell'ennesimo) ciclo, senza però uscire ma riprendendo dal suo inizio. Proprio come quando si programma in C, insomma.
declare e typeset
sono equivalenti, e servono entrambi a dichiarare una variabile. Ma perché, ti chiederai? Non basta scrivere var=valore? Perché possiamo aggiungere delle opzioni. -r definisce la variabile readonly (non si può ridefinirla); -x la esporta automaticamente; -i la definisce come un numero intero.
pushd directory, popd e dirs
servono per avere uno stack (una pila) di directory. Nel primo caso, ci si sposta sulla directory indicata ma si spinge giù per lo stack quella dove ci si trova al momento. Nel secondo caso, si toglie dallo stack l'ultima directory che si era messa, e ci si sposta là. Con dirs puoi vedere qual è al momento lo stack. In realtà ci puoi anche pacioccare un po', modificando l'ordine presente, ma non ho voglia di spiegartelo.
echo
manda in output i suoi argomenti, dopo avere fatto tutta la sostituzione dei parametri. Ci sono due opzioni interessanti: con -n non viene scritto il linefeed finale, e quindi non si va a capo e si può continuare a scrivere con altre echo; con -e vengono interpretati alcuni alcuni caratteri speciali preceduti da un backslash \, più o meno come nella printf() del C.
enable -n name
disabilita (!) il comando name interno a bash. (Cioè, -n significa "no"!) Utile se ad esempio si vuole usare /bin/test invece che il test di bash.
exec command
termina bash e fa partire il comando command; se si vuole terminare la shell senza remore (o quasi...) i comandi da usare sono exit oppure bye.
export var1, var2, ...
serve ad esportare le variabili indicate nella linea di comando, in modo che rimangano nell'"ambiente" della shell e anche i comandi lanciati da essa le conoscano.
history
mostra la storia dei comandi passati, in modo che se ne possa richiamare uno per mezzo del suo numero, ad esempio. Se si vuole, si può anche salvare la storia ancora più passata, cioè i comandi eseguiti le ultime volte che ci si è collegati. Per default, infatti, vendono salvati gli ultimi 500 comandi interattivi! C'è persino la possibilità di editare il file che li contiene, con il comando interno fc.

** L'ultima parola: test

Prima ho accennato al comando test: mi sembra un ottimo modo per terminare questa rapida esposizione di bash. Il comando si presenta in due forme equivalenti: test expr , oppure [ expr ] (mi raccomando, lascia gli spazi prima e dopo le parentesi quadre!) e si occupa di fare un test sull'espressione condizionale. Il risultato del comando è 0 se il test corrisponde al vero e 1 se è falso: in questo modo si può scrivere qualcosa del tipo

      bash$ if [ -d /usr/local/telem ]
      > grep ".mau." /usr/local/telem/telem???.txt
(ehi! visto il prompt di secondo livello?). Alcuni operatori utili:

Bene, questo termina la mia minirassegna su bash. E per tutto il resto? Beh, puoi sempre chiedere aiuto in giro o meglio ancora leggerti la man page ("pagina"? no, sono almeno 60 pagine. Forse stampandola si riduce un po', però) su bash (ghigno)...

.mau.