Le stringhe - Soluzione

In questo tutorial non verrà presentata la possibile soluzione nella sua interezza, ma soltano le parti interessanti, che dovranno essere opportunamente integrate per fornire la soluzione completa e compilabile. Per ottenere un programma completo e funzionante, si tratta pertanto di scrivere la funzione main che dichiara le variabili necessarie e che chiama opportunamente le funzioni descritte.

Estrazione del basename

La funzione che determina il basename può essere la seguente:

void basename(const char *percorso, char *b) {
    int pos = trova_ultimo_separatore(percorso);
    strcpy(b, percorso + pos + 1);
}

che opera come segue:

  • prima viene trovato l'indice dell'ultimo separatore (pos) usando trova_ultimo_separatore
  • con strcpy viene copiata la porzione di stringa che va da pos + 1 fino alla fine della stringa percorso all'interno della stringa b
    • questa istruzione funziona perché percorso è un indirizzo, ovvero l'indirizzo del primo carattere della stringa (percorso è un puntatore a char e viene usato per effettuare un passaggio di parametri per riferimento, vedi S6.6)
    • quindi spostandosi di pos + 1 caratteri si ottiene l'indirizzo del primo carattere del basename
    • il tutto è reso possibile dal fatto che i caratteri sono memorizzati nella stringa in posizioni contigue della memoria (si ricordi che si tratta di un vettore, vedi S5.9.1)

La funzione trova_ultimo_separatore può essere definita come segue:

int trova_ultimo_separatore(const char *percorso)
{
    int pos = strlen(percorso) - 1;
    while (pos >= 0 && percorso[pos] != '/')
        pos--;
    return pos;
}

e può operare come segue:

  • pos viene inizializzato pari all'indice dell'ultimo carattere della stringa percorso, usando la strlen la quale restituisce la lunghezza della stringa in numero di caratteri (S6.11)
  • il ciclo while (S4.7) continua a decrementare di uno (pos--) l'indice fintanto che non viene incontrato il primo separatore (partendo da destra) oppure si arriva al valore zero (si sono verificati tutti i caratteri della stringa)
  • viene restituito il valore di pos, che varrà 0 se non ci sono separatori, oppure l'indice pos > 0 corrispondente al primo carattere '/' incontrato (il più a destra)

Ricorda che

  • l'operatore && è l'AND logico (S11.5.1)
  • l'operatore != significa "diverso da" (S11.4.1)
  • la costante '/'è una costante intera di tipo char che corrisponde al carattere ASCII / (S5.5)

Estrazione del dirname

Per l'estrazione del dirname si può usare la seguente funzione:

void dirname(const char *percorso, char *b) {
    int i;
    int n = trova_ultimo_separatore(percorso);

    /* Scrittura del terminatore tenendo anche conto del caso n == -1 */
    if (n >= 0) {
        /* Copia in b i primi n caratteri del percorso */
        for (i = 0; i < n; i++)
            b[i] = percorso[i];
        b[n] = '\0';
    } else
        b[0] = '\0';
}

Prima viene trovato l'indice n dell'ultimo separatore usando la funzione trova_ultimo_separatore illustrata nel punto precedente.

Se il valore di n è positivo, si svolgono le seguenti operazioni:

  • con il ciclo for (S4.6) si copiano i primi n caratteri da percorso a b
  • con b[n] = 0 si pone il carattere successivo all'ultimo copiato pari a zero, terminando la stringa

Altrimenti di pone a zero il primo carattere della stringa con b[0] = '\0', che corrisponde a impostare la stringa b pari alla stringa vuota. Questo significa che il percorso non contiene directory ma solo un nome di file.

ATTENZIONE: Ricorda che la costante '\0'è una costante intera di tipo char che corrisponde al carattere ASCII di valore numerico zero (S5.5). Non è il carattere '0', che invece ha valore numerico 48 (T5.3).

Estrazione del tipo di percorso

La funzione può essere realizzata come segue:

int assoluto(const char *percorso)
{
    if (percorso[0] == '/') return 1;
    return 0;
}

Essa restituisce 1 (e quindi "vero") se il primo carattere della stringa percorso, che è percorso[0], corrisponde al carattere '/', altrimenti restituisce 0.

La return 1 termina la funzione, quindi la return 0 viene eseguita solo se il primo carattere percorso[0] è diverso da '/'. Di conseguenza, in questo caso l'else non serve. Un esempio simile è riportato in T5.3.

Nel main bisognerà stampare il valore corretto in output a seconda del valore restituito da assoluto.

Estrazione dell'estensione

Per determinare l'estensione, conviene riformulare la funzione trova_ultimo_separatore generalizzandola per cercare l'ultimo carattere, che specificato come argomento:

int trova_ultimo_carattere(const char *percorso, char c)
{
    int pos = strlen(percorso) - 1;
    while (pos >= 0 && percorso[pos] != c)
        pos--;
    return pos;
}

Questa operazione di "riformulazione" di una funzinalità esistente si chiama in gergo refactoring. Si notino le modifiche minime alla funzione originale.

prova-tu Modifica il programma per usare la nuova funzione trova_ultimo_carattere anche nelle altre funzioni già sviluppate.

La funzione trova_ultimo_carattere può essere usata nella seguente funzione estensione:

int estensione(char *percorso, char *ext) {
    int pos_punto = trova_ultimo_carattere(percorso, '.');
    int pos_sep = trova_ultimo_carattere(percorso, '/');
    int len = strlen(percorso);
    
    /* il punto più a destra è a sinistra del separatore più a destra */
    if (pos_punto < pos_sep)
        return 0;
    /* il punto più a destra è l'ultimo carattere della stringa (no estensione) */
    if (pos_punto + 1 == len)
        return 0;
    /* c'è almeno un punto nel percorso */
    if (pos_punto > 0) {
        strcpy(ext, percorso + pos_punto + 1);
        return 1;
    }
    return 0;
}

la quale opera nel modo seguente:

  • usando la funzione trova_ultimo_carattere, trova la posizione dell'ultimo punto (pos_punto) e dell'ultimo separatore (pos_sep)
  • il primo if causa la terminazione dalla funzione segnalando che non c'è estensione - restituendo 0 con return 0 - se il carattere punto più a destra rimane a sinistra del separatore più a destra (es. /home/dir.con.punto/file); altrimenti...
  • il secondo if esce dalla funzione segnalando che non c'è estensione - restituendo 0 - se il punto più a destra è l'ultimo carattere della stringa (es. /home/utente/nomefile., dove ovviamente non c'è estensione); altrimenti...
  • il terzo if copia l'estensione della stringa di uscita ext se c'è un punto nel percorso e restituisce 1 (attenzione che si arriva a questo punto quando le condizioni dei precedenti due if sono false)
  • altrimenti termina senza trovare un'estensione, con l'ultimo return 0

Analisi completa

Nel calcolo di basename e dirname bisogna fare attenzione ad alcuni casi particolari:

  • se il percorso indica un nome di file "puro", cioè senza alcun separatore, allora il suo dirname è la directory corrente . (punto)
  • se il percorso termina in coda con uno o più separatori, essi devono essere eliminati sia dal dirname che dal basename (attenzione che in questo caso il basename è corriponde al nome di una directory, non di un file; questo però non cambia nulla nella suddivisione tra dirname e basename)
  • se il percorso coincide con la directory speciale /, allora sia il basename che il dirname sono pari a /
  • se c'è un unico separatore, e si trova all'inizio del percorso, allora il dirname è la directory radice /

Si modifichi il programma in modo che basename e dirname tengano in considerazione questi casi particolari. I nuovi elementi calcolati dovranno essere stampati dopo i marcatori:

[BASENAME_COMPLETO] e [DIRNAME_COMPLETO]

in modo da consentire la verifica tramite il comando

./pvcheck -f percorsi2.test ./percorsi

utilizzando il file di test percorsi2.test