fsnotify is a Go library to provide cross-platform filesystem notifications on
Windows, Linux, macOS, and BSD systems.
-fsnotify requires Go 1.16 or newer.
+Go 1.16 or newer is required; the full documentation is at
+https://pkg.go.dev/github.com/fsnotify/fsnotify
-API docs: https://pkg.go.dev/github.com/fsnotify/fsnotify
-
-It's best to read the documentation at pkg.go.dev, as it's pinned to the last
+**It's best to read the documentation at pkg.go.dev, as it's pinned to the last
released version, whereas this README is for the last development version which
-may include additions/changes.
+may include additions/changes.**
---
### Will a file still be watched when it's moved to another directory?
No, not unless you are watching the location it was moved to.
-### Are all subdirectories watched too?
+### Are subdirectories watched too?
No, you must add watches for any directory you want to watch (a recursive
watcher is on the roadmap: [#18]).
[#18]: https://github.com/fsnotify/fsnotify/issues/18
-### Do I have to watch the Error and Event channels in a separate goroutine?
-As of now, yes (you can read both channels in the same goroutine, you don't need
-a separate goroutine for both channels; see the example).
+### Do I have to watch the Error and Event channels in a goroutine?
+As of now, yes (you can read both channels in the same goroutine using `select`,
+you don't need a separate goroutine for both channels; see the example).
### Why don't notifications work with NFS, SMB, FUSE, /proc, or /sys?
fsnotify requires support from underlying OS to work. The current NFS and SMB
-----------------------
### Linux
When a file is removed a REMOVE event won't be emitted until all file
-descriptors are closed. It will emit a CHMOD though:
+descriptors are closed; it will emit a CHMOD instead:
fp := os.Open("file")
os.Remove("file") // CHMOD
fp.Close() // REMOVE
+This is the event that inotify sends, so not much can be changed about this.
+
The `fs.inotify.max_user_watches` sysctl variable specifies the upper limit for
the number of watches per user, and `fs.inotify.max_user_instances` specifies
the maximum number of inotify instances per user. Every Watcher you create is an
"instance", and every path you add is a "watch".
-These are also exposed in /proc as `/proc/sys/fs/inotify/max_user_watches` and
+These are also exposed in `/proc` as `/proc/sys/fs/inotify/max_user_watches` and
`/proc/sys/fs/inotify/max_user_instances`
To increase them you can use `sysctl` or write the value to proc file:
sysctl fs.inotify.max_user_instances=128
To make the changes persist on reboot edit `/etc/sysctl.conf` or
-`/usr/lib/sysctl.d/50-default.conf` (some systemd systems):
+`/usr/lib/sysctl.d/50-default.conf` (details differ per Linux distro; check your
+distro's documentation):
fs.inotify.max_user_watches=124983
fs.inotify.max_user_instances=128
[#11]: https://github.com/fsnotify/fsnotify/issues/11
[#15]: https://github.com/fsnotify/fsnotify/issues/15
-
-Related Projects
-----------------
-- [notify](https://github.com/rjeczalik/notify)
-- [fsevents](https://github.com/fsnotify/fsevents)
"errors"
)
-// Watcher watches a set of files, delivering events to a channel.
+// Watcher watches a set of paths, delivering events on a channel.
//
// A watcher should not be copied (e.g. pass it by pointer, rather than by
// value).
// os.Remove("file") // Triggers Chmod
// fp.Close() // Triggers Remove
//
+// This is the event that inotify sends, so not much can be changed about this.
+//
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
// for the number of watches per user, and fs.inotify.max_user_instances
// specifies the maximum number of inotify instances per user. Every Watcher you
// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
-// /usr/lib/sysctl.d/50-default.conf (on some systemd systems):
+// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
+// your distro's documentation):
//
// fs.inotify.max_user_watches=124983
// fs.inotify.max_user_instances=128
// Events sends the filesystem change events.
//
// fsnotify can send the following events; a "path" here can refer to a
- // file, directory, symbolic link, or special files like a FIFO.
+ // file, directory, symbolic link, or special file like a FIFO.
//
// fsnotify.Create A new path was created; this may be followed by one
// or more Write events if data also gets written to a
// fsnotify.Remove A path was removed.
//
// fsnotify.Rename A path was renamed. A rename is always sent with the
- // old path as [Event.Name], and a Create event will be
+ // old path as Event.Name, and a Create event will be
// sent with the new name. Renames are only sent for
// paths that are currently watched; e.g. moving an
// unmonitored file into a monitored directory will
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
//
- // fsnotify.Chmod Attributes were changes (never sent on Windows). On
- // Linux this is also sent when a file is removed (or
- // more accurately, when a link to an inode is
- // removed), and on kqueue when a file is truncated.
+ // fsnotify.Chmod Attributes were changed. On Linux this is also sent
+ // when a file is removed (or more accurately, when a
+ // link to an inode is removed). On kqueue it's sent
+ // and on kqueue when a file is truncated. On Windows
+ // it's never sent.
Events chan Event
// Errors sends any errors.
//
// A path will remain watched if it gets renamed to somewhere else on the same
// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created.
+// re-created, or if it's moved to a different filesystem.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
// Watching individual files (rather than directories) is generally not
// recommended as many tools update files atomically. Instead of "just" writing
// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination, removing the original, or some
+// temporary file is moved to to destination removing the original, or some
// variant thereof. The watcher on the original file is now lost, as it no
// longer exists.
//
-// Instead, watch the parent directory and use [Event.Name] to filter out files
-// you're not interested in. There is an example of this in cmd/fsnotify/file.go
+// Instead, watch the parent directory and use Event.Name to filter out files
+// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
func (w *Watcher) Add(name string) error {
return nil
}
"golang.org/x/sys/unix"
)
-// Watcher watches a set of files, delivering events to a channel.
+// Watcher watches a set of paths, delivering events on a channel.
//
// A watcher should not be copied (e.g. pass it by pointer, rather than by
// value).
// os.Remove("file") // Triggers Chmod
// fp.Close() // Triggers Remove
//
+// This is the event that inotify sends, so not much can be changed about this.
+//
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
// for the number of watches per user, and fs.inotify.max_user_instances
// specifies the maximum number of inotify instances per user. Every Watcher you
// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
-// /usr/lib/sysctl.d/50-default.conf (on some systemd systems):
+// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
+// your distro's documentation):
//
// fs.inotify.max_user_watches=124983
// fs.inotify.max_user_instances=128
// Events sends the filesystem change events.
//
// fsnotify can send the following events; a "path" here can refer to a
- // file, directory, symbolic link, or special files like a FIFO.
+ // file, directory, symbolic link, or special file like a FIFO.
//
// fsnotify.Create A new path was created; this may be followed by one
// or more Write events if data also gets written to a
// fsnotify.Remove A path was removed.
//
// fsnotify.Rename A path was renamed. A rename is always sent with the
- // old path as [Event.Name], and a Create event will be
+ // old path as Event.Name, and a Create event will be
// sent with the new name. Renames are only sent for
// paths that are currently watched; e.g. moving an
// unmonitored file into a monitored directory will
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
//
- // fsnotify.Chmod Attributes were changes (never sent on Windows). On
- // Linux this is also sent when a file is removed (or
- // more accurately, when a link to an inode is
- // removed), and on kqueue when a file is truncated.
+ // fsnotify.Chmod Attributes were changed. On Linux this is also sent
+ // when a file is removed (or more accurately, when a
+ // link to an inode is removed). On kqueue it's sent
+ // and on kqueue when a file is truncated. On Windows
+ // it's never sent.
Events chan Event
// Errors sends any errors.
//
// A path will remain watched if it gets renamed to somewhere else on the same
// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created.
+// re-created, or if it's moved to a different filesystem.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
// Watching individual files (rather than directories) is generally not
// recommended as many tools update files atomically. Instead of "just" writing
// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination, removing the original, or some
+// temporary file is moved to to destination removing the original, or some
// variant thereof. The watcher on the original file is now lost, as it no
// longer exists.
//
-// Instead, watch the parent directory and use [Event.Name] to filter out files
-// you're not interested in. There is an example of this in cmd/fsnotify/file.go
+// Instead, watch the parent directory and use Event.Name to filter out files
+// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
func (w *Watcher) Add(name string) error {
name = filepath.Clean(name)
if w.isClosed() {
return nil
}
-// WatchList returns all paths added with Add() (and are not yet removed).
+// WatchList returns all paths added with [Add] (and are not yet removed).
func (w *Watcher) WatchList() []string {
w.mu.Lock()
defer w.mu.Unlock()
"golang.org/x/sys/unix"
)
-// Watcher watches a set of files, delivering events to a channel.
+// Watcher watches a set of paths, delivering events on a channel.
//
// A watcher should not be copied (e.g. pass it by pointer, rather than by
// value).
// os.Remove("file") // Triggers Chmod
// fp.Close() // Triggers Remove
//
+// This is the event that inotify sends, so not much can be changed about this.
+//
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
// for the number of watches per user, and fs.inotify.max_user_instances
// specifies the maximum number of inotify instances per user. Every Watcher you
// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
-// /usr/lib/sysctl.d/50-default.conf (on some systemd systems):
+// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
+// your distro's documentation):
//
// fs.inotify.max_user_watches=124983
// fs.inotify.max_user_instances=128
// Events sends the filesystem change events.
//
// fsnotify can send the following events; a "path" here can refer to a
- // file, directory, symbolic link, or special files like a FIFO.
+ // file, directory, symbolic link, or special file like a FIFO.
//
// fsnotify.Create A new path was created; this may be followed by one
// or more Write events if data also gets written to a
// fsnotify.Remove A path was removed.
//
// fsnotify.Rename A path was renamed. A rename is always sent with the
- // old path as [Event.Name], and a Create event will be
+ // old path as Event.Name, and a Create event will be
// sent with the new name. Renames are only sent for
// paths that are currently watched; e.g. moving an
// unmonitored file into a monitored directory will
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
//
- // fsnotify.Chmod Attributes were changes (never sent on Windows). On
- // Linux this is also sent when a file is removed (or
- // more accurately, when a link to an inode is
- // removed), and on kqueue when a file is truncated.
+ // fsnotify.Chmod Attributes were changed. On Linux this is also sent
+ // when a file is removed (or more accurately, when a
+ // link to an inode is removed). On kqueue it's sent
+ // and on kqueue when a file is truncated. On Windows
+ // it's never sent.
Events chan Event
// Errors sends any errors.
//
// A path will remain watched if it gets renamed to somewhere else on the same
// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created.
+// re-created, or if it's moved to a different filesystem.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
// Watching individual files (rather than directories) is generally not
// recommended as many tools update files atomically. Instead of "just" writing
// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination, removing the original, or some
+// temporary file is moved to to destination removing the original, or some
// variant thereof. The watcher on the original file is now lost, as it no
// longer exists.
//
-// Instead, watch the parent directory and use [Event.Name] to filter out files
-// you're not interested in. There is an example of this in cmd/fsnotify/file.go
+// Instead, watch the parent directory and use Event.Name to filter out files
+// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
func (w *Watcher) Add(name string) error {
w.mu.Lock()
w.userWatches[name] = struct{}{}
return nil
}
-// WatchList returns all paths added with Add() (and are not yet removed).
+// WatchList returns all paths added with [Add] (and are not yet removed).
func (w *Watcher) WatchList() []string {
w.mu.Lock()
defer w.mu.Unlock()
//
// A path will remain watched if it gets renamed to somewhere else on the same
// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created.
+// re-created, or if it's moved to a different filesystem.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
// Watching individual files (rather than directories) is generally not
// recommended as many tools update files atomically. Instead of "just" writing
// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination, removing the original, or some
+// temporary file is moved to to destination removing the original, or some
// variant thereof. The watcher on the original file is now lost, as it no
// longer exists.
//
-// Instead, watch the parent directory and use [Event.Name] to filter out files
-// you're not interested in. There is an example of this in cmd/fsnotify/file.go
+// Instead, watch the parent directory and use Event.Name to filter out files
+// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
func (w *Watcher) Add(name string) error {
return nil
}
"golang.org/x/sys/windows"
)
-// Watcher watches a set of files, delivering events to a channel.
+// Watcher watches a set of paths, delivering events on a channel.
//
// A watcher should not be copied (e.g. pass it by pointer, rather than by
// value).
// os.Remove("file") // Triggers Chmod
// fp.Close() // Triggers Remove
//
+// This is the event that inotify sends, so not much can be changed about this.
+//
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
// for the number of watches per user, and fs.inotify.max_user_instances
// specifies the maximum number of inotify instances per user. Every Watcher you
// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
-// /usr/lib/sysctl.d/50-default.conf (on some systemd systems):
+// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
+// your distro's documentation):
//
// fs.inotify.max_user_watches=124983
// fs.inotify.max_user_instances=128
// Events sends the filesystem change events.
//
// fsnotify can send the following events; a "path" here can refer to a
- // file, directory, symbolic link, or special files like a FIFO.
+ // file, directory, symbolic link, or special file like a FIFO.
//
// fsnotify.Create A new path was created; this may be followed by one
// or more Write events if data also gets written to a
// fsnotify.Remove A path was removed.
//
// fsnotify.Rename A path was renamed. A rename is always sent with the
- // old path as [Event.Name], and a Create event will be
+ // old path as Event.Name, and a Create event will be
// sent with the new name. Renames are only sent for
// paths that are currently watched; e.g. moving an
// unmonitored file into a monitored directory will
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
//
- // fsnotify.Chmod Attributes were changes (never sent on Windows). On
- // Linux this is also sent when a file is removed (or
- // more accurately, when a link to an inode is
- // removed), and on kqueue when a file is truncated.
+ // fsnotify.Chmod Attributes were changed. On Linux this is also sent
+ // when a file is removed (or more accurately, when a
+ // link to an inode is removed). On kqueue it's sent
+ // and on kqueue when a file is truncated. On Windows
+ // it's never sent.
Events chan Event
// Errors sends any errors.
//
// A path will remain watched if it gets renamed to somewhere else on the same
// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created.
+// re-created, or if it's moved to a different filesystem.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
// Watching individual files (rather than directories) is generally not
// recommended as many tools update files atomically. Instead of "just" writing
// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination, removing the original, or some
+// temporary file is moved to to destination removing the original, or some
// variant thereof. The watcher on the original file is now lost, as it no
// longer exists.
//
-// Instead, watch the parent directory and use [Event.Name] to filter out files
-// you're not interested in. There is an example of this in cmd/fsnotify/file.go
+// Instead, watch the parent directory and use Event.Name to filter out files
+// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
func (w *Watcher) Add(name string) error {
w.mu.Lock()
if w.isClosed {
return <-in.reply
}
-// WatchList returns all paths added with Add() (and are not yet removed).
+// WatchList returns all paths added with [Add] (and are not yet removed).
func (w *Watcher) WatchList() []string {
w.mu.Lock()
defer w.mu.Unlock()
)
// Depending on the system, a single "write" can generate many Write events; for
-// example compiling a large Go program can generate hundreds of Write events.
+// example compiling a large Go program can generate hundreds of Write events on
+// the binary.
//
// The general strategy to deal with this is to wait a short time for more write
// events, resetting the wait period for every new event.
// Start listening for events.
go dedupLoop(w)
- // Add all paths.
+ // Add all paths from the commandline.
for _, p := range paths {
err = w.Add(p)
if err != nil {
"github.com/fsnotify/fsnotify"
)
+// Watch one or more files, but instead of watching the file directly it watches
+// the parent directory. This solves various issues where files are frequently
+// renamed, such as editors saving them.
func file(files ...string) {
if len(files) < 1 {
exit("must specify at least one file to watch")
// Start listening for events.
go fileLoop(w, files)
- // Add all files.
+ // Add all files from the commandline.
for _, p := range files {
st, err := os.Lstat(p)
if err != nil {
+// Command fsnotify provides example usage of the fsnotify library.
package main
import (
)
var usage = `
-fsnotify is a library to provide cross-platform file system notifications for
-Go. This utility serves as an example and debugging tool.
+fsnotify is a Go library to provide cross-platform file system notifications.
+This command serves as an example and debugging tool.
https://github.com/fsnotify/fsnotify
// Start listening for events.
go watchLoop(w)
- // Add all paths.
+ // Add all paths from the commandline.
for _, p := range paths {
err = w.Add(p)
if err != nil {
// File operation that triggered the event.
//
- // This is a bitmask as some systems may send multiple operations at once.
+ // This is a bitmask and some systems may send multiple operations at once.
// Use the Event.Has() method instead of comparing with ==.
Op Op
}
# more time to write this than doing it manually, but ah well 🙃
watcher=$(<<EOF
-// Watcher watches a set of files, delivering events to a channel.
+// Watcher watches a set of paths, delivering events on a channel.
//
// A watcher should not be copied (e.g. pass it by pointer, rather than by
// value).
// os.Remove("file") // Triggers Chmod
// fp.Close() // Triggers Remove
//
+// This is the event that inotify sends, so not much can be changed about this.
+//
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
// for the number of watches per user, and fs.inotify.max_user_instances
// specifies the maximum number of inotify instances per user. Every Watcher you
// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
-// /usr/lib/sysctl.d/50-default.conf (on some systemd systems):
+// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
+// your distro's documentation):
//
// fs.inotify.max_user_watches=124983
// fs.inotify.max_user_instances=128
//
// A path will remain watched if it gets renamed to somewhere else on the same
// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created.
+// re-created, or if it's moved to a different filesystem.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
// Watching individual files (rather than directories) is generally not
// recommended as many tools update files atomically. Instead of "just" writing
// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination, removing the original, or some
+// temporary file is moved to to destination removing the original, or some
// variant thereof. The watcher on the original file is now lost, as it no
// longer exists.
//
-// Instead, watch the parent directory and use [Event.Name] to filter out files
-// you're not interested in. There is an example of this in cmd/fsnotify/file.go
+// Instead, watch the parent directory and use Event.Name to filter out files
+// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
EOF
)
)
watchlist=$(<<EOF
-// WatchList returns all paths added with Add() (and are not yet removed).
+// WatchList returns all paths added with [Add] (and are not yet removed).
EOF
)
// Events sends the filesystem change events.
//
// fsnotify can send the following events; a "path" here can refer to a
- // file, directory, symbolic link, or special files like a FIFO.
+ // file, directory, symbolic link, or special file like a FIFO.
//
// fsnotify.Create A new path was created; this may be followed by one
// or more Write events if data also gets written to a
// fsnotify.Remove A path was removed.
//
// fsnotify.Rename A path was renamed. A rename is always sent with the
- // old path as [Event.Name], and a Create event will be
+ // old path as Event.Name, and a Create event will be
// sent with the new name. Renames are only sent for
// paths that are currently watched; e.g. moving an
// unmonitored file into a monitored directory will
// probably want to wait until you've stopped receiving
// them (see the dedup example in cmd/fsnotify).
//
- // fsnotify.Chmod Attributes were changes (never sent on Windows). On
- // Linux this is also sent when a file is removed (or
- // more accurately, when a link to an inode is
- // removed), and on kqueue when a file is truncated.
+ // fsnotify.Chmod Attributes were changed. On Linux this is also sent
+ // when a file is removed (or more accurately, when a
+ // link to an inode is removed). On kqueue it's sent
+ // and on kqueue when a file is truncated. On Windows
+ // it's never sent.
EOF
)