\nuovoarticolo{Gabriele Falcioni}{Conchiglie e Molluschi, ovvero: Shell \& CLI-Only
Programs}{%
Gabriele Falcioni \\
Via E.~Cialdini 50 \\
I-60122 Ancona AN \\
Tel.:~(071)205124
}{%
}{italian}{%
{\em Meekness}, n.: Uncommon patience in planning a revenge that is worth
while.}{Ambrose Bierce}

%\section*{Conchiglie e Molluschi, ovvero: Shell \& CLI-Only Programs}

%	Gabriele Falcioni

\sez{Introduzione}

A partire dalla Release 2\footnote{Per {\em Release 2\/} il sottoscritto intende il Kickstart versione 37.175
in quanto, per i nostri scopi, molte funzioni hanno cominciato a esser tali
soltanto a partire dalla V37 e, a esser franchi, altre necessitano almeno
della V39$\ldots$} del sistema operativo il programmatore medio\footnote{Si
dice che la Setta dei {\em Real Programmers\/} abbia fatto numerosi proseliti tra le
file degli sviluppatori \amiga{} e che costoro, ancor oggi, si divertano a
produrre software che funzioni a partire dal Kickstart 1.0. La leggenda vuole che
alcuni di loro si nascondano, sotto falso nome, tra le file del CATS ¶{:-)}.}
\amiga{} ha finalmente trovato un ambiente molto confortevole per sviluppare
utility per la Shell. In effetti, una delle librerie che hanno subìto una
pesante\,---\,e quanto mai necessaria\,---\,revisione è proprio la
¶{dos.library}, che ha beneficiato di una totale riscrittura in linguaggi
meno eccentrici (bye bye BCPL!) e si è arricchita di nuove, pratiche funzioni.

Molti programmi, in particolare utility DOS (``CLI-only'') di vario genere che
richiedono la manipolazione di file, possono ora fare a meno delle librerie
ANSI C e appoggiarsi quasi esclusivamente alle funzioni contenute nella ROM.
Per effetto di questa ottimizzazione è possibile ridurre notevolmente le
dimensioni del codice eseguibile, diminuendo così lo sfruttamento di RAM e
memoria di massa.

Nel seguito vedremo come usare una serie di moduli appositamente
sviluppati per essere impiegati in utility di questo genere, oltre ad alcuni
esempi concreti di utilizzo. Tutto questo è stato realizzato sfruttando le
funzioni offerte dalla nuova ¶{dos.library}

\sez{Il codice di startup}

\sottosez{Considerazioni generali.}

La maggior parte delle utility DOS è scritta in C e necessita di
un modulo di startup che inizializzi l'ambiente di esecuzione e attivi
tutti quegli extra offerti dal proprio sistema di sviluppo.
In alcuni casi il codice di startup diviene ridondante: un esempio possibile
è costituito dalle utility funzionanti solo a partire dalla Release 2, nelle
quali l'analisi degli argomenti del programma può essere eseguita mediante la
funzione ¶{ReadArgs()} della ¶{dos.library}, rendendo inutile il codice di
startup dedicato a tale funzione.

Analogamente, inizializzare le librerie ANSI per l'I/O standard è uno spreco
di codice tanto più sentito quanto più breve è la lunghezza del programma.

Esistono casi in cui il modulo di startup è vitale. Il SAS/C offre, tra i
vari extra, la possibilità di generare codice puro\footnote{Codice puro nel
senso inteso dalla Shell, ovvero che può essere aggiunto alla lista dei
comandi residenti.} con estrema semplicità. Tale caratteristica
è altamente desiderabile in una utility shell, soprattutto se questa sarà
comunemente usata in script AmigaDOS. Il modulo di startup del SAS/C è però
costruito per funzionare a partire dalla V33, allo scopo di inizializzare
tutta una serie di servizi accessori offerta dal compilatore. Nel caso
abbiate bisogno di un modulo di startup minimale, che funzioni dalla V37 e
che supporti codice puro, è il caso di considerare la possibilità di usare
¶{shellcres2.o}.

\sottosez{Il modulo ¶{shellcres2.o}.}

In base alle considerazioni precedenti è nato ¶{shellcres.o}, un modulo di
startup particolarmente vantaggioso in caso il programma che lo utilizza soddisfi
i seguenti requisiti:

\begin{enumerate}
\item Il programma
può essere lanciato solo da Shell. Il codice per trattare il caso Workbench
è stato eliminato in base alle considerazioni secondo cui un programma
privo d'icona non deve essere lanciato dal suddetto (e normalmente non
succede), e un programma che deve essere eseguito da Workbench non
si avvantaggia dell'essere aggiunto alla lista dei comandi residenti
(oltretutto il Workbench non usa detta lista).
\item Il programma richiede, per
funzionare, la versione V37 o superiore della ¶{dos.library} e si basa sulla
¶{ReadArgs()} e sulle nuove funzioni di I/O per recuperare i suoi argomenti e per
le comuni necessità di I/O. Questo modulo di startup apre automaticamente la
¶{exec.library} e la ¶{dos.library} V37+, o fallisce con un codice di errore
AmigaDOS 122 (¶{ERROR\_INVALID\_RESIDENT\_LIBRARY}).
\item Il programma è breve e si
avvantaggia della possibilità di essere aggiunto alla lista dei comandi
residenti della Shell. Questo modulo di startup è lungo circa 200 byte e
supporta la tecnica elegante adottata dal SAS/C per rendere puro il codice,
compatibilmente con ¶{cres.o}.
\end{enumerate}

Per evitare conflitti ed errori nell'uso del modulo di startup, è stata
ridefinita l'interfaccia tra il suddetto e la funzione di partenza del
programma utente:

\begin{verbatim}
/* user code entry point */
void __stdargs early_main(void);

/* exit code entry point */
void late_exit(long code);
\end{verbatim}

Si noti che:
\begin{enumerate}
\item Nessun parametro viene passato alla funzione principale. Tutto ciò che
interessa dell'ambiente di esecuzione è ricavabile tramite opportune
chiamate alla ¶{dos.library}.
\item La funzione per terminare il programma supporta il passaggio dei
parametri sia nello stack che nei registri (opzione ¶{PARMS=register}
del SAS/C); il parametro ¶{code} verrà usato come valore di ritorno del
programma. Terminare la ¶{early\_main()} con return equivale a
¶{late\_exit(0)}.
\item Il codice di questo modulo è veramente minimale. Poiché ne viene
distribuito il sorgente, chiunque sarà in grado di adattarlo alle sue
particolari necessità; un esempio potrebbe essere quello di aggiungere il
supporto per i costruttori e i distruttori, così come implementati nel SAS/C
6.X.
\end{enumerate}

Naturalmente questo modulo è progettato per l'utilizzo con il compilatore
e il linker del SAS/C. Gli utenti degli altri sistemi di sviluppo devono
necessariamente rifarsi ai moduli di startup distribuiti con il Developer
Kit della Commodore-\amiga{}, i quali usano una tecnica alternativa.

Come semplice esempio di applicazione di questo modulo si può esaminare il
programma ¶{WildStar}, una semplice utility per alterare e controllare
l'abilitazione della wildcard ``*'' nelle nuove funzioni di pattern
matching della ¶{dos.library}.

\sez{Selezione di file e espressioni regolari}

\sottosez{Considerazioni generali.}

Consideriamo l'ampia \linebreak classe di utility costituita dai manipolatori di file (in
particolare i filtri). Generalmente, tali programmi compiono delle operazioni
su uno o più file, generandone quindi altri contenenti i risultati. Alcuni riproducono
nella directory destinazione l'albero completo delle subdirectory in cui si trovano
i file sorgenti.

Il problema comune a questi programmi è quello della selezione dei file
su cui agire e quello di replicare interi alberi di directory.
Poiché è nostro interesse offrire soluzioni simili a quelle usate
nei comandi di sistema allo scopo di mantenere una certa coerenza nell'interfaccia
utente e nel comportamento del software, le nostre necessità possono essere
formalizzate come segue:

\begin{itemize}
\item Il template sarà del tipo ¶{FILE/M,TO/K,ALL/S}.

\item ¶{FILE/M} è un elenco di file e/o directory che rappresentano
l'insieme dei file di input. Possono essere espressioni regolari in
stile AmigaDOS.

\item ¶{TO/K} è il file o la directory (nel caso di più file) destinazione.
In caso sia richiesto, il programma deve essere in grado di generare un
appropriato albero di subdirectory all'interno della directory destinazione.
Per i programmi più semplici questo parametro potrebbe non essere richiesto,
e la funzione di replica dell'albero delle directory non sarebbe necessaria.

\item ¶{ALL/S} è uno switch usato per far includere nella selezione dei file di
input tutti quelli contenuti nelle subdirectory delle directory specificate.

\item Poiché ci interessano i file, le directory saranno trattate come
semplici contenitori di file e ignorate nella maggior parte dei casi.

\item Il nome di una directory che non contiene espressioni regolari è
considerato sinonimo di ``tutti i file della directory''. Esempio:
specificando ¶{pippo} otterremo ¶{pippo/\#?}.

\end{itemize}

Particolarmente utile per i nostri scopi è la serie di funzioni offerta
dalla nuova ¶{dos.library}. Tra queste troviamo ¶{MatchFirst()} e
¶{MatchNext()}. Per loro tramite è facilmente risolvibile il problema di
rintracciare una serie di file data una espressione regolare che li descrive;
sfortunatamente, a causa di una carente documentazione nell'uso dei flag di
controllo,\footnote{Il sottoscritto è stato incapace di adoperare il famigerato flag
¶{APF\_DOWILD}; dopo un po' di prove l'unico risultato ottenuto è stata la
perdita di una certa quantità di memoria (?). Sarei ben lieto che qualcuno
riuscisse a smentirmi$\ldots$} risulta non banale l'implementazione dell'opzione ¶{ALL/S}. Per
replicare un intero albero di directory la ¶{dos.library} offre inoltre
solo un punto di partenza.

Per risolvere tale problema è stata sviluppata la libreria ¶{filefinder.lib}.

\sottosez{La ¶{filefinder.lib}.}

Questa libreria contiene un certo numero di funzioni che consentono un'agevole
implementazione del modello di selezione di file precedentemente indicato.

Il contenuto della libreria può essere diviso in due gruppi distinti. Un gruppo di
funzioni implementa la selezione di file, mentre l'altro supporta la replica di interi
alberi di directory.

%Di seguito sono elencati, per ogni gruppo, i prototipi delle funzioni
%definite. Alcune di esse sono, al momento, implementate come macro.
%
%· Ricerca di file propriamente detta (modulo FileFinder):
%
%struct FileFinder *CreateFileFinder(Tag, ... );
%struct FileFinder *CreateFileFinderA(
%	struct TagItem *);
%VOID DeleteFileFinder(struct FileFinder *);
%
%VOID SetFileFinderArgs(
%	struct FileFinder *,Tag, ... );
%VOID SetFileFinderArgsA(
%	struct FileFinder *,struct TagItem *);
%
%BOOL FindFiles(struct FileFinder *);
%
%struct AnchorPath *GetAnchorPath(
%	struct FileFinder *);
%UBYTE *GetFoundFileName(struct FileFinder *);
%struct FileInfoBlock *GetFoundFileInfoBlock(
%	struct FileFinder *);
%BPTR OpenFoundFile(struct FileFinder *,LONG);
%BPTR LockFoundFile(struct FileFinder *,LONG);
%
%· Replica di alberi di directory (modulo TreeFilter):
%
%struct TreeFilter *CreateTreeFilter(Tag, ... );
%struct TreeFilter *CreateTreeFilterA(
%	struct TagItem *);
%VOID DeleteTreeFilter(struct TreeFilter *);
%
%BOOL FilterTree(struct TreeFilter *);
%
%BPTR GetSourceFH(struct TreeFilter *);
%BPTR GetTargetFH(struct TreeFilter *);
%UBYTE *GetSourceName(struct TreeFilter *);
%UBYTE *GetTargetName(struct TreeFilter *);

Entrambi i moduli consistono di un costruttore e un distruttore per la struttura
di controllo, una funzione principale, delle funzioni di supporto per estrarre i risultati
della funzione principale e altre funzioni secondarie.

\sottosez{Il modulo FileFinder.}

Questo modulo offre una semplice interfaccia per cercare uno o più file i
cui pathname possono contenere espressioni regolari in stile AmigaDOS. L'uso
del modulo si riduce alla creazione della struttura di controllo con gli opportuni
parametri mediante la funzione ¶{CreateFileFinderA()}, l'iterazione della funzione
¶{FindFiles()} e, al termine, la deallocazione della struttura di controllo con
¶{DeleteFileFinder()}.

La funzione ¶{CreateFileFinderA()} è basata su tag list e quindi facilmente espandibile.

Quando ¶{FindFiles()} segnala di aver trovato un file, il codice utente può ricavarne
il pathname, avere accesso al ¶{FileInfoBlock} per il file in questione, ottenere un
¶{FileLock} o un ¶{FileHandle} su di esso.

In caso di problemi è possibile ricavare il codice di errore da una
chiamata a ¶{IoErr()}. Se è richiesto uno speciale controllo sulle directory da
esaminare, il modulo può chiamare un ¶{Hook} utente per modificare a piacimento
la strategia di analisi delle directory incontrate. Per ulteriori dettagli
sui tag accettati si vedano i relativi autodoc.

Come semplice esempio di applicazione del modulo si veda ¶{WordCount}, una
riedizione del popolare tool per il conteggio di caratteri, parole e linee
di file ASCII. In questa versione, ¶{WordCount} supporta file multipli e il
calcolo dei totali; questo è utile, ad esempio, per gareggiare con i propri
amici a chi sviluppa il progetto con il sorgente più lungo ¶{:-)}.

Un altro esempio interessante è ¶{LibMan}, un programma più complesso del
precedente. Si tratta di un manager di shared library e device; tra le varie
opzioni permette di aggiungere nuovi moduli alle liste di sistema, rimuovere
quelli non usati, rimpiazzare i vecchi con i nuovi, mostrare a video la
lista dei moduli caricati in memoria sia in formato breve che lungo
(completo). Tutto questo può essere fatto usando sia nomi multipli che
espressioni regolari. Il modulo FileFinder è stato usato per pilotare la
ricerca su disco dei moduli da caricare in memoria. Come questo programma
dimostra, la flessibilità del modulo FileFinder è tale da trovare
interessanti applicazioni anche al di fuori del ristretto ambito dei file
processor.

\sottosez{Il modulo TreeFilter.}

Questo modulo offre una semplice interfaccia per replicare sulla directory
destinazione l'albero delle directory che contengono il file di input, prima
di aprirvi il corrispondente file di output. L'uso del modulo è analogo a
quello del FileFinder e consiste nella creazione della struttura di controllo
mediante ¶{CreateTreeFilterA()}, iterazione di ¶{FilterTree()}, deallocazione della
struttura di controllo con ¶{DeleteTreeFilter()}.

Quando ¶{FilterTree()} segnala di aver trovato un file, il codice utente può
avere accesso ai pathname e/o ai ¶{FileHandle} dei file sorgente e destinazione.

In caso di problemi è possibile ricavare il codice di errore da una chiamata
a ¶{IoErr()}. Il modulo accetta un ¶{Hook} utente (opzionale) per comunicare al
codice utente ogni singolo passo compiuto dalla ¶{FilterTree()}; la sua utilità
immediata è quella di poter stampare sulla console l'albero dei file trovati. Per
ulteriori dettagli sui tag accettati si vedano i relativi autodoc.

	Come esempio di applicazione del modulo si veda ¶{Tab}, un filtro per la
compressione o l'espansione di tabulazioni, un programma vitale per chi,
come me, usa nel proprio editor tabulazioni tutt'altro che
standard.\footnote{Per la cronaca, tutti i sorgenti citati in queste dolenti note
contengono tabulazioni di lunghezza 3; se usate una lunghezza 8 ora sapete
come convertire al volo le tabulazioni in innocui spazi$\ldots$} Il
programma può convertire direttamente interi alberi di file, cosa che fa
risparmiare tempo e fatica. Un'altra caratteristica interessante è la
capacità di riconoscere le stringhe di un linguaggio e di lasciarle intatte
quando si opera la compressione di spazi in tabulazioni, cosa che ne rende
sicuro l'uso per i sorgenti. Combinando opportunamente le fasi di espansione
e di compressione è possibile convertire le tabulazioni di un file da una
lunghezza a un'altra, quasi senza sforzo.

\sez{Navigazione nel file system}

\sottosez{Considerazioni generali.}

La sfortunata comunità degli utenti \msdos{} può godere di tool come le Norton
Utilities o gli altrettanto famosi PCTools. Analogamente, gli utenti Mac hanno
il Finder che, nella sua ultima incarnazione, offre delle interessanti possibilità
per il recupero di file da parti semidimenticate di mastodontici dischi rigidi.
La navigazione della struttura di un file system da shell non è invece un'attività
particolarmente supportata nel mondo \amiga{}.

Per chi ha una ramificazione massiccia del file system su hard disk (il mio arriva al nono
livello), spostarsi tra le directory può diventare un problema serio.
Nonostante la Shell e l'AmigaDOS facciano del loro meglio per semplificare
il compito,\footnote{La Shell permette di cambiare directory semplicemente digitandone
il pathname al prompt dei comandi; l'AmigaDOS ha in assoluto la miglior
gestione dei pathname mai concepita da un DOS, permettendo di specificare un
path di qualsiasi livello relativamente alla directory corrente$\ldots$
} passare velocemente da un punto all'altro del file system è, a
dir poco, fastidioso per chiunque non sia un dattilografo$\ldots$

\sottosez{XCD.}

La X sta per eXtended. Il tool in questione è un sostituto del comando CD di
sistema; per rimpiazzare CD basta aggiungere ¶{xcd} alla lista dei comandi
residenti della Shell con il nome di CD. Notare che la stessa Shell cerca CD
in detta lista, quando l'utente ha specificato il pathname di una directory
al posto di un comando.

¶{xcd} offre i seguenti servizi:

\begin{itemize}
\item Cambia la directory corrente a quella specificata con un pathname che
può contenere anche espressioni regolari in ogni nodo. Nel caso più directory
soddisfino il pattern, verrà scelta la prima.
\item Può cercare la directory dal nome specificato in tutto l'albero del
file system. Il nome può contenere espressioni regolari.
\item Cambia la directory corrente a quella del processo CLI di cui è
specificato il numero. Utile per sincronizzare rapidamente due Shell sulla
stessa directory.
\item Tiene traccia della directory usata prima di quella corrente in una
variabile di ambiente locale alla Shell e permette di raggiungerla
immediatamente. Utile per passare rapidamente tra due directory differenti o
come UNDO dell'ultimo CD effettuato.
\item Può visualizzare l'ASL directory requester per
scegliere via mouse la directory da raggiungere.
\item Può stampare sulla console il pathname completo di una directory
invece di usarla come nuova directory corrente.
\end{itemize}

Insieme al sorgente del comando si trova uno script AmigaDOS che definisce
alcuni alias per sfruttare i servizi di ¶{xcd} e altri due alias per salvare e
recuperare il pathname della directory corrente.


\sez{Conclusioni}

In sintesi, la nuova ¶{dos.library} permette di sviluppare utility DOS
piccole, efficienti e, soprattutto, con un'interfaccia e un comportamento
allineato a quello dei comandi di sistema. Poiché recentemente la
Commodore-\amiga{} ha dichiarato la Release 2 la nuova base di compatibilità
per il parco software \amiga{}, non esiste più nessun motivo per vedere ancora
utility non aderenti alla {\em Amiga User Inferface Style Guide}.

Il software presentato è stato sviluppato e compilato con il SAS/C V6.51
e testato con il Kickstart V37 (spero che non dia problemi con le versioni
superiori) sull'\amiga{} 3000 dell'autore.

Gli eseguibili e i sorgenti sono liberamente distribuibili sotto le
condizioni della licenza GNU versione 2 o successiva.

Per finire, vorrei richiamare l'attenzione sulle dimensioni degli
eseguibili, in generale decisamente contenute. In particolare, mi sembra
degno di nota il confronto tra le dimensioni del comando ¶{copy} (5580 byte
per un filtro che non altera i file) con l'utility ¶{tab} (4948 byte per un
convertitore di tabulazioni)$\ldots$

\finearticolo
