0

This question already has an answer here:

I have an interesting question, one that may, or may not be easy :( I've been trying to work out a way to do it integrating options of the 'find' command on linux.

Basically, I have a directory tree, within which could be multiple examples of the files that either need keeping, or deleting. All the files share the same extensions, for sake of argument .XX (although the idea would be to be as generic as possible so it could be any file). I want to delete all .XX files within the directory tree, EXCEPT where the files lie within a specific folder (will always be the same title, in the example called YYY).

Say I have to following structure (where each sub directory could end up containing many many many files that I don't want to touch or affect alongside those that I do):

folder_root:
|
|--> Subdir_1/interesting_file_1.XX
|--> Subdir_1/interesting_file_2.XX
|--> Subdir_1/unrelated_interesting_file_1.AA
|--> Subdir_2
|--> Subdir_3/interesting_file_3.XX
|--> YYY/interesting_file_4.XX
|--> YYY//interesting_file_5.XX

I want to be left with:

folder_root:
|
|--> Subdir_1/unrelated_interesting_file_1.AA
|--> YYY/interesting_file_4.XX
|--> YYY/interesting_file_5.XX

The note is, the directory YYY could be anywhere, and there could be many of them, so having a list of directory paths that need excluding to hand to build a large exclusion list isn't possible.

First I did a basic Find as such: find . -iname "*.XX". Then I looked at adding the -printf "%h\n" to output the directories that contain the .XX files. What I'm struggling to do, is take that outputted list, and use it to inform the process of deleting, or as the case may be, not deleting. I suppose I could use grep to remove any YYY folders from the output into a temp file, use a while read loop, then pushd and popd to move in, and then out of the folders, using a simple find . -iname "*.XX" -delete within each sub-directory (then using the -empty switch within find to clean up any empty directories left as a result). This does however, feel very blunt force, with a very large sledgehammer, that could be very system intensive, especially when dealing with potentially hundreds of sub-directories.

I'm interested if there is a 'better' way, that is slicker, generally less intensive overall and reliable (especially if you end up having to run it three or four times for three or four different file types)?

There may not be, but its worth a quick ask :) Why use a sledgehammer when a simple hammer would do!? :)

Final note, I cannot add additional shell commands to the system (and the systems used are a mix of Ubuntu and Centos), so where possible, it would need to use the pre-existing command set (assume no other modules are installed except Tree, which I do know about). I hope the question is clear, and 'generic' enough to be useful to others in different situations if a simple answer is found.

marked as duplicate by don_crissti, steeldriver, G-Man, X Tian, DarkHeart Mar 10 '18 at 12:36

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • I'm reading that thread, and i'm not gonna lie, I think I can see where it might be considered a duplication, although I'm not wanting to delete all files, simply one type of file. Based on the accepted answer, I'm not sure its clear how that would be augmented from 'all files' to 'only one type'.... – Owen Morgan Mar 9 '18 at 19:35
  • you replace -type f with \( -type f -name '*.XX' \) – don_crissti Mar 9 '18 at 19:57
0

I don't think you need pushd popd etc. because find produces full pathnames.

Any readline approach will have interesting results if a filename contains a carriage return followed by an asterisk.

The general solution is to generate a null-terminated list of all candidates with find, winnow with grep, execute with xargs:

find . -iname "*.XX" -print0 | grep -vzf keep_these.txt | xargs -0 echo rm -- # remove the echo to ACTUALLY delete files

"keep_these.txt" is a list of directories to exclude, one per line, including leading and trailing slashes, like:

/YYY/ 
/I love this directory/

If you don't need regex to identify your directories (just fixed strings), add -F to the grep.

  • Interestingly, when running that command, the xargs (which I've never seen before, or used), prints what effectly is the rm command to the terminal window, but doesn't actually do anything... however, If I copy the terminal output, and then run that, it does. not sure if that's the intention or not! Other than that, I think I know how this works, and its certainly neat and tidy! I'm gonna have to look into this again tomorrow :) what would this do if the output of the find / grep is blank? is that what the -r option is for in xargs? – Owen Morgan Mar 9 '18 at 20:01
  • Derp! I think its the addition of the 'echo' that's causing the above behaviour correct? – Owen Morgan Mar 9 '18 at 20:03
  • yes that is a safety measure. Remove the echo when you're happy with the output. If there is no output, you'll get an error message, like if you just ran "rm" by itself at the command line. As you've guessed, -r will suppress that error. Note also my edit, adding "--" to the rm command, which marks an explicit end to rm options, preventing filenames like -rf from being interpreted as options. – Oh My Goodness Mar 9 '18 at 22:01
0

This should work:

find folder_root/ -name Subdir_2 -prune -o -name Subdir_3 -prune -a -type f

If this finds the correct set of files, you can then add -delete to the end of the find command.

Not the answer you're looking for? Browse other questions tagged or ask your own question.