Using golang fsnotify package as watcher
In golang we have fsnotify package which can be used to monitor files
and directories for any types of changes. fsnotify is not available in
standard
library of gloang so we need to install it using go get command:
# go get "github.com/fsnotify/fsnotify"
go: finding module for package github.com/fsnotify/fsnotify
go: downloading github.com/fsnotify/fsnotify v1.6.0
go: found github.com/fsnotify/fsnotify in github.com/fsnotify/fsnotify v1.6.0
Following are the list of events which currently are supported by
fsnotify at the time of writing this tutorial:
fsnotify.Create: triggered when a file or directory is createdfsnotify.Write: triggered when a file is modifiedfsnotify.Remove: triggered when a file or directory is removedfsnotify.Rename: triggered when a file or directory is renamedfsnotify.Chmod: triggered when the permissions of a file or directory are modified
For updated content you may referfsnotify README
These events are defined as constants in the fsnotify package, and you
can use them to check for specific events in your code. For example, you
can check if an event is a write event using
event.Op&fsnotify.Write == fsnotify.Write.
Although you must understand that the events that are generated by the filesystem notifications are OS-specific and might not always be accurate. Also, the events generated by the filesystem notifications are not guaranteed to be in order.
You can use these events to monitor and take some action based on the file system changes. Please let me know if you have further questions.
Example-1: Using fsnotify to monitor changes in a file
In this example we use fsnotify package to watch for changes in a text
file:
package main
import (
"fmt"
"log"
"github.com/fsnotify/fsnotify"
)
func main() {
// setup watcher
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
done := make(chan bool)
// use goroutine to start the watcher
go func() {
for {
select {
case event := <-watcher.Events:
// monitor only for write events
if event.Op&fsnotify.Write == fsnotify.Write {
fmt.Println("Modified file:", event.Name)
}
case err := <-watcher.Errors:
log.Println("Error:", err)
}
}
}()
// provide the file name along with path to be watched
err = watcher.Add("file.txt")
if err != nil {
log.Fatal(err)
}
<-done
}
In this example, we first create a new watcher using the
fsnotify.NewWatcher() function. Then we start a goroutine that waits
for events on the watcher’s Events and Errors channels. When an event is
received, we check if the event is a write event using the
fsnotify.Write constant and if so, we print the name of the modified
file.
We then add the file that we want to watch to the watcher using the
watcher.Add("file.txt") function. In my case since the file exist in
local directory hence I have not give absolute path.
You can also watch for other file system events like fsnotify.Create,
fsnotify.Remove, fsnotify.Rename and fsnotify.Chmod as well.
On one terminal we execute our code
# go run main.go
On another terminal I will make some changes to file.txt:
# echo hello >> file.txt
Immediately we get a notification on our code’s STDOUT
# go run main.go
Modified file: file.txt
^Csignal: interrupt
Press Ctrl+C to exit the watcher.
Example-2: Using fsnotify to monitor a directory for changes
Similar to our previous example, we can also configure a watcher for a directory instead of file:
package main
import (
"fmt"
"log"
"github.com/fsnotify/fsnotify"
)
func main() {
// setup watcher
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
done := make(chan bool)
// use goroutine to start the watcher
go func() {
for {
select {
// provide the list of events to monitor
case event := <-watcher.Events:
if event.Op&fsnotify.Create == fsnotify.Create {
fmt.Println("File created:", event.Name)
}
if event.Op&fsnotify.Write == fsnotify.Write {
fmt.Println("File modified:", event.Name)
}
if event.Op&fsnotify.Remove == fsnotify.Remove {
fmt.Println("File removed:", event.Name)
}
if event.Op&fsnotify.Rename == fsnotify.Rename {
fmt.Println("File renamed:", event.Name)
}
if event.Op&fsnotify.Chmod == fsnotify.Chmod {
fmt.Println("File permissions modified:", event.Name)
}
case err := <-watcher.Errors:
log.Println("Error:", err)
}
}
}()
// provide the directory to monitor
err = watcher.Add("/tmp")
if err != nil {
log.Fatal(err)
}
<-done
}
In this example, we use the fsnotify.NewWatcher() function to create a
new watcher and then add the directory that we want to monitor to the
watcher using watcher.Add("/tmp"). You can provide your own directory
instead of /tmp.
We then start a goroutine that waits for events on the watcher’s Events
and Errors channels, and when an event is received, we check if the
event is a create, write, remove, rename or chmod event using the
corresponding constants provided by fsnotify package, and if so, we
print the name of the file or directory that was modified and the type
of modification.
Output:
# go run main.go
File created: /tmp/file
File permissions modified: /tmp/file
File removed: /tmp/file
File created: /tmp/go-build1867431975
File removed: /tmp/go-build1867431975
File created: /tmp/staticcheck2690929288
File removed: /tmp/staticcheck2690929288
File modified: /tmp/staticcheck2690929288
Example-3: Monitor a directory recursively using fsnotify() and filepath.Walk()
By default fsnotify package does not support recursively monitoring a
directory and its subdirectories. It only watches for changes in the
immediate children of the directory that you added to the watcher, and
it doesn’t recursively watch subdirectories and their children.
So we will use fsnotify with filepath.Walk() function to recursively
walk through the directory tree and add each file and directory to the
watcher individually.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/fsnotify/fsnotify"
)
func main() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
fmt.Println(err)
return
}
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event := <-watcher.Events:
if event.Op&fsnotify.Create == fsnotify.Create {
fmt.Println("File created:", event.Name)
}
if event.Op&fsnotify.Write == fsnotify.Write {
fmt.Println("File modified:", event.Name)
}
if event.Op&fsnotify.Remove == fsnotify.Remove {
fmt.Println("File removed:", event.Name)
}
if event.Op&fsnotify.Rename == fsnotify.Rename {
fmt.Println("File renamed:", event.Name)
}
if event.Op&fsnotify.Chmod == fsnotify.Chmod {
fmt.Println("File permissions modified:", event.Name)
}
case err := <-watcher.Errors:
log.Println("Error:", err)
}
}
}()
filepath.Walk("/tmp", func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
watcher.Add(path)
}
return nil
})
<-done
}
In this example, we first create a new watcher using the
fsnotify.NewWatcher() function and start a
goroutine
that waits for events on the watcher’s Events and Errors
channels,
and when an event is received, we check if the event is a write event
using fsnotify.Write constant and if so, we print the name of the
modified file.
Then we use the filepath.Walk function to recursively walk through the
directory tree and for each directory, we add it to the watcher using
the watcher.Add(path) function.
We execute the code and in parallel terminal we will make some changes to sub-directories of the path which is being monitored"
# go run main.go
File created: /tmp/gopls-workspace-mod1600390560/file1
File modified: /tmp/gopls-workspace-mod1600390560/file1
File removed: /tmp/gopls-workspace-mod1600390560/file1
The changes performed:
[root@server goexamples]# touch /tmp/gopls-workspace-mod1600390560/file1
[root@server goexamples]# echo hello > /tmp/gopls-workspace-mod1600390560/file1
[root@server goexamples]# rm -f /tmp/gopls-workspace-mod1600390560/file1
Example-4: Monitor symbolic link file using fsnotify()
We can use the fsnotify package to watch for changes in a symbolic
link. The process is similar to monitoring a regular file or directory,
but you need to add the path of the symbolic link to the watcher. Here
is our example:
package main
import (
"fmt"
"log"
"github.com/fsnotify/fsnotify"
)
func main() {
// create a new watcher
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
// close the watcher when the function exits
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event := <-watcher.Events:
// monitor write event.
// You can add more events supported by fsnotify
if event.Op&fsnotify.Write == fsnotify.Write {
fmt.Println("Modified symlink:", event.Name)
}
case err := <-watcher.Errors:
log.Println("Error:", err)
}
}
}()
// add your symbolic link path and file to monitor
err = watcher.Add("/tmp/file")
if err != nil {
log.Fatal(err)
}
<-done
}
This code uses the fsnotify package to watch for changes in a symbolic
link. It first creates a new watcher using the fsnotify.NewWatcher()
function, and it starts a goroutine that waits for events on the
watcher’s Events channel. Inside the goroutine, it uses a select
statement to handle the events, checking if the event is a write event
using the fsnotify.Write constant and if so, it prints the name of the
modified symbolic link.
The defer statement before the watcher.Close() function is used to
close the watcher when the function exits.
It then adds the symbolic link to the watcher using the
watcher.Add("/tmp/file") function, and it starts the goroutine to wait
for events.
We have created a symbolic link for the sake of this code:
# ln -s /root/goexamples/file.txt /tmp/file
# ls -l /tmp/file
lrwxrwxrwx 1 root root 25 Jan 15 18:34 /tmp/file -> /root/goexamples/file.txt
Let’s start our monitor script:
# go run main.go
Modified symlink: /tmp/file
Modified symlink: /tmp/file
On a different terminal I did below changes to both original file and the symbolic link
# echo hello >> /tmp/file
# echo hello >> /root/goexamples/file.txt
as we can see in the go run main.go output above, both the events have
been captured.
Example-5: Monitor FIFO (named pipe) using fsnotify()
We can use same code as we used for regular file and symbolic link to also monitor a FIFO (named PIPE) file. Here is a sample code to monitor WRITE events into the FIFO file:
package main
import (
"fmt"
"log"
"github.com/fsnotify/fsnotify"
)
func main() {
// create a new watcher
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
// close the watcher when the function exits
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event := <-watcher.Events:
// monitor write event.
// You can add more events supported by fsnotify
if event.Op&fsnotify.Write == fsnotify.Write {
fmt.Println("Modified FIFO file:", event.Name)
}
case err := <-watcher.Errors:
log.Println("Error:", err)
}
}
}()
// provide the path to your fifo file
err = watcher.Add("/tmp/fifo")
if err != nil {
log.Fatal(err)
}
<-done
}
In this example, we first create a new watcher using the
fsnotify.NewWatcher() function. Then we start a goroutine that waits
for events on the watcher’s Events channel, and when an event is
received, it checks if the event is a write event using fsnotify.Write
constant and if so, it prints the name of the modified FIFO file.
Then we use the watcher.Add("/path/to/fifo") function to add the FIFO
file to the watcher and start the goroutine to wait for events.
I hope you are familiar with how FIFO works, as unless there is an active reader for the FIFO, the watcher will not work.
Let’s create a temporary FIFO file:
# mkfifo /tmp/fifo
On a different terminal, I will add a reader to this FIFO file using
tail command:
# tail -f /tmp/fifo
On a different terminal I will start this watcher program:
# go run main.go
Modified FIFO file: /tmp/fifo
Modified FIFO file: /tmp/fifo
Modified FIFO file: /tmp/fifo
Modified FIFO file: /tmp/fifo
Now from a different terminal we can add contents to our FIFO file and it should be visible to tail command output and the same event should be captured by our golang code.
Summary
We shared multiple examples using fsnotify to create a watcher service
which can watch different resources and report the event. We learned how
to monitor,
- single file
- symbolic link
- FIFO File
- single directory
- directory and files recursively
References
Go
language how detect file changing? - Stack Overflow
How
to listen to fifo wait for a fifo file’s output in golang
Watch
recursive directories in go-inotify - linux - Stack Overflow

![Golang watcher (fsnotify) Examples [In-Depth Tutorial]](/golang-watcher-fsnotify/golang-watcher.jpg)
