How to Create a Bash File Filtering Tool

How to Create a Bash File Filtering Tool

One of the scripts I reach for most often isn’t fancy at all — it’s a simple tool that scans through a directory and filters files based on some criteria: extension, size, modification date, or content. Once you understand the building blocks, you can adapt this pattern to filter almost anything. In this guide, I’ll walk you through building a genuinely useful Bash file filtering tool from the ground up.

What Is a File Filtering Tool?

A file filtering tool scans a set of files (usually within a directory) and selects only the ones matching specific conditions — for example, all .log files larger than 10MB, or all files modified in the last 7 days. Instead of manually scrolling through a folder, you let the script do the sorting for you.

Step 1: Filtering by File Extension

Let’s start simple — a script that lists all files with a specific extension in a given directory.

#!/bin/bash

directory=$1
extension=$2

for file in "$directory"/*."$extension"
do
    if [ -f "$file" ]
    then
        echo "$file"
    fi
done

Sample run:

$ ./filter.sh /home/user/documents txt
/home/user/documents/notes.txt
/home/user/documents/todo.txt

How it works: The script takes two arguments — a directory and an extension — then uses a glob pattern (*.txt) to match files. The -f check makes sure we only report actual files (in case the pattern doesn’t match anything, avoiding a literal “no such file” string being printed).

Step 2: Filtering by File Size

Now let’s build a filter that finds files above a certain size, which is incredibly useful for cleaning up disk space.

#!/bin/bash

directory=$1
min_size_kb=$2

find "$directory" -type f -size +"${min_size_kb}"k

Sample run:

$ ./filter_by_size.sh /var/log 500
/var/log/syslog
/var/log/nginx/access.log

How it works: find is a much more powerful tool than a simple glob loop for this kind of task. The -size +500k option matches files larger than 500 kilobytes. The + sign means “greater than”; you could use - for “less than,” or no sign for “exactly.”

Step 3: Filtering by Modification Date

Finding recently modified (or old, stale) files is another common need.

#!/bin/bash

directory=$1
days=$2

find "$directory" -type f -mtime -"$days"

Sample run:

$ ./filter_by_date.sh /home/user/downloads 7
/home/user/downloads/report.pdf
/home/user/downloads/invoice.docx

How it works: -mtime -7 matches files modified within the last 7 days. Flip the sign (-mtime +7) to instead find files older than 7 days — perfect for identifying stale files to archive or delete.

Step 4: Filtering by Content (Grep-Based Filtering)

Sometimes you don’t care about metadata — you want files that actually contain certain text.

#!/bin/bash

directory=$1
search_term=$2

grep -rl "$search_term" "$directory"

Sample run:

$ ./filter_by_content.sh /home/user/projects "TODO"
/home/user/projects/app.py
/home/user/projects/notes.md

How it works: grep -r searches recursively through the directory, and -l tells grep to print only the filenames that contain a match, rather than every matching line.

Step 5: Combining Multiple Filters into One Tool

Now let’s build something more complete — a single script that combines extension, size, and date filters, controlled with command-line flags.

#!/bin/bash

# Usage: ./file_filter.sh -d DIRECTORY [-e EXTENSION] [-s MIN_SIZE_KB] [-m DAYS]

directory=""
extension=""
min_size=""
days=""

while getopts "d:e:s:m:" opt
do
    case $opt in
        d) directory=$OPTARG ;;
        e) extension=$OPTARG ;;
        s) min_size=$OPTARG ;;
        m) days=$OPTARG ;;
        *) echo "Usage: $0 -d DIRECTORY [-e EXTENSION] [-s MIN_SIZE_KB] [-m DAYS]"
           exit 1 ;;
    esac
done

if [ -z "$directory" ]
then
    echo "Error: Directory (-d) is required."
    exit 1
fi

find_command=(find "$directory" -type f)

if [ -n "$extension" ]
then
    find_command+=(-name "*.$extension")
fi

if [ -n "$min_size" ]
then
    find_command+=(-size +"${min_size}"k)
fi

if [ -n "$days" ]
then
    find_command+=(-mtime -"$days")
fi

echo "Running filter with the following criteria:"
echo "Directory: $directory"
[ -n "$extension" ] && echo "Extension: .$extension"
[ -n "$min_size" ] && echo "Minimum size: ${min_size}KB"
[ -n "$days" ] && echo "Modified within: $days days"
echo "------------------------------------------"

"${find_command[@]}"

Sample run:

$ ./file_filter.sh -d /var/log -e log -s 100 -m 30
Running filter with the following criteria:
Directory: /var/log
Extension: .log
Minimum size: 100KB
Modified within: 30 days
------------------------------------------
/var/log/nginx/access.log
/var/log/syslog

How it works:

  1. getopts parses command-line flags (-d, -e, -s, -m), storing each value in $OPTARG.
  2. We build up a find command as a Bash array (find_command), appending options only for the filters the user actually specified.
  3. Finally, "${find_command[@]}" expands the array back into a properly separated command and executes it.

Using an array here (rather than a plain string) is important — it avoids word-splitting and quoting issues that would otherwise cause the script to break on filenames or paths containing spaces.

Step 6: Adding an Action (Not Just Listing)

A filtering tool becomes even more useful when it can act on the results — for example, moving matched files somewhere else.

#!/bin/bash

directory=$1
extension=$2
destination=$3

mkdir -p "$destination"

find "$directory" -type f -name "*.$extension" -print0 | while IFS= read -r -d '' file
do
    mv "$file" "$destination"
    echo "Moved: $file"
done

Sample run:

$ ./move_filtered.sh /home/user/downloads pdf /home/user/documents/pdfs
Moved: /home/user/downloads/invoice.pdf
Moved: /home/user/downloads/report.pdf

How it works: -print0 and read -r -d '' together handle filenames safely, even ones containing spaces or unusual characters, by using a null byte as the delimiter instead of a newline — this is the safest way to loop over find results in Bash.

How This Works Internally

Real-World Use Cases

find /var/log -type f -name "*.log" -mtime +30 -delete
find ~/Downloads -type f -name "*.jpg" -exec mv {} ~/Pictures/ \;
find / -type f -size +1G 2>/dev/null
grep -rl "DEBUG=true" /etc/myapp/

Best Practices

Security Considerations

Optimization Tips

Troubleshooting Common Issues

Frequently Asked Questions

Q: What’s the difference between using find and a plain for loop with globbing for filtering files? A: find supports far richer criteria (size, modification time, permissions, depth) and handles filenames with special characters safely when combined with -print0; a glob-based for loop is simpler but more limited and more fragile with unusual filenames.

Q: How do I make my filtering tool recursive? A: find is recursive by default; a simple for file in dir/* loop is not, unless you add ** globbing (shopt -s globstar) or nest additional loops for subdirectories.

Q: Can I filter files by permission or ownership too? A: Yes — find supports -perm for permissions and -user/-group for ownership, which can be combined with the other filters shown above.

Q: How do I preview what a filter would delete without actually deleting anything? A: Run the find command with -print (or no action flag at all, which prints by default) before adding -delete, so you can review the matched files first.

Common Mistakes to Avoid

Summary

Building a file filtering tool in Bash is really about combining the right building blocks: find for locating files by metadata (name, size, date), grep for content-based filtering, and getopts for a clean command-line interface. Once you combine these into a single flexible script — using arrays to safely build up find commands and -print0/read -d '' to handle filenames safely — you end up with a genuinely reusable tool you’ll find yourself reaching for again and again.

References

Exit mobile version