MENU

JavaScriptファイル操作

【Node.js】 ワイルドカードを使用したファイル一覧取得

更新日:2021/10/04

 

ディレクト下のファイルをワイルドカードを使用して取得したいというケースは意外と多いです。
Node.jsにはファイル名を取得するメソッドがありますが、ワイルドカードでの判定ができません。

 

そこで今回は、何か良い方法がないか考えてみました。

 

globを使用した場合

 

ディレクトリ内のファイルをワイルドカードを使用してリストアップするときは、globモジュールが便利です。

 

globの準備

 

globは標準モジュールではないので、インストールする必要があります。

 

globのインストール


npm install glob

 

globの使い方

 

使用するときは、requireまたはimportで読み込みます。

 

globの読み込み


const glob = require("glob");

 

通常の使い方

 

単純にリストアップするとき、globの構文は次のようになります。

 

globの構文

 

glob( パス , コールバック関数 );
glob( パス , オプション , コールバック関数 );

 

 

コールバック関数は、エラーを示すフラグと、ファイル名(文字列)のリストを受け取ります。

 

使用例

 


glob( "*.txt" , function(err, files){
    if(err) {
        console.log(err);return;
    }
    console.log(files);
});

 

受け取るファイル名の形式は、指定したパスの形式に合わせられます。

 

ファイル名のみ: "*.txt" ⇒ ["abc.txt","123.txt"]
相対パス: "dir/*.txt" ⇒ ["dir/abc.txt","dir/123.txt"]
フルパス: "c:/data/dir\*.txt" ⇒ ["c:/data/dir/abc.txt","c:/data/dir/123.txt"]

 

ただし、オプションabsoluteを有効にした場合、フルパスになります。

 

globでは、パス区切りとして "/" を使用します。
Windowsでは"\"でも期待した結果を得られますが、"/"を使っておいた方がよさそうです。

 

ステータス等を取得する

 

ステータス等取得したい場合はGlobオブジェクトを作成します。
このときオプションのstatを有効にします。

 

ステータス等を取得

 


const mg = new glob.Glob(path , { stat:true } , ( err , files ) =>{
    if(err) {
        console.log(err);return;
    }
    console.log(files); // ファイル名の一覧
    console.log( mg.statCache );  // ステータスの一覧
} );

 

statCacheは、次のような情報を持つオブジェクトです。

 

{
   ファイルパス : Stats {
      dev: 1714223727,
      mode: 33206,
      nlink: 1,
      uid: 0,
      gid: 0,
      rdev: 0,
      blksize: 4096,
      ino: 80501843339343250,
      size: 1175,
      blocks: 8,
      atimeMs: 1632901106585.9707,
      mtimeMs: 1632898244510.8936,
      ctimeMs: 1632898263513.1685,
      birthtimeMs: 1592288684363.3054,
      atime: 2021-09-29T07:38:26.586Z,
      mtime: 2021-09-29T06:50:44.511Z,
      ctime: 2021-09-29T06:51:03.513Z,
      birthtime: 2020-06-16T06:24:44.363Z
    },
    ファイルパス : Stats { },
    ファイルパス : Stats { },
}

 

syncさせる

 

ファイルの検索終了を待ってから次のコードを実行させる場合、glob.syncを使用します。
コールバックは指定せずに、結果は戻り値で受け取ります。

 

glob.sync使用例

 


const files = glob.sync( "*.txt" );
console.log( files );

※二番目の引数にオプションを指定可能です

 

ステータスを取得したい場合は、Globオブジェクトを作成してsyncオプションとstatオプションを有効にします。

 

syncでステータスを取得

 


const mg = new glob.Glob(path , { sync:true , stat:true } );
console.log( mg.statCache );

 

ディレクトリのみ取得する

 

パスの最後に "/" を指定するとディレクトリのみ取得できます。

 

ディレクトリのみ取得

 


glob( "*/" , function(err, files){
    if(err) {
        console.log(err);return;
    }
    console.log(files);
});

 

ファイルのみ取得する

 

ファイルのみ取得するには、nodirオプションを有効にします。

 

ファイルのみ取得

 


glob( "*" , {nodir:true} , function(err, files){
    if(err) {
        console.log(err);return;
    }
    console.log(files);
});

 

特定のファイルやディレクトリを除外する

 

特定のファイルやディレクトリを除外するには、ignoreオプションを指定します。

 

ignoreオプションは文字列の配列で、各要素はパターン指定できます。
ただし、ファイルやディレクトリの名称と一致する必要があります。

 

例: "abc.jpg" ⇒ abc.jpgは除外されるが、xxabc.jpgは除外されない

 

ファイルのみ取得

 


glob( "*" , {ignore:["abc.jpg","x-*.png"]} , function(err, files){
    if(err) {
        console.log(err);return;
    }
    console.log(files);
});

 

globのパス指定

 

第一引数には、検索するパスのパターンを指定します。

 

フルパスでも相対パスでも指定できます。
また、マッチングはファイル名だけでなく、パスにも適用されます。

 

例えばWindowsで次のようにパス指定したとします。

 

c:/Users/name/+(dir1|dir2)/*.txt

 

この場合、次の二つのフォルダが検索されます。

 

c:/Users/name/dir1/*.txt
c:/Users/name/dir2/*.txt

 

globのワイルドカード( * )などのパターンは、次のようになっています。

 

globのパスパターン
パターンマッチ例意味
**.txtabc.txt 、 .txt0個以上の文字列にマッチ
?a?c.txtabc.txt 、 a1c.txt1文字にマッチ
[ - ]abc.mp[3-4]abc.mp3 、 abc.mp4範囲内の1文字にマッチ
[! - ]または[^ - ]abc.mp[!4-5]abc.mp3 、 abc.mp6範囲外の1文字にマッチ
!( | | )abc.!(jpg|png)abc.gif指定された文字列以外にマッチ
+( | | )abc.+(jpg|png)abc.jpg 、abc.png指定された文字列にマッチ
?( | | )abc.?(jpg|png)abc.jpg 、abc.png 、abc.指定された文字列が0回

または1回出現するとマッチ

*( | | )abc.mp*(3|4)abc.mp3 、abc.mp4 、

abc.mp 、abc.mp333

指定された文字列が
0回以上出現するとマッチ
@( | | )abc.@(mp[3-4]|pdf|png)abc.mp3 、abc.mp4 、

abc.pdf 、abc.png

パターンのどれかに
一致するとマッチ
{ , }a{bcde,xyz}.txtabcde.txt 、 axyz.txt文字列にマッチ
**c:/User/**/abc.txtc:/User/name1/abc.txt

c:/User/name2/abc.txt

パスに**のみ指定した場合
シンボリックリンク以外の
ディレクトリに一致

 

.txtなど"."で始まるファイルは、"*txt"や"*.txt"などで一致しません。
一致させたい場合は、オプション dot を true にセットする必要があります。

 

パターンをチェックする前に、{ }が展開されます。

 

a{bcde,b/dir/a,[1-9]}.txt
⇒ abcde.txt , a/dir/a.txt , a[1-9].txt

 

globのオプション指定

 

第二引数に関数以外のオブジェクトを指定すると、オプションとみなされます。

 

オプション指定例

 


const opt = {
    dot : true,
    silent : true,
    nodir : true,
    ignore : ["*.txt"]
};
glob( "*" ,opt , function(err, files){
    if(err) {
        console.log(err);return;
    }
    console.log(files);
});

 

globのオプション
プロパティ名意味デフォルト
cwdカレントディレクトリを指定文字列process.cwd()の結果
rootパスのルート文字列path.resolve

(オプション.cwd,"/")の結果

dot"."で始まるファイルを一致させる真偽値false
nomount指定パスが"/"で始まるとき、

結果にオプション.rootを結合しない

真偽値false
markディレクトリの一致に/文字を追加する真偽値false
nosort結果を並び替えない真偽値false
statステータスを取得する真偽値false
silentエラーを標準エラーに出力しない真偽値false
strictエラー時に中断する真偽値false
cache以前のキャッシュを使用する真偽値false
statCacheステータスのキャッシュを使用する真偽値false
symlinksシンボリックリンクのキャッシュを使用する真偽値false
sync検索完了を待つ 真偽値false
nouniqueパターン展開時の

ファイル重複チェックを無効にする

真偽値false
nonull空の結果を抑制しパターン

自体を含むセットを返す

真偽値false
debugデバッグログを有効にする真偽値false
nobrace{ }の展開を無効化真偽値false
noglobstar"**"を"*"として扱う真偽値false
noext+(a|b) パターンを無効にする真偽値false
nocase大文字と小文字を区別しない真偽値false
matchBaseパスに"/"がない場合、

下位ディレクトリも対象とする

真偽値false
nodirファイルのみ一致真偽値false
ignore除外条件の指定。

こちらを参照。

文字列配列
follow**に一致するシンボリックリンク

はディレクトリを参照する。
循環リンクで多くの重複が
発生する可能性がある

真偽値false
realpathfs.realpathを呼び出す。真偽値false
absolute結果を絶対パスで返す真偽値false
fsファイルシステムオブジェクト組み込みfsモジュール

自作コードで実装する

 

globは汎用的な機能を盛り込んでいるので、コード量がそれなりに多いです。
さらに第三者提供によるライブラリモジュールのため、不具合が混在している可能性を覚悟する必要があります。

 

ワイルドカードを使用したファイル一覧取得は、目的を限定すれば、それほど多くのコードを記述しなくてもよい場合が多いです。
そこで、次のような条件でコードを作成してみます。

 

条件

 

 

  1. 検索パスをフルパスで指定する
  2. 検索パスが"/"または"\"で終わる場合ディレクトリが指定したとみなし全ファイルを対象とする
  3. 検索パスのファイル名部分のみ次の二つのワイルドカード指定可能

    *:0文字以上の任意の文字
    ?:任意1文字

  4. globの+( | | )パターンも使用可
  5. ファイルのみ取得する
  6. ステータスも取得する

 

2は、パスのステータスを取得してディレクトリかどうかを判断する方が妥当かもしれません。
しかし記事として掲載する関係上、少しでもコードを短くするためにこのような仕様になっています。

 

4はglobの他のパターンも検討したのですが、正規表現への変換が非常に難しく検証にも時間がかかるため、一番簡単なパターンのみ採用しています。パターンを増やしたいときは、素直にglobを使った方が良いと思います。

 

コード

 

ワイルドカードを使用したファイル一覧取得

 


const fs =  require("fs/promises");
const fpath = require("path");

    // エラー終了
const errorEnd = msg => {console.error(msg);process.exit(-1);}

const searchDir = async (searchPath) => {
        // パスをディレクトリとファイル名に分割
        // "/"で終わるならファイル名無しとみなす
        const  {dir,base} = searchPath.endsWith("/") || searchPath.endsWith("\\")
                            ? { dir:searchPath , base:null } : fpath.parse( searchPath );

        // ファイル名のチェックをおこなう正規表現オブジェクト作成
        const fileMatch = base === null ? null
            : new RegExp("^"
                + base.replace(/(\+)(\(.*?\))|([*?+.{}()\[\]^$|])/g,
                (...s)=> {
                    switch (s[1]===undefined ? s[0] : s[1]){
                        case "+" : return s[2];
                        case "*" : return ".*?";
                        case "?" : return ".";
                        default: return "\\"+s[0];
                    }
                } )
            + "$");

        // ディレクトリ内のファイルを取得
        const files = await fs.readdir(dir,{ withFileTypes:true }).catch(e=>({err:e}));
        if( files.err ) errorEnd( files.err.message );

        // 対象ファイルを抽出
        const result = files.filter( dirent=>{
                 // ファイルかどうか
            if( !dirent.isFile() ) return false;
                // 正規表現によるマッチング
            return fileMatch === null ? true
                : fileMatch.test( dirent.name );

        }).map(  // ステータスの取得
             dirent =>fs.stat( fpath.join( dir , dirent.name ) )
                        .then(
                            status => ({name:dirent.name,stat:status})
                            ,e=> ({name:dirent.name,error:e})
                        )
        );
        // ステータスの取得を待つ
        Promise.all( result ).then( e=>console.log(e) );

};

 

searchDir関数は、パスを引数として受け付けます。

 

 

使用例

 


searchDir( "c:\\user\\xxxx\\*.png" );

 

なお、このコードはWindows10のみでテストしています。
他のOSでも動作するはずですが、保証はしていません。

 

簡単な解説

 

ファイル取得処理を関数化してあります。
この関数内で awaitを使用したかったので、関数にasyncを付与しています。

 

async/awaitについては、次の記事を読んでみてください。
【JavaScript】 async/awaitの処理の流れを図で解説します

 

パスのチェック

 

関数内のは、指定されたパスをディレクトリとファイル名に分割しています。
ただしパスをディレクトリと判断した場合は、ファイル名が null になります。

 

ファイル名を正規表現にコンバート

 

が、今回の関数で一番難しい箇所です。
ワイルドカードが含まれたファイル名を、正規表現のパターンに変換しています。

 

コード上では、ファイル名を次のような正規表現パターンでマッチさせています。
/(\+)(\(.*?\))|([*?+.{}()\[\]^$|])/g

 

このパターンは、+( ・・・ )のとき、"+"と、"( ・・・ )"がキャプチャされます。
2番目の"( ・・・ )"は正規表現の条件一致パターンとみなせるので、一番目の"+"だけ削除します。

 

*?+.{}()\[\]^$|のいずれかのとき、各文字がキャプチャされます。
"*"は、0文字以上をマッチさせる".*?"に置き換えます。
"?"は、一文字をマッチさせる"."に置き換えます。
他は正規表現のメタ文字なので、\でエスケープしています。

 

ファイル名読み込み

 

は、ディレクトリ下のファイル名を読み込んでいます。
コードの冒頭で"fs/promises"をrequireしているので、fs.readdirはPromiseを返します。
返ってきた値にawaitキーワードが作用して、Promiseの結果が出るまで次のコードの実行を待ちます。
なお、後ろに続くcatchはエラーを捕捉して、ここで返された値がawaitの結果となります。

 

またfs.readdirのwithFileTypesオプションをtrueにすると、ファイルの種類を特定できるfs.Direntオブジェクトの配列が結果として返ります。
ファイルかどうかをチェックしたいとき、便利です。

 

対象ファイルの抽出

 

は、③で取得したファイル情報から条件に合うものを抽出しています。
Arrayのfilterメソッドは、各要素に対してコールバック関数を呼び出して、その結果がtrueのもので新しい配列を作成します。

 

コールバック関数内では、まずはファイルかどうかを確認しています。
次に正規表現のマッチングテストをおこなっています。

 

ステータスの取得

 

は、④で抽出したファイルのステータスを取得するPromiseオブジェクトを作成しています。
ここではmapメソッドを使用しているので、Promiseオブジェクトの配列が生成されます。

 

fs.statが作成したPromiseはステータスのみ返します。
そのままではファイル名が消えてしまうので、thenでファイル名を含んだオブジェクトを作成して返しています。
こうすることで、Promiseの結果がそのオブジェクトになります。

 

なおthenでエラーを捕捉していますが、エラーが発生する状況を再現できなかったため、この部分に関してはテストできていません。
ご了承ください。

 

結果待ち

 

は、⑤で生成したPromiseオブジェクトの結果を待ちます。

 

Promise.allは配列中のPromiseのうち一つでも拒否されると、その時点で結果が返ります。
しかしこのコードでは、thenの2番目の引数で拒否を受け取って値を返すことで、拒否されたことを打ち消しています。
そのため最終的には拒否されないので、全ての実行結果を得ることができます。

 

最終的な結果として、次のような配列を取得できます。

 

[
  {
    name: 'xxxxx.xx',
    stat: Stats {
      dev: 1714223727,
      mode: 33206,
      nlink: 1,
      uid: 0,
      gid: 0,
      rdev: 0,
      blksize: 4096,
      ino: 7036874418757113,
      size: 2656,
      blocks: 8,
      atimeMs: 1633330217113.4949,
      mtimeMs: 1633330146693.6936,
      ctimeMs: 1633330160880.8123,
      birthtimeMs: 1632902753072.5093,
      atime: 2021-10-04T06:50:17.113Z,
      mtime: 2021-10-04T06:49:06.694Z,
      ctime: 2021-10-04T06:49:20.881Z,
      birthtime: 2021-09-29T08:05:53.073Z
    }
  },
  { name: 'xxxxxx',stat: Stats { } },
  { name: 'xxxxxx',stat: Stats { } },
]

 

 

けーちゃんおススメJavaScript入門書

  • スラスラ読める JavaScript ふりがなプログラミング
  • プログラム未経験者がJavaScript始めるならコレ!
    コードを掲載して自分で理解しろという投げっぱなしな入門書とは異なり、コードに一つ一つどんなことをやっているかをふりがなという形式で解説しています。
    それでいてJavaScriptの基礎と応用を学べる良書です。
  • これからWebをはじめる人のHTML&CSS、JavaScriptのきほんのきほん
  • JavaScriptの機能を実践で活かすにはHTMLやCSSの知識が不可欠です。
    しかしそれらの知識があることが前提として書かれている書籍が多い中、この本は総合的な知識を身に着けることができます。
    HTMLやCSSの知識も不安な方には、ぴったりの一冊です
  •  

    入門書の役割は、自分のやりたいことをネットで調べることができるようになるための、基礎的な知識の獲得です。
    まずはこれらの本でしっかりと基礎知識を身につけましょう。
    そしてもっと高度なことや専門的なことはネットで調べ、情報が足りないと感じたら書籍を購入してください。


    記事の内容について

     

    こんにちはけーちゃんです。
    説明するのって難しいですね。


    「なんか言ってることおかしくない?」
    たぶん、こんなご意見あると思います。

    裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。

    そんなときは、ご意見もらえたら嬉しいです。

    ご意見はこちら。
    https://note.affi-sapo-sv.com/info.php

    【お願い】

    お願い

    ■このページのURL


    ■このページのタイトル


    ■リンクタグ


    ※リンクして頂いた方でご希望者には貴サイトの紹介記事を作成してリンクを設置します。
    サイト上部の問い合わせよりご連絡ください。
    ただしサイトのジャンルによっては、お断りさせていただくことがあります。