How to Search for Files and Directories

July 5, 2023

Back to Articles

We are going to use the Unix find utility which comes standard on MacOS. Just open a terminal and type which find to see see where your binary is housed. As long as you don't get a response that says find not found, you're good to go!

An initial real world example

You're in a directory with lots of files and you want to find all your Javascript files (ones that end in .js). What do you do?

find . -type f -name "*.js"

Find operates by receiving a path and expression. In our example, the path is the current directory (the dot), and the expression is -type f -name "*.js". As you probably guessed, there are many options at your disposal to use in concocting the perfect search expression.

Setting up some use cases

Feel free to copy this command to set up a searching environment, if you wish to follow along.

mkdir find_environment && cd find_environment && mkdir -p desserts/{pie/{pumpkin/{large,medium,small},apple/{large,medium}},cake/{chocolate/{large,small},carrot/{large,medium,small}}}

Ensure you do not leave spaces within the curly braces above, or it won't work.

So what did we just create? To find out, just type find . to see all the directories recursively. By recursively, I mean that find starts at the current directory and works its way downward through each child directory of the tree. Kind of like this.

.
└── desserts
    ├── cake
    │   ├── carrot
    │   │   ├── large
    │   │   ├── medium
    │   │   └── small
    │   └── chocolate
    │       ├── large
    │       └── small
    └── pie
        ├── apple
        │   ├── large
        │   └── medium
        └── pumpkin
            ├── large
            ├── medium
            └── small

In this way, find is a great alternative to the ls command if you want to see deeper.

By the way, to get that cool readout above, I used another CLI utility called tree.

What if we only want to find files concerning carrot cake and apple pie? You can supply multiple directories to the path segment.

$ find desserts/cake/carrot desserts/pie/apple
desserts/cake/carrot
desserts/cake/carrot/large
desserts/cake/carrot/medium
desserts/cake/carrot/small
desserts/pie/apple
desserts/pie/apple/large
desserts/pie/apple/medium

In Unix-like file systems, everything is a file. Directories are just a special type of file. To find out what kind of file something is, just type ls -l or ll and see the first letter of the permission readout. If it's a d, then it's a directory. If it's a -, then it's a normal file.

By default, find doesn't care what type of file it is, it's going to find normal files and directories exactly the same. So what if we only want to find normal files? Then we need to supply an option to the expression segment.

find . -type f
find . -type d

Since we didn't create any normal files, the return value was empty when we supplied f, but we received everything in response when we supplied d.

Now let's populate this folder with some files. Since we're programmers, going to each directory and creating file seems unsatisfactory. So... here's a script. 🙂

for dir in ./*;do;for dir2 in $dir/*;do;for dir3 in $dir2/*;do;touch $dir3/$(basename $dir3).{txt,html,css,js}&&for dir4 in $dir3/*;do;[ -d $dir4 ]&&touch $dir4/$(basename $dir4).{txt,js,html,css}||continue;done;done;done;done;

Just ensure your current working directory is find_enviroment in this case.

Ok, now if you type find ., you should see a lot more files. Now find only the files we just created.

find . -type f

Your boss comes to you and says, "I need to know how many 'large' text files we have under pie, ASAP!". Phew, find to the rescue.

find desserts/pie -type f -name "large.txt"

But wait, don't be over-confident in your ability to count lines. Better pipe the output to the wc utility (word count).

find desserts/pie -type f -name "large.txt" | wc -l

The answer is 2! You get to keep your job.

Not so fast, scope creep has gone into effect. We need all the large files, and medium ones too, and all types, not just .txt, and also chocolate cake!

No problem!

find desserts/{pie,cake/chocolate} -type f \( -name "large.*" -o -name "medium.*" \) | wc -l

Notice the use of the -o flag (or) within the escaped parentheses to supply alternative expressions.

The answer is 20!

Now a customer is requesting a list of all products you sell that contain an 'a' and end with an 'e' in HTML format. This means actual types of pie and cake, not the sizes. To do this we need to specify a certain depth. In our case, 4. Four levels deep: {current directory}/desserts/{pie,cake}/{types}.

$ find . -depth 4  -regex '.*a.*e.html'
./desserts/pie/apple/apple.html
./desserts/cake/chocolate/chocolate.html

Great! But now the customer is complaining about the paths left in, because they are confusing. Can you please only give the file itself?

For this, we will execute a command on the output with the -exec option.

$ find . -depth 4  -regex '.*a.*e.html' -exec basename {} \;
apple.html
chocolate.html

The {} represents each line in the return, and you need to escpe the final semicolon for some reason.

Great! Looks like your a find hero! To see all the capabilities of find, make sure to refer to the manual by typing man find.

Hope you find a lot of stuff on your computer.

-🥑

Back to Articles