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

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