18

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?

Pablo A
  • 2,712
dasj19
  • 625

6 Answers6

22

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 behavior is to exit after the first event occurs.

And here is a script that monitors continuously, 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
Rahul
  • 13,589
12

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
terdon
  • 242,166
  • 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
  • 1
    It would be a good idea to use md5sum -- "$file" instead of md5sum "$file" to handle the case where a file begins with a -. Of course you should also make your implementation of md5sum supports the -- end of options delimiter. – Harold Fischer Dec 28 '19 at 01:44
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.

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
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
magor
  • 3,752
  • 2
  • 13
  • 28
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.

Lambert
  • 12,680
1
#!/bin/bash
# pass one or more folders as arguments
while true; do
  for f in "$@"; do
    date
    echo "Checking $f and subfolders"
    find=$(find "$f" -type f)
    while read -r f2; do
      # strip non-alphanumeric from filename for a variable var name
      v=${f2//[^[:alnum:]]/}
      r=$(md5sum "$f2")
      if [ "$r" = "${!v}" ]; then
        echo "Identical $f2"
      else
        echo "Different $f2"
      fi
      eval "${v}=\$r"
    done <<< "$find"
  done
  sleep 2
done
dw1
  • 151
  • A nice solution. But it will fail when restarted. All old files will be recognized as different files. Maybe this good use a counter. when count=0 then it's started the very first time. – Juergen Schulze Mar 01 '23 at 10:39
  • 1
    right now you are stripping numbers as well from filename. This leads to trouble when names only differs in numbers. it must be [:alnum:] not [:alpha:] – Juergen Schulze Mar 01 '23 at 13:42
0

I changed @dw1 version to not detect old files as new files when script is running the very first time.

#!/bin/bash
# pass one or more folders as arguments
i=0 # to check if watcher is run the first time to falsefully detect unchanged files
while true; do
    for f in "$@"; do
        date
        echo "Checking $f and subfolders"
        if [ -z "$(ls -A "$f")" ]; then
            continue # skip if folder is empty to prevent error
        fi
        find=$(find "$f" -type f)
        while read -r f2; do
            # strip non-alphanumeric from filename for a variable var name
            v=${f2//[^[:alnum:]]/}
            r=$(md5sum "$f2")
            if [ "$i" = "1" ]; then
                if [ "$r" = "${!v}" ]; then
                    echo "Identical $f2"
                else
                    echo "Different $f2"
                fi
            fi
            eval "${v}=\$r"
        done <<< "$find"
    done
    sleep 2
    i=1;
done