Section 1 of 18

1 Before you start

Before you begin, take a few minutes to check what you already know and how confident you feel. You will see the same questions again at the end of the module — this helps both you and us measure what you have learned. Click an option for every question and confidence rating, then click Next to continue.

Pre-test

Which command finds every .py file anywhere under the current folder?

Pre-test

What does the * in find . -name '*.csv' mean?

Pre-test

Which find call lists only directories, not files?

Pre-test

Why pair find -print0 with xargs -0?

Pre-test

What does the {} mean in find . -name '*.sh' -exec chmod +x {} \;?

Pre-test

Which find option matches files modified within the last 24 hours?

Pre-test

How do you find files LARGER than 100 megabytes?

Pre-test

In find -exec, what is the difference between ending the command with \; and ending it with +?

Pre-confidence

I can find files anywhere under a folder by name, type, and size.

Not at all confident
Fully confident
Pre-confidence

I know how to run a command on every file find returns, using -exec or xargs.

Not at all confident
Fully confident
Pre-confidence

I know why -print0 / -0 matter when filenames might contain spaces.

Not at all confident
Fully confident
Section 2 of 18

2 Introduction

When your project folders start to grow, locating a specific dataset or script can feel like searching for a needle in a haystack. This is where the find command becomes your best friend. Instead of clicking through endless folders, find acts as a highly customizable search engine right inside your terminal. xargs is its partner - it takes the list of files find produces and runs a command on each.

In this section, we will learn about the following:

  • find's basic shape: where to look, then what to look for.
  • Common predicates: -name, -iname, -type, -size, -mtime.
  • Acting on what you find: -delete and -exec.
  • xargs - safer, more flexible way to act on a file list.
Section 3 of 18

3 find - The Basic Shape

While grep finds text inside files, find finds the files themselves. It is the right tool for "where did I put that script?", "show me every .csv under this folder tree", or "delete every empty file older than a week".

$ find .
.
./cohort_2026.csv
./vitals
./vitals/patient_001.txt
./vitals/patient_002.txt
...

In the example above, the dot (.) is the current folder. find . means "show me everything starting from where I am standing right now".

find . takes the current directory as its starting point and walks the whole subtree beneath it, stepping into every folder it meets so that each nested file and folder is visited in turn.
find . takes the current directory as its starting point and walks the whole subtree beneath it, stepping into every folder it meets so that each nested file and folder is visited in turn.

To make this command truly useful, we need to add predicates to narrow the results down.

Section 4 of 18

4 Filtering With Predicates

Section 4.1 of 18

4.1 By name - -name and -iname

The most common way to search is by the name of the file. You can use an asterisk (*) as a wildcard to find patterns.

$ find . -name 'patient_*.txt'
./vitals/patient_001.txt
./vitals/patient_002.txt
$ find . -iname '*.CSV' # case-insensitive

The Wildcard Trap: Always wrap your search patterns in quotes (like '*.txt'). If you forget the quotes, bash will try to guess what *.txt means before the find command even runs. This can cause the command to only search your immediate folder instead of the whole tree!

An unquoted wildcard is expanded by the shell against the current directory before find ever runs, so quoting the pattern is what lets find do its own matching across every level of the tree.
An unquoted wildcard is expanded by the shell against the current directory before find ever runs, so quoting the pattern is what lets find do its own matching across every level of the tree.
Section 4.2 of 18

4.2 By type - -type

Sometimes you only want to look for folders, or you only want to look for files. The -type flag lets you filter by the exact kind of item:

  • -type f - regular files only.
  • -type d - directories only.
  • -type l - symbolic links.
$ find . -type d
.
./vitals
./scripts
./archive
find returns every entry by default, and -type narrows the results to a single kind of entry, whether files, directories, or symbolic links.
find returns every entry by default, and -type narrows the results to a single kind of entry, whether files, directories, or symbolic links.
Section 4.3 of 18

4.3 By size - -size

If you need to clear up disk space or find an unusually large dataset, you can filter by exact, minimum, or maximum sizes.

  • -size +1G - bigger than 1 GiB.
  • -size -100k - smaller than 100 KiB.
  • -size 0 - exactly zero bytes (empty files).
$ find /var/log -type f -size +100M
In find, the size sign sets the direction of the comparison, so +100M matches files larger than the threshold, -100M matches smaller ones, and a bare 0 matches only empty files.
In find, the size sign sets the direction of the comparison, so +100M matches files larger than the threshold, -100M matches smaller ones, and a bare 0 matches only empty files.
Section 4.4 of 18

4.4 By modification time - -mtime

Have you ever saved a file, immediately forgotten where you put it, and wished you could just search for "things I worked on today"?

The modification time (-mtime) flag does exactly that, measuring in days:

  • -mtime -7 - modified within the last 7 days.
  • -mtime +30 - modified more than 30 days ago.
  • -mtime 0 - modified today.
$ find ~/cohort -mtime -1
# what did I touch in the last 24 hours?
The sign on -mtime sets direction in time, where -N matches files newer than N days, +N matches files older than N days, and a bare N matches the single day N days back.
The sign on -mtime sets direction in time, where -N matches files newer than N days, +N matches files older than N days, and a bare N matches the single day N days back.
Section 4.5 of 18

4.5 Combining predicates

The true magic of find is that you do not have to use just one filter. You can stack them together to create incredibly specific queries.

$ find . -type f -name 'patient_*.txt' -size +10k
# patient files over 10 KiB, anywhere under the current tree
find treats multiple tests as a single logical AND, so every flag you add removes more entries and leaves only the files that satisfy every condition at once.
find treats multiple tests as a single logical AND, so every flag you add removes more entries and leaves only the files that satisfy every condition at once.

Try these codes out and see what responses you will get!

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
$ find . -name 'cohort_*.csv'
$ find . -type d
$ find . -size 0
Section 5 of 18

5 Acting On What You Find

Finding your files is only half the battle. Usually, you want to do something with them—like delete old logs, compress large datasets, or move files into an archive. You have three main ways to take action.

Section 5.1 of 18

5.1 -delete - careful with this

If you want to remove files, find has a built-in -delete flag. However, use this with extreme caution. Just like the rm command, there is no recycle bin and no "undo."

$ find . -name '*.tmp'
./build/scratch.tmp
./build/cache.tmp
$ find . -name '*.tmp' -delete
# now they are gone
Running find on its own previews exactly which files match, while adding -delete erases every matched file permanently, with no recycle bin and no undo.
Running find on its own previews exactly which files match, while adding -delete erases every matched file permanently, with no recycle bin and no undo.

Note: Always run your find command without the -delete flag first. Look at the printed list to guarantee you aren't accidentally matching files you want to keep. Only add -delete once you are 100% sure.

Section 5.2 of 18

5.2 -exec - run a command per match

If you want to do something other than delete (like compress, move, or change permissions), use the -exec flag. This tells find to run a specific command on every single file it matches.

The syntax looks a little strange at first:

{} acts as a placeholder for the file's name.

\; tells find where your command ends. (You must include the backslash so the shell doesn't misinterpret the semicolon)

$ find . -name '*.sh' -exec chmod +x {} \;
# make every .sh under here executable
$ find . -name '*.log' -exec gzip {} \;
# compress every log file
find -exec applies a command to every matched file in turn, substituting each filename into {} and terminating with ;.
find -exec applies a command to every matched file in turn, substituting each filename into {} and terminating with ;.

Running commands one by one with \; is slow. If you have thousands of files, replace \; with + at the end. This tells find to batch the files together and process them all at once

$ find . -name '*.log' -exec gzip {} +
Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
$ find scripts -name '*.sh'
$ find scripts -name '*.sh' -exec grep -l TODO {} \;
In find -exec, ending with ; runs the command once per matched file while ending with + hands many files to a single invocation, so + launches the program far fewer times and is much faster on large file sets.
In find -exec, ending with ; runs the command once per matched file while ending with + hands many files to a single invocation, so + launches the program far fewer times and is much faster on large file sets.
Section 6 of 18

6 xargs - Build Commands From a List

xargs is a companion tool that takes a list of items (like the output from find) and feeds them into another command. It is incredibly fast, and it's the right tool to use when your list of files comes from somewhere else, like a text file or a grep search.

$ find . -name '*.log' | xargs rm
# delete every .log file under here
xargs collects the items arriving on its standard input and rebuilds them as positional arguments to the command you name, which is why find ... | xargs rm can delete files that rm would otherwise never receive from a pipe.
xargs collects the items arriving on its standard input and rebuilds them as positional arguments to the command you name, which is why find ... | xargs rm can delete files that rm would otherwise never receive from a pipe.
Section 6.1 of 18

6.1 The whitespace problem - use -print0 and -0

By default xargs splits its input on whitespace. If any filename has a space, that breaks. The fix: both sides use null bytes as separators. For example, if a colleague names a file final report v2.log, xargs will break it into three separate chunks: final, report, and v2.log. This can cause catastrophic errors!

To fix this, get into the habit of pairing find -print0 with xargs -0. This forces both tools to use invisible "null bytes" to separate files instead of spaces, making it perfectly safe.

$ find . -name '*.log' -print0 | xargs -0 rm
# safe even if filenames contain spaces
Because xargs splits on whitespace, a space in a filename turns one name into several; find -print0 paired with xargs -0 uses the null byte as the only separator, and a null can never occur inside a name, so each filename stays intact.
Because xargs splits on whitespace, a space in a filename turns one name into several; find -print0 paired with xargs -0 uses the null byte as the only separator, and a null can never occur inside a name, so each filename stays intact.
Section 6.2 of 18

6.2 xargs -I {} for one-at-a-time

If you need the filename more than once, or somewhere other than the end, use -I {}. This lets you use the {} placeholder exactly like you did with -exec:

$ find . -name 'patient_*.txt' | xargs -I {} mv {} archive/
Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
$ find vitals -name 'patient_*.txt'
$ find vitals -name 'patient_*.txt' | xargs wc -l
The -I {} flag turns {} into a placeholder that xargs replaces with one input item per run, so each filename lands exactly where you mark it in the command rather than being appended at the end.
The -I {} flag turns {} into a placeholder that xargs replaces with one input item per run, so each filename lands exactly where you mark it in the command rather than being appended at the end.
Section 7 of 18

7 Common Pitfalls

If your command isn't working, check this list:

  • Unquoted search patterns: If you write find . -name *.csv (no quotes), your terminal will expand *.csv before find runs, usually breaking your search. Always quote your wildcards: '*.csv'.
  • Forgetting the \; on -exec: If you get the error "find: missing argument to -exec", you forgot to end your command with \; (or +).
  • Running xargs without -0: If your command is doing weird things to files with spaces in their names, you forgot to use the -print0 and -0 safety net.
  • Running -delete blindly: If your pattern matched more than you thought, you just lost real files. Always run the filter alone first to verify!
Section 8 of 18

8 Check your understanding

You have reached the end of the module. Try the same questions again — your answers here, paired with your pre-test answers, are how we measure what the module taught you. Answer every question and confidence rating, then click Submit and see results to view your score.

Post-test

Which command finds every .py file anywhere under the current folder?

Post-test

What does the * in find . -name '*.csv' mean?

Post-test

Which find call lists only directories, not files?

Post-test

Why pair find -print0 with xargs -0?

Post-test

What does the {} mean in find . -name '*.sh' -exec chmod +x {} \;?

Post-test

Which find option matches files modified within the last 24 hours?

Post-test

How do you find files LARGER than 100 megabytes?

Post-test

In find -exec, what is the difference between ending the command with \; and ending it with +?

Post-confidence

I can find files anywhere under a folder by name, type, and size.

Not at all confident
Fully confident
Post-confidence

I know how to run a command on every file find returns, using -exec or xargs.

Not at all confident
Fully confident
Post-confidence

I know why -print0 / -0 matter when filenames might contain spaces.

Not at all confident
Fully confident
Section 9 of 18

9 Your results

Here is how your post-test answers compare with your pre-test answers. The pre/post pairing is the most reliable way to see what this module actually taught you.

Your score

Submit the post-test to see your results.

Muddiest point

What is the one thing from this module that is still unclear to you?

Rate this module

Overall, how would you rate this module?

How likely are you to recommend this module to a peer? (0 = not at all, 10 = extremely likely)