How to loop through a directory recursively to delete files with certain extensions

Bash

Bash Problem Overview


I need to loop through a directory recursively and remove all files with extension .pdf and .doc. I'm managing to loop through a directory recursively but not managing to filter the files with the above mentioned file extensions.

My code so far

#/bin/sh

SEARCH_FOLDER="/tmp/*"

for f in $SEARCH_FOLDER
do
	if [ -d "$f" ]
	then
		for ff in $f/*
		do		
			echo "Processing $ff"
		done
	else
		echo "Processing file $f"
	fi
done

I need help to complete the code, since I'm not getting anywhere.

Bash Solutions


Solution 1 - Bash

As a followup to mouviciel's answer, you could also do this as a for loop, instead of using xargs. I often find xargs cumbersome, especially if I need to do something more complicated in each iteration.

for f in $(find /tmp -name '*.pdf' -or -name '*.doc'); do rm $f; done

As a number of people have commented, this will fail if there are spaces in filenames. You can work around this by temporarily setting the IFS (internal field seperator) to the newline character. This also fails if there are wildcard characters \[?* in the file names. You can work around that by temporarily disabling wildcard expansion (globbing).

IFS=$'\n'; set -f
for f in $(find /tmp -name '*.pdf' -or -name '*.doc'); do rm "$f"; done
unset IFS; set +f

If you have newlines in your filenames, then that won't work either. You're better off with an xargs based solution:

find /tmp \( -name '*.pdf' -or -name '*.doc' \) -print0 | xargs -0 rm

(The escaped brackets are required here to have the -print0 apply to both or clauses.)

GNU and *BSD find also has a -delete action, which would look like this:

find /tmp \( -name '*.pdf' -or -name '*.doc' \) -delete

Solution 2 - Bash

find is just made for that.

find /tmp -name '*.pdf' -or -name '*.doc' | xargs rm

Solution 3 - Bash

Without find:

for f in /tmp/* tmp/**/* ; do
  ...
done;

/tmp/* are files in dir and /tmp/**/* are files in subfolders. It is possible that you have to enable globstar option (shopt -s globstar). So for the question the code should look like this:

shopt -s globstar
for f in /tmp/*.pdf /tmp/*.doc tmp/**/*.pdf tmp/**/*.doc ; do
  rm "$f"
done

Note that this requires bash ≥4.0 (or zsh without shopt -s globstar, or ksh with set -o globstar instead of shopt -s globstar). Furthermore, in bash <4.3, this traverses symbolic links to directories as well as directories, which is usually not desirable.

Solution 4 - Bash

If you want to do something recursively, I suggest you use recursion (yes, you can do it using stacks and so on, but hey).

recursiverm() {
  for d in *; do
    if [ -d "$d" ]; then
      (cd -- "$d" && recursiverm)
    fi
    rm -f *.pdf
    rm -f *.doc
  done
}

(cd /tmp; recursiverm)

That said, find is probably a better choice as has already been suggested.

Solution 5 - Bash

Here is an example using shell (bash):

#!/bin/bash

# loop & print a folder recusively,
print_folder_recurse() {
	for i in "$1"/*;do
		if [ -d "$i" ];then
			echo "dir: $i"
			print_folder_recurse "$i"
		elif [ -f "$i" ]; then
			echo "file: $i"
		fi
	done
}


# try get path from param
path=""
if [ -d "$1" ]; then
	path=$1;
else
	path="/tmp"
fi

echo "base path: $path"
print_folder_recurse $path

Solution 6 - Bash

This doesn't answer your question directly, but you can solve your problem with a one-liner:

find /tmp \( -name "*.pdf" -o -name "*.doc" \) -type f -exec rm {} +

Some versions of find (GNU, BSD) have a -delete action which you can use instead of calling rm:

find /tmp \( -name "*.pdf" -o -name "*.doc" \) -type f -delete

Solution 7 - Bash

For bash (since version 4.0):

shopt -s globstar nullglob dotglob
echo **/*".ext"

That's all.
The trailing extension ".ext" there to select files (or dirs) with that extension.

Option globstar activates the ** (search recursivelly).
Option nullglob removes an * when it matches no file/dir.
Option dotglob includes files that start wit a dot (hidden files).

Beware that before bash 4.3, **/ also traverses symbolic links to directories which is not desirable.

Solution 8 - Bash

This method handles spaces well.

files="$(find -L "$dir" -type f)"
echo "Count: $(echo -n "$files" | wc -l)"
echo "$files" | while read file; do
  echo "$file"
done

Edit, fixes off-by-one

function count() {
    files="$(find -L "$1" -type f)";
    if [[ "$files" == "" ]]; then
        echo "No files";
        return 0;
    fi
    file_count=$(echo "$files" | wc -l)
    echo "Count: $file_count"
    echo "$files" | while read file; do
        echo "$file"
    done
}

Solution 9 - Bash

The following function would recursively iterate through all the directories in the \home\ubuntu directory( whole directory structure under ubuntu ) and apply the necessary checks in else block.

function check {
        for file in $1/*      
        do
        if [ -d "$file" ]
        then
                check $file                          
        else
               ##check for the file
               if [ $(head -c 4 "$file") = "%PDF" ]; then
                         rm -r $file
               fi
        fi
        done     
}
domain=/home/ubuntu
check $domain

Solution 10 - Bash

There is no reason to pipe the output of find into another utility. find has a -delete flag built into it.

find /tmp -name '*.pdf' -or -name '*.doc' -delete

Solution 11 - Bash

This is the simplest way I know to do this: rm **/@(*.doc|*.pdf)

** makes this work recursively

@(*.doc|*.pdf) looks for a file ending in pdf OR doc

Easy to safely test by replacing rm with ls

Solution 12 - Bash

The other answers provided will not include files or directories that start with a . the following worked for me:

#/bin/sh
getAll()
{
  local fl1="$1"/*;
  local fl2="$1"/.[!.]*; 
  local fl3="$1"/..?*;
  for inpath in "$1"/* "$1"/.[!.]* "$1"/..?*; do
    if [ "$inpath" != "$fl1" -a "$inpath" != "$fl2" -a "$inpath" != "$fl3" ]; then 
      stat --printf="%F\0%n\0\n" -- "$inpath";
      if [ -d "$inpath" ]; then
        getAll "$inpath"
      #elif [ -f $inpath ]; then
      fi;
    fi;
  done;
}

Solution 13 - Bash

I think the most straightforward solution is to use recursion, in the following example, I have printed all the file names in the directory and its subdirectories.

You can modify it according to your needs.

#!/bin/bash    
printAll() {
    for i in "$1"/*;do # for all in the root 
        if [ -f "$i" ]; then # if a file exists
            echo "$i" # print the file name
        elif [ -d "$i" ];then # if a directroy exists
            printAll "$i" # call printAll inside it (recursion)
        fi
    done 
}
printAll $1 # e.g.: ./printAll.sh .

OUTPUT:

> ./printAll.sh .
./demoDir/4
./demoDir/mo st/1
./demoDir/m2/1557/5
./demoDir/Me/nna/7
./TEST

It works fine with spaces as well!

Note: You can use echo $(basename "$i") # print the file name to print the file name without its path.

OR: Use echo ${i%/##*/}; # print the file name which runs extremely faster, without having to call the external basename.

Solution 14 - Bash

Just do

find . -name '*.pdf'|xargs rm

Solution 15 - Bash

If you can change the shell used to run the command, you can use ZSH to do the job.

#!/usr/bin/zsh

for file in /tmp/**/*
do
    echo $file
done

This will recursively loop through all files/folders.

Solution 16 - Bash

The following will loop through the given directory recursively and list all the contents :

for d in /home/ubuntu/*; 
do 
    echo "listing contents of dir: $d"; 
    ls -l $d/; 
done

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionElitmiarView Question on Stackoverflow
Solution 1 - BashJames ScrivenView Answer on Stackoverflow
Solution 2 - BashmouvicielView Answer on Stackoverflow
Solution 3 - BashTomekView Answer on Stackoverflow
Solution 4 - BashfalstroView Answer on Stackoverflow
Solution 5 - BashEricView Answer on Stackoverflow
Solution 6 - BashOliver CharlesworthView Answer on Stackoverflow
Solution 7 - Bashuser2350426View Answer on Stackoverflow
Solution 8 - BashTJRView Answer on Stackoverflow
Solution 9 - BashK_3View Answer on Stackoverflow
Solution 10 - BashZakView Answer on Stackoverflow
Solution 11 - BashecotechieView Answer on Stackoverflow
Solution 12 - BashTrevTheDevView Answer on Stackoverflow
Solution 13 - BashMostafa WaelView Answer on Stackoverflow
Solution 14 - BashNaviView Answer on Stackoverflow
Solution 15 - BashAmin NAIRIView Answer on Stackoverflow
Solution 16 - BashS.K. VenkatView Answer on Stackoverflow