In a small script to monitor a folder for new file

2019-06-05 12:26发布

问题:

I'm using this script to monitor the downloads folder for new .bin files being created. However, it doesn't seem to be working. If I remove the grep, I can make it copy any file created in the Downloads folder, but with the grep it's not working. I suspect the problem is how I'm trying to compare the two values, but I'm really not sure what to do.

#!/bin/sh

downloadDir="$HOME/Downloads/"
mbedDir="/media/mbed"

inotifywait -m --format %f -e create $downloadDir -q | \
while read line; do
    if [ $(ls $downloadDir -a1 | grep '[^.].*bin' | head -1) == $line ]; then
        cp "$downloadDir/$line" "$mbedDir/$line"
    fi
done

回答1:

The ls $downloadDir -a1 | grep '[^.].*bin' | head -1 is the wrong way to go about this. To see why, suppose you had files named a.txt and b.bin in the download directory, and then c.bin was added. inotifywait would print c.bin, ls would print a.txt\nb.bin\nc.bin (with actual newlines, not \n), grep would thin that to b.bin\nc.bin, head would remove all but the first line leaving b.bin, which would not match c.bin. You need to be checking $line to see if it ends in .bin, not scanning a directory listing. I'll give you three ways to do this:

First option, use grep to check $line, not the listing:

if echo "$line" | grep -q '[.]bin$'; then

Note that I'm using the -q option to supress grep's output, and instead simply letting the if command check its exit status (success if it found a match, failure if not). Also, the RE is anchored to the end of the line, and the period is in brackets so it'll only match an actual period (normally, . in a regular expression matches any single character). \.bin$ would also work here.

Second option, use the shell's ability to edit variable contents to see if $line ends in .bin:

if [ "${line%.bin}" != "$line" ]; then

the "${line%.bin}" part gives the value of $line with .bin trimmed from the end if it's there. If that's not the same as $line itself, then $line must've ended with .bin.

Third option, use bash's [[ ]] expression to do pattern matching directly:

if [[ "$line" == *.bin ]]; then

This is (IMHO) the simplest and clearest of the bunch, but it only works in bash (i.e. you must start the script with #!/bin/bash).

Other notes: to avoid some possible issues with whitespace and backslashes in filenames, use while IFS= read -r line; do and follow @shellter's recommendation about double-quotes religiously.

Also, I'm not very familiar with inotifywait, but AIUI its -e create option will notify you when the file is created, not when its contents are fully written out. Depending on the timing, you may wind up copying partially-written files.

Finally, you don't have any checking for duplicate filenames. What should happen if you download a file named foo.bin, it gets copied, you delete the original, then download a different file named foo.bin. As the script is now, it'll silently overwrite the first foo.bin. If this isn't what you want, you should add something like:

if [ ! -e "$mbedDir/$line" ]; then
    cp "$downloadDir/$line" "$mbedDir/$line"
elif ! cmp -s "$downloadDir/$line" "$mbedDir/$line"; then
    echo "Eeek, a duplicate filename!" >&2
    # or possibly something more constructive than that...
fi


标签: bash inotify