05 April 2009

(Linux) シェルスクリプト凄し ~ ファイル名小文字一括変換

ちょっとした処理を行うにも、C言語は手放せないわけですが、シェルスクリプトも捨てたもんじゃ無いですね。

デジカメからコピーしてきた画像ファイルを、全て小文字に変換するのに、C言語の場合は次のような感じになる。

chgfilename.cpp
#include <stdio.h> #include <stdlib.h> #include <dirent.h> /* opendir(), readdir() */ #include <string.h> #include <ctype.h> /* tolower() */ int main(int argc, const char *argv[]); char *StrLwr(char *str); int main(int argc, const char *argv[]) { struct dirent *dp; // use by readdir() char strPath[NAME_MAX+1]; // base dir char strFnameSrc[NAME_MAX + 1]; // store filename by readdir() char strFullPathSrc[NAME_MAX*2 + 1]; char strFnameDst[NAME_MAX + 1]; // lower case filename char strFullPathDst[NAME_MAX*2 + 1]; int i, j, nResult; if(argc != 2) { puts("usage: changefname [path]\n"); return 1; // エラー終了 } // プログラム引数をパス名に読み込む strcpy(strPath, argv[1]); printf("Target path : %s\n", strPath); // パス名末尾が / でない場合、/ を付加する if(strPath[strlen(strPath)-1] != '/') strcat(strPath, "/"); // ディレクトリを開く DIR *dir = opendir (strPath); // ディレクトリが開けなかった場合 if(dir == NULL) { printf("can't open dir : %s\n", strPath); return 1; // エラー終了 } i = 0; // 変更対象ファイルのカウント while ((dp = readdir (dir)) != NULL) { if(dp->d_type != DT_REG) continue; // 通常ファイルかどうかのチェック strcpy(strFnameSrc, dp->d_name); strcpy(strFnameDst, strFnameSrc); StrLwr(strFnameDst); // 小文字化 if(!strcmp(strFnameDst, strFnameSrc)) continue; // 小文字化しても変化無い場合の判定 printf("%s -> %s\n", strFnameSrc, strFnameDst); i++; } printf("Affect file : %d\nChange filename ? [y/n] :", i); char ch = getchar(); if(tolower(ch) != 'y') { closedir(dir); puts("Process is canceled by user\n"); return EXIT_SUCCESS; } // ディレクトリ読み込み位置を最初に戻す rewinddir(dir); j = 0; // 変更ファイルのカウント while ((dp = readdir (dir)) != NULL) { if(dp->d_type != DT_REG) continue; // 通常ファイルかどうかのチェック strcpy(strFnameSrc, dp->d_name); sprintf(strFullPathSrc, "%s%s", strPath, strFnameSrc); strcpy(strFnameDst, strFnameSrc); StrLwr(strFnameDst); // 小文字化 sprintf(strFullPathDst, "%s%s", strPath, strFnameDst); if(!strcmp(strFnameDst, strFnameSrc)) continue; // 小文字化しても変化無い場合の判定 // ファイル名の変更 nResult = rename(strFullPathSrc, strFullPathDst); printf("(%s) %s -> %s\n", (nResult == 0) ? "changed":"ERROR", strFnameSrc, strFnameDst); if(!nResult) j++; } closedir(dir); printf("processed %d files. (%d files error)\nprocess end\n", j, i-j); return EXIT_SUCCESS; } char *StrLwr(char *str) { char *ptr; for(ptr = str; *ptr; ptr++) { *ptr = tolower(*ptr); } return str; }

と、これくらいのプログラム規模になるわけだが、これをシェルスクリプトで作成すると…

chgfilename.sh
#!/bin/sh for filename in $(find $1); do if [ -f $filename ]; then # 変更対象のファイル名を画面表示 basename $filename |grep -q '[[:upper:]]' 2> /dev/null && basename $filename # ファイル名の変更 basename $filename |grep -q '[[:upper:]]' 2> /dev/null \ && mv -i $filename $(echo $filename | tr 'A-Z' 'a-z') fi done

このコンパクトさはすごい。 ただ、あまりにマニアック過ぎるプログラムになる上、デバッグが出来ないので常用するにはちょっとね…