In questo brevissimo tutorial vediamo come si possa, usando il C standard, leggere e scrivere file di testo. È richiesta una conoscenza minimale del C.

1. Quando già si sa quali file leggere e scrivere


In questo primo codice di esempio assumiamo di conoscere già quali file vorremo leggere e scrivere; il loro nome (ed eventualmente il percorso) verranno pertanto inclusi direttamente nel codice sorgente del programma. Si tratta del caso più semplice, e pertanto lo esaminiamo per primo.
Aprite il vostro editor di testo preferito e digitate il seguente programma:

#include <stdio.h>
#include <stdlib.h>

#define kFileDaLeggere          "input.txt"
#define kFileDaScrivere         "output.txt"
#define kLunghezzaMaxStringa    100

#define kErroreIO               -1
#define kErroreMemoria          -2
#define kNessunErrore           0

double      numero1,numero2,numero3;
char        *stringa;

int main (void);
int LeggiDaFile(void);
int ScriviSuFile(void);

int main (void)
{
    int         errore;
    
    errore=LeggiDaFile();
    if(errore)
        return errore;
    errore=ScriviSuFile();
    if(errore)
        return errore;
    
    return kNessunErrore;
}

int LeggiDaFile(void)
{
    FILE        *ingresso;
    
    printf("Lettura dal file %s\n",kFileDaLeggere);
    ingresso=fopen(kFileDaLeggere,"r");
    if(ingresso==NULL)
        return kErroreIO;
    stringa=calloc(kLunghezzaMaxStringa,sizeof(char));
    if(stringa==NULL)
        return kErroreMemoria;
    fscanf(ingresso,"%lg %lg %lg %s",&numero1,&numero2,&numero3,stringa);
    printf("Numero 1: %g\nNumero 2: %g\nNumero 3: %g\nStringa: %s\n",numero1,numero2,numero3,stringa);
    printf("\n");
    fclose(ingresso);
    
    return kNessunErrore;
}

int ScriviSuFile(void)
{
    FILE        *uscita;
    
    printf("Scrittura sul file %s\n",kFileDaScrivere);
    uscita=fopen(kFileDaScrivere,"w");
    if(uscita==NULL)
        return kErroreIO;
    fprintf(uscita,"%g %g %g %s\n",numero1,numero2,numero3,stringa);
    printf("Ora puoi aprire il file %s e verificare che contenga una copia di %s\n",kFileDaScrivere,kFileDaLeggere);
    printf("\n");
    return kNessunErrore;
}


Salvatelo come demoIO.c. Sempre col vostro editor di testo preferito, create anche un nuovo file, che salverete come input.txt nella stessa cartella in cui avete salvato demoIO.c, che abbia il seguente contenuto:

25.14 33.75 -3.14e24 Teodorico


Aprite il Terminale e recatevi nella cartella in cui avete salvato i due file. Compilate il programma col comando

gcc -o demoIO demoIO.c


Se non ci sono errori, eseguitelo col comando

./demoIO


Osservate che l'output dovrebbe essere simile a questo:

Lettura dal file input.txt
Numero 1: 25.14
Numero 2: 33.75
Numero 3: -3.14e+24
Stringa: Teodorico

Scrittura sul file output.txt
Ora puoi aprire il file output.txt e verificare che contenga una copia di input.txt


Dal Finder, aprite il file output.txt che è stato creato nella stessa cartella in cui avete salvato gli altri due, e verificate che contiene esattamente quello che conteneva input.txt.
Come è avvenuto tutto questo? Esaminiamo il codice nel dettaglio. I nomi dei file di ingresso e di uscita sono memorizzati a livello di codice nelle due costanti kFileDaLeggere e kFileDaScrivere. Notate che i file vanno specificati col loro percorso; in assenza di questo, il programma li cerca o li colloca nella stessa cartella in cui si trova l'eseguibile; in alternativa, dovete specificare il nome del file con un percorso relativo alla cartella in cui si trova l'eseguibile o assoluto.
Quattro variabili globali (numero1, numero2, numero3 e stringa) saranno deputate a conservare il contenuto del file di input così da poterlo riscrivere nel file di output.
Le funzioni che si occupano di leggere e scrivere i file sono ovviamente LeggiDaFile() e ScriviSuFile(). La prima apre il file di ingresso mediante la funzione fopen(). Essa vuole come primo argomento il nome del file da aprire (con il suo percorso, relativo o assoluto), e come secondo argomento un parametro che specifica come vada aperto il file. Qui specifichiamo "r", che indica che il file va aperto in lettura (reading). Discuteremo più avanti degli altri parametri disponibili. Se il file viene correttamente trovato ed aperto, la variabile ingresso di tipo puntatore a FILE contiene un riferimento al file specificato; se no contiene il valore NULL, che può essere controllato per verificare se ci sono stati errori.
La lettura dal file si fa con una funzione assolutamente analoga a scanf() di nome fscanf(); l'unica differenza è che fscanf() anziché leggere dallo standard input (ovvero dalla tastiera) legge dal file specificato come primo argomento; il secondo argomento individua il formato di ciò che si va a leggere, e a seguire ci sono i puntatori alle variabili in cui verranno inseriti i contenuti del file. A parte il primo argomento (il puntatore al file), l'uso di fscanf() è assolutamente identico all'uso di scanf(), e alla documentazione su quest'ultima si rimanda il lettore.
Il file viene quindi chiuso con una chiamata a fclose().
La scrittura sul file si effettua un modo assolutamente analogo, le uniche differenze essendo il parametro da passare a fopen() (ora gli passiamo "w" per indicare la scrittura su file, o writing), e la funzione usata per la scrittura dei contenuti dei file: fprintf(), identica a printf() nell'uso (e ancora una volta rimandiamo il lettore alla documentazione su printf() per il dettaglio della sintassi) con la sola differenza che richiede, come primo argomento, il puntatore al file di uscita.

2. Quando ancora non si sa quali file leggere e scrivere

È possibile che in sede di scrittura del programma ancora non si sappia quali file verranno letti o scritti; tale scelta sarà infatti rinviata in fase di runtime. In questo caso non è possibile includere il nome del file e il suo percorso direttamente nel codice del programma, ma è meglio lasciare che sia l'utente a specificare su quali file intende agire. È naturalmente possibile prevedere una funzione del programma che chiede all'utente di immettere nome e percorso dei file di ingresso e di uscita, ma è possibile anche un'altra strada che esaminiamo qui brevemente: specificare i nomi dei file di interesse direttamente quando si lancia il programma tramite il Terminale.
Aprite il vostro editor di testo preferito e digitate il seguente codice, che è una variante di quello precedente:

#include <stdio.h>
#include <stdlib.h>

#define kLunghezzaMaxStringa    100

#define kErroreIO               -1
#define kErroreMemoria          -2
#define kErroreArgomenti        -3
#define kNessunErrore           0

double      numero1,numero2,numero3;
char        *stringa;

int main (int argc, char *argv[]);
int LeggiDaFile(char *fin);
int ScriviSuFile(char *fout);

int main (int argc, char *argv[])
{
    int         errore;
    
    if(argc != 3)
    {
        printf("Devi specificare un file di ingresso e uno di uscita!\n");
        return kErroreArgomenti;
    }
    errore=LeggiDaFile(argv[1]);
    if(errore)
        return errore;
    errore=ScriviSuFile(argv[2]);
    if(errore)
        return errore;
    
    return kNessunErrore;
}

int LeggiDaFile(char *fin)
{
    FILE        *ingresso;
    
    printf("Lettura dal file %s\n",fin);
    ingresso=fopen(fin,"r");
    if(ingresso==NULL)
        return kErroreIO;
    stringa=calloc(kLunghezzaMaxStringa,sizeof(char));
    if(stringa==NULL)
        return kErroreMemoria;
    fscanf(ingresso,"%lg %lg %lg %s",&numero1,&numero2,&numero3,stringa);
    printf("Numero 1: %g\nNumero 2: %g\nNumero 3: %g\nStringa: %s\n",numero1,numero2,numero3,stringa);
    printf("\n");
    fclose(ingresso);
    
    return kNessunErrore;
}

int ScriviSuFile(char *fout)
{
    FILE        *uscita;
    
    printf("Scrittura sul file %s\n",fout);
    uscita=fopen(fout,"w");
    if(uscita==NULL)
        return kErroreIO;
    fprintf(uscita,"%g %g %g %s\n",numero1,numero2,numero3,stringa);
    printf("Ora puoi aprire il file %s.\n",fout);
    printf("\n");
    return kNessunErrore;
}


Salvatelo come demoIOargomenti.c. Nella stessa cartella accertatevi che ci sia il file di testo input.txt già creato in precedenza, e abbiate cura di cancellare il file output.txt. Compilate il programma col comando

gcc -o demoIOargomenti demoIOargomenti.c


ed eseguitelo col comando

./demoIOargomenti input.txt output.txt


Osservate che nella cartella in cui si trova il programma viene creato il file output.txt, che come prima contiene una copia del file input.txt. Le funzioni di lettura da e di scrittura su file sono sempre le stesse, semplicemente abbiamo sfruttato la possibilità offerta dal C di ricevere degli argomenti nella funzione main(), argomenti che vengono specificati dall'utente all'atto di eseguire il programma. La funzione main() infatti ha ora due argomenti. Il primo, argc di tipo int, contiene il numero di argomenti specificati dall'utente quando ha eseguito il programma; essa vale sempre almeno 1, in quanto il primo argomento è sempre il nome del programma stesso. Il secondo, argv[], è un'array di puntatori a char (ovvero un'array di stringhe), e contiene (sotto forma di stringhe per l'appunto) i vari argomenti passati dall'utente al programma. Nel nostro caso, il programma richiede che l'utente specifichi esattamente e in quest'ordine un solo file di ingresso e un solo file di uscita, quindi argc deve valere 3 (nome del programma, file di ingresso, file di uscita) e l'array argv[] conterrà, in corrispondenza degli indici 0, 1 e 2 rispettivamente il nome del programma, il nome del file di ingresso e il nome del file di uscita. Questi ultimi due vengono passati ad argomento a LeggiDaFile() e a ScriviSuFile().

3. Osservazioni


4. I parametri di fopen()

Qui di seguito sono indicati i parametri che, racchiusi tra virgolette, possono essere passati a fopen() per specificare la modalità con cui andrà aperto il file: Buon divertimento! : )