|
Appunti informatica |
|
Visite: 4300 | Gradito: | [ Grande appunti ] |
Leggi anche appunti:Vettori di interrupt o puntatori?Vettori di interrupt o puntatori? Degli interrupt e dei loro vettori si parla Allocazione dinamica della memoriaAllocazione dinamica della memoria Quando è dichiarata una variabile, il compilatore Gli interrupt: utilizzoGli interrupt: utilizzo Gli interrupt sono routine, normalmente operanti a livello |
L'interprete dei comandi (COMMAND.COM nella configurazione DOS standard) fornisce una interfaccia per l'esecuzione dei programmi (a volte definiti comandi esterni ) e rende disponibili alcuni comandi interni, così detti in quanto implementati mediante routine interne all'interprete stesso (COPY DEL DIR, etc.), nonché la capacità, utilizzabile dalle applicazioni opportunamente progettate, di effettuare redirezioni (vedere pag.
L'esecuzione di sequenze di comandi interni ed esterni può essere automatizzata mediante i file batch, che l'interprete è in grado di leggere ed eseguire riga per riga: ognuna contiene, in formato ASCII, un singolo comando; sono inoltre disponibili istruzioni per il controllo del flusso elaborativo, quali FOR IF (utilizzabili direttamente da prompt) e GOTO. Per i dettagli circa la sintassi dei comandi interni si rimanda alla manualistica DOS; in questa sede si vuole sottolineare che, per mezzo di questi soltanto, è spesso difficile (se non impossibile) implementare algoritmi di una certa complessità. Ad esempio, la IF consente di controllare il contenuto del registro ERRORLEVEL (vedere pag. ) o di verificare l'esistenza di un file
IF EXIST PIPPO GOTO TROVATO
ECHO PIPPO NON C'E'
GOTO END
:TROVATO
:END
Non è tuttavia possibile effettuare test su alcuna caratteristica del file stesso, quali data, ora, dimensione, né accertare se si tratti piuttosto di una directory.
L'elenco dei principali limiti all'efficacia dei file batch può continuare a lungo: non vi è modo di inserire in comandi batch stringhe contenenti data e ora di elaborazione, o parti di esse; non è possibile ritardare l'esecuzione di un comando ad un'ora specificata; il comando COPY fallisce se il file origine ha dimensione 0 byte; non vi sono strumenti in grado di estrarre da un flusso ASCII parti di testo in modo 'mirato', ad eccezione del programma FIND, che ne visualizza le righe contenenti (o no) una data stringa; l'output di un comando non può essere utilizzato come parte di un successivo comando; una modifica alla lista degli argomenti del comando FOR richiede che sia modificato il file batch contenente il comando stesso
Dette limitazioni sono superate con una minima interattività da parte dell'utilizzatore, ma possono originare problemi quasi insormontabili laddove vi sia la necessità di una completa automazione (ad esempio in elaborazioni notturne): il presente paragrafo presenta alcuni programmi volti a superare le carenze cui si è fatto cenno . Lo scopo è fornire un insieme di spunti e idee perfettibili e, al tempo stesso, adattabili a piacere secondo le specifiche esigenze di ciascuno.
La semplicità del listato che segue è assolutamente disarmante: il programma legge lo standard input ed immediatamente termina, valorizzando il registro ERRORLEVEL a se lo stream stdin è vuoto o contiene solo spazi, tabulazioni o caratteri di ritorno a capo; in caso contrario ERRORLEVEL è azzerato.
EMPTYLVL.C - 20/01/93 - Barninga_Z!
Legge una stringa da STDIN e setta il registro errorlevel a 1 se la
stringa contiene solo spazi e/o tabs e/o CR e/o LF. In caso contrario
il registro e' posto a 0.
Compilato sotto Borland C++ 3.01:
bcc -O -rd emptylvl.c
#include <stdio.h>
#include <string.h>
#define MAXHEAP 4096
#define MAXBUF 2048
#define NULLCHARS ' tnr'
extern unsigned _heaplen = MAXHEAP;
int main(void)
Come si vede, l'unica particolarità tecnica di qualche rilevanza è costituita dall'assegnazione di un valore predefinito alla variabile _heaplen, per fissare la massima dimensione dello heap (vedere pag. ) e limitare così al minimo indispensabile la quantità di memoria necessaria per il caricamento e l'esecuzione del programma.
EMPTYLVL si rivela utile in molte situazioni: ad esempio ci consente di scoprire se un file è vuoto, o se si tratta di una directory. Vediamo un esempio:
IF EXIST PIPPO GOTO TROVATO
ECHO PIPPO NON C'E'
GOTO END
:TROVATO
DIR | FIND 'PIPPO' | FIND '<DIR>' | EMPTYLVL
IF ERRORLEVEL 1 GOTO PIPPOFIL
ECHO PIPPO E' UNA DIRECTORY
GOTO END
:PIPPOFIL
DIR | FIND 'PIPPO' | FIND ' 0 ' | EMPTYLVL
IF ERRORLEVEL 1 GOTO PIPPODAT
ECHO IL FILE PIPPO E' VUOTO
GOTO END
:PIPPODAT
TYPE PIPPO | MORE
:END
Le prime righe del batch non rappresentano una novità rispetto quanto sopra accennato. Al contrario, la riga
DIR | FIND 'PIPPO' | FIND '<DIR>' | EMPTYLVL
merita qualche commento. Il comando DIR produce l'elenco di tutti i file e subdirectory presenti nella directory di default: il simbolo ' ' ne effettua il piping al primo comando FIND (esterno), che lo scandisce e scrive, a sua volta, sullo standard output solo le righe contenenti la stringa 'PIPPO'. Ma l'output del primo FIND è trasformato (ancora mediante piping) in standard input per il secondo comando FIND, che scrive sul proprio standard output solo le righe contenenti la stringa '<DIR>': ne segue che se il file PIPPO è, in realtà, una directory, lo standard input di EMPTYLVL contiene la riga ad essa relativa dell'output originario di DIR; se invece PIPPO è un file, lo standard input di EMPTYLVL risulta vuoto. Nel primo caso, pertanto, ERRORLEVEL è posto a e la IF non effettua il salto: viene visualizzata la stringa 'PIPPO E' UNA DIRECTORY'. Nel secondo caso ERRORLEVEL vale e l'esecuzione salta all'etichetta :PIPPOFIL, a partire dalla quale, con un algoritmo del tutto analogo a quello testè commentato, si verifica se la dimensione di PIPPO riportata da DIR . Se il file non è vuoto, il suo contenuto viene visualizzato, una schermata alla volta, grazie all'azione combinata del comandi TYPE (interno) e MORE (esterno).
Il programma DATECMD è concepito per consentire l'inserimento automatico di data e ora, o parti di esse, nei comandi DOS. Esso scandisce la propria command line alla ricerca di sequenze note di simboli, che definiamo, per comodità, macro, e le sostituisce con la parte di data o ora che rappresentano, valorizzata in base a data e ora di sistema. Ogni macro è costituita dal carattere ' ', seguito da una lettera che identifica il valore di sostituzione: così, ad esempio, @M indica il giorno del mese, espresso con due cifre. Le macro ammesse ed il loro significato sono elencati di seguito.
|
il carattere at ( ); utile per inserire una ' ' nella command line |
|
le virgolette ( ); utile per inserire le virgolette nella command line |
@A |
l'anno; espresso con quattro cifre (es.: |
@a |
l'anno; espresso con due sole cifre (es.: |
@M |
il mese; espresso con due cifre (es.: |
@m |
il mese; espresso con una sola cifra se la prima è (es.: |
@G |
il giorno del mese; espresso con due cifre (es.: |
@g |
il giorno del mese; espresso con una sola cifra se la prima è (es.: |
@R |
il giorno dell'anno; espresso con tre cifre (es.: |
@r |
il giorno dell'anno; espresso con una o due cifre se le prime sono (es.: |
@O |
l'ora; espressa con due cifre (es.: |
@o |
l'ora; espressa con una sola cifra se la prima è (es.: |
@I |
il minuto; espresso con due cifre (es.: |
@i |
il minuto; espresso con una sola cifra se la prima è 0 (es.: |
@S |
il secondo; espresso con due cifre (es.: |
@s |
il secondo; espresso con una sola cifra se la prima è (es.: |
@E |
il giorno della settimana; è indicato il nome intero (es.: Lunedì |
@e |
il giorno della settimana; indicato mediante i primi tre caratteri del nome (es.: Lun |
@W |
il giorno della settimana; espresso con un numero da (Domenica) a (Sabato) |
Vediamo un esempio pratico: se il comando
datecmd echo Sono le @O:@I:@S di @E @g/@M/@a, @r^ giorno dell'anno: @'Ciao, @@!'@
viene eseguito alle 14:52:20 del 2 novembre 1994, DATECMD esegue in realtà il comando
echo Sono le 14:52:20 di Mercoledì 2/11/94, 306^ giorno dell'anno: 'Ciao, @!'
DATECMD accetta inoltre due opzioni, ‑d e ‑v, che devono precedere la command line da eseguire. La prima consente di specificare uno 'slittamento' di data, positivo o negativo. Se nel comando dell'esempio precedente si antepone ‑v‑1 alla command line di DATECMD (cioè a echo), l'ouput prodotto diventa
echo Sono le 14:52:20 di Martedì 1/11/94, 305^ giorno dell'anno: 'Ciao, @!'
L'opzione ‑v, invece, richiede a DATECMD di visualizzare soltanto, ma non eseguire, la command line risultante a seguito della risoluzione delle macro.
Segue il listato, ampiamente commentato, del programma (vedere pag. e seguenti circa l'implementazione di PARSEOPT.OBJ; le funzioni di manipolazione delle date sono descritte a pag.
DATECMD.C - Barninga_Z! - 27/04/1993
Esegue command lines DOS con la possibilita' di parametrizzarle rispetto
alla data e all'ora: nella command line, digitata dopo il nome del
programma (DATECMD), possono essere inserite delle macro, costituite dal
carattere macro (@) e da uno dei caratteri elencati nel sorgente (nella
definizione dell'array delle macro). Dette macro vengono espanse nella
stringa con il corrispondente significato. In ogni command line possono
comparire piu' macro, ed ogni macro può comparire piu' volte. La macro
@' viene espansa nelle virgolette ('); la macro @@ viene espansa nella
atsign (@). L'utilita' di queste macro e' evidente nel caso in cui si
debbano inserire le virgolette nella command line o nel caso in cui una
sequenza di caratteri che non deve essere espansa comprenda in casualmente
i caratteri costituienti una macro. Ad esempio, @M viene espanso nel mese
corrente; per non espanderlo bisogna digitare @@M (@@ viene espanso in
@ e la M non viene modificata). Se la command line e' preceduta dalla
opzione -v essa viene solo visualizzata e non eseguita. Se è preceduta
da -d si possono specificare i giorni di differenza rispetto alla data
attuale (+|- gg).
_stklen e _heaplen sono settate per ridurre l'ingombro in memoria del
programma, visto che la command line e' eseguita con una system(). Per
questo motivo, inoltre, l'interprete dei comandi deve essere accessibile.
Compilato sotto Borland C++ 3.1:
bcc -k- -O -d datecmd.c parseopt.obj date2jul.obj jul2date.obj isleapyr.obj
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <process.h>
#include <dir.h>
#include <errno.h>
#include <ctype.h>
#include 'parseopt.h' // per la gestione delle opzioni di command line
#define _PRG_ 'DATECMD' // nome del programma
#define _VERSION_ '1.5' // versione
#define _YEAR_ '94' // anno di rilascio
#define MAXCMD 128 // max lungh. cmd line (ENTER incl.)
#define MACRO_FLAG '@' // carattere che segnala la macro
#define DQUOTE_CHR 'x27' // sostituisce le virgolette
#define SWITCH '-' // switch character per opzioni
#define YEARLEN 365 // giorni dell'anno non bisestile
#define WEEKLEN 7 // giorni della settimana
#define WDAYSHORTLEN 3 // lung. stringa breve giorno sett.
// prototipi delle funzioni del corpo del programma
long date2jul(int day,int month,int year);
int isleapyear(int year);
int jul2date(long jul,int *day,int *month,int *year);
int main(int argc,char **argv);
void adjustOrdinal(int baseYear,int shift);
void adjustWeekDay(int len);
void fatalError(int errNum);
void help(void);
char *initProg(int argc,char **argv);
int isDigitStr(char *string);
char *parseCmd(char *cmdArgs);
// Ottimizzazione dell'uso della memoria
extern unsigned _heaplen = 4096;
extern unsigned _stklen = 2048;
// Messaggi di errore e di aiuto
#define _E_BADARGS 0
#define _E_CMDLONG 1
#define _E_BADOPTIONS 2
#define _E_ALLOCMEM 3
char *weekdays[] = ;
char *errors[] = ;
char *helptext = '
Uso: DATECMD [-v][-d+|-g] CmdLinen
CmdLine è la command line da eseguire (-v visualizza soltanto, -d modifica lan
data di +/- g giorni); può contenere una o più delle seguenti macro:n
// Variabili globali per inizializzazione e altri scopi
struct tm *tData; // contiene data e ora correnti allo startup
char tStr[20]; // buffer per sprintf() temporanee
// gruppo delle funzioni per il macro processing. Ciascuna di esse viene
// invocata se e' incontrata la macro corrispondente (vedere l'array macros)
// e restituisce un puntatore a stringa, che deve essere passato a strcpy()
// per copiare l'espansione della macro nella stringa di comando che sara'
// passata a spawn()
char *retMacroFlag(void) // restituisce il carattere MACRO_FLAG
char *retDoubleQuote(void) // restituisce il carattere '''
char *getYear(void) // anno, 4 cifre
char *getYear1(void) // anno, 2 cifre
char *getMonth(void) // mese, 2 cifre
char *getMonth1(void) // mese, 1 o 2 cifre
char *getDay(void) // giorno del mese, 2 cifre
char *getDay1(void) // giorno del mese, 1 o 2 cifre
char *getOrdinal(void) // giorno dell'anno, 3 cifre
char *getOrdinal1(void) // giorno dell'anno, 1 o 2 o 3 cifre
char *getHour(void) // ora, 2 cifre
char *getHour1(void) // ora, 1 o 2 cifre
char *getMin(void) // minuto, 2 cifre
char *getMin1(void) // minuto, 1 o 2 cifre
char *getSec(void) // secondo, 2 cifre
char *getSec1(void) // secondo, 1 o 2 cifre
char *getWeekDay(void) // giorno della settimana
char *getWeekDay1(void) // giorno della settimana 3 lettere
char *getWeekDayNum(void) // giorno della settimana numero
// definizione della struttura di gestione delle macro come tipo di dato e
// dichiarazione dell'array di strutture che definisce tutte le macro
typedef struct MACRO;
MACRO macros[] = ,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
// organizzazione delle opzioni
#define DISPLAY_ONLY 1 // opzione solo visualizza cmd line
#define DAY_SHIFT 2 // opzione shift data in giorni
unsigned options; // variabile per contenere i bits delle opzioni
char *optionS = 'd:v'; // stringa definizione opzioni
#pragma warn -par
#pragma warn -rvl
// l'opzione -d consente di specificare uno scostamento in giorni dalla data
// corrente. Lo scostamento puo' essere positivo (es: 1 = doamni) o negativo
// (es: -1 = ieri). La funzione esegue tutti i controlli formali e modifica
// di conseguenza i dati di lavoro.
int valid_d(struct OPT *vld,int cnt) // convalida opzione d
// l'opzione -v forza DATECMD a non eseguire la command line costruita con
// l'espansione delle macro, bensi' a visualizzarla solamente. Qui viene
// settato il flag che indica che l'opzione e' stata richiesta
int valid_v(struct OPT *vld,int cnt) // convalida opzione v
// gestione delle opzioni specificate in modo errato
int err_handle(struct OPT *vld,int cnt) // opzioni errate
#pragma warn .par
#pragma warn .rvl
// array che associa ogni ozione alla corrispondente funzione di validazione
static struct VOPT vfuncs[] = ,
,
,
// corpo del programma (dopo main() le funzioni sono in ordine alfabetico)
int main(int argc,char **argv) // pilota tutte le operazioni
// adjustOrdinal() modifica il numero di giorno nell'anno (tm_yday) in base
// al numero di giorni di shift della data (opzione -d), tenendo presente che
// lo shift potrebbe generare un cambiamento di anno in piu' o in meno.
void adjustOrdinal(int baseYear,int shift)
else
// adjustWeekDay() modifica il numero di giorno nella settimana (tm_wday) in
// base al numero di giorni di shift della data (opzione -d), tenendo presente
// che lo shift potrebbe generare un cambiamento di settimama.
void adjustWeekDay(int shift)
tData->tm_wday %= WEEKLEN;
if(temp && tData->tm_wday)
tData->tm_wday = WEEKLEN-tData->tm_wday;
// fatalError() esce a DOS quando si verifica un errore, visualizzando un
// messaggio dall'array errors.
void fatalError(int errNum)
// help() stampa una videata di aiuto usando il campo comment di dell'array
// di strutture MACRO macros per visualizzare la descrizioni delle macro
void help(void)
// initProg() controlla i parametri e concatena gli elementi di argv per
// ricostruire la command line di DATECMD in un'unica stringa.
char *initProg(int argc,char **argv)
*initArgs = NULL;
for(i = argc-optn[0].val; ; )
return(initArgs);
// isDigitStr() controlla se una stringa contiene solo 0-9
int isDigitStr(char *string)
// parseCmd() ricerca ed espande le macro presenti nella command line di
// DATECMD, costruendo il comando da eseguire, nel quale compaiono, in luogo
// delle macro, gli elementi di data e ora desiderati.
char *parseCmd(char *cmdArgs)
if(macros[mIndex].symbol)
else
fatalError(_E_CMDLONG);
break;
}
default:
cmdLine[j++] = cmdArgs[i];
}
}
return(cmdLine);
DATECMD può rivelarsi particolarmente utile quando vi sia la necessità di dare ad un file, mediante i comandi DOS COPY o REN, un nome dipendente dalla data e ora in cui l'operazione stessa è effettuata. Si pensi, ad esempio, ad una procedura che, quotidianamente, scrive il risultato delle proprie elaborazioni nel file OUTPUT.DAT: qualora sia necessario conservare a lungo i file generati, può risultare comodo riservare loro una directory e copiarveli di giorno in giorno, rinominandoli in modo appropriato. Il solo modo di inserire il comando in un file batch, senza necessità alcuna di intervento interattivo da parte dell'utente, consiste nel servirsi di DATECMD
datecmd copy d:procoutput.dat c:procout@a@M@G.dat
Se ogni file deve essere conservato per una settimana soltanto, il comando presentato necessita appena un piccolo aggiustamento:
datecmd copy d:procoutput.dat c:procoutoutput.@e
Infatti, dal momento che la macro @e viene sostituita dai tre caratteri iniziali del nome del giorno della settimana, è evidente che ogni giorno il nuovo file sovrascrive quello copiato sette giorni prima.
Va ancora sottolineato che DATECMD consente una gestione avanzata di piping e redirezione: ad esempio, il comando
datecmd copy d:procoutput.dat c:procout@a@M@G.dat >> batch.ctl
redirige lo standard output di DATECMD in coda al file BATCH.CTL, mentre il comando
datecmd 'copy d:procoutput.dat c:procout@a@M@G.dat >> batch.ctl' > nul
aggiunge a BATCH.CTL lo standard output del comando COPY e sopprime quello di DATECMD; si noti l'uso delle virgolette, indispensabili per evitare che il DOS interpreti la redirezione facente parte della command line che deve essere eseguita da DATECMD
Gli esempi presentati poco sopra nascondono una insidia: il comando DOS COPY fallisce se il file origine ha dimensione pari a 0 byte; la conseguenza è che nella directory dedicata alla memorizzazione dei file di output potrebbero mancarne alcuni
FCREATE aggira il problema, consentendo la creazione di un file della dimensione voluta. Il file generato, qualora abbia dimensione maggiore di 0, contiene esclusivamente byte nulli (zero binario).
FCREATE.C - Barninga Z! - 27/10/1994
Crea un file della dimensione indicata sulla riga di comando.
Compilato sotto Borland C++ 3.1
bcc fcreate.c
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sysstat.h>
#define PRG 'FCREATE'
#define VER '1.0'
#define YEAR '94'
#define OPENMODE O_WRONLY+O_CREAT+O_TRUNC+O_BINARY
#define OPENATTR S_IREAD+S_IWRITE
#define E_SYNTAX 1
#define E_OPEN 2
#define E_CHSIZE 3
#define E_CLOSE 4
#define E_PARM 5
// tutte le operazioni sono effettuate in main()
int main(int argc,char **argv)
// controllo del valore di size
if((size = atol(argv[1])) < 0L)
retcode = 0;
// apertura del file: se non esiste viene creato; se esiste e' troncato a 0
if((handle = open(argv[2],OPENMODE,OPENATTR)) == -1)
else
else
// se l'impostazione della nuova dimensione e' riuscita si calcola la nuova
// dimensione del file per verifica
size = filelength(handle);
// chiusura del file
if(close(handle))
}
// test e azione conseguente in caso di errore o meno
if(!retcode)
printf('%s: created %s (%ld bytes)n',PRG,argv[2],size);
return(retcode);
FCREATE richiede due parametri: il primo esprime la dimensione desiderata per il file; il secondo è il nome di questo. Insieme con EMPTYLVL (pag. ) e DATECMD (pag. ) è possibile implementare un algoritmo privo di criticità:
DIR 'D:PROCOUTPUT.DAT' | FIND ' 0 ' | EMPTYLVL
IF ERRORLEVEL 1 GOTO NONZERO
ECHO IL FILE OUTPUT.DAT HA DIMENSIONE 0 BYTES: UTILIZZO FCREATE
DATECMD FCREATE 0 C:PROCOUT@a@M@G.OUT
GOTO END
:NONZERO
ECHO IL FILE OUTPUT.DAT HA DIMENSIONE MAGGIORE DI 0 BYTES: UTILIZZO COPY
DATECMD COPY D:PROCOUTPUT.DAT C:PROCOUT@a@M@G.OUT
:END
FCREATE può validamente essere utilizzato per generare file aventi la funzione di placeholder, cioè per riservare spazio ad utilizzi futuri.
Per rendere le cose più complicate, assumiamo che il drive D:, sul quale viene prodotto OUTPUT.DAT dalla ormai nota procedura, non sia un disco fisicamente presente nel personal computer su cui lavoriamo, ma si tratti, al contrario, di un drive remoto , reso disponibile da una seconda macchina: il server di rete. La procedura, a sua volta, è eseguita su un terzo personal computer, anch'esso collegato al network ed in grado di accedere al medesimo disco condiviso. E' evidente, a questo punto, che non ha alcun senso lanciare il nostro file batch prima che la procedura in questione abbia terminato le proprie elaborazioni. Nell'ipotesi che ciò avvenga poco prima delle 23:00, chi non desideri trascorrere le proprie serate alla tastiera, in trepidante attesa, deve necessariamente adottare qualche provvedimento.
TIMEGONE è il provvedimento necessario : esso controlla se è trascorsa l'ora (ed eventualmente la data) indicata e termina, valorizzando di conseguenza il registro ERRORLEVEL. In particolare, ERRORLEVEL vale se l'ora è trascorsa; se non lo è.
L'ora da tenere sotto controllo deve essere specificata sulla command line di TIMEGONE nel formato hhmmss (due cifre per l'ora, due per i minuti, due per i secondi): ad esempio, il comando
timegone 060400
indica le ore 6:04. I minuti e i secondi devono essere sempre specificati.
TIMEGONE consente di indicare, in aggiunta all'ora, anche una data: allo scopo sono riconosciute tre opzioni di command line: ‑d consente di specificare il giorno, sempre espresso con due cifre. Ad esempio, il 12 del mese si indica con ‑d12. L'opzione ‑m specifica il mese: febbraio, per esempio, si indica con ‑m02. Infine, l'opzione ‑y permette di indicare l'anno, in 4 cifre. Il 1994, pertanto, si specifica con ‑y1994. Il comando
timegone -d03 -m11 170000
richiede a TIMEGONE di verificare se siano trascorse le ore 17 del 3 novembre: dal momento che l'anno non è specificato, esso non viene controllato (il risultato del test è indipendente dall'anno di elaborazione).
Mentre l'indicazione di giorno, mese e anno è facoltativa, l'ora deve essere sempre specificata. Tuttavia, in luogo dell'espressione in formato hhmmss può essere digitato un asterisco (' ') per forzare il programma a ricavare date e ora dalla stringa assegnata alla variabile d'ambiente TIMEGONE: questa ha formato YYYYMMGGhhmmss (l'indicazione dell'ora è preceduta da quattro cifre per l'anno, due per il mese, due per il giorno); è lecito specificare zeri per anno, mese e giorno (sempre in numero di quattro, due e, rispettivamente, ancora due) se si desidera che siano ignorati. La condizione dell'esempio precedente può essere espressa valorizzando la variabile TIMEGONE con il comando
set timegone=00001103170000
ed eseguendo successivamente
timegone *
Le opzioni eventualmente specificate sulla riga di comando hanno precedenza rispetto alla variabile d'ambiente; questa, infine, è ignorata se a TIMEGONE è passata, quale parametro, l'ora. Così
timegone -d04 *
verifica se sono trascorse le 17 del 4 novembre (fermo restando il valore della variabile TIMEGONE dell'esempio precedente).
TIMEGONE riconosce una quarta opzione: ‑r richiede che il risultato del test sia invertito (ERRORLEVEL vale se data e ora non sono trascorse, altrimenti).
Segue il listato, ampiamente commentato, del programma (vedere pag. e seguenti circa l'implementazione di PARSEOPT.OBJ; le funzioni di manipolazione delle date sono descritte a pag.
TIMEGONE.C - Barninga Z! - 29/09/94
Setta errorlevel a seconda che la data e l'ora correnti superino o meno
quelle richieste. Vedere helpStr per i dettagli della sintassi e delle
opzioni.
Compilato sotto Borland C++ 3.1
bcc timegone.c isleapyear.obj parseopt.obj
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dos.h>
#include 'parseopt.h'
#define YEAR 1
#define MONTH 2
#define DAY 4
#define REVERSE 8
#define SWCHAR '-'
#define ILLMSG 'Invalid option'
#define YEARLEN 4
#define MONTHLEN 2
#define DAYLEN 2
#define DATELEN (YEARLEN+MONTHLEN+DAYLEN)
#define TIMELEN 6
#define DTLEN (DATELEN+TIMELEN)
#define TVARSYM '*'
#define TVARNAME 'TIMEGONE'
#define PRG 'TIMEGONE'
#define REL '1.0'
#define YR '94'
#define RETERR 255
#define FALSE 0
#define TRUE 1
#define ASCIIBASE 0x30
#define MIN_YEAR 1980
#define MAX_YEAR 2099
char *helpStr = '
n
options: one or more of the following:n
-dDD day DD of the month (always 2 digits).n
-mMM month MM of the year (always 2 digits).n
-yYYYY year YYYY (always 4 digits).n
-r reverse the result.n
n
time time, in hhmmss format (always 6 digits). If time is the string '*',n
the environment variable TIMEGONE is used. It must contain an
datetime string in YYYYMMGGhhmmss format; YYYY=0000, MM=00, GG=00n
cause year, month and day respectively to be ignored.n
n
Command line parameters, if given, always override the environment variable.n
n
Errorlevel is set to 1 if the (date and) time specified has gone; it is set ton
0 if not yet. The -r option causes the result to be reversed (0 if gone, 1 ifn
not yet). If an error occurs, Errorlevel is set to 255.nn
int isleapyear(int year);
int main(int argc,char **argv);
int hasTimeGone(void);
int isDateValid(void);
int isNumeric(char *string);
int valid_d(struct OPT *vld,int cnt);
int valid_m(struct OPT *vld,int cnt);
int valid_r(struct OPT *vld,int cnt);
int valid_y(struct OPT *vld,int cnt);
int err_handle(struct OPT *vld,int cnt);
#pragma warn -rvl
#pragma warn -par
#pragma warn -aus
// l'opzione -d consente di specificare il giorno per comporre una data da
// controllare insieme all'ora. Qui sono effettuati tutti i controlli formali;
// 00 e' valido e indica che qualsiasi giorno va bene
int valid_d(struct OPT *vld,int cnt)
// l'opzione -m consente di specificare il mese per comporre una data da
// controllare insieme all'ora. Qui sono effettuati tutti i controlli formali;
// 00 e' valido e indica che qualsiasi mese va bene
int valid_m(struct OPT *vld,int cnt)
// l'opzione -r rovescia il risultato del test: se il momento e' trascorso
// ERRORLEVEL e' settato a 0 invece che a 1, e viceversa
int valid_r(struct OPT *vld,int cnt)
// l'opzione -y consente di specificare il anno per comporre una data da
// controllare insieme all'ora. Qui sono effettuati tutti i controlli formali;
// 0000 e' valido e indica che qualsiasi anno va bene
int valid_y(struct OPT *vld,int cnt)
// controlla la validita' formale dell'ora indicata sulla command line. Il
// formato deve essere hhmmss. In luogo dell'ora sulla cmdline si puo'
// specificare un asterisco: in tal caso il programma verifica se esiste la
// variabile d'ambiente TIMEGONE e ricava data e ora dalla stringa as essa
// assegnata.
int valid_time(struct OPT *vld,int cnt)
// si effettua il controllo di numericita' sulla stringa YYYYMMDD e si
// valorizzano i campi della struttura date
if((strlen(vld->arg) != DTLEN) || !isNumeric(vld->arg))
if(sscanf(vld->arg,'%4d%02d%02d',
(options & YEAR) ? &dummy : (int *)&da.da_year,
(options & MONTH) ? &dummy : (int *)&da.da_mon,
(options & DAY) ? &dummy : (int *)&da.da_day) < 3)
err_handle(NULL,NULL);
// vld->arg e' forzato a puntare alla seconda parte della stringa, avente
// formato hhmmss
vld->arg += DATELEN;
}
else
// da qui in poi l'elaborazione e' identica sia nel caso di ora passata sulla
// command line che di utilizzo della variabile TIMEGONE, in quanto, in
// entrambi i casi, vld->arg punta a una stringa in formato hhmmss
if((strlen(vld->arg) != TIMELEN) || !isNumeric(vld->arg))
err_handle(NULL,NULL);
// valorizzazione dei campi della struttura dostime_t
if(sscanf(vld->arg,'%02d%02d%02d',(int *)&ti.hour,
(int *)&ti.minute,
(int *)&ti.second) < 3)
err_handle(NULL,NULL);
// controllo di validita' formale della data
if(!isDateValid())
err_handle(NULL,NULL);
// controllo di validita' formale dell'ora
if((ti.hour > 23) || (ti.minute > 59) || (ti.second > 59))
err_handle(NULL,NULL);
// gestione degli errori: visualizzazione della stringa di help e del valore
// di ERRORLEVEL
int err_handle(struct OPT *vld,int cnt)
#pragma warn .par
#pragma warn .rvl
#pragma warn .aus
unsigned options;
// ogni opzione e' associata alla corrispondente funzione di validazione
struct VOPT valfuncs[] = ,
,
,
,
,
,
// elenco delle opzioni: quelle seguite da ':' vogliono un argomento
char *optionS = 'd:m:ry:';
struct date da;
struct dostime_t ti;
// main() analizza le opzioni via parseopt() ed effettua il test su data/ora
// via hasTimeGone()
int main(int argc,char **argv)
else
printf('%s: errorlevel set to %d.n',PRG,retval);
return(retval);
// controlla se la data/ora attuale ha superato la data/ora specificate via
// opzioni e/o variabile d'ambiente
int hasTimeGone(void)
// isDateValid() controlla la correttezza formale della data, verificando che
// per anno, mese e giorno siano verificati valori plusibili
int isDateValid(void)
extern struct date da;
// se il gmese e' < 0 o > 12 errore
if((da.da_mon < 0 ) || (da.da_mon > 12))
return(0);
// se l'anno non e' 0 deve essere compreso tra gli estremi di validita'; se lo
// e' si determina se e' bisestile
if(da.da_year)
else
// se l'anno e' 0 occorre ammettere che potrebbe essere bisestile, chissa'
monLenTbl[1] += 1;
// se il giorno non e' 0 esso deve essere positivo e minore o uguale al numero
// di giorni del mese; se il mese e' 0 bisogna ammettere che il 31 e' valido
if(da.da_day)
else
if(da.da_day > 31) // sempre e comunque errore!!
return(0);
}
return(1); // 1 = data OK
// controlla la numericita' della stringa ricevuta come parametro
int isNumeric(char *string)
return(TRUE);
Vediamo, finalmente, TIMEGONE al lavoro nel nostro esempio pratico:
:ATTESA
TIMEGONE 230000
IF ERRORLEVEL 1 GOTO ADESSO
GOTO ATTESA
:ADESSO
ECHO LE ORE 23:00 SONO APPENA TRASCORSE
DIR 'D:PROCOUTPUT.DAT' | FIND ' 0 ' | EMPTYLVL
IF ERRORLEVEL 1 GOTO NONZERO
ECHO IL FILE OUTPUT.DAT HA DIMENSIONE 0 BYTES: UTILIZZO FCREATE
DATECMD FCREATE 0 C:PROCOUT@a@M@G.OUT
GOTO END
:NONZERO
ECHO IL FILE OUTPUT.DAT HA DIMENSIONE MAGGIORE DI 0 BYTES: UTILIZZO COPY
DATECMD COPY D:PROCOUTPUT.DAT C:PROCOUT@a@M@G.OUT
:END
Il batch può essere eseguito a qualsiasi ora: TIMEGONE forza un loop sulle prime quattro righe del file sino alle 23:00 (se il batch è lanciato dopo le 23 il flusso elaborativo salta immediatamente all'etichetta :ADESSO
Sempre più difficile: il famigerato OUTPUT.DAT contiene una sezione di testo che costituisce la base per alcune postelaborazioni. Ipotizziamo che essa abbia il seguente layout:
* RIEPILOGO *
* FINE *
Il contenuto della sezione, per il momento, non è rilevante: l'obiettivo è estrarla in modo automatico da OUTPUT.DAT e scriverla in un secondo file, da processare in seguito. La utility SELSTR rappresenta una soluzione: il comando
selstr '* RIEPILOGO *' '* FINE *' < output.dat > riepilog.dat
scrive sullo standard output le righe di testo, lette dallo standard input, a partire da quella contenente la prima stringa specificata e conclude l'estrazione con la riga contenente la seconda. Ancora una volta, perciò, è possibile sfruttare le capacità di redirezione offerte dal sistema operativo.
Tuttavia, SELSTR è in grado di fare di più. Esso, infatti, accetta sulla riga di comando alcune opzioni tramite le quali è possibile modificarne il comportamento, indicando se l'estrazione debba iniziare o terminare ad una riga specificata: per la descrizione dettagliata delle opzioni e dei loro parametri si veda il testo assegnato alla variabile helpStr, nel listato del programma. Qui vale la pena di soffermarsi sull'opzione ‑c, che consente di determinare la modalità di caching degli stream. In particolare, ‑ci richiede a SELSTR di attivare il caching dello standard input; ‑co richiede l'attivazione del caching per lo standard output; infine, ‑cb attiva il caching per entrambi gli stream. L'uso attento dell'opzione ‑c permette di ottenere un significativo incremento della efficienza di elaborazione.
Segue il listato, ampiamente commentato, del programma (vedere pag. e seguenti circa l'implementazione di PARSEOPT.OBJ
SELSTR.C - Barninga Z! - 20/09/94
Seleziona una porzione di file in base a stringhe specificate come
delimitatori di inizio o fine. Vedere helpStr per i dettagli.
Compilato sotto Borland C++ 3.1
bcc selstr.c parseopt.obj
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include 'parseopt.h'
#define PRG 'SELSTR'
#define VER '1.0'
#define YEAR '94'
#define MAXLIN 1024
#define RET_ERR 255
#define BUFSIZE 16348
#define FROMNUM 1
#define FROMBEG 2
#define FROMLIN 4
#define TONUM 8
#define CACHE_I 16
#define CACHE_O 32
#define FROMBEG_C 'b'
#define FROMLIN_C 'l'
#define CACHE_I_C 'i'
#define CACHE_O_C 'o'
#define CACHE_B_C 'b'
#define SWCHAR '-'
#define ILLMSG 'Invalid option'
int main(int argc,char **argv);
int search(char *string1,char *string2);
int valid_c(struct OPT *vld,int cnt);
int valid_f(struct OPT *vld,int cnt);
int valid_t(struct OPT *vld,int cnt);
int err_handle(struct OPT *vld,int cnt);
int ctl_strings(struct OPT *vld,int cnt);
unsigned long startNum = 1L;
unsigned long stopNum = 0xFFFFFFFFL;
char *helpStr = '
options:n
-c: cache data streams:n
-ci: cache standard inputn
-co: cache standard outputn
-cb: cache both stdin and stdoutn
-f: line selection origin mode:n
-fn: from linenumber: lines are selected from line n (n > 0) to then
line containing string1 or to the line defined by -t, whichevern
comes first.n
-fb: from beginning: lines are selected from BOF to the linen
containing string1 or to the line defined by -t, whichever comesn
first. Same as -f1.n
-fl: from line (default mode): lines are selected from the linen
containing string1 to EOF. Selection stops at the linen
containing string2, if given and found, or at the line defined by -t.n
-t: line selection end mode:n
-tn: to linenumber: lines are selected from the origin defined by -fn
to line n (n > 0). Anyway, selection stops at the linen
containing string1 (or string2 for -fl), if found.n
ErrorLevel: 255: error;n
1: ok, one or more lines selected;n
0: ok, no lines selected.
unsigned options = FROMLIN;
#pragma warn -rvl
#pragma warn -par
#pragma warn -aus
// validazione dell'opzione -c per la richiesta di caching degli streams.
// Sono attivati i buffers di cache richiesti
int valid_c(struct OPT *vld,int cnt)
options |= CACHE_I;
if(*vld->arg == CACHE_I_C)
break;
case CACHE_O_C:
if(setvbuf(stdout,NULL,_IOFBF,BUFSIZE))
options |= CACHE_O;
break;
default:
err_handle(NULL,NULL);
}
// l'opzione -f consente di specificare quale deve essere la prima linea
// copiata da stdin a stdout: quella contenente il primo nonoption item
// (default), quella il cui numero e' specificato come argomento, o la prima
// riga del file
int valid_f(struct OPT *vld,int cnt)
return;
}
startNum = atol(vld->arg);
options &= ~FROMLIN;
options |= FROMNUM;
// l'opzione -t consente di specificare quale deve essere l'ultima riga del
// file copiata da stdin a stdout. -t consente di indicare un numero di riga;
// se -t non e' specificata la selezione termina alla riga contenente il
// secondo nonoption item sulla command line; in assenza di questo la selezione
// termina a fine file
int valid_t(struct OPT *vld,int cnt)
// gsetione errore di sintassi: visualizzazione del messaggio di help e
// uscita con errore
int err_handle(struct OPT *vld,int cnt)
// controllo dei parametri non-opzione presenti sulla command line: possono
// essere nessuno, uno o due a seconda delle opzioni richieste
int ctl_strings(struct OPT *vld,int cnt)
return(++instance);
#pragma warn .par
#pragma warn .rvl
#pragma warn .aus
// array di strutture che associano ad ogni opzione la corrispondente
// funzione di validazione
struct VOPT valfuncs[] = ,
,
,
,
,
// stringa elencante le opzioni ammesse. Quelle seguite da ':' richiedono un
// argomento
char *optionS = 'c:f:t:';
// main() scandisce la command line via parseopt() e, se le opzioni sono
// regolari, procede nell'elaborazione via search()
int main(int argc,char **argv)
if(!opt[0].val)
err_handle(NULL,NULL); // non c'e' string1
return(search(argv[argc-opt[0].val],argv[argc-opt[0].val+1]));
// search() legge stdin riga per riga e, secondo le opzioni e le stringhe
// specificate sulla riga di comando seleziona le righe da visualizzare su
// stdout
int search(char *string1,char *string2)
// la selezione di righe prosegue fino a che non e' incontrata la riga finale
// definita da stopNum (-t) o segnalata dalla presenza di string2
for(; fgets(line,MAXLIN,stdin) && (i < stopNum); i++)
}
// se la selezione deve partire dalla prima riga allora si copiano su stdout
// tutte le righe sino a quella contenente string1, che in questo caso
// permette di selezionare la riga di stop. Se era stato specificata una riga
// di stop con -t, allora e' controllata stopNum (che altrimenti contiene il
// massimo valore esprimible con un long, rendendo ininfluente il test)
else
}
return(lFlag ? 1 : 0); // 1 = copiate righe; 0 = nessuna riga copiata
Ecco la nuova versione del nostro file batch:
:ATTESA
TIMEGONE 230000
IF ERRORLEVEL 1 GOTO ADESSO
GOTO ATTESA
:ADESSO
ECHO LE ORE 23:00 SONO APPENA TRASCORSE
DIR 'D:PROCOUTPUT.DAT' | FIND ' 0 ' | EMPTYLVL
IF ERRORLEVEL 1 GOTO NONZERO
ECHO IL FILE OUTPUT.DAT HA DIMENSIONE 0 BYTES: UTILIZZO FCREATE
DATECMD FCREATE 0 C:PROCOUT@a@M@G.OUT
GOTO END
:NONZERO
ECHO IL FILE OUTPUT.DAT HA DIMENSIONE MAGGIORE DI 0 BYTES: UTILIZZO COPY
DATECMD COPY D:PROCOUTPUT.DAT C:PROCOUT@a@M@G.OUT
ECHO GENERAZIONE DEL FILE DI RIEPILOGO
SELSTR -cb '* RIEPILOGO *' '* FINE *' < D:PROCOUTPUT.DAT > C:PROCRIEPILOG.DAT
IF ERRORLEVEL 255 GOTO SELERROR
IF ERRORLEVEL 1 GOTO END
ECHO IL FILE OUTPUT.DAT NON CONTIENE LA SEZIONE DI RIEPILOGO
GOTO END
:SELERROR
ECHO ERRORE NELLA GENERAZIONE DEL RIEPILOGO
:END
La sezione estratta da OUTPUT.DAT, presente sul disco remoto, è scritta nel file RIEPILOG.DAT, sul disco locale. Dal momento che SELSTR restituisce in ERRORLEVEL un codice indicativo dello stato di uscita, è possibile effettuare in modo semplice la gestione degli errori: come si vede, in caso di errore di elaborazione viene restituito : il flusso del file batch salta all'etichetta SELERROR ed è visualizzato un opportuno messaggio. Se ERRORLEVEL vale , significa che l'elaborazione è terminata regolarmente ed è stata estratta almeno una riga di testo; altrimenti ERRORLEVEL contiene necessariamente , che indica terminazione regolare, ma senza estrazione di testo: non rimane che segnalare che il contenuto di OUTPUT.DAT non è quello atteso.
A questo punto vi è la necessità (ebbene sì) di postelaborare RIEPILOG.DAT: a scopo di esempio, ipotizziamo che tra riga iniziale e finale ne sia presente un numero variabile, tutte aventi il formato descritto di seguito:
+-CODICE-¦+-DATA-¦+-FILE-¦+-IMPORTO-¦
La sezione estratta, priva di riga inizale e finale, ha pertanto un aspetto analogo a quello delle righe seguenti:
099355476719940722OPER000100120344720
098446273319940722OPER003400009873325
088836288219940831OPER102800014436255
094553728219940912OPER001700225467520
062253725519941004OPER013100067855490
084463282919941103OPER000700127377385
E' necessario generare una tabella, da scrivere in un nuovo file, in cui solamente i campi DATA IMPORTO e CODICE siano riportati in modo leggibile e nell'ordine indicato. E' inoltre necessario generare un secondo file, ogni riga del quale contenga esclusivamente il campo FILE, a scopo di elaborazione successiva.
Strumento atto allo scopo è la utility CUT, ispirata all'omonimo comando Unix, del quale implementa le caratteristiche principali (in sostanza, la capacità di estrarre colonne di testo da un file), affiancandovi alcune novità.
CUT legge lo standard input, da ogni riga del quale estrae i caratteri occupanti le posizioni specificate mediante l'opzione della command line ‑c e li scrive sullo standard output. Tutti i messaggi sono scritti sullo standard error, al fine di non influenzare l'eventuale redirezione dell'output prodotto.
E' possibile specificare più istanze dell'opzione ‑c per richiedere l'estrazione di più colonne di testo: queste possono sovrapporsi parzialmente o totalmente; inoltre la posizione di partenza può essere superiore a quella di arrivo (in questo caso CUT estrae i caratteri da destra a sinistra). Vediamo un esempio: nell'ipotesi che lo standard input di CUT sia costituito dalla riga
ABCDE12345FGHIJ67890
il comando
cut -c1-3 -c2-5 -c8-5 -c15-25
scrive sullo standard output la riga
ABCBCDE321EJ67890
Si noti che le posizioni dei caratteri sono numerate a partire da 1. Inoltre, per default, laddove la lunghezza della riga di standard input non sia sufficiente a soddisfare interamente uno degli intervalli specificati (come nel caso ‑c15-25), vengono comunque estratti almeno i caratteri presenti: sono disponibili, però, tre opzioni di command line con le quali è possibile gestire tali situazioni in modo differente. In particolare, ‑d forza CUT a scartare la riga di input, mentre ‑x determina l'interruzione dell'elaborazione con ERRORLEVEL pari a (il valore di default per una terminazione regolare è ). Ancora, l'opzione ‑p consente di specificare una stringa dalla quale estrarre i caratteri necessari a compensare quelli mancanti nello standard input: il comando
cut -p#=@ -c15-25
estrae dallo standard input dell'esempio precedente la riga
J67890#=@#=
Alcuni caratteri della stringa ' ' sono ripetuti, in quanto la sua lunghezza non è sufficiente; al contrario, i caratteri eventualmente eccedenti la lunghezza necessaria sono ignorati.
CUT riconosce inoltre alcune opzioni che consentono di inserire stringhe in testa (‑b) e in coda (‑e) alle righe di standard output, nonché tra gli intervalli di caratteri (‑m). Vediamone un esempio di utilizzo nel solito file batch:
:ATTESA
TIMEGONE 230000
IF ERRORLEVEL 1 GOTO ADESSO
GOTO ATTESA
:ADESSO
ECHO LE ORE 23:00 SONO APPENA TRASCORSE
DIR 'D:PROCOUTPUT.DAT' | FIND ' 0 ' | EMPTYLVL
IF ERRORLEVEL 1 GOTO NONZERO
ECHO IL FILE OUTPUT.DAT HA DIMENSIONE 0 BYTES: UTILIZZO FCREATE
DATECMD FCREATE 0 C:PROCOUT@a@M@G.OUT
GOTO END
:NONZERO
ECHO IL FILE OUTPUT.DAT HA DIMENSIONE MAGGIORE DI 0 BYTES: UTILIZZO COPY
DATECMD COPY D:PROCOUTPUT.DAT C:PROCOUT@a@M@G.OUT
ECHO GENERAZIONE DEL FILE DI RIEPILOGO
SELSTR -cb '* RIEPILOGO *' '* FINE *' < D:PROCOUTPUT.DAT > C:PROCRIEPILOG.DAT
IF ERRORLEVEL 255 GOTO SELERROR
IF ERRORLEVEL 1 GOTO POSTELAB
ECHO IL FILE OUTPUT.DAT NON CONTIENE LA SEZIONE DI RIEPILOGO
GOTO END
:SELERROR
ECHO ERRORE NELLA GENERAZIONE DEL RIEPILOGO
:POSTELAB
ECHO GENERAZIONE DELLA TABELLA DI RIEPILOGO
TYPE C:PROCRIEPILOG.DAT FIND /V '* RIEPIL' | FIND /V '* FINE *' > C:PROCTR.TMP
ECHO TABELLA DI RIEPILOGO > C:PROCTR.TXT
ECHO. >> C:PROCTR.TXT
ECHO DATA IMPORTO CODICE >> C:PROCTR.TXT
ECHO +-------- ----- ------ ----+ >> C:PROCTR.TXT
CUT -b'¦ ' -m' ¦ ' -e' ¦' -c11-18 -c27-37 -c1-10 < C:PROCTR.TMP >> C:PROCTR.TXT
ECHO +-------- ----- ------ ----+ >> C:PROCTR.TMP
ECHO GENERAZIONE DELLA LISTA DI FILES
CUT -c19-26 < C:PROCTR.TMP > C:PROCFILES.DAT
DEL C:PROCTR.TMP
:END
Il comando FIND è utilizzato con l'opzione /V per generare un file temporaneo (TR.TMP) contenente tutte le righe di RIEPILOG.DAT, eccetto la prima e l'ultima (intestazione e chiusura); TR.TMP è poi elaborato con CUT per produrre la tabella desiderata (TR.TXT) e il file contenente le sole occorenze del campo FILES
Il contenuto del file TR.TXT è il seguente:
TABELLA DI RIEPILOGO
DATA IMPORTO CODICE
19940722 |
00120344720 |
0993554767 |
19940722 |
00009873325 |
0984462733 |
19940831 |
00014436255 |
0888362882 |
|
|
|
19940912 |
00225467520 |
0945537282 |
19941004 |
00067855490 |
0622537255 |
19941103 |
00127377385 |
0844632829 |
Il contenuto di FILES.DAT è invece:
OPER0001
OPER0034
OPER1028
OPER0017
OPER0131
OPER0007
Segue il listato commentato di CUT (vedere pag. e seguenti circa l'implementazione di PARSEOPT.OBJ
CUT.C - Barninga Z! - 1994
Utility che riprende ed amplia le funzionalita' del comando CUT di Unix.
Circa la sintassi vedere la variabile helpStr.
Compilato sotto Borland C++ 3.1
bcc -O -d -rd -k- cut.c parseopt.obj
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include 'parseopt.h'
#define PRG 'CUT' // nome del programma
#define REL '1.0' // versione
#define YEAR '94' // anno di rilascio
#define ERRCOD 255 // codice errore uscita programma
#define MAXLIN 2048 // massima lunghezza di una riga
#define MAXPAD 128 // max lungh. stringa di padding
#define CUTLEN 4 // max 4 cifre per opz. -c
#define CUTOPT 1 // -c
#define EXITOPT 2 // -x
#define DISCRDOPT 4 // -d
#define PADOPT 8 // -p
#define MIDOPT 16 // -s
#define BEGOPT 32 // -b
#define ENDOPT 64 // -e
#define SWITCH '-' // switch character per opzioni
// CUTTER e' il template di struttura che controlla la formazione delle
// colonne di testo: start contiene l'offset del primo byte; stop quello
// dell'ultimo. In pratica sono gli estremi del range da copiare. start
// puo' essere maggiore di stop, nel qual caso i bytes sono copiati in
// ordine inverso
typedef struct CUTTER;
char *helpStr = '
Syntax is: cut option(s)n
Default exit code: 0. Input: stdin; Output: stdout; Msgs: stderr. Options are:n
-bBEGSTR BEGSTR is the string to be inserted at the beginning of eachn
output line.n
-cSTART-STOP START-STOP specify a range of characters to be copied from eachn
input line to output. If START is greater than STOP, charactersn
in the range are reversed. More than one range can be given;n
anyway, at least one range is required. See also -d -p -x.n
-d If one or more -c ranges fall outside an input line, the linen
itself will be discarded. By default, columns are output evenn
if one or more ranges are not full. -d -p -x are mutuallyn
exclusive.n
-eENDSTR ENDSTR is the string to be appended at the end of each outputn
line.n
-mMIDSTR MIDSTR is the string to be inserted between two -c ranges; usefuln
only if more than one range is given.n
-pPADSTR PADSTR is the string used to pad output columns of -c rangesn
falling outside a stdin line. When -p is not requested, outputn
lines can have different length. -p -d -x are mutually exclusive.n
-x If one or more -c ranges fall outside an input line, the programn
will immediately return 255. -x -d -p are mutually exclusive.n
Displays this help screen and exits with a return code of 255.
char *crgtMsg = '%s %s: cuts stdin into columns. Barninga Z! '%s. Help: -?n';
char *eLinEnd = 'Cut pointers past end of line';
char *eOptCut = 'No cut option specified';
char *eOptFmt = 'Invalid option format';
char *eOptGen = 'Invalid option or option format';
char *eOptSeq = 'Invalid option sequence';
CUTTER *cutArr; // struttura di controllo
char padStr[MAXPAD]; // stringa per il padding
char begStr[MAXPAD]; // stringa di apertura
char midStr[MAXPAD]; // stringa di fincatura
char endStr[MAXPAD]; // stringa di chiusura
int valid_b(struct OPT *vld,int cnt);
int valid_c(struct OPT *vld,int cnt);
int valid_d(struct OPT *vld,int cnt);
int valid_e(struct OPT *vld,int cnt);
int valid_m(struct OPT *vld,int cnt);
int valid_p(struct OPT *vld,int cnt);
int valid_x(struct OPT *vld,int cnt);
int hlp_handle(struct OPT *vld,int cnt);
int err_handle(struct OPT *vld,int cnt);
int main(int argc,char **argv);
void cutstream(char *line,char *buffer,char *bufprint,int maxlen);
#pragma warn -par
#pragma warn -rvl
// se specificata opzione -b la stringa argomento deve essere copiata nello
// spazio apposito (begStr). Questa stringa e' scritta in testa ad ogni riga
// di standard output prodotta dal programma.
int valid_b(struct OPT *vld,int cnt)
// almeno una opzione -c deve sempre essere specificata. Qui vengono effettuati
// tutti i controlli di liceita' sull'argomento (estremi del range di colonne
// da estrarre da stdin) e per ogni range e' allocata una struttura CUTTER,
// formando cosi' un array di strutture che descrivono come deve essere
// 'ritagliato' stdin.
int valid_c(struct OPT *vld,int cnt)
else
if(!isdigit(vld->arg[i]))
err_handle(NULL,(int)eOptFmt); // errore: non e' - o una cifra
if(!flag)
err_handle(NULL,(int)eOptFmt); // errore: non c'e' il -
if((i-flag) > CUTLEN)
err_handle(NULL,(int)eOptFmt); // errore: numero troppo alto
if(!cutArr) // solo prima volta!!
if(!(cutArr = (CUTTER *)malloc(sizeof(CUTTER))))
err_handle(NULL,(int)sys_errlist[errno]);
sscanf(vld->arg,'%d-%d',&(cutArr[instance].start),&(cutArr[instance].stop));
if(!cutArr[instance].start || !cutArr[instance].stop)
err_handle(NULL,(int)eOptFmt);
++instance;
if(!(cutArr = (CUTTER *)realloc(cutArr,(instance+1)*sizeof(CUTTER))))
err_handle(NULL,(int)sys_errlist[errno]);
cutArr[instance].start = cutArr[instance].stop = NULL;
return(options |= 1);
// l'opzione -d richiede che se uno o entrambi gli estermi di un range (-c)
// 'cadono' al di fuori della riga attualmente processata la riga stessa
// deve essere scartata. L'opzione -d e' alternativa a -p e -x.
int valid_d(struct OPT *vld,int cnt)
// se specificata opzione -e la stringa argomento deve essere copiata nello
// spazio apposito (endStr). Questa stringa e' scritta in coda ad ogni riga
// di standard output prodotta dal programma.
int valid_e(struct OPT *vld,int cnt)
// se specificata opzione -m la stringa argomento deve essere copiata nello
// spazio apposito (midStr). Questa stringa e' scritta, in ogni riga di
// standard output prodotta dal programma, tra due range di caratteri estratti
// (-c) da stdin, se almeno 2 istanze di -c sono state specificate. Se ne e'
// stata richiesta una sola, l'opzione -m e' ignorata.
int valid_m(struct OPT *vld,int cnt)
// se specificata opzione -p la stringa argomento deve essere copiata nello
// spazio apposito (padStr). Questa stringa e' utilizzata, in ogni riga di
// standard output prodotta dal programma, per riempire il range richiesto
// (-c), se il numero di caratteri estratti da stdin e' minore dell'ampiezza
// specificata per il range stesso. E' significativa solo se uno o entrambi
// gli estremi di un range 'cadono' al di fuori della riga di stdin attualmente
// processata. E' alternativa a -d e -x.
int valid_p(struct OPT *vld,int cnt)
// l'opzione -x richiede che se uno o entrambi gli estermi di un range (-c)
// 'cadono' al di fuori della riga attualmente processata l'elaborazione deve
// essere interrotta e restituito un codice di errore. Alternativa a -p e -x.
int valid_x(struct OPT *vld,int cnt)
// visualizza la videata di help se e' specificata opzione -?
int hlp_handle(struct OPT *vld,int cnt)
// gestisce tutte le situazioni in cui e' necessario visualizzare un messaggio
// ed uscire con un errore. Se il parametro vld e' NULL significa che la
// chiamata e' fatta esplicitamente dal programmatore e quindi e' visualizzato
// il messaggio il cui indirizzo e' passato come secondo parametro (con
// opportuno cast!). Se vld non e' NULL, allora err_handle() e' chiamata da
// parseopt() e quindi e' visualizzato un messaggio generico.
int err_handle(struct OPT *vld,int cnt)
#pragma warn .par
#pragma warn .rvl
// array che associa le opzioni alla funzione di validazione corrispondente
struct VOPT vfuncs[] = ,
,
,
,
,
,
,
,
,
// stringa di elenco delle opzioni; se una lettera e' seguita da ':' significa
// che l'opzione richiede un argomento
char *optionStr = 'b:c:de:m:p:x?';
unsigned options; // flags delle opzioni
// main() valida le opzioni via parseopt() e gestisce il ciclo di estrazione
// dei range per ogni riga di input. Tutti i messaggi del programma, incluso
// il copyright, sono scritti su stderr; in tal modo la redirezione
// dell'output prodotto non li include.
int main(int argc,char **argv)
while(gets(line))
cutstream(line,inibuf,buffer,maxlen);
return(0);
// cutstream() estrae i range dalla riga di testo ricevuta come parametro e
// scrive la riga output su stdout. Si noti che il paramtero bufprint e'
// l'indirizzo originale del buffer: questo infatti deve essere stampato
// tutto anche se cutstream() ne lavora solo una parte (se c'e' begStr). E'
// evidente che in assenza di opzione -b, buffer e bufprint contengono lo
// stesso indirizzo.
void cutstream(char *line,char *buffer,char *bufprint,int maxlen)
// se uno solo degli estremi e' fuori dalla riga, allora una parte del range
// deriva da caratteri della riga, la cui fine diviene uno dei due estremi del
// range stesso (start o stop, a seconda della direzione). Il numero di
// caratteri di padding e' pari al numero di caratteri non estraibili dalla
// riga, cioe' alla parte 'scoperta' del range.
else
}
if(len) // si copia solo se la riga non e' vuota!
// il ciclo gestisce l'operazione di copia; l'applicazione a start di un
// incremento positivo o negativo a seconda dei casi permette di gestire
// entrambe le situazioni (da sx a dx o viceversa)
for(--start, --stop; j < maxlen-1; start += inc)
// se e' richiesta -p si copia la stringa di padding nel buffer per il numero
// di caratteri ancora mancanti nel range. Se la stringa e' piu' lunga del
// necessario viene troncata; se e' piu' corta viene ripetuta sino a riempire
// tutto lo spazio.
if(options & PADOPT)
for(k = 0; j < maxlen-1 && lim; lim--)
++i;
// se e' specificata -m e ci sono ancora range da estrarre (l'ultimo elemento
// dell'array di struct CUTTER contiene NULL in entrambe i campi per segnalare
// la fine) allora si concatena al buffer la stringa midStr. Non si usa
// strcat() per essere sicuri di non andare oltre lo spazio rimanente nel
// buffer.
if((options & MIDOPT) && cutArr[i].start)
for(k = 0; j < maxlen-1 && midStr[k]; )
buffer[j++] = midStr[k++];
else
// se invece quello appena estratto e' l'ultimo range ed e' specificata -e
// si concatena al buffer la endStr.
if((options & ENDOPT) && !cutArr[i].start)
for(k = 0; j < maxlen-1 && endStr[k]; )
buffer[j++] = endStr[k++];
}
buffer[j] = NULL; // tappo!
puts(bufprint);
Lanciando CUT con l'opzione sulla riga di comando viene visualizzato un testo di aiuto, che ne riassume le principali caratteristiche ed opzioni.
Veniamo a FILES.DAT (generato da CUT), ogni riga del quale, ai fini del nostro esempio, rappresenta un nome di file: ipotizziamo che su ciascuno di essi sia necessario ripetere la medesima elaborazione. L'interprete DOS implementa il comando FOR, che non è in grado di accettare una lista variabile di parametri (vedere pag. ): risulta quindi impossibile utilizzare FILES.DAT allo scopo. La utility DOLIST offre un rimedio: essa esegue una riga di comando, che viene considerata suddivisa in due parti, di cui la seconda opzionale, inserendo tra di esse la riga letta dallo standard input. Se da questo possono essere lette più righe, il comando viene iterato per ciascuna di esse.
La complessità del meccanismo è solo apparente: nell'ipotesi che l'elaborazione del nostro esempio consista nell'eseguire un secondo file batch il cui parametro sia ogni volta un diverso file, la procedura diviene la seguente:
:ATTESA
TIMEGONE 230000
IF ERRORLEVEL 1 GOTO ADESSO
GOTO ATTESA
:ADESSO
ECHO LE ORE 23:00 SONO APPENA TRASCORSE
DIR 'D:PROCOUTPUT.DAT' | FIND ' 0 ' | EMPTYLVL
IF ERRORLEVEL 1 GOTO NONZERO
ECHO IL FILE OUTPUT.DAT HA DIMENSIONE 0 BYTES: UTILIZZO FCREATE
DATECMD FCREATE 0 C:PROCOUT@a@M@G.OUT
GOTO END
:NONZERO
ECHO IL FILE OUTPUT.DAT HA DIMENSIONE MAGGIORE DI 0 BYTES: UTILIZZO COPY
DATECMD COPY D:PROCOUTPUT.DAT C:PROCOUT@a@M@G.OUT
ECHO GENERAZIONE DEL FILE DI RIEPILOGO
SELSTR -cb '* RIEPILOGO *' '* FINE *' < D:PROCOUTPUT.DAT > C:PROCRIEPILOG.DAT
IF ERRORLEVEL 255 GOTO SELERROR
IF ERRORLEVEL 1 GOTO POSTELAB
ECHO IL FILE OUTPUT.DAT NON CONTIENE LA SEZIONE DI RIEPILOGO
GOTO END
:SELERROR
ECHO ERRORE NELLA GENERAZIONE DEL RIEPILOGO
:POSTELAB
ECHO GENERAZIONE DELLA TABELLA DI RIEPILOGO
TYPE C:PROCRIEPILOG.DAT FIND /V '* RIEPIL' | FIND /V '* FINE *' > C:PROCTR.TMP
ECHO TABELLA DI RIEPILOGO > C:PROCTR.TXT
ECHO. >> C:PROCTR.TXT
ECHO DATA IMPORTO CODICE >> C:PROCTR.TXT
ECHO +-------- ----- ------ ----+ >> C:PROCTR.TXT
CUT -b'¦ ' -m' ¦ ' -e' ¦' -c11-18 -c27-37 -c1-10 < C:PROCTR.TMP >> C:PROCTR.TXT
ECHO +-------- ----- ------ ----+ >> C:PROCTR.TMP
ECHO GENERAZIONE DELLA LISTA DI FILES
CUT -c19-26 < C:PROCTR.TMP > C:PROCFILES.DAT
DEL C:PROCTR.TMP
ECHO ELABORAZIONE DELLA LISTA DI FILES IN FILES.DAT
DOLIST 'CALL ELABFILE.BAT' < FILES.LST
IF ERRORLEVEL GOTO FILERROR
GOTO END
:FILERROR
ECHO ERRORE NELLA ELABORAZIONE DELLA LISTA DI FILES
:END
DOLIST esegue la riga di comando una volta per ogni riga presente nel file FILES.DAT: con i dati dell'esempio, sono generati e lanciati i seguenti comandi:
CALL ELABFILE.BAT OPER0001
CALL ELABFILE.BAT OPER0034
CALL ELABFILE.BAT OPER1028
CALL ELABFILE.BAT OPER0017
CALL ELABFILE.BAT OPER0131
CALL ELABFILE.BAT OPER0007
Si noti l'utilizzo delle virgolette: esse sono necessarie in quanto CALL ed ELABFILE.BAT devono costituire insieme la prima parte del comando. Senza le virgolette DOLIST interpreterebbe CALL come prima porzione della command line e ELABFILE.BAT come seconda, inserendo tra esse la riga letta da FILES.DAT. I comandi eseguiti sarebbero perciò
CALL OPER0001 ELABFILE.BAT
CALL OPER0034 ELABFILE.BAT
CALL OPER1028 ELABFILE.BAT
CALL OPER0017 ELABFILE.BAT
CALL OPER0131 ELABFILE.BAT
CALL OPER0007 ELABFILE.BAT
con scarse possibilità di successo.
Segue il listato di DOLIST, ampiamente commentato.
DOLIST.C - Barninga_Z! - 17/10/93
Esegue un comando dos su una lista di argomenti. Esempio
dolist copy c:backups*.bak < files.lst
copia tutti i files elencati nel file files.lst nella dir c:backups
attribuendo a ciascuno estensione .bak. Il files contenente gli
argomenti deve essere in formato ascii, una riga per ogni comando.
In pratica dolist compone ed esegue il comando:
comando riga_della_lista comando_2a_parte
In uscita ERRORLEVEL e' settato come segue:
0 = comando eseguito (indipenentemente dal suo risultato)
1 = elaborazione interrotta (errore di sintassi della cmdline di DOLIST)
2 = comando non eseguito (system fallita)
3 = comando non eseguito (command line da eseguire > 127 caratteri)
Compilato sotto Borland C++ 3.1:
bcc -d -rd -k- dolist.c
#pragma warn -pia
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PRG 'DOLIST'
#define VER '1.0'
#define YEAR '93'
#define SEP_STR ' '
#define NUL_CHRS ' tnr'
#define MAXCMD 128
#define MAXLIN 256
// main() gestisce tutte le operazioni necessarie
int main(int argc,char **argv)
else
if(!system(cmd)) // esecuzione!
printf('%s: eseguito: %sn',PRG,cmd);
else
}
else
}
}
}
return(retCode);
DOLIST restituisce in ERRORLEVEL un valore determinato dallo stato dell'elaborazione: in particolare, indica che tutti i comandi sono stati eseguiti; evidenza un errore di sintassi nella riga di comando; indica il fallimento della chiamata alla funzione di libreria system(), che invoca l'interprete dei comandi DOS , infine, segnala che la lunghezza del comando composto mediante l'inserimento della riga di standard input tra prima e seconda parte della riga di comando risulta eccessiva
Che cosa accade in ELABFILE.BAT? Le ipotesi del nostro esempio si complicano sempre più (ma è l'ultimo sforzo, promesso!): la prima riga di ciascuno dei file elencati in FILES.DAT contiene un codice numerico, espresso in formato ASCII, che esprime uno stato relativo alle elaborazioni del riepilogo. La stringa indica che quella particolare operazione è stata conclusa senza errori (e pertanto non vi sono altre righe nel file); tutti gli altri codici segnalano errori (il file contiene altre righe, descrittive dell'errore stesso). Scopo di POSTELAB.BAT è generare un file contenente le sole segnalazioni di errore.
Anche in questo caso il sistema Unix è fonte di ispirazione: la utility CMDSUBST consente di eseguire comandi DOS formati in tutto o in parte dall'output di altri comandi . Vediamo subito un esempio: il comando
cmdsubst echo $type c:autoexec.bat$
produce un output costituito dalla prima riga del file AUTOEXEC.BAT. Infatti, CMDSUBST scandisce la command line alla ricerca dei caratteri ' ed esegue, come un normale comando DOS, quanto tra essi compreso. La prima riga dello standard output prodotto dal comando è inserita nella command line originale, in luogo della stringa eseguita. Infine, è eseguita la command line risultante. Nell'esempio testè presentato, pertanto, viene dapprima lanciato il comando
type c:autoexec.bat
Nell'ipotesi che la prima riga del file sia
@ECHO OFF
la command line risultante, eseguita da CMDSUBST
echo @ECHO OFF
la quale, a sua volta, produce lo standard output
@ECHO OFF
E' possibile specificare più subcomandi, ma non è possibile nidificarli: in altre parole, è valida una command line come:
cmdsubst echo $echo pippo$ $echo pluto$ $echo paperino$
che produce l'output
pippoplutopaperino
ma non lo è, o meglio, non è interpretata come forse ci si aspetterebbe, la seguente:
cmdsubst echo $echo $echo pippo$$
L'output prodotto è
echo is ON echo pippo$
Infatti, CMDSUBST individua il primo subcomando nella sequenza '$echo $', mentre la coppia ' ' è sostituita con un singolo carattere ' ' (è questa, del resto, la sintassi che consente di specificare una command line contenente il ' ' medesimo).
Torniamo al nostro caso pratico: il file ELABFILE.BAT può dunque essere strutturato come segue
@ECHO OFF
CMDSUBST IF @$TYPE D:PROC%1$@==@000@ GOTO END
IF ERRORLEVEL 1 GOTO ERRORE
SELSTR -f2 STRINGA_INESISTENTE < D:PROC%1 >> C:PROCERRORI.DAT
GOTO END
:ERRORE
ECHO ERRORE NELL'ESECUZIONE DELL'ELABORAZIONE DI %1
:END
La sostituzione effettuata da CMDSUBST genera un test IF dalla sintassi lecita; SELTSR estrae dal file tutto il testo a partire dalla seconda riga (opzione ‑f), nell'ipotesi che la stringa 'STRINGA_INESISTENTE' non compaia mai nei file elaborati. La variabile è sostituita dal DOS con il parametro passato a ELABFILE.BAT dal batch chiamante (nell'esempio si tratta del nome del file da elaborare). Si noti, infine, che nel batch chiamante è opportuno inserire, prima della riga che invoca DOLIST, un comando di cancellazione del file C:PROCERRORI.DAT, dal momento che SELSTR vi scrive lo standard output in modalità di append (il testo è aggiunto in coda al file, se esistente; in caso contrario questo è creato e 'allungato' di volta in volta).
Si ha perciò:
ECHO ELABORAZIONE DELLA LISTA DI FILES IN FILES.DAT
IF EXIST C:PROCERRORI.DAT DEL C:PROCERRORI.DAT
DOLIST 'CALL ELABFILE.BAT' < FILES.LST
IF ERRORLEVEL 1 GOTO FILERROR
Il listato di CMDSUBST, ampiamente commentato, è riportato di seguito.
CMDSUBST.C - Barninga Z! - 1994
Esegue command line sostituendo la prima riga dello stdout di un altro
comando compreso tra i caratteri $. Vedere stringa di help per i
particolari.
Compilato sotto Borland C++ 3.1
bcc cmdsubst.c
#pragma warn -pia
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <alloc.h>
#include <dir.h>
#include <process.h>
#define PRG 'CMDSUBST'
#define VER '1.0'
#define YEAR ''93'
#define SUBST_FLAG '$'
#define MAXCMD 128
#define TEMPLATE 'CSXXXXXX'
#define ROOTDIR '?:'
#define OUTREDIR '>'
#define SEPSTR ' '
#define EOL 'n'
char *helpStr = '
The command line is searched for '$' characters. If found, a string between twon
'$' is executed as a command line and the first line of its standard outputn
replaces it in the CMDSUBST command line itself. Example: if the first line ofn
the file c:autoexec.bat isnn
@ECHO OFFnn
the command linenn
CMDSUBST echo $type c:autoexec.bat$nn
causes the commandnn
echo @ECHO OFFnn
to be actually executed. If a CMDSUBST command line contains a '$', it must ben
typed twice.n
// il template SUBCTL costituisce lo strumento per il controllo della
// sostituzione dei comandi nella command line. Il campo init referenzia
// il primo byte del comando, il campo len ne contiene la lunghezza. Viene
// allocato un array di strutture, ogni elemento del quale controlla la
// sostituzione di un comando.
typedef struct SUBCTL;
int main(int argc,char **argv);
char *execSubCmd(char *subCmd,char *tempPath);
char *getCmdLine(int argc,char **argv);
char *getSubCmdOutput(char *outString,char *tempPath);
SUBCTL *initSubCtl(SUBCTL *ctl,int items,char *cmdBase);
char *makeCmdLine(SUBCTL *ctl,char *cmdLine,char *tempPath);
char *makeTempPath(char *tempPath);
SUBCTL *parseCmdLine(char *cmdLine);
// main() controlla l'esecuzione del programma in 4 fasi: ricostruzione
// della command line; creazione di un file temporaneo per la gestione
// dello stdout dei programmi; scansione della command line ed esecuzione
// dei subcomandi in essa contenuti; costruzione della nuova command line
// e sua esecuzione.
int main(int argc,char **argv)
if(!makeTempPath(tempPath))
if(!(ctl = parseCmdLine(cmdLine)))
if(cmdLine = makeCmdLine(ctl,cmdLine,tempPath))
system(cmdLine);
return(0);
// execSubCmd() esegue un subcomando compreso nella command line di CMDSUBST.
// Viene costruita una command line contenente il subcomando e l'istruzione
// di redirezione del suo standard output nel file temporaneo il cui nome
// e' stato costruito da makeTempPath(). L'output e' poi analizzato dalla
// getSubCmdOutput() e il file temporaneo e' cancellato. Il sottocomando e'
// eseguito via system().
char *execSubCmd(char *subCmd,char *tempPath)
else
fprintf(stderr,'%s: error: subcmd string too long.n',PRG);
return(ptr);
// getCmdLine() ricostruisce la command line passata a CMDSUBST concatenando
// in un buffer static tutti gli elementi di argv. Il buffer deve essere
// static perche' l'indirizzo e' restituito alla funzione chiamante. In
// alternativa lo si potrebbe allocare con malloc()
char *getCmdLine(int argc,char **argv)
cmdLine[strlen(cmdLine)-1] = NULL;
return(cmdLine);
// getSubCmdOutput() apre il file temporaneo contenente lo standard output
// del subcomando eseguito e ne legge la prima riga, che deve essere
// sostituita al subcomando stesso nella command line di CMDSUBST.
char *getSubCmdOutput(char *outString,char *tempPath)
return(outString);
// initSubCtl() inizializza una struttura di controllo del subcomando,
// riallocando l'array. La prima chiamata a initSubCtl() le passa un NULL
// come puntatore all'array, cosi' la funzione puo' usare realloc() anche
// per allocare la prima struttura.
SUBCTL *initSubCtl(SUBCTL *ctl,int items,char *cmdBase)
ctl[items-1].init = cmdBase+strlen(cmdBase);
ctl[items-1].len = 0;
return(ctl);
// makeCmdLine() costruisce la nuova command line che CMDSUBST deve eseguire
// sostituendo ai sottocomandi la prima riga del loro standard output. La
// command line cosi' costruita e' restituita a main(), che la esegue via
// system().
char *makeCmdLine(SUBCTL *ctl,char *cmdLine,char *tempPath)
} while(ctl[i++].len);
return(newCmdLine);
// makeTempPath() prepara un nome di file temporaneo completo di path formato
// dal drive di default e dalla root. Il file temporaneo e' percio' scritto
// in root ed e' destinato a contenere lo stdout del comando eseguito
char *makeTempPath(char *tempPath)
// parseCmdLine() scandisce la command line passata a CMDSUBST per individuare
// eventuali subcomandi racchiusi tra i '$'. Per ogni subcomando e' allocata
// una nuova struttura SUBCTL nell'array. Se sono incontrati due '$'
// consecutivi, si presume che essi rappresentino un solo '$' effettivo nella
// command line: uno e' scartato e non ha luogo alcuna sostituzione. La
// funzione non e' in grado di gestire sostituzioni nidificate.
SUBCTL *parseCmdLine(char *cmdLine)
}
}
return(flag ? NULL : ctl);
Qualora non sia possibile effettuare la sostituzione nella command line (riga di comando risultante eccessivamente lunga o caratteri ' ' scorrettamente appaiati), o la funzione system() restituisca un codice di errore CMDSUBST termina con un valore di ERRORLEVEL diverso da
La definizione comandi esterni è spesso applicata ai soli programmi facenti parte del sistema operativo in senso stretto (DISKCOPY FORMAT, etc.).
FOR consente di ripetere un comando su una lista di argomenti elencati tra parentesi tonde: non vi è modo di utilizzare una lista esterna al batch, quale, ad esempio, un file ASCII gestito automaticamente da altre procedure.
Si tratta di programmi progettati espressamente per automatizzare elaborazioni eseguite in parallelo da diverse macchine su dati condivisi in rete; tuttavia essi possono, comunque, risultare utili in ambienti meno complessi: si pensi a procedure eseguite su una medesima macchina, ma in multitasking (ad esempio quali task DOS in Microsoft Windows 3.x). Del resto non è affatto esclusa la possibilità di servirsene produttivamente in semplici procedure eseguite in modo standalone e monotasking.
In poche parole: lo standard output di DIR non viene visualizzato, bensì è trasformato dal DOS in standard input per il comando successivo (in questo caso FIND
DATECMD esegue la propria command line effettuando una DOS shell, cioè invocando una seconda istanza (transiente) dell'interprete dei comandi. Ne segue che COMMAND.COM (o, comunque, l'interprete utilizzato) deve essere disponibile (nella directory corrente o in una di quelle elencate nella variabile d'ambiente PATH) e deve esserci memoria libera in quantità sufficiente per il caricamento dell'interprete stesso e per l'esecuzione del comando da parte di questo. Vedere pag. 135.
Può trattarsi di un inconveniente grave qualora, ad esempio, altre procedure abbiano necessità di accedere comunque ai file (o, quanto meno, di verificarne l'esistenza) per operare correttamente.
Si dice remoto (in contrapposizione a locale) un disco appartenente ad una macchina diversa da quella sulla quale si svolge la sessione di lavoro. Se più personal computer sono collegati in rete, ciascuno di essi può utilizzare, in aggiunta alle proprie risorse locali, quelle remote rese disponibili dalle macchine configurate come server. L'utilizzo alle risorse remote avviene in modo condiviso, in quanto più personal computer possono accedervi contemporaneamente.
Qualcosa si può fare anche senza TIMEGONE, con l'aiuto di EMPTYLVL (pag. ). Innanzitutto si deve creare un file ASCII contenente un CR (ASCII ), che, per comodità, indichiamo col nome CR.TXT. Occorre poi inserire nella procedura batch una sequenza di istruzioni analoga alla seguente:
:ASPETTA
TIME < CR.TXT | FIND '15:09' | EMPTYLVL
IF ERRORLEVEL 1 GOTO ASPETTA
Lo standard input del comando TIME è rediretto da CR.TXT, con l'effetto di terminare il comando senza modificare l'ora di sistema; lo standard output è invece rediretto, mediante piping, sullo standard input di FIND, che cerca la stringa contenente l'ora desiderata (nell'esempio le 15:09); infine, EMPTYLVL memorizza in ERRORLEVEL se la stringa non è trovata (l'ora non è quella voluta). I limiti della soluzione descritta sono evidenti: in primo luogo il batch deve essere lanciato necessariamente prima dell'ora specificata, in quanto la stringa non viene trovata anche se quella è già trascorsa; inoltre si tratta di un algoritmo applicabile con difficoltà al comando DATE, in particolare quando si desideri effettuare il test su parte soltanto della data. La utility CUT (presentata a pag. ) può venire in soccorso, ma si introducono, comunque, complicazioni notevoli al listato qui riprodotto.
Si tratta di una tecnica di ottimizzazione delle operazioni di I/O, implementata mediante algoritmi, più o meno sofisticati, di gestione di buffer, che consentono di limitare il numero di accessi alle periferiche hardware (con particolare riferimento ai dischi). Sull'argomento vedere pag.
DOLIST esegue la command line effettuando una DOS shell, cioè invocando una seconda istanza (transiente) dell'interprete dei comandi. Ne segue che COMMAND.COM (o, comunque, l'interprete utilizzato) deve essere disponibile (nella directory corrente o in una di quelle elencate nella variabile d'ambiente PATH) e deve esserci memoria libera in quantità sufficiente per il caricamento dell'interprete stesso e per l'esecuzione del comando da parte di questo. Vedere pag. 135.
In realtà non esiste, in Unix, un comando analogo a CMDSUBST. Tuttavia la shell standard (l'equivalente dell'interprete dei comandi in ambiente DOS) scandisce la command line alla ricerca di stringhe racchiuse tra apici inversi (il carattere ' '), le esegue come veri e propri comandi e le sostutuisce, nella command line stessa, con lo standard output prodotto.
CMDSUBST utilizza il carattere ' ' in luogo dell'apice inverso per comodità: quest'ultimo non è presente sulla tastiera italiana.
Appunti su: creare un file in dos concatenando la data, |
|