PDA

View Full Version : [C] Opendir e funzioni derivate


Baio84
02-05-2012, 12:02
Buongiorno a tutti. Vi espongo subito il mio problema.

Voglio creare un programmino facile facile, che mi rinomini i file che metto in una cartella. In pratica saranno tutti file immagine che vorrò rinominare 1.jpg, 2.jpg, 3.jpg, etc....

Il problema è che non sono riuscito a trovare in rete un qualche aiuto valido.

Ovviamente mi sono avvicinato da poco al C, ma conoscendo un po' di PHP un po' mi sono districato. Credo che faccio del casino con la definizione delle variabili e con queste funzioni (opendir, readdir, etc...) che magari non sono molto usate.

Qua vi posto un inizio di codice:

#include <stdio.h>
#include <dirent.h>

main ()
{
int nrimm=0;
DIR * apertura;
struct dirent * file;

printf ("%s\n\n\n%s", "Il programma è creato da Baiocchi Davide", "Premi un tasto qualsiasi per continuare");
if (getchar())
{
apertura = opendir("Immagini");
while (file = readdir(apertura))
{

nrimm++;
}

}
printf ("%d", nrimm);
getchar ();
}

Fin qui tutto bene, nella cartella Immagini mi conta tutti i file presenti e mi restituisce quanti sono. Ora vorrei, però che non contasse i file di ritorno alla cartella superiore, cioè \. e \.. . Ho provato con un if a evitare che me li conteggi ma mi dà qualche problema di conversione con il valore ritornato da read(apertura).

So che potrebbero mancare alcuni passaggi ed ci potrebbero essere errori banali, ma mi basterebbe solo qualche dritta per proseguire.

Ringrazio chiunque voglia aiutarmi :ave:

clockover
02-05-2012, 22:50
non capisco ma é solo un problema di conteggio???

Baio84
02-05-2012, 23:05
Ho scritto il post alla svelta e mi sono spiegato male.

Il codice sopra riportato è giusto e mi conta tutti i file presenti in una cartella. Però se ne sono inseriti 10, me ne conta 12 perchè tiene conto anche delle cartelle di ritorno \. e \.. . Per non tenerne conto dovrei integrare il codice in questo modo

#include <stdio.h>
#include <dirent.h>

main ()
{
int nrimm=0;
DIR * apertura;
struct dirent * file;

printf ("%s\n\n\n%s", "Il programma è creato da Baiocchi Davide", "Premi un tasto qualsiasi per continuare");
if (getchar())
{
apertura = opendir("Immagini");
while (file = readdir(apertura))
{
if(file != "\." and file != "\..")
{
nrimm++;
}
}

}
printf ("%d", nrimm);
getchar ();
}

Però a questo punto mi dà errore di cast

Gimli[2BV!2B]
02-05-2012, 23:47
Array di char: strcmp (http://linux.die.net/man/3/strcmp)#include <stdio.h>
#include <string.h>

main ()
{
char *base = "ramarro!" ;
char *test = "piccione?" ;
if( strcmp( base, test ) == 0 )
printf( "\"%s\" è uguale a \"%s\"\n", base, test ) ;
else
printf( "\"%s\" e \"%s\" son diverse!\n", base, test ) ;

char *test1 = "ramarro!" ;
if( strcmp( base, test1 ) == 0 )
printf( "\"%s\" è uguale a \"%s\"\n", base, test1 ) ;
else
printf( "\"%s\" e \"%s\" son diverse!\n", base, test1 ) ;

getchar ();
}Non trascurare poi il fatto che la struct dirent ritornata da readdir (http://linux.die.net/man/3/readdir) contiene un po' di campi:struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* offset to the next dirent */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported
by all file system types */
char d_name[256]; /* filename */
};Direi che a te interessa solo d_name while (file = readdir(apertura))
printf ("%s\n", file->d_name);

P.S. la struttura dirent potrebbe essere differente a seconda dell'ambiente di sviluppo, che non hai specificato.

Baio84
03-05-2012, 14:30
Intanto ti ringrazio, perchè con le tue dritte ho proseguito un pochino.

Ho modificato il codice per rendermi conto dei file che andavo a leggere. I file . e .. venivano letti, per cui ho rimodificato il codice in modo tale che non venivano conteggiati

#include <stdio.h>
#include <dirent.h>

main ()
{
int nrimm=0;
DIR * apertura;
struct dirent * file;

printf ("%s\n\n\n%s", "Il programma è creato da Baiocchi Davide", "Premi un tasto qualsiasi per continuare");
if (getchar())
{
apertura = opendir("Immagini");
while (file = readdir(apertura))
{
if(file->d_name != "." || file->d_name != "..")
{
printf ("%s", file->d_name);
nrimm++;
}
}

}
printf ("%d", nrimm);
getchar ();
}

In questo modo dovrebbe conteggiarmi e scrivermi tutti i file ad eccezione di . e .. . Ma questo non avviene conteggiandomi e scrivendo anche questi due.

Ho ripreso mano ad un manuale di c, perchè credo che ne abbia un po' bisogno.

Lavoro sotto ambiente windows. La libreria dirent.h va bene, o meglio usare le API (uso il termine, però non so cosa voglia dire o cosa siano :p . Credo librerie apposite di windows) come ho letto in giro?

Baio84
22-05-2012, 13:49
Rieccomi qua! Dopo un po' di studio ho sistemato il problema del controllo ma riscontro un piccolo problema

#include <stdio.h>
#include <dirent.h>
#include <string.h>

main ()
{
int nrimm=0;
DIR * apertura;
struct dirent * file;
char *unpunto=".", *duepunti="..";

printf ("%s\n\n\n%s", "Il programma è creato da Baiocchi Davide", "Premi un tasto qualsiasi per continuare");
if (getchar())
{
apertura = opendir("Immagini");
while (file = readdir(apertura))
{
if(strcmp(file->d_name, unpunto) != 0 || strcmp(file->d_name, duepunti) != 0)
{
printf ("%s", file->d_name);
nrimm++;
}
}

}
printf ("%d", nrimm);
getchar ();
}

Come mi aveva suggerito Gimli, ho corretto con strcmp, ma prima di farlo ho voluto solidificare un po' le mie basi.

Il nuovo problema riguarda l'OR logico. Se mettendo a confronto la stringa file->d_name risultasse uguale a . o a .. , l'istruzione nell'if non dovrebbe essere eseguita. Nel mio caso è come se quell'if non ci fosse stampandomi tutti i nomi dei file compreso . e .. . C'è per caso un errore di sintassi?

EDIT: Penso di aver già capito dove sbaglio. E' un errore di logica nell'utilizzo dell'operatore OR. Ora non posso provare, ma penso che correggendo in:

if(!(strcmp(file->d_name, unpunto) != 0 || strcmp(file->d_name, duepunti) != 0)) oppure

if(!(strcmp(file->d_name, unpunto) != 0) || !(strcmp(file->d_name, duepunti) != 0))

credo di risolvere

Gimli[2BV!2B]
22-05-2012, 20:47
Quando vuoi stampare?
Quando ( d_name è diverso da unpunto e d_name è diverso da duepunti ) :)

Certo, se vuoi "farlo strano" c'è sempre De Morgan (http://it.wikipedia.org/wiki/Leggi_di_De_Morgan)! (solitamente si usa per semplificare o rendere più leggibile)
Nelle altre due proposte hai quasi ottenuto l'equivalenza, devi però usare tutti i not insieme.

Baio84
23-05-2012, 00:07
Avevo risolto con il NOT, ma il tuo post mi ha fatto ragionare e al posto dell'OR logico, avevo bisogno dell'AND. :doh:

Sono andato avanti e sono riuscito a creare un vettore contenente il nuovo nome del file da sostituire a quello vecchio. Il problema ora è che la funzione rename non va a buon fine e non mi rinomina il file (premetto che poi userò il programma per rinominare file immagine, però per ora sto provando con dei file txt).

#include <stdio.h>
#include <dirent.h>
#include <string.h>

main ()
{
int nrimm = 0, rinomina;
DIR *apertura;
struct dirent *file;
char *unpunto = ".", *duepunti = "..";
char nuovonome[20];
char *estensione = ".txt";

printf ("%s\n\n\n%s", "Il programma è creato da Baiocchi Davide", "Premi un tasto qualsiasi per continuare");
if (getchar())
{
apertura = opendir("Immagini");
while (file = readdir(apertura))
{
if (strcmp (file->d_name, unpunto) != 0 && strcmp (file->d_name, duepunti) != 0)
{
printf ("%s\n", file->d_name);
nrimm++;
sprintf (nuovonome, "%d", nrimm);
strcat (nuovonome, estensione);
printf ("%s\n", nuovonome);
rinomina = rename (file->d_name, nuovonome);
if ( rinomina == 0 )
printf ("Ok");
else
printf ("Errore");
}
}

}
closedir (apertura);
getchar();
}

Continua a restituire -1. Ci sono tanti printf per controllare se è tutto giusto.

Gimli[2BV!2B]
23-05-2012, 00:23
I file si trovano nella cartella Immagini, però stai usando rename con solo il nome del file.
Occorre o il percorso assoluto o quello relativo, sia di file di origine che di destinazione.

Baio84
23-05-2012, 09:29
Vero anche quello. Mi blocco per delle pirlate. :doh: :doh: :doh:

Modificherò così:

#include <stdio.h>
#include <dirent.h>
#include <string.h>

main ()
{
int nrimm = 0, rinomina;
DIR *apertura;
struct dirent *file;
char *unpunto = ".", *duepunti = "..";
char nuovonome[20];
char vecchionome[40];

printf ("%s\n\n\n%s", "Il programma è creato da Baiocchi Davide", "Premi un tasto qualsiasi per continuare");
if (getchar())
{
apertura = opendir("Immagini");
while (file = readdir(apertura))
{
if (strcmp (file->d_name, unpunto) != 0 && strcmp (file->d_name, duepunti) != 0)
{
printf ("%s\n", file->d_name);
nrimm++;
sprintf (vecchionome, "Immagini\%s", file->d_name);
sprintf (nuovonome, "Immagini\%d.txt", nrimm);
printf ("%s\n", nuovonome);
rinomina = rename (vecchionome, nuovonome);
if ( rinomina == 0 )
printf ("Ok");
else
printf ("Errore");
}
}

}
closedir (apertura);
getchar();
}

Ho rimodellato tutto perchè era un po' un casotto con tutti gli strcat. Ora mi sembra più pulito e più facile da comprendere. Dopo provo se è tutto a posto

EDIT: Allora così la funzione rename funziona restituendomi 0. Il problema è che il file "fisico" non viene rinominato. Ho bisogno di qualche permesso? Di solito alla fine dei programmi va inserito un 'return 0;', magari devo inserire anch'io un return specifico al mio caso?

Gimli[2BV!2B]
23-05-2012, 21:30
Solitamente il main è definito come int main() in quanto deve ritornare il risultato dell'esecuzione del programma.
In caso di successo zero, in caso di errore diverso da zero (potenzialmente con un valore differente per ogni errore importante che blocca l'esecuzione).
Questo però non influenza rename.

Riguardo ai problemi rimanenti, molto utili i warning del compilatore (ti posto quelli che posso osservare io con gcc):gimli@kwankey:~/Scrivania$ gcc -Wall --pedantic -otest test.c
test.c:5:1: warning: return type defaults to ‘int’ [-Wreturn-type]
test.c: In function ‘main’:
test.c:18:10: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
test.c:24:47: warning: unknown escape sequence: '\%' [enabled by default]
test.c:25:45: warning: unknown escape sequence: '\%' [enabled by default]
test.c:38:1: warning: control reaches end of non-void function [-Wreturn-type]In primo luogo puoi vedere l'avviso sul tipo di dato di ritorno della funzione main non definita.
Poi ecco le parti interessanti per la mancata rinomina: il carattere \ serve a fare l'escape del carattere successivo o serve per indicare caratteri non stampabili (il \n che già utilizzi, per esempio).

Per fare in modo che venga interpretato come backslash e non come escape è necessario raddoppiarlo (ovvero fare l'escape di esso stesso...):sprintf (vecchionome, "Immagini\\%s", file->d_name);Potrebbe anche funzionare utilizzando lo slash anche in Win (condizionale perché non mi capita praticamente mai di utilizzare direttamente le funzioni che agiscono sul file system, mi sembra di ricordare che siano in grado di interpretare entrambi gli slash, ma potrei confondermi con Java o altro ed ora non riesco a rintracciare esempi):sprintf (vecchionome, "Immagini/%s", file->d_name);

Baio84
24-05-2012, 00:46
Dunque, il problema degli slash l'avevo notato anch'io ma non sono riuscito a modificare il post. Quindi non mi resta che aggiungere int alla funzione main.

Con questi accorgimenti non mi funziona ancora.

Con i vari controlli printf:
- il vecchio nome risulta giusto: Immagini\vecchio nome.txt;
- il nuovo nome è giusto: Immagini\1.txt;
- il controllo del valore della funzione rename è zero, stampandomi Ok.

A questo punto guardo nella cartella Immagini, ma non è cambiato niente.

Avevo letto che forse era un problema riguardo ad opendir, cioè che un file possa essere rinominato solo quando non sia in lettura, cioè solo quando si è effettuato la chiusura della directory con closedir. Ho provato ad effettuare la rinomina senza utilizzare opendir, ma è la stessa cosa: tutto corretto ma non modifica niente.

Mi chiedo dove stia sbagliando :muro:

EDIT: Ma "£$%&/! Funziona tutto! Era Avast, il mio adorabile antivirus a non modificarmi un piffero. Per far funzionare tutto senza disattivarlo per forza, devo dargli ordine di eseguire il programma lo stesso, o devo implementare il codice con qualcosa? Un'ultima cosa, i caratteri accentati non me li stampa corretti. Deve essere un errore di codifica. Dove devo aggiustare per risolvere? Per essere d'aiuto, per creare il codice e poi compilarlo utilizzo CodeBlocks

Gimli[2BV!2B]
24-05-2012, 23:35
Per evitare Avast non ho idee e non l'ho mai usato su pc di mia competenza.
Quando aggiorni un qualsiasi software si accorge che è stato aggiornato e ti chiede conferma prima di eseguirlo?
In tal caso credo sia possibile solo aggiungere un'eccezione per il programma.

Per gli accenti posso solo spararti una soluzione che dovrai studiare ed adattare a Code::Blocks, Mingw su piattaforma Windows.
Potrebbe contenere imprecisioni o non utilizzare la strada più corretta/comoda perché non ho ancora molta esperienza con codifiche unicode e set di caratteri.
Ovviamente critiche e suggerimenti sono ben accetti!#include <stdio.h>
#include <wchar.h>
#include <stdlib.h>
#include <locale.h>
#include <dirent.h>

#define WFILENAME_MAX FILENAME_MAX * 3

int main ()
{
int nrimm = 0, rinomina = 1 ;
DIR *apertura;
struct dirent *file;
wchar_t unpunto[] = L".", duepunti[] = L"..";

wchar_t wnome_old[ FILENAME_MAX + 1 ] ;
wchar_t wnome_new[ FILENAME_MAX + 1 ] ;
char nome_old[ WFILENAME_MAX + 1 ] ;
char nome_new[ WFILENAME_MAX + 1 ] ;

wchar_t wcartella[] = L"Voil\x00E0" ;/*cartella Voilà*/ (http://www.utf8-chartable.de/)
char cartella[ WFILENAME_MAX + 1 ] ;

wchar_t wd_name[ WFILENAME_MAX + 1 ] ;

setlocale( LC_ALL,"" ) ;

wcstombs( cartella, wcartella, WFILENAME_MAX ) ;
apertura = opendir( cartella ) ;
if( apertura == NULL )
return 1;

while ( ( file = readdir( apertura ) ) != NULL )
{
mbstowcs( wd_name, file->d_name, FILENAME_MAX ) ;
if( wcscmp( wd_name, unpunto ) != 0 && wcscmp( wd_name, duepunti ) != 0 )
{
nrimm++;
swprintf( wnome_old, FILENAME_MAX, L"%ls/%ls", wcartella, wd_name ) ;
swprintf( wnome_new, FILENAME_MAX, L"%ls/%d.txt", wcartella, nrimm ) ;
wprintf( L"%ls -> %ls", wnome_old, wnome_new ) ;

wcstombs( nome_old, wnome_old, WFILENAME_MAX ) ;
wcstombs( nome_new, wnome_new, WFILENAME_MAX ) ;
rinomina = rename( nome_old, nome_new ) ;
if( rinomina == 0 )
wprintf( L": Ok\n" ) ;
else
wprintf( L": Errore!\n" ) ;
}
}
closedir( apertura ) ;

return 0 ;
}
gimli@kwankey:~$ ls -la Voilà/
totale 24
drwxr-xr-x 2 gimli gimli 4096 mag 24 23:33 .
drwxr-xr-x 4 gimli gimli 4096 mag 24 23:25 ..
-rw-r--r-- 1 gimli gimli 2 mag 24 23:00 Àèìòù.txt
-rw-r--r-- 1 gimli gimli 2 mag 24 23:02 ®Àºæþ¢‘.txt
-rw-r--r-- 1 gimli gimli 2 mag 24 22:59 Українська.txt
-rw-r--r-- 1 gimli gimli 2 mag 24 23:24 日本語.txt
gimli@kwankey:~$ cc -Wall --pedantic -std=c99 -otest test.c && ./test
Voilà/Àèìòù.txt -> Voilà/1.txt: Ok
Voilà/®Àºæþ¢‘.txt -> Voilà/2.txt: Ok
Voilà/Українська.txt -> Voilà/3.txt: Ok
Voilà/日本語.txt -> Voilà/4.txt: Ok
gimli@kwankey:~$ ls -la Voilà/
totale 24
drwxr-xr-x 2 gimli gimli 4096 mag 24 23:25 .
drwxr-xr-x 4 gimli gimli 4096 mag 24 23:25 ..
-rw-r--r-- 1 gimli gimli 2 mag 24 23:00 1.txt
-rw-r--r-- 1 gimli gimli 2 mag 24 23:02 2.txt
-rw-r--r-- 1 gimli gimli 2 mag 24 22:59 3.txt
-rw-r--r-- 1 gimli gimli 2 mag 24 23:24 4.txt

Baio84
25-05-2012, 01:18
Orpo :eek: !

La codifica me la studio per bene!

Arrivato a far funzionare il programmino, mi modifico il codice secondo le mie esigenze. In pratica: inserisco nella cartella Immagini delle foto con relativa miniatura; quindi mi ritrovo, ad esempio, con la foto 0665.jpg e la sua miniatura 0665_small.jpg. Dunque, con il programma voglio rinominare tutte le foto in ordine numerico insieme alla loro relativa miniatura: 1.jpg, 1_small.jpg, 2.jpg, 2_small.jpg, ecc.. Come risultato avrò tutte le foto rinominate nella cartella Immagini, e tutte le miniature rinominate in una cartella small che vado a creare all'interno di Immagini.

Arriviamo al problema: il contatore fa le bizze. I primi due file li rinomina correttamente con l'1; dal secondo in poi, me li rinomina con numeri sempre più grandi (tipo il secondo è 26481, il terzo 1781427309). Poi mi blocca il programma perchè la lunghezza della stringa è limitata.

Non capisco dove sia l'errore perchè utilizzo un normalissimo operatore di aumento nrimm++.

#include <stdio.h>
#include <dirent.h>
#include <string.h>

int main ()
{
int nrimm = 0, nrimm_small = 0, rinomina;
DIR *apertura;
struct dirent *file;
char *unpunto = ".", *duepunti = "..";
char nuovonome[20];
char vecchionome[40];
char *percorso = "Immagini\\small";
char *small = "small";


printf ("\n%s\n%s\n%s\n%s\n%s\n\n\n%s",
"**************************************************",
"* *",
"* Il programma è creato da Baiocchi Davide *",
"* *",
"**************************************************",
"Premi un tasto qualsiasi per continuare");

getchar ();
mkdir (percorso); //Creo la cartella Immagini\small
apertura = opendir("Immagini");
while (file = readdir(apertura))
{
if (strcmp (file->d_name, unpunto) != 0 && strcmp (file->d_name, duepunti) != 0 && strcmp (file->d_name, small) != 0) //Ho aggiunto che non deve tenermi conto della cartella small creata
{
printf ("%s\n", file->d_name);
sprintf (vecchionome, "Immagini\\%s", file->d_name); //Inserisco in vecchionome il nome corrente del file

if ( strstr (file->d_name, small) ) //Ho inserito questo controllo per capire se l'immagine si riferisce a una foto
{ //o a una miniatura (per poterla rinominare e spostare nella cartella small)
nrimm_small++;
sprintf (nuovonome, "Immagini\\small\\%d_small.jpg", nrimm_small);
}
else
{
nrimm++;
sprintf (nuovonome, "Immagini\\%d.jpg", nrimm);
}

printf ("%s\n", nuovonome);
rinomina = rename (vecchionome, nuovonome);
if ( rinomina == 0 )
printf ("Ok");
else
printf ("Errore");
printf ("\n\n");
}
}
closedir (apertura);

getchar ();

return 0;
}

Gimli[2BV!2B]
25-05-2012, 21:18
La prima cosa che noto è che nuovonome può contenere al massimo 19 caratteri + \0 prima di cominciare a scrivere in zone di memoria adiacenti, mentre tu ci scrivi stringhe lunghette: Immagini\small\%d_small.jpg (26 + 1 di chiusura con %d < 10).
vecchionome invece è più ciccio fin dall'inizio.

Baio84
26-05-2012, 01:20
Cosa dirti, hai avuto ragione anche qua :old: :winner:

Ora il programma calza a pennello per quello che voglio fare!
Il problema della codifica lo risolverò con calma più avanti.

Grazie mille per avere avuto pazienza e voglia di darmi una mano! :cincin:

Gimli[2BV!2B]
26-05-2012, 12:33
Prego! :)
Dal primo post hai fatto dei buoni progressi, buon proseguimento!