fsnotifyはGoで書かれたファイルシステムの通知ライブラリで、ファイルシステム内のファイルやディレクトリの変更を監視し、変更が発生した際にアプリケーションに通知することができます。また、Linux、macOS、Windowsなど様々なオペレーティングシステムでクロスプラットフォームで動作し、それぞれのオペレーティングシステムのファイルシステム通知メカニズム(Linuxのinotify、macOSのFSEvents、WindowsのReadDirectoryChangesWなど)を利用しています。

Go 1.17以降が必要であり、完全なドキュメントは以下で確認できます:https://pkg.go.dev/github.com/fsnotify/fsnotify

異なるオペレーティングシステムへのサポート:

バックエンド オペレーティングシステム ステータス
inotify Linux サポート済み
kqueue BSD、macOS サポート済み
ReadDirectoryChangesW Windows サポート済み
FEN illumos サポート済み
fanotify Linux 5.9+ サポートされていません
AHAFS AIX AIX分岐; 管理者とテスト環境の不足により実験的
FSEvents macOS x/sys/unixのサポートが必要
USN Journals Windows x/sys/windowsのサポートが必要
Polling すべてのオペレーティングシステム サポートされていません

AndroidとSolarisについては、まだテストされていません。

使用例

fsnotifyの使用例は以下の通りです(但し、これに限定されません):

  1. リアルタイムファイル同期: fsnotifyはファイルシステムの変更をリアルタイムで監視し、リアルタイムファイル同期を実装するのに適しています。ソースファイルに変更があった際、変更内容を即座にターゲットファイルに同期することで、ファイルの一貫性を確保します。
  2. 自動ビルド: fsnotifyはプロジェクトのソースコードや依存ファイルの変更を監視し、変更があった際にビルドコマンドをトリガーとすることで、自動ビルドを実珅することができます。これにより手動ビルドの時間と手間を節約し、開発効率を向上させることができます。
  3. ファイルバックアップ: fsnotifyはバックアップが必要なファイルやディレクトリの変更を監視し、変更があった際に即座にバックアップを開始することができます。これによりデータの安全性が確保され、ファイルの紛失や損傷によるデータ損失を防ぐことができます。
  4. リアルタイムログ監視: fsnotifyはログファイルの作成、修正、削除といった操作を監視し、ログファイルに変更があった際にログ監視プログラムをトリガーすることで、ログ内容の変更を実時間で監視することができます。
  5. ファイルシステムセキュリティ監視: fsnotifyはファイルシステムでのファイルアクセス、修正、削除などのセキュリティイベントを監視できます。これによりファイルシステムのセキュリティモニタリングが可能となり、即座に不正なイベントを特定・記録することができます。

使用例

基本的な例:

package main

import (
    "log"

    "github.com/fsnotify/fsnotify"
)

func main() {
    // 新しい監視者を作成する。
    watcher, err := fsnotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    defer watcher.Close()

    // イベントをリッスンする。
    go func() {
        for {
            select {
            case event, ok := <-watcher.Events:
                if !ok {
                    return
                }
                log.Println("イベント:", event)
                if event.Has(fsnotify.Write) {
                    log.Println("変更されたファイル:", event.Name)
                }
            case err, ok := <-watcher.Errors:
                if !ok {
                    return
                }
                log.Println("エラー:", err)
            }
        }
    }()

    // 監視するパスを追加する。
    err = watcher.Add("/tmp")
    if err != nil {
        log.Fatal(err)
    }

    // メインゴルーチンをブロックする。
    <-make(chan struct{})
}

より詳細なドキュメントはgodocから確認できます: https://pkg.go.dev/github.com/fsnotify/fsnotify

よくある質問

ファイルが別のディレクトリに移動された場合も監視されますか?

いいえ、移動先の場所が監視対象でない限り、監視されません。

サブディレクトリは監視されていますか?

いいえ、監視したいディレクトリごとに監視を追加する必要があります(再帰的な監視は計画されています:#18)。

エラーチャンネルとイベントチャンネルの両方をゴルーチンで同時に監視する必要がありますか?

はい。selectを使用して、同じゴルーチンで両方のチャンネルから読み取ることができます(各チャンネルごとに個別のゴルーチンを開始する必要はありません。例を参照してください)。

NFS、SMB、FUSE、/proc、または/sysで通知が機能しないのはなぜですか?

fsnotifyは基礎となるオペレーティングシステムからのサポートが必要です。現在のNFSとSMBプロトコルはファイルの通知のためのネットワークレベルのサポートを提供しておらず、/procと/sysの仮想ファイルシステムもサポートしていません。

これはポーリング監視プログラムを使用することで修正できます(#9)、ただし、まだ実装されていません。

なぜ多くのChmodイベントを受信していますか?

一部のプログラムは、macOSのSpotlight、ウイルス対策プログラム、バックアップアプリケーション、その他一部の既知のアプリケーションなどが属性の変更を大量に生成する場合があります。一般的には、Chmodイベントを無視するのが最良の方法です。それらはしばしば役に立たず、問題を引き起こすことがあります。

macOSのSpotlightインデックス作成により複数のイベントが発生することがあります(#15を参照)。ネイティブなFSEventsの実装があるまでの一時的な解決策として、フォルダを Spotlightのプライバシー設定 に追加することがあります(#11を参照)。

ファイルの監視がうまく機能しない

通常、個々のファイルを監視することはお勧めしません(ディレクトリではなく)ため、多くのプログラム、特にエディタは、ファイルをアトミックに更新します。それは一時ファイルに書き込み、それをターゲットの場所に移動し、元のファイルを上書きします(またはその変形)。元のファイルの監視は失われた状態になります。

その結果、停電やクラッシュは、書きかけのファイルを残すことがありません。

親ディレクトリを監視し、Event.Nameを使用して興味のないファイルをフィルタリングしてください。cmd/fsnotify/file.goに例があります。

特定のプラットフォームの注意事項

Linux

ファイルが削除された場合、REMOVEイベントはすべてのファイルディスクリプタが閉じられるまで発行されず、その時点でCHMODイベントが発行されます:

fp := os.Open("file")
os.Remove("file")        // CHMOD
fp.Close()               // REMOVE

これはinotifyが送信するイベントであり、それにあまり変更を加えることはありません。

fs.inotify.max_user_watchesシスカル変数は、ユーザーごとの最大監視数を指定し、fs.inotify.max_user_instancesはユーザーごとのinotifyインスタンスの最大数を指定します。作成する各Watcherは「インスタンス」であり、追加する各パスは「監視」となります。

これらは/procにも見つけることができ、パスは /proc/sys/fs/inotify/max_user_watches/proc/sys/fs/inotify/max_user_instances です。

これらを増やすには、sysctlを使用するか、値をprocファイルに書き込むことができます:

sysctl fs.inotify.max_user_watches=124983
sysctl fs.inotify.max_user_instances=128

変更を再起動後に有効にするには、/etc/sysctl.conf または /usr/lib/sysctl.d/50-default.conf を編集してください(詳細は各Linuxディストリビューションごとに異なる場合がありますので、ディストリビューションのドキュメントを参照してください):

fs.inotify.max_user_watches=124983
fs.inotify.max_user_instances=128

限界値に達すると、「デバイスの空き容量がありません」 または「開いているファイルが多すぎます」というエラーが発生します。

kqueue(macOS、すべてのBSDシステム)

kqueueは、観察されているファイルごとにファイルディスクリプタを開く必要があります。したがって、5つのファイルを含むディレクトリを観察している場合、6つのファイルディスクリプタが必要です。これらのプラットフォームでは、システムの "最大オープンファイル" 限界により、より早く制限に達します。

システムの最大オープンファイル数を制御するために、kern.maxfiles および kern.maxfilesperproc のsysctl変数を使用することができます。