1

I'm doing a script in Bash which aims to clean all the junk files I may have in a directory. I created an array with all the file patterns that I want to remove.

Example

Imagine that I have a dir called temp and inside it I have files which match the patterns in the array below

fileJunk=(".inca*" ".cat*" "*Cat%" "*inca*")

Then I do a find for each one of the items as below

for j in "${fileJunk[@]}"
do
    myList=( `find temp -maxdepth 2 -type f -iname $j` )
    for z in "${myList[@]}"
    do
        rm $z >& /dev/null      
        if [ $? -eq 0 ]
        then
            echo -e "SUCCESS: File $j was deleted\n"
        fi
    done        
done

The problem is that the find command finds all the files except the ones that inca should match.

Doing this directly on the command line worked only if I enclosed *inca* with double quotes. So I've changed the array to:

fileJunk=(".inca*" ".cat*" "*Cat%" "\"*inca*\"")

This way I'm passing "*inca*" to the find command instead of *inca*. However this only works on the command line, not using the script?

More info Just to be clear my question, by the end, is: why when I use the command find in the shell with "*inca*" it works and within the script doesn't ?

  • Instead of adding the quotes in your array, what happens if you quote it in the find command? i.e. `find temp -maxdepth 2 -type f -iname "$j"` – Steven W. Klassen Sep 03 '18 at 15:12

2 Answers2

2

When working with variables in Bash you should almost always put any in double quotes. I'd change your code around like this:

for j in "${fileJunk[@]}"
do
    myList=( $(find temp -maxdepth 2 -type f -iname "$j") )
    for z in "${myList[@]}"
    do
        rm -f "$z" >& /dev/null      
        if [ $? -eq 0 ]
        then
            echo -e "SUCCESS: File $j was deleted\n"
        fi
    done
done

For example, if we were to pull out the first for loop and run it, echoing out the find command:

$ fileJunk=(".inca*" ".cat*" "*Cat%" "*inca*")
$ for j in "${fileJunk[@]}";do echo "find temp -maxdepth 2 -type f -iname \"$j\""; done
find temp -maxdepth 2 -type f -iname ".inca*"
find temp -maxdepth 2 -type f -iname ".cat*"
find temp -maxdepth 2 -type f -iname "*Cat%"
find temp -maxdepth 2 -type f -iname "*inca*"

These are the forms of the find command that you want to be running here.

Debugging Bash

If you're still encountering issues even with the quotes, I'd enable your script so that it's verbose. You can do this by setting set -x.

For example:

$ set -x; for j in "${fileJunk[@]}";do find /tmp -maxdepth 2 -type f -iname "$j"; done; set +x
+ set -x
+ for j in '"${fileJunk[@]}"'
+ find /tmp -maxdepth 2 -type f -iname '.inca*'
+ for j in '"${fileJunk[@]}"'
+ find /tmp -maxdepth 2 -type f -iname '.cat*'
+ for j in '"${fileJunk[@]}"'
+ find /tmp -maxdepth 2 -type f -iname '*Cat%'
+ for j in '"${fileJunk[@]}"'
+ find /tmp -maxdepth 2 -type f -iname '*inca*'
+ set +x
slm
  • 15,396
  • 12
  • 109
  • 124
  • There is no sane reason to capture the `find` output to a variable and then loop over the variable. – tripleee Sep 04 '18 at 04:37
  • But yeah, +1 for https://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-shell-variable – tripleee Sep 04 '18 at 04:45
  • @tripleee - I'm showing the OP how to do it since that's what was asked. That would be one sane reason 8-). – slm Sep 04 '18 at 04:46
  • @triplee: I am not insane, I am just a beginner with bash scripting who doesn't know yet all the tricks behind it ;-) Thanks for the link, it was quite useful. – Pedro Cardoso Sep 04 '18 at 09:13
  • No offense, it's a common antipattern to use a variable for things you don't actually need to store – tripleee Sep 04 '18 at 09:32
  • The OP attempted to edit this answer, I guess the following was meant as a comment. *"pcardoso73: Adding double quotes didn't solve the problem for the last one. (inca) :-)"* – slm Sep 04 '18 at 11:17
1

You want to avoid running find multiple times over a potentially large directory tree.

Running find in parentheses will cause the shell to attempt to parse its output into tokens. This will fail if find finds filenames which contain spaces or newlines.

Suppressing the output from rm and then printing your own output message seems misdirected. If removing a file fails, you want to know why it failed. If you also want a message when it succeeds, use -v.

find temp -maxdepth 2 -type f \(
    -iname ".inca*" -o -iname ".cat*" -o \
    -iname "*Cat%" -o -iname "*inca*" \) \
    -exec rm -f -v {} +

If you want to generate the find command from an array of expressions, try

inames=()
joiner='('
for j in "${fileJunk[@]}"; do
    inames+=($joiner "-iname" "$j")
    joiner='-o'
inames+=(')')
find temp -maxdepth 2 -type f "${inames[@]}" \
    -exec rm -f -v {} +

You will notice how quoting the value of "$j" is required in order to prevent the shell from expanding it, which incidentally is also the answer to your original acute question. See also When to wrap quotes around a shell variable?

As an aside, the shell's flow control statements already examine $? behind the scenes for you, so you should almost never need to examine it explicitly yourself. Anything which looks like

command
if [ $? -eq 0 ]; then

is better written

if command; then

because the very purpose of if and its friends while and until is to run a command and examine its exit status.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • The OP attempted to edit this answer; I guess the following was meant as a comment. *'When I created a list it was with the goal for the user to be able to change it easily. Looping the find command doesn't give me any problem except for "inca" and the real list is double the size of the example one.'"* – tripleee Sep 04 '18 at 09:33