To implement a file watcher, we will utilize the fsnotify package, which provides a cross-platform way to watch for file system events. This tutorial will guide you through the installation of the package, setting up a basic file watcher, and handling different types of file events.

Prerequisites

  • Go installed on your machine (version 1.11 or higher).
  • Basic understanding of Go programming language.

Installation

First, create a new directory for your Go project and navigate into it:

mkdir file-watcher
cd file-watcher

Next, initialize a new Go module:

go mod init file-watcher

Now, install the fsnotify package:

go get github.com/fsnotify/fsnotify

Creating the File Watcher

Create a new file named main.go in your project directory:

package main

import (
    "fmt"
    "log"
    "github.com/fsnotify/fsnotify"
)

func main() {
    // Create a new file watcher
    watcher, err := fsnotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    defer watcher.Close()

    // Start a goroutine to listen for events
    go func() {
        for {
            select {
            case event, ok := <-watcher.Events:
                if !ok {
                    return
                }
                // Handle file events
                if event.Op&fsnotify.Write == fsnotify.Write {
                    fmt.Println("Modified file:", event.Name)
                }
                if event.Op&fsnotify.Create == fsnotify.Create {
                    fmt.Println("Created file:", event.Name)
                }
                if event.Op&fsnotify.Remove == fsnotify.Remove {
                    fmt.Println("Deleted file:", event.Name)
                }
                if event.Op&fsnotify.Rename == fsnotify.Rename {
                    fmt.Println("Renamed file:", event.Name)
                }
            case err, ok := <-watcher.Errors:
                if !ok {
                    return
                }
                log.Println("Error:", err)
            }
        }
    }()

    // Add a directory to watch
    err = watcher.Add("./watched_directory")
    if err != nil {
        log.Fatal(err)
    }

    // Block forever
    <-make(chan struct{})
}

Explanation of the Code

  1. Imports: We import necessary packages, including fsnotify for file watching and log for error handling.
  2. Creating a Watcher: We create a new file watcher instance using fsnotify.NewWatcher().
  3. Goroutine for Event Listening: A goroutine listens for file system events and handles them appropriately. We check the type of event (write, create, remove, rename) and print a message to the console.
  4. Adding a Directory to Watch: We specify the directory we want to monitor. Make sure to create a directory named watched_directory in your project folder.
  5. Blocking the Main Goroutine: We use a channel to block the main goroutine indefinitely, allowing the watcher to continue running.

Running the File Watcher

To run the file watcher, execute the following command in your terminal:

go run main.go

Now, create a directory named watched_directory in your project folder:

mkdir watched_directory

You can now test the file watcher by creating, modifying, or deleting files within the watched_directory. For example, create a new file:

touch watched_directory/test.txt

You should see an output like:

Created file: watched_directory/test.txt

Handling File Events

The current implementation simply prints messages to the console. However, you can extend this functionality to perform more complex actions, such as:

  • Reloading configuration files.
  • Triggering builds or deployments.
  • Sending notifications.

To demonstrate this, let's modify the file watcher to execute a command when a file is created. You can use the os/exec package to run shell commands.

Add the following import statement at the beginning of your main.go:

import (
    "os/exec"
)

Then, modify the if event.Op&fsnotify.Create == fsnotify.Create section in the event handling goroutine:

if event.Op&fsnotify.Create == fsnotify.Create {
    fmt.Println("Created file:", event.Name)
    // Execute a command (for example, a shell command)
    cmd := exec.Command("echo", "A file was created!")
    err := cmd.Run()
    if err != nil {
        log.Println("Error executing command:", err)
    }
}

Now, when you create a file in the watched directory, the program will execute the echo command.

Conclusion

In this tutorial, we've built a simple file watcher in Go using the fsnotify package. We covered how to monitor a directory for file system events and respond to those events with appropriate actions. This file watcher can be further enhanced to suit more complex use cases.

Learn more with useful resources