35

I have seen this answer.

You should consider using inotifywait, as an example:

inotifywait -m /path -e create -e moved_to |
    while read path action file; do
        echo "The file '$file' appeared in directory '$path' via '$action'"
        # do something with the file
    done

My question is that, the above script watches a directory for creation of files of any type, but how do I modify the inotifywait command to report only when a file of certain type/extension is created (or moved into the directory) - e.g. it should report when any .xml file is created.

WHAT I TRIED:

I have run the inotifywait --help command, and have read the command line options. It has --exclude <pattern> and --excludei <pattern> commands to EXCLUDE files of certain types (by using regEx), but I need a way to INCLUDE just the files of a certain type/extension.

3
  • BTW, using path above is probably not the best variable name if you want to play with this first in a shell. If you use it, then you will not be able to use commands and everything since this has basically just overridden the standard PATH. Hence I recommend using an alternative var name, like fpath instead. i.e. while read fpath action file then any standard command usually available from your shell will still be. Oct 9 '18 at 9:17
  • it is kind of ridiculous that there isn't an include option.
    – erandros
    Sep 9 '19 at 18:12
  • They finally added an --include option, as per @ericcurtin's answer below. Jun 28 '20 at 15:43
29

how do I modify the inotifywait command to report only when a file of certain type/extension is created

Please note that this is untested code since I don't have access to inotify right now. But something akin to this ought to work:

inotifywait -m /path -e create -e moved_to |
    while read path action file; do
        if [[ "$file" =~ .*xml$ ]]; then # Does the file end with .xml?
            echo "xml file" # If so, do your thing here!
        fi
    done
4
  • 3
    This does work and I have tested it with inotifywait. Apr 7 '17 at 19:42
  • 2
    How do you run this? Is it a one-time command, or does it need to be running in a loop or anything like that? Perhaps as part of incron (I hope not).
    – SDsolar
    Nov 20 '17 at 0:25
  • @SDsolar It varies. For me, I usually run inotify either with nohup or start it with a custom systemd service. Most often the latter. Sep 12 '18 at 14:25
  • what's the diff between inotify and inotifywait?
    – user359907
    Mar 4 '20 at 8:10
11

Whilst the double-negative approach of the previous answer is a nice idea since (as TMG noted) it does indeed shift the job of filtering to inotifywait, it is not correct.

For example, if a file ends in as then it will not match [^j][^s]$ because the final letter s does not match [^s], therefore it will not be excluded.

In Boolean terms, if S is the statement:

"the final letter is s"

and J is the statement:

"the penultimate letter is j"

then the value of the --exclude parameter should semantically equate to not(J and S), which by De Morgan's laws is not(J) or not(S).

Another potential problem is that in zsh, $path is a built-in variable representing the array equivalent of $PATH, so the while read path ... line will completely mess up $PATH and cause everything to become unexecutable from the shell.

Therefore the correct approach is:

inotifywait -m --exclude "[^j].$|[^s]$" /path -e create -e moved_to |
    while read dir action file; do
        echo "The file '$file' appeared in directory '$dir' via '$action'"
    done

Note the . which is needed after [^j] to ensure that the match is applied in the penultimate position, and also that the | character (representing the boolean OR mentioned above) should not be escaped here because --exclude takes POSIX extended regular expressions.

However, please see and upvote @ericcurtin's answer which for newer versions of inotifywait is a far cleaner approach.

2
  • What if your code is C code and you want to include .c and .h files
    – ericcurtin
    Jan 24 '20 at 15:12
  • --exclude '[^ch]$|[^.].$' should achieve that (although I haven't tested). Jan 26 '20 at 1:55
10

Use a double negative:

inotifywait -m --exclude "[^j][^s]$" /path -e create -e moved_to |
    while read path action file; do
        echo "The file '$file' appeared in directory '$path' via '$action'"
    done

This will only include javascript files

9
  • 3
    Nice, I like that it shifts the filtering to inotifywait rather than have it generate huge output for all files and filter afterwards (like in the other answer). Haven't tried, but I guess that would perform a bit faster.
    – TMG
    Mar 14 '18 at 9:18
  • This is just what I was looking for. How to modify the --exclude input to watch for .ext files. This is run only once or in a for loop? Jul 9 '18 at 4:26
  • It's a daemon, so it will run until it crashes or you shut it down :)
    – tofsjonas
    Jul 10 '18 at 9:30
  • Sorry, missed the first question. inotifywait -m --exclude "[^.][^e][^x][^t]$" /home/jonas/Skrivbord/test -e create -e moved_to | while read path action file; do echo "The file '$file' appeared in directory '$path' via '$action'" done I'm sure the regex can be optimized, but this baby works :)
    – tofsjonas
    Jul 10 '18 at 20:29
  • 2
    As @AdamSpiers notes, this answer is incorrect, because it would trigger on any file ending in jX or Xs (.ja, .jb, .jc… ..as, .bs. .cs…) Aug 25 '20 at 9:16
9

--include and --includei are actual options in the master version of the binaries at least:

https://github.com/inotify-tools/inotify-tools/tree/master

2
  • 1
    This is by far the best approach now, despite currently being lower-voted than the other answers. Jun 28 '20 at 15:45
  • 2
    Apparently, the include options are only available from version 3.20.1. Aug 25 '20 at 9:30
4

Starting with inotifywait version 3.20.1 you can use an --include option.

inotifywait example with --include

Here's an example that uses --include to run a script when a wav file is added to a directory.

#!/bin/sh

# Usage
#
# Call the script with the directory you want to watch as an argument. e.g.:
# watchdir.sh /app/foo/
#
#
# Description:
# Uses inotifywait to look for new files in a directory and process them:
# inotifywait outputs data which is passed (piped) to a do for subcommands.
#
#
# Requirements:
# Requires inotifywait which is part of inotify-tools.
# e.g. yum install -y inotify-tools or apt-get install -y inotify-tools.
#
#
# See also:
# https://linux.die.net/man/1/inotifywait
# https://github.com/inotify-tools/inotify-tools/wiki#info
# (Might be interested in pyinotify too.)


echo "Watch $1 for file changes..."


# Be careful not to confuse the output of `inotifywait` with that of `echo`.
# e.g. missing a `\` would break the pipe and write inotifywait's output, not
# the echo's.
inotifywait \
  $1 \
  --monitor \
  -e create \
  --timefmt '%Y-%m-%dT%H:%M:%S' \
  --format '%T %w %f %e' \
  --include "\.wav" \
| while read datetime dir filename event; do
  echo "Event: $datetime $dir$file $event"
  echo "Now, we could pass $datetime $dir $filename and $event to some other command."

  echo "Here's the extensionless filename:" ${filename%.*}
  echo "And the extension if needed:" ${filename##*.}

  python3 /app/phono.py --custom $dir$filename $dir${filename%.*}.txt /tmp/${filename%.*}
done


# --format:
#   %T  Replaced with the current Time in the format specified by the --timefmt option, which should be a format string suitable for passing to strftime(3).
#   %w  This will be replaced with the name of the Watched file on which an event occurred.
#   %f  When an event occurs within a directory, this will be replaced with the name of the File which caused the event to occur. Otherwise, this will be replaced with an empty string.
#   %e  Replaced with the Event(s) which occurred, comma-separated.
#   %Xe Replaced with the Event(s) which occurred, separated by whichever character is in the place of 'X'.
#
# There's no --include option, but there's --exclude, which can be used to the same effect as an include.


# test the script by creating a file in the watched directory, e.g.
# touch /app/foo/file1.wav # will trigger the script
# touch /app/foo/file1.txt # will not trigger the script

Installing (or compiling) inotify-tools

inotifywait is part of inotify-tools. You can install it via the usual way, e.g.:

apt-get install -y inotify-tools

or

yum install -y inotify-tools

But if only older versions are available from the repos, you may want to compile from source. If so:

cd /tmp/inotify-tools/
wget https://github.com/inotify-tools/inotify-tools/releases/download/3.20.2.2/inotify-tools-3.20.2.2.tar.gz
tar xzvf inotify-tools-3.20.2.2.tar.gz
cd inotify-tools-3.20.2.2
./configure --prefix=/usr --libdir=/lib64
make
make install
0

In my case I was trying to filter the output of inotifywait using something like grep, in order to wait for the creation of a single file with a particular extension. The trouble with this approach is that grep will just hang.

For some reason a SIGPIPE signal is either not sent, or not processed by inotifywait. I found this question, which ran into the same issue I had, and process substitution can be used as a workaround:

grep -E .*iso -m1 < <(inotifywait -qm -e create .)

Process substitution does not work in certain shells (eg. sh), so this does not work in every case.


From the other answers given, I could not find the --include option. I will likely be experimenting with --exclude.

3
  • If your version doesn't have --include, it's too old and you should see if you can upgrade since if you can that will be the easiest solution by far. Aug 24 '20 at 16:17
  • I downloaded a brand new version, which doesn't have it. I see --include now in the source code though. I was originally looking at github.com/inotify-tools/inotify-tools/blob/master/man/… , which does not mention --include in the same way it mentions --exclude.
    – Matt
    Aug 24 '20 at 18:51
  • According to another answer on this page, you need at least 3.20.1. Aug 25 '20 at 19:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.