kick the base

Houdiniと、CG技術と、日々のこと。

ノンプログラマーのための正規表現入門 2

今回はノンプログラマー向けの正規表現入門記事その2です。

ノンプログラマーのための正規表現入門 1の続編で、先日書いたPhotoshop/JSX: フォルダ内の画像を開き、調整レイヤーを追加して保存するのコードを改善する内容となります。

前回のコード

上記の記事をご覧の上お読みいただけると分かりやすいかと思います。

さて、前回の記事では最終的に下記のようなコードとなりました。

(function () {
    var folder = Folder.selectDialog('画像があるフォルダを選択してください');
    if (!folder)
        return;
    var files = folder.getFiles(/jpe?g/);
    for (var i = 0; i < files.length; i++) {
        var file = files[i];
        app.open(file);
        app.doAction('add_adjustment_layer', 'MyAction');
        var exportPath = file.path + '/' + file.name.replace(/jpe?g/, '');
        app.activeDocument.saveAs(new File(exportPath));
    }
})();

一読して「このコード、問題があるぞ」と思った方もおられると思います。正規表現/jpe?g/の部分です。その問題と、それに対処するようコードを修正していきましょう。

修正、その前に

コード修正は闇雲に行ってはいけません。問題を把握した上で適切に対処していきましょう。

フォルダ内の画像ファイルを読み込む部分だけを取り出して実行してみましょう。デカルトの言うように、困難は分割せよです。

(function () {
    var folder = Folder.selectDialog('画像があるフォルダを選択してください');
    if (!folder) 
        return;        
    var files = folder.getFiles(/jpe?g/);
    alert(files); //ここを追加
})();

for文の処理をトルツメし、alert(files);を追加しました。alertデバッグは原始的なデバッグ方法ですが、手軽なので簡単なデバッグには向いています。

このコードを使って下記ファイルが入っているフォルダで実験してみます。(imgがフォルダ名)

img
├── a.jpg
├── b.jpeg
└── c.mov
  1. Photoshopのメニューバーからでファイル > スクリプト > 参照でJSXを実行します。
  2. 「画像があるフォルダを選択してみましょう」とダイアログが表示されるので、デスクトップにあるimgフォルダを選択し開くを押します。
  3. アラートウィンドウに下記文字が表示されます。

~/Desktop/img/a.jpg,~/Desktop/img/b.jpeg

拡張子jpgjpegともに配列に格納されていますね。また拡張子movはちゃんと除外されています。

一見問題なさそうですね、どこが悪いのでしょう。

問題のあるパターンマッチ

続けて、デスクトップにあるこんなフォルダで試してみましょう。

img
├── a.jpg
├── b.jpeg
└── jpg_dayo.mov

再度スクリプトを実行してみましょう。こんなアラートが表示されます。

~/Desktop/img/a.jpg,~/Desktop/img/b.jpeg,~/Desktop/img/jpg_dayo.mov

拡張子movも格納されてしまいました。なぜでしょう。

正規表現/jpe?g/は文字列の中にjpgjpegがあれば、場所にかかわらずマッチしてしまいます。jpg_dayo.movはさすがにわざとらしいですが、こんなケースはいかがでしょう。

デスクトップにjpgフォルダを作った場合です。

jpg
├── a.txt
├── b.gif
├── c.pdf
└── e.css

アラートウィンドウは下記のようになります。

~/Desktop/jpg/a.txt,~/Desktop/jpg/b.gif,~/Desktop/jpg/c.pdf,~/Desktop/jpg/e.css

拡張子はjpgでもjpegでもありませんが、すべてマッチしてしまいました!これはまずいですね。パスの中にあるjpgフォルダに反応してしまったわけですね。

さあ、これを修正しましょう。

マッチする場所を指定する

いままでのコードでは途中のjpgjpegに反応してしまったのが問題でしたね。ということはパスの最後が.jpg.jpegになっているというパターンを指定してあげれば良さそうです。

修正前 修正後
/jpe?g/ /\.jpe?g$/

こんな修正を施しました。正規表現の頭に\.を、最後に$を追加しただけです。簡単ですね。

ここで追加した箇所を説明しましょう。

追加した文字 意味
\. ドットを表現します
$ 行末を表現するメタ文字です

\.のバックスラッシュはエスケープ文字といい、その後に続く文字はメタ文字じゃないよと伝える記号になります。バックスラッシュを使わない.は改行文字以外のどの1文字にもマッチするメタ文字です。

つまり/\.jpe?g$/パスの最後が.jpgと、パスの最後が.jpegにマッチするということになります。これでパスの途中やファイル名にjpgjpegが入ってても安心ですね。さあ、再度試してみましょう。

imgフォルダを指定したときは下記のようにjpg_dayo.movを除外してくれました。

~/Desktop/img/a.jpg,~/Desktop/img/b.jpeg

jpgフォルダを指定したときは、フォルダ内に拡張子jpgjpegがないので空になります。

これで良さそうですね!

修正する

先程の修正を施したスクリプトは下記のようになります。

(function () {
    var folder = Folder.selectDialog('画像があるフォルダを選択してください');
    if (!folder)
        return;
    var files = folder.getFiles(/\.jpe?g$/);
    for (var i = 0; i < files.length; i++) {
        var file = files[i];
        app.open(file);
        app.doAction('add_adjustment_layer', 'MyAction');
        var exportPath = file.path + '/' + file.name.replace(/\.jpe?g$/, '');
        app.activeDocument.saveAs(new File(exportPath));
    }
})();

しかし/\.jpe?g$/が2回出てくるのもキレイではないですね。こんな感じで変数reに正規表現をまとめてみましょう。

(function () {
    var folder = Folder.selectDialog('画像があるフォルダを選択してください');
    if (!folder)
        return;
    var re = /\.jpe?g$/;
    var files = folder.getFiles(re);
    for (var i = 0; i < files.length; i++) {
        var file = files[i];
        app.open(file);
        app.doAction('add_adjustment_layer', 'MyAction');
        var exportPath = file.path + '/' + file.name.replace(re, '');
        app.activeDocument.saveAs(new File(exportPath));
    }
})();

これで完成です!

まとめ

JSXは使い捨てのツールとして作成する場合も多いので、運用でカバーしても良いと思います。特に今回はユーザーもケースも限定的だったため、前回のコードでも悪くはないと思いますが、正規表現を見直すことでより良いコードになりました。

正規表現はとても奥深い世界なので、興味が出たら勉強してみることをオススメします。