Passaggio dei Nomi dei File come Argomenti della Riga di Comando in Linguaggio C

Concetti Chiave
  • I nomi dei file possono essere passati come argomenti della riga di comando a un programma in linguaggio C.
  • La funzione main può essere definita con due parametri, int argc e char *argv[], per ricevere gli argomenti della riga di comando.
  • argc rappresenta il numero di argomenti passati, mentre argv è un array di stringhe che contiene i singoli argomenti.
  • argv[0] contiene il nome del programma, mentre argv[1], argv[2], ..., argv[argc-1] contengono gli argomenti passati.
  • Utilizzare gli argomenti della riga di comando per specificare i nomi dei file rende il programma più flessibile e facile da usare.

Specificare i Nomi dei File in un Programma

Quando si scrive un programma in linguaggio C che deve operare su uno o più file, sorge l'esigenza di specificare quali file devono essere aperti e utilizzati dal programma stesso.

Tecnicamente ci sono tre modi per specificare i nomi dei file da aprire in un programma C:

  1. Hardcoding: inserire direttamente i nomi dei file nel codice sorgente del programma. Questo metodo è semplice ma poco flessibile, poiché richiede la modifica del codice ogni volta che si desidera utilizzare un file diverso.

    Inoltre, lo svantaggio principale di questo approccio è che o si usano percorsi relativi (che dipendono dalla directory di esecuzione del programma) o si usano percorsi assoluti (che possono variare da sistema a sistema).

  2. Richiesta interattiva: il programma può chiedere all'utente di inserire i nomi dei file durante l'esecuzione. Questo metodo è più flessibile rispetto al primo, ma richiede l'interazione dell'utente ogni volta che il programma viene eseguito.

    Si potrebbe, ad esempio, utilizzare la funzione scanf() per leggere i nomi dei file da tastiera. Tuttavia, questo approccio può essere scomodo per l'utente, specialmente se il programma deve essere eseguito più volte con file diversi.

  3. Argomenti della riga di comando: il metodo più flessibile e comunemente usato consiste nel passare i nomi dei file come argomenti della riga di comando quando si avvia il programma. In questo modo, l'utente può specificare i file da utilizzare senza dover modificare il codice o interagire durante l'esecuzione.

    Questo è, ad esempio, l'approccio utilizzato dalla stragrande maggioranza dei programmi da linea di comando nei sistemi operativi Unix/Linux e Windows. Lo stesso compilatore gcc utilizza questo metodo per specificare i file sorgente da compilare.

Poiché la terza opzione è la più flessibile e potente, in questa lezione ci concentreremo su come implementarla in un programma C.

Specificare i file tramite la Riga di Comando

Abbiamo già visto nelle lezioni precedenti che la funzione main in linguaggio C può essere definita in due modi:

  1. Senza argomenti:

    int main(void);
    

    In questo caso, la funzione main non riceve alcun argomento dalla riga di comando.

  2. Con argomenti:

    int main(int argc, char *argv[]);
    

    In questo caso, la funzione main riceve due argomenti: argc e argv. Dove argc è un intero che rappresenta il numero di argomenti passati al programma dalla riga di comando, mentre argv è un array di stringhe (array di puntatori a char) che contiene i singoli argomenti.

Nelle lezioni precedenti abbiamo visto come utilizzare questi argomenti per leggere i parametri di configurazione del programma. In particolare, abbiamo visto che argv è un array jagged di stringhe, ossia ogni elemento di argv è una stringa, ossia un array di caratteri terminato dal carattere nullo \0, di lunghezza variabile. Inoltre, argv possiede altre caratteristiche importanti:

  1. argv[0], ossia il primo elemento dell'array, contiene il nome con cui il programma è stato eseguito (che può includere il percorso completo o relativo del file eseguibile).
  2. argv[1], argv[2], ..., argv[argc-1] contengono gli argomenti passati al programma dalla riga di comando.
  3. argv[argc] è sempre NULL, ossia un puntatore nullo, che indica la fine dell'array e viene adoperato come sentinella.

Detto questo possiamo sfruttare questi argomenti per specificare i nomi dei file da aprire nel nostro programma.

Ad esempio, supponiamo di voler scrivere un programma che apre un file e lo copia in un secondo file. Possiamo specificare i nomi dei file di input e output come argomenti della riga di comando in questo modo:

$ ./mio_programma input.txt output.txt

In questo esempio, input.txt è il nome del file di input da cui leggere i dati, mentre output.txt è il nome del file di output in cui scrivere i dati copiati.

Per realizzare lo scheletro di questo programma in linguaggio C, possiamo definire la funzione main con gli argomenti argc e argv, e poi utilizzare argv[1] e argv[2] per ottenere i nomi dei file di input e output. Ecco un esempio di come potrebbe apparire il codice:

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

int main(int argc, char *argv[]) {
    // Controllare che siano stati forniti i nomi dei file
    if (argc != 3) {
        fprintf(stderr, "Uso: %s <file_input> <file_output>\n", argv[0]);
        return EXIT_FAILURE;
    }

    const char *file_input = argv[1];
    const char *file_output = argv[2];

    // Aprire i file di input
    FILE *infile = fopen(file_input, "r");
    if (infile == NULL) {
        printf("Errore nell'apertura del file di input: %s\n", file_input);
        return EXIT_FAILURE;
    }

    // Aprire i file di output
    FILE *outfile = fopen(file_output, "w");
    if (outfile == NULL) {
        printf("Errore nell'apertura del file di output: %s\n", file_output);
        // Il file di input deve essere chiuso prima di terminare
        fclose(infile);
        return EXIT_FAILURE;
    }

    // Qui andrebbe il codice per copiare i dati da infile a outfile

    // Chiudere i file
    fclose(infile);
    fclose(outfile);

    return EXIT_SUCCESS;
}

In questo esempio, il programma controlla che siano stati forniti esattamente due argomenti (oltre al nome del programma). Se il numero di argomenti è errato, stampa un messaggio di uso corretto e termina con un codice di errore.

Successivamente, il programma apre i file di input e output utilizzando i nomi forniti tramite argv[1] e argv[2]. Se l'apertura di uno dei file fallisce, viene stampato un messaggio di errore e il programma termina.

Esempio: Programma verificare se un file può essere aperto

Proviamo a mettere insieme le conoscenze acquisite finora, apertura di file, chiusura di file e argomenti della riga di comando, per scrivere un semplice programma che verifica se uno o più file possono essere aperti in lettura.

Questo programma accetterà come argomenti della riga di comando i nomi dei file da verificare, separati da spazi. Per ognuno di essi, verificherà due cose:

  1. Se il file esiste.
  2. Se il file può essere aperto in lettura.

Il secondo punto è abbastanza importante, perché un file potrebbe esistere ma l'utente potrebbe non avere i permessi necessari per aprirlo in lettura. Quindi il primo punto non è sufficiente per garantire che il file possa essere effettivamente aperto.

Infine, il programma stamperà a video il risultato della verifica per ogni file.

Ecco un esempio di come potrebbe apparire il codice di questo programma:

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

int verifica_file(const char *nome_file) {
    FILE *file = fopen(nome_file, "r");
    if (file == NULL) {
        return 0; // Il file non può essere aperto
    }
    fclose(file);
    return 1; // Il file può essere aperto
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        // Numero insufficiente di argomenti
        printf("Uso: %s <file1> [<file2> ... <fileN>]\n", argv[0]);
        return EXIT_FAILURE;
    }

    for (int i = 1; i < argc; i++) {
        const char *nome_file = argv[i];
        if (verifica_file(nome_file)) {
            printf("Il file '%s' può essere aperto in lettura.\n", nome_file);
        } else {
            printf("Il file '%s' NON può essere aperto in lettura.\n", nome_file);
        }
    }

    return EXIT_SUCCESS;
}

In questo esempio, la funzione verifica_file tenta di aprire il file specificato in modalità lettura. Se l'apertura ha successo, il file viene chiuso immediatamente e la funzione restituisce 1, indicando che il file può essere aperto. Se l'apertura fallisce, la funzione restituisce 0.

Nella funzione main, il programma controlla che sia stato fornito almeno un nome di file come argomento. Poi, per ogni nome di file passato, chiama la funzione verifica_file e stampa il risultato della verifica. In questo modo, l'utente può facilmente verificare se i file specificati possono essere aperti in lettura semplicemente eseguendo il programma con i nomi dei file come argomenti della riga di comando:

$ ./verifica_file file1.txt file2.txt file3.txt
Il file 'file1.txt' può essere aperto in lettura.
Il file 'file2.txt' NON può essere aperto in lettura.
Il file 'file3.txt' può essere aperto in lettura.