1

In my root directory, I have a couple folders named something like AA, BB, CC etc., each containing files in the format AA1001.txt, BB1002.txt etc. In my root folder, I also have a file all_to_delete which has a bunch of file names separated with newlines, thus looking something like this:

AA1004.txt

BB3004.txt

BB3005.txt ...

I now want to go through all subdirs in my root directory and delete all files that match the given filename. Until now, I have tried something like:

while read line; do find . -type f -name $line -exec rm -f {} \;; done Though, this cannot work as already while read line; do find . -type f -name $line; done does not match any file (as the find gives its output as ./AA/AA1001.txt ...)

Do you guys have a solution for me?

3 Answers 3

1

Given a file with filenames, the easiest thing to do would be to read it line by line, and pass it to find. However, this will result in a separate instance of find for each file name and can become very slow for large lists of files and many files in a directory tree.

Instead, I would do something like this:

find . -type f -name "*txt" | grep -wFf to_delete.txt | xargs -I{} rm '{}'

The trick is to give grep your file as a list of patterns to search for (-f). The -F makes sure your filenames are treated as strings and not regular expressions, that way your file names ca contain special characters like * or [ or |. You then pass to xargs and use quoted '{}', otherwise it fails on white space, to delete the files.

NOTE: This assumes that your file names are all unique, that one name cannot be contained in another. For example, that you don't have files called foo and foobar. If you do, given a pattern foo, this will delete both files. To avoid this use:

while IFS= read -r line; do find . -name "$line" -delete; done < to_delete.txt 

From man find:

   -delete
          Delete files; true if removal succeeded. 
4
  • I had a comment about overlapping filenames all set to go, when I saw your edit. As with all the other solutions using the find command, this one will fail if any of the filenames (specifically, any of the lines in to_delete.txt) contains a wildcard character: *, ?, or []. Jan 29, 2014 at 0:09
  • @Scott quite right about overlapping, I noticed just after posting so deleted the answer while I was editing. As for wildcards, both solutions deal with them correctly (I tested with *). Perhaps you missed the F option to grep. I'll edit and make that clearer. Thanks for the edit by the way, my n key is problematic.
    – terdon
    Jan 29, 2014 at 0:17
  • It’s true that I glossed over the -F, and, in retrospect, “all the other solutions using the find command” was too broad, but perhaps you missed my point. In the pathological case where you have a file named *, what happens if you do find . –name "*" …? Jan 29, 2014 at 0:24
  • @Scott ah, in that case, my first solution works fine, my second fails and deletes everything. Fair enough :).
    – terdon
    Jan 29, 2014 at 0:28
1

How literal is your description of your problem?  Are the directory names all two characters?  Will file BB3004.txt always be in directory BB?  If yes, then you don’t need find; just extract the directory name from the first two characters of the file name:

while read -r line
do
    dir=$(expr "$line" : '\(..\)')
    echo rm "$dir/$line"
    rm "$dir/$line"
done < all_to_delete
4
  • +1. I added -r to let it deal with backslashes but you realize that if there is a file called * it gets expanded to /* right? If you're lucky, you'll get an error message :).
    – terdon
    Jan 29, 2014 at 0:40
  • Yes, they are completely literal. Cool, I didn't know about this kind of substring extraction!
    – conipo
    Jan 29, 2014 at 10:15
  • Well, I guess it’s my turn to thank you for the edit. It took me a few minutes to figure out what you mean; namely, if “line” (filename) is ever only one character long, “dir” gets set to null (because expr fails, because there aren’t a first two characters). OK, true, that would be a good thing to test for. But note that I’m working under the assumption that, as in the examples in the question, all filenames are at least two characters long. And even if a * gets in there, all it will do is try to remove a file called /* -- since it’s quoted, it won’t be expanded. Jan 29, 2014 at 23:37
  • Now something I just discovered and don’t 100% understand is that expr fails (with an error message) if the first argument is -- (two minus signs). But I do know how to fix that: expr X"$x" : X'\(..\)'. Jan 29, 2014 at 23:37
1

There are many different ways to accomplish that, but here's how I'd do it:

Created a test dir with a few files in it:

% ls ./teste
lala  lele  lolo  lulu

Created a file listing the ones I'd like to delete:

% cat to_delete.txt 
lele
lolo
lulu

Here I loop trough each line of the 'to_delete.txt' file passing the file name to the find command and then finally removing them:

% while read filename; do find ./teste -name ${filename} -print0 | xargs -0 rm -vf; done < to_delete.txt 
removed `./teste/lele'
removed `./teste/lolo'
removed `./teste/lulu'

Done:

% ls ./teste                                                                                      
lala
5
  • Nice, but don't use for loops for file names. They fail if the name contains a space. The while loop is the correct solution.
    – terdon
    Jan 28, 2014 at 23:41
  • @terdon, you are correct. The for loop goes nuts when there are spaces in the target file name. Totally unexpected! I've removed it in favor of the less fragile 'while read'.
    – lh0n42
    Jan 29, 2014 at 0:41
  • AH, now I can upvote with a clear conscience :)
    – terdon
    Jan 29, 2014 at 0:42
  • Are the {} around filename needed? This works as well. What is the difference though when using -print0 and xargs -0 as opposed to leaving -print0 and -0 out?
    – conipo
    Jan 29, 2014 at 10:25
  • @Jonathan: The curly brackets {} are not necessary in this example, however I consider always using it a good coding practice. If you leave print0 and -0 out, the rm command will fail if the file names contain spaces or other special characters. You can find out more about those options at their respective man pages[123]. [1] man7.org/linux/man-pages/man1/bash.1.html#EXPANSION - [2] man7.org/linux/man-pages/man1/find.1.html#EXPRESSIONS - [3] man7.org/linux/man-pages/man1/xargs.1.html#OPTIONS
    – lh0n42
    Jan 29, 2014 at 11:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .