8

I am trying to make a script that detects if any of the files in a directory were changed within a 2 seconds interval. What I have so far is:

#!/bin/bash
for FILE in "${PWD}/*"
do
    SUM1="$(md5sum $FILE)"
    sleep 2
    SUM2="$(md5sum $FILE)"
    if [ "$SUM1" = "$SUM2" ];
    then
        echo "Identical"
    else
        echo "Different"
    fi
done

This outputs only one Time the value "Identical", I want it to check each file and output "Identical" or "Different" for each file.

EDIT: Can this be done without installing the inotify-tools package?

9

As others have explained, using inotify is the better solution. I'll just explain why your script fails. First of all, no matter what language you are programming in, whenever you try to debug something, the first rule is "print all the variables":

$ ls
file1  file2  file3
$ echo $PWD    
/home/terdon/foo
$ for FILE in "${PWD}/*"; do echo "$FILE"; done
/home/terdon/foo/*

So, as you can see above, $FILE is actually expanded to $PWD/*. Therefore, the loop is only run once on the string /home/terdon/foo/* and not on each of the files in the directory individually. Then, the md5sum command becomes:

md5sum /home/terdon/foo/*

In other words, it runs md5sum on all files in the target directory at once and not on each of them.

The problem is that you are quoting your glob expansion and that stops it from being expanded:

$ echo "*"
*
$ echo *
file1 file2 file3

While variables should almost always be quoted, globs shouldn't since that makes them into strings instead of globs.

What you meant to do is:

for FILE in "${PWD}"/*; do ...

However, there is no reason to use $PWD here, it's not adding anything useful. The line above is equivalent to:

for FILE in *; do

Also, avoid using CAPITAL letters for shell variables. Those are used for the system-set environmental variables and it is better to keep your own variables in lower case.

With all this in mind, here's a working, improved version of your script:

#!/bin/bash
for file in *
do
    sum1="$(md5sum "$file")"
    sleep 2
    sum2="$(md5sum "$file")"
    if [ "$sum1" = "$sum2" ];
    then
        echo "Identical"
    else
        echo "Different"
    fi
done
  • While for FILE in "${PWD}"/*; do works on the same set as for FILE in *; do it is not exactly equivalent because the latter one does not include the pathnames. – Lambert May 18 '16 at 10:40
  • 1
    @Lambert true, but it makes no difference here since, by definition, the script will be run from $PWD – terdon May 18 '16 at 10:52
7

You can use inotify-tools definitely from command line, e.g. like this :

inotifywait -r  -m /dir/to/monitor/

From man inotifywait

-m, --monitor

Instead of exiting after receiving a single event, execute indefinitely. The default behaviour is to exit after the first event occurs.

And here is a script that monitors continously, copied from the man file of inotifywait :

#!/bin/sh
while inotifywait -e modify /var/log/messages; do
  if tail -n1 /var/log/messages | grep apache; then
    kdialog --msgbox "Blah blah Apache"
  fi
done
5

You can use the inotify-tools package to monitor all changes in a folder in real time. For example, it contains the inotifywait tool, which you could use like :

> inotifywait /tmp
Setting up watches.
Watches established.
/tmp/ MODIFY test

You can use flags to filter certain events only or certain files. The inotifywatch tool collects filesystem usage statistics and outputs counts of each inotify event.

You can find more examples here for instance.

If you want to monitor with other tools, you can use find with the -mmin parameter (modified minutes). Since 2 seconds is like 0.033 minutes, you could use :

find . -type f -mmin 0.033
1

If you want to monitor on a two second interval you can surround your check with:

while true
do
    <your steps>
    sleep 2
done

While this will sequentially test for files and will wait 2 seconds for each file found I suggest to transform your check into a function:

function _check_file()
{
    SUM1=$(md5sum "$@")
    sleep 2
    SUM2=$(md5sum "$@")
    if [ "$SUM1" == "$SUM2" ];
    then
        echo "$@: Identical"
    else
        echo "$@: Different"
    fi
}

Which can be used in the while loop:

while true
do
    for FILE in "${PWD}/"*
    do
        if [ -f "$FILE" ]
        then
            _check_file "$FILE" &
        fi
    done
    sleep 2
done

Please note the & ampersand to perform the checks in the background to perform the file checks in parallel. Note that this may impact on performance depending on the number of files found in the directory.

Also note that I changed the echo lines to include the filename ("$@") to visualize which file is found identical/different.

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.