How to recursively delete files of a specific type

I am reading the gzip documentation incorrectly and now I need to delete a ton of ".gz" files from many directories inside each other. I tried to use "find" to find all .gz files. However, whenever there is a file with a space in the name, rm will interpret it as another file. And whenever a dash appears, rm interprets that as a new flag. I decided to use "sed" to replace spaces "\" and spaces with "\ -" and this is what I came up with.

find . -type f -name '*.gz' | sed -r 's/\ /\\ /g' | sed -r 's/\ -/ \\-/g'

      

When I run a find / sed query on a file named "Test-File-for-show.gz" for example, I get the output

./Test\ \-\ File\ \-\ for\ \-\ show.gz

      

Which seems to be acceptable for rm, but when I run

rm $(find . -type f -name '*.gz'...)

      

I get

rm: cannot remove './Test\\': No such file or directory
rm: cannot remove '\\-\\': No such file or directory
rm: cannot remove 'File\\': No such file or directory
rm: cannot remove '\\-\\': No such file or directory
...

      

I have not used sed extensively, so I have to assume that I am doing something wrong with regexes. If you know what I am doing wrong or if you have a better solution please let me know.

+3


source to share


2 answers


Adding backslashes before spaces protects spaces from expansion in the shell source code. But the command output in command substitution does not go through shell parsing, it only undergoes wildcard expansion and field splitting. Adding backslashes before spaces does not protect them from field splitting.

Adding backslashes before the dash is completely useless, as it rm

interprets the dash as special and does not interpret the backslashes as special.

The output find

is generally ambiguous: filenames can contain newlines, so you cannot use a newline as the filename separator. Parsing the output will find

usually break if you are not dealing with filenames in a known limited character set, and this is often not the easiest way.

find

has a built-in way to execute external programs: action -exec

. There is no parsing, so it is not subject to any problem with special characters in filenames. (A path that begins with -

can still be interpreted as an option, but all paths begin with .

as this path goes through a directory.)



find . -type f -name '*.gz' -exec rm {} +

      

Many implementations find

(Linux, Cygwin, BSD) can delete files without calling an external utility:

find . -type f -name '*.gz' -delete

      

See Why is my shell script choking on spaces or other special characters? for more information on writing robust shell scripts.

+7


source


No need to connect to sed etc. Instead, you can use the -exec

on flag find

, which allows you to execute a command for each of the command results.

For example, for your case, this will work:

find . -type f -name '*.gz' -exec rm {} \;

      

which is roughly equal to:

find . -type f -name '*.gz' -exec rm {} +

      



The latter does not open a subshell for each result, which makes it faster.


From man find

:

-exec command

Run the command; true if 0 is returned. All of the following arguments to search are considered command arguments as long as the argument consisting of ;' is encountered. The string

{} 'equals is replaced by the current filename, processed in all its place in command arguments, not just arguments where it is as in some search options. Both of these constructs may need to be escaped (with `\ ') or quoted to protect them from shell expansion. See the EXAMPLES section for examples of using the -exec option. The specified command runs once for each associated file. The command is executed in the start directory. There are unavoidable security problems associated with using the -exec action; you should use the -execdir option.

+1


source







All Articles