2

So as the title describes, I want to recursively delete all files which match a naming pattern given by the user, but only if the file is empty. Here is my attempt:

#!/bin/bash

_files="$1"
[ $# -eq 0 ] && { echo "Usage: $0 filename"; exit 1; }
[ ! -f "$_files" ] && { echo "Error: $0 no files were found which match given naming structure."; exit 2; }
for f in $(find -name $_files)
do

    if [ -s "$f" ] 
    then
        echo "$f has some data."
            # do something as file has data
    else
        echo "$f is empty. Deleting file."
        rm $f
    fi
done

Example output:

./remove_blank.sh *.o*
./Disp_variations_higher_0.o1906168 has some data.
./remove_blank.sh *.e*
./Disp_variations_higher_15.e1906183 is empty. Deleting file.

As you can see, the code works, but only for one file at a time. Should be a relatively simple fix to get it to work, but I extremely new to bash scripting and can't seem to figure it out. Sorry for the noobish question. I did some research to find an answer but didn't find exactly what I needed. Thanks in advance for any help.

Edit I have found two different solutions to the problem. As @David Z's suggestion, one can fix this by 1st deleting the Error checking part of the script as well as putting quotes around the $_files variable in the find function. Then the code looks like this:

#!/bin/bash
_files=$1
[ $# -eq 0 ] && { echo "Usage: $0 filename"; exit 1; }
for f in $(find -name "$_files")

do

    if [ -s $f ] 
    then
        echo "$f  has some data."
        # do something as file has data
    else
        echo "$f is empty. Deleting file."
        rm $f
    fi

done

Or, one can also simply change the for loop to for f in "$@", which allows the error check to be kept in the script. I am not sure which method is better but will update again if I find out.

cwm5412
  • 87
  • 7

1 Answers1

2

It looks like the way you're invoking the script, the shell expands the pattern before running your script. For example, in

./remove_blank.sh *.o*

the shell converts *.o* to the list of filenames that match that pattern, and then what it actually runs is something like

./remove_blank.sh Disp_variations_higher_0.o1906168 other_file.o12345 ...

But you only check the first argument in your script, so that's the one that winds up getting deleted.

Solution: quote the pattern when you run the script.

./remove_blank.sh '*.o*'

You will also need to remove the test [ ! -f "$_files" ] ... because $_files is being set to the pattern (such as *.o*), not a filename. In fact, you might want to rename the variable to make that clear. And finally, you need to quote the variable in your find command,

... $(find -name "$_files") ...

so that the pattern makes it all the way through to find without being converted into filenames by the shell.

There are some other issues with the script but you might want to ask about those on Code Review. What I've identified here is just the minimum needed to get it working.


Incidentally, you can accomplish this whole task using find itself, which will at least give cleaner code:

find -name "$_files" -type f -empty -delete
David Z
  • 128,184
  • 27
  • 255
  • 279
  • I tried your suggestion but it throws the error message: Error: ./remove_blank.sh no files were found which match given naming structure. From input: ./remove_blank.sh '*.o*' And yes, I made sure that there are *.o* files in the directory I am running the script. – cwm5412 Sep 28 '17 at 09:22
  • Ah, right, I'll update to clarify about that. – David Z Sep 28 '17 at 09:28
  • That did the trick David, thanks for that. However I did find a substitute which allows me to keep the error check. I simply replace the `for f in $(find -name "$_files")` line with `for f in "$@"` as per the suggestion of this article: https://stackoverflow.com/questions/19458104/how-do-i-pass-a-wildcard-parameter-to-a-bash-file Are there any pro/con trade-offs for the two methods? – cwm5412 Sep 28 '17 at 09:48