1. Overview

In this tutorial, we’ll learn how to use inotifywait to monitor a directory tree for changes. For instance, we’ll see how we can detect when a new file is created.

We’ll start with an introduction to the problem. Then, we’ll learn about inotify events. Finally, we’ll talk about the inotifywait command and how to use it to solve the problem.

2. Dependencies

Since our examples will be using the inotifywait command, we’ll want to make sure it is installed. If it isn’t installed, we can add it by installing the inotify-tools package. If we have a Debian distribution or similar, we can run the apt-get command as root to install the package:

$ apt-get install inotify-tools

On the other hand, if we have a Red Hat distribution or similar, we can use the yum command, but first, we have to install the epel-release repository:

$ yum install epel-release
$ yum install inotify-tools

3. Problem Introduction

Let’s start with a directory called main, which contains several subdirectories. With the tree command, we can see our main directory tree:

$ tree main/
main/
├── 1
│   ├── 1
│   └── 2
│       └── 1
├── 2
└── 3
    ├── 1
    └── 2
        └── 1
            └── 1

10 directories, 0 files

First, we’ll be monitoring this directory tree. For instance, we can know when a file is created or removed, opened, written, read, or closed. Then, we’ll use this information to run other programs when we detect an event on this directory tree. For instance, we can alert the user when a process deletes a file in this directory tree.

4. Understanding inotify Events

In Linux, we can use the inotify interface to monitor a directory or a file. We do this by adding a watch to the directory or file. When we add a watch to a file, we can monitor it. For example, we’ll know when a process opens, modifies, reads closes, moves, or deletes the file. When we add a watch to a directory, we also monitor for events in any file within that directory. Additionally, we can know if another process creates, deletes, or moves a file in that directory. We can choose to monitor for all events or to monitor only some of them. The system will notify us when any of those events happen.

5. Using inotifywait

We can use the inotifywait command to monitor a directory tree. To monitor a directory tree, including its subdirectories, we’ll use the -r parameter. Also, we have to use the -m parameter. This configures inotifywait to keep watching the directory forever. Otherwise, inotifywait exits after the first event. Let’s see how to monitor for any event in our main directory:

$ inotifywait -m -r main
Setting up watches. Beware: since -r was given, this may take a while!
Watches established.

With that, the inotifywait command will continue to run in the foreground, waiting for events. Now, while inotifywait is still running, let’s open a new shell and run echo example > main/1/2/file1. This command creates a new file inside the main/1/2 folder. Let’s return to the shell where we run inotifywait to see its output:

main/1/2/ CREATE file1
main/1/2/ OPEN file1
main/1/2/ MODIFY file1
main/1/2/ CLOSE_WRITE,CLOSE file1

We can see that the inotifywait output has three columns. First, the base directory, then the event, and finally, the file that triggered that event. Now, let’s create a new folder called 1 inside the main/2 directory. Then, we’ll create a new file called file1 inside the new directory:

$ mkdir main/2/1
$ echo example > main/2/1/file1

Let’s see the inotifywait output:

main/2/ CREATE,ISDIR 1
main/2/ OPEN,ISDIR 1
main/2/ ACCESS,ISDIR 1
main/2/ CLOSE_NOWRITE,CLOSE,ISDIR 1
main/2/1/ CREATE file1
main/2/1/ OPEN file1
main/2/1/ MODIFY file1
main/2/1/ CLOSE_WRITE,CLOSE file1

We can notice that inotifywait automatically watches for events in the new directory. Finally, let’s see how we can specify the exact events we want to monitor. To do this, we’ll use the -e parameter and add the desired events separated by commas. First, let’s finish the previous inotifywait command with Control+C. Then, let’s monitor only for create and modify events:

$ inotifywait -m -r -e create,modify main

Now, let’s write to the main/1/2/file1 file and then create a new empty file called file2 inside the main/1/2 directory:

$ echo example2 >> main/1/2/file1
$ touch main/1/2/file2

Let’s see the new output:

main/1/2/ MODIFY file1
main/1/2/ CREATE file2

6. Integrating the inotifywait Output With Another Script

So far, we have learned how to use inotifywait. So now, let’s create a script to run another program depending on the events inotifywait reports. First, let’s write a function that runs xmessage to inform the user that a file was removed:

$ file_removed() {
    xmessage "$2 was removed from $1" &
}

This function receives two parameters. The first one is the directory, and the other is the file that was removed. Also, we’ll run xmessage in the background, so the function doesn’t block. Then, let’s write two functions to write to a log file when a file is modified and created:

$ file_modified() {
    TIMESTAMP=`date`
    echo "[$TIMESTAMP]: The file $1$2 was modified" >> monitor_log
}
$ file_created() {
    TIMESTAMP=`date`
    echo "[$TIMESTAMP]: The file $1$2 was created" >> monitor_log
}

Now, we can parse the inotifywait output feeding it to a while loop. We’ll split the directory, event, and file field from the output using the read command. Next, we can use the case command to decide which function to call depending on the event. Additionally, we’ll add the -q parameter to the inotifywait command so that inotifywait only prints the events without printing the initial message. Let’s put all of this together in a script called monitor_directory_tree.sh. This script receives the directory to monitor as the first parameter. Then, it calls the corresponding function when inotifywait reports an event:

#!/bin/bash

file_removed() {
    xmessage "$2 was removed from $1" &
}

file_modified() {
    TIMESTAMP=`date`
    echo "[$TIMESTAMP]: The file $1$2 was modified" >> monitor_log
}

file_created() {
    TIMESTAMP=`date`
    echo "[$TIMESTAMP]: The file $1$2 was created" >> monitor_log
}

inotifywait -q -m -r -e modify,delete,create $1 | while read DIRECTORY EVENT FILE; do
    case $EVENT in
        MODIFY*)
            file_modified "$DIRECTORY" "$FILE"
            ;;
        CREATE*)
            file_created "$DIRECTORY" "$FILE"
            ;;
        DELETE*)
            file_removed "$DIRECTORY" "$FILE"
            ;;
    esac
done

Let’s test this script by first running monitor_directory_tree.sh in the background. Then we’ll remove the main/3/2/1 directory. Next, we’ll write some text to the main/1/2/file1 file. Finally, we’ll create and write to the main/file1 file:

$ ./monitor_directory_tree.sh main &
[1] 4800
$ rm -r main/3/2/1
$ echo example >> main/1/2/file1
$ echo example >> main/file1

With this, we can notice that two xmessage alerts appeared. One alert says “1 was removed from main/3/2/1” and the other alert says “1 was removed from main/3/2”. This is because when we run rm -r main/3/2/1, it also removed the main/3/2/1/1 subdirectory. Now, let’s look at the monitor_log file:

$ cat monitor_log
[Tue Sep 21 21:49:31 -03 2021]: The file main/1/2/file1 was modified
[Wed Sep 21 21:49:34 -03 2021]: The file main/file1 was created
[Wed Sep 21 21:49:34 -03 2021]: The file main/file1 was modified

We can see, the log shows that we modified main/1/2/file1. Then it shows that the file main/file1 was created and modified, just as we did.

7. Conclusion

In this article, we saw how we could monitor a directory tree. We first saw an introduction to the inotify interface. Then, we saw how we could use inotifywait to watch for events in the directory and its subdirectories. And finally, we learned how to integrate the inotifywait output with other scripts.

Comments are closed on this article!