2009年03月13日

scandirかopendirか悩んでみた。

opendirでディレクトリー一覧を取得すると順番がばらばらなのでソートしないといけない。
またストリームとして扱いたくてもtelldirは役立たず、seekは無能であり
探索しても使われている様子が無いことからバグ以前にこの関数は存在しない扱いにしないといけないと理解した。
そこでdirent.hをみたのだが「scandir」なる関数があった。
今回はscandirを使いjpg拡張子だけ選んでみたり
scandirのソートを直接qsortで置き換えるところまでやってみた。
manを頼りに使ってみるとループを組まない一発命令なのでちょっとみ便利そうにみえた。
しかしmallocとfreeを多用していて好みではない。
とりあえず使ってみた。

前提

//type
typedef struct dirent Dirent;


今回用の独自の変数。

//const
const char *dirname =".";

//var
Dirent **namelist;
int d_cnt;


namelistはDirentのポインターの配列のポインター、
d_cntはscandirの返す配列の数に当たる。
scandir関数はこのポインターを必要とする。

定義はこんな感じ

int scandir(
const char *dirname, struct dirent ***namelist,
int (*select)(struct dirent *),
int (*compar)(const void *, const void *));


Direntは星3つです。ポインターリストのポインター。

関数ポインタは宣言で明示的に型指定されているせいか
&selfuncとしなくてもよいようだがすっきりしないのは配列の扱いと一緒。

返り値は要素数で

d_cnt = scandir(dirname, &namelist, selfunc, sortfunc);

とするとd_cntがリストの数になる。

mallocでnamelistを確保するから開放しろと言うのはいいとして
固定長である個別のdirentをひとつひとつ開放しなくてはいけないというのが微妙にきにいらない。

opendirと同様の方法の場合

d_cnt = scandir(dirname, &namelist, NULL, NULL);

でリストが取得でき
処理後


for (i = 0; i < d_cnt; ++i) {
free(namelist[i]);
}
free(namelist);

と開放する。

manでは組み込みサブルーチンのalphasortがあわせて解説されていて

d_cnt = scandir(dirname, &namelist, NULL, alphasort);

でリストはアルファベット順に整列される。

select関数はreaddirのループ内に相当し
返り値が
FALSE(0)ならばnamelistに加えられない。
FALSE(1)ならばnamelistに加えられる。

ここでselectのサンプルとして
実用的に拡張子が「.jpg」「.JPG」「.c」だけを抽出してみる。


char exts[2][4]={".jpg",".JPG"};

int selects(Dirent *dir){
//var
int *l1,*l2,n;
char a;
//begen
a=dir->d_namlen;
if ( a<4) return 0;
a-=4;
l2=(int *)&(dir->d_name[a]);
if ((*l2 == *(int *)&exts[0])
||((*l2 == *(int *)&exts[1]))) return (1);
return (0);
}


4byte未満のものははねて
文字を一つずつ追わず後ろ4文字をintとして比較している。
拡張子が1文字の場合は3byte未満をはねてshort型で比較すればいいだろう。

そして

d_cnt = scandir(dirname, &namelist, selects, alphasort);


これで拡張子が.jpgか.JPGのものをリストアップしてくれる。

繰り返しファイルリストを取得する場合
scandirの個別要素は一回のリストアップごとにfreeしないといけないが
freelist自体は複数回scandirをするならば最後に一回freeすれば良いだろうと
mallocの仕様に期待する。

Direntの要素は
d_fileno,d_reclen,d_type,d_namlen,d_name[]
このうち32bitのd_filenoはあまり使わなさそうなのでselectで何かのポインターを入れるのも手だ。
d_typeでファイルタイプまでわかる。
alphasortはqsortにd_nameのポインターを渡されていると思うので
別のコンペアルーチンの場合
そこからstatでファイルサイズやatimeなどをみるのも手だろう。

ここでいつ何をするかという事が問題になってくる。
ほんとうはselectsでいろいろとやった方がコールバック的ですっきりする。
しかしそうしてしまうとソートされていない。
つまりアルファソートはデータ挿入時ではなくてデータ挿入終了時に行われている事が分かる。manにもqsortと書いてある。

そこでsortを自前でやってみた。

int dir_cmp (const void *arg0, const void *arg1){
//var
Dirent *d0 =*(Dirent **)arg0;
Dirent *d1 =*(Dirent **)arg1;
//begen
return strcmp(d0->d_name,d1->d_name);
}



d_cnt = scandir(dirname, &namelist, NULL, NULL);
qsort(namelist, d_cnt, 4, dir_cmp);


でソート完了。
この場合namelistはpointer Listなのでサイズは4
こうやっておけば比較関数も自由に組めるのわけだ。
ちなみにqsortにalphasortをかましたらうまく動かなかった。

しかしqsortを直接呼んでいたらscandirのメリットはあんまりなくなってしまう。
深い階層までまとめてくれるわけでもないし
結局一度は完全にスキャンしているわけでopendir+readdirと比べると
mallocを自動でやってくれることだけがメリットだろうか。

ポインターリストを使うにしてもdirentデータは個別にmallocするのではなくて
データエリアとしてまとめたい。
現実問題としてCFのマウントしたディレクトリーからスキャンしてリストを作る場合
scandirではだめなわけで自分で作った方が良いと感じた。

しかしついでにqsortを覚えたことを良としよう。
posted by Xo_ox at 19:50| Comment(0) | FreeBSDアプリ | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

この広告は180日以上新しい記事の投稿がないブログに表示されております。