Find -exec Command in Linux with Examples

Written by: Zachary Blackstein   |   Last updated: June 8, 2023

1. Introduction

The find command is a nifty tool for locating files that can be based on a broad range of search filters. But let's face it: we often crave more than mere discovery; we yearn to unleash actions upon those found files.

This is where the -exec swoops in, caped and ready. This will let us execute custom actions on listed files by find, including deleting, copying, renaming, and much more. This mighty feature allows us to seamlessly invoke Linux commands like grep, chmod, or mv on files directly from within the find command.

In this tutorial, we'll discuss the nitty-gritty aspects of using the -exec action in the find command. Get ready! we explore rules, examples, and practical demonstrations. So, grab your command toolbox, and let's plunge in!

2. What it is -exec in find?

The find command is a powerful tool with a very extensive manual page. When you leaf through its man page, you'll notice that find classifies its switches or flags mainly into four parts: options, tests, operators, and actions. Under actions, is where -exec is located.

So, a short answer to our questions is: -exec is an action of find.

However, what exactly does "actions" mean in the find command?

2.1. Actions in find

An action is what you tell the find command to do once it finds your searched-for files. To be more precise, "actions" refer to the operations or tasks that you can perform on files or directories that match your search criteria. This way you can interact and manipulate the found files directly with a single command.

Examples of actions in find include deleting files, printing information about files, or executing commands on files.

Find command let you choose from its own native actions like -ls to readily get some info on found files:

$ find . -name "*.txt" -ls

However, if you felt that the native actions fall short of your needs, you can leverage -exec. It will let you create custom actions through other Linux commands, including another find command:

$ find . -name "*.txt" -exec ls -laS

The -exec action provides you with a seamless way to integrate custom actions into the find workflow. You can use it with many commands to easily manipulate a large number of files, as you'll see in later examples.

3. How to use -exec

Using the -exec is a no-brainer actually, you should think of it just as another argument (option) of the find command. While formulating your command, -exec is often positioned after you finish entering the search arguments of find.

This switch can be intimidating at first to most Linux beginners due to its rather distinctive syntax. However, with the right introductions, it will gradually become familiar and intuitive for future use.

3.1. Syntax

When you want to use find and leverage -exec, here's the synopsis that you should keep in mind:

find [path] [arguments] -exec [cmd] {} [delimiter]

In the -exec part, here's what each element represents:

  • cmd is any command available in your Linux system
  • {} The curly braces represent the file found by find
  • delimiter is a trailing delimiter marking the end of -exec arguments — represented by a semicolon (;) or a plus sign (+).

3.2. Toy Examples

Let's check a simple example that gets you to see all -exec's parameters mentioned above in action.

First thing first, let's take a glimpse inside our working directory:

$ ls
1.png   2.png  4.png  6.png  8.png  bouhannana  top-quotes
10.png  3.png  5.png  7.png  9.png  top-notes
...

Suppose you want to find files in the current directory that start with "top". A suitable command for this purpose would be:

$ find . -name "top*"
./top-del
./top-quotes

The find command above went over each file in the current directory (denoted by a single dot), and applied the -name test to grab only the ones that begin with the prefix "top".

At this point, you don't want to just look at the output but rather carry out an action, such as displaying the content of each file. Enter the -exec action — now you can simply append it to the previous command with an appropriate tool to display file content, like cat.

Now, behold the -exec command:

$ find . -name "top*" -exec cat {} \;
Fear is pain arising from the anticipation of evil
A man who suffers before it is necessary, suffers more than is necessary.

The first argument "{}" in our cat command can be confusing to a lot of people, but its role is quite simple. Our find command will put the pathname of each found file wherever the special curly braces argument {} exist in the command (after -exec). Put another way, behind the scenes, find executed these cat commands: cat top-notes and cat top-quotes.

Every -exec action in find should end with a delimiter to indicate where the arguments stop. Our examples use the semicolon which is a shell command separator. But since the semicolon character is special to the shell, we should always wrap it in quotes or escape it with a backslash.

3.3. Delimiters and -exec Behavior

As seen in the syntax section, the -exec action accepts two delimiters — a semicolon (;) or a plus sign (+). It's worth noting that -exec distinguishes between these two delimiters and choosing one over the other will make it execute its command differently. Also, it's imperative to know this distinction now and spare yourself from potentially countless moments of head-scratching and confusion in the future.

Semicolon (;): This delimiter instructs -exec to execute the given command (e.g, cat) each time a file is found. Let's consider the previous example:

$ find . -name "top*" -exec cat {} \;

Each time our command finds a file, it will execute an instance of the cat command with the found file pathname. Don't bother going back to the previous sentence. An example is always like a breath of fresh air for our understanding. So, this is how -exec runs the cat command:

$ cat ./top-notes
$ cat ./top-quotes

The (;) delimiter, will make -exec execute the cat command repeatedly for each and every matched file.

Plus Sign (+): In contrast to (;), this delimiter works with -exec to pass all found files as arguments to the given command at once. This means, -exec will invoke the command a single time. Let's consider the same command example but with a trailing plus sign:

$ find . -name "top*" -exec cat {} +

This command runs in the background as so:

$ cat ./top-notes ./top-quotes

This simply means when you use the + symbol, the find command groups and slots all found files' pathnames in, as a stand-in for the {} special argument. The use of this delimiter reduces the overhead of executing the command for each file.

Note that the + character has no special meaning to the shell, so you won't have to escape it or enclose it in quotes to be interpreted by find.

4. Practical Examples

In this section, we'll talk about several examples of find -exec in close action, so you'll connect the dots and get a clearer picture. Seeing some practical use cases of -exec in conjunction with other Linux commands will enable you to use it effectively. Seeing multiple use cases also gives an understanding of when it's time to unleash this handy action and avoid manual command typing.

4.1. Deleting Files

To delete items from our Linux system, we usually lean on the rm command. As you might know, it's a good idea to be cautious and use rm interactively, so it prompts you before it actually removes any files.

Let's form a find command to match files with the .tmp extension, then append the -exec action to execute rm:

$ find . -type f -name "*.tmp" -exec rm -i {} +
rm: remove regular empty file './1.tmp'? y
rm: remove regular empty file './2.tmp'? y
rm: remove regular empty file './3.tmp'? y

Our command searches for files with a filename that ends with ".tmp", then passes the found items to rm to remove them. In the -exec part, makes use of its interactive option -i to confirm with you before it actually deletes the selected file. Now you can simply type y (or yes) to confirm, or type n (or no) to reject.

4.2. Renaming Files

Many users use the mv command to rename files. You can get there simply by following this syntax: "mv oldname newname". where the first argument denotes the current filename and the second signifies the new filename.

When renaming a bunch of files at once, you'll often find yourself wanting to make specific changes rather than giving them all the same name. This means adding prefixes or suffixes, or even replacing certain characters with others (such as spaces with underscores), or swapping between lowercase and uppercase letters.

In this example, let's hunt down all files that begin with "img-" using find, and then append -exec coupled with mv to add a ".png" extension to each found item:

$ find . -type f -name "img-*" -exec mv {} {}.png \;

As you know by now from the mv syntax mentioned above, the first argument {} signifies the current found filename, meaning that the second argument will append the ".png" string to this selected filename.

Moreover, this example can only work using the semicolon delimiter (;), because find won't accept two instances of {} when you use the + delimiter. This means, -exec will call mv for each searched-for file because mv isn't capable to rename a group of files at once.

Just like with rm, always double-check your -exec mv command or make sure to have backups of your files before performing any mass renaming operations to avoid any unintended consequences

4.3. Copying Files

A useful command to utilize with the  -exec action is cp. Sure, this comes in handy for copying multiple items, but its true value shines when you want to back up data before performing mass operations on files.

Let's copy directories that include "topsecret" in their filenames:

$ find . -type d -name "*topsecret*" -exec cp {} -r backup \;

This command copies all directories (thanks to the -type d test) that match our -name "*topsecret*" test into the target directory backup. The -r (recursive) option of the cp command, ensures to copy directories recursively, preserving their original structure.

4.4. Searching Files

The go-to command for searching files is grep. You can use this command to locate specific text within specific files. If we use grep in conjunction with find, you can think of it as passing data through two funnels: one to winnow out matched files and another to grab the desired string within them.

Say you have numerous log files and you want to search for a specific text within the ones that are seven days old. You want to identify the resources accessed by a particular user, let's say precisely seven days ago. A practical command that fits this scenario is:

$ find . -type f -mtime -7 -exec grep ad_bouhannana {} \;

Our find command will locate files that are modified 7 days ago using the -mtime -7 test, and then the -exec action will invoke mv to search for occurrences of "ad_bouhannana" within them.

5. Advanced Examples

Now that you've mastered the -exec action to effortlessly use it for your mass file operations. It may be a good idea to stick around for a little bit and take a deeper dive into its more advanced applications.

5.1. Using Multiple -exec actions

Imagine you have a directory brimming with a diverse collection of scripts, each with its own unique extension. Now, envision a scenario where you desire to organize these scripts into separate directories based on their respective extensions. For instance, you'd want to copy .cpp files into a directory called "Cpp", and .py files into a directory called "Python".

One simple approach to solve this task is applying two -exec actions for each specified search test. Let's check the following command:

$ find . -name "*.cpp" -exec cp {} home/zaaiy/Cpp \; -o -name "*.py" -exec cp {} home/zaaiy/Python \;

This command comprises of two sets: the first set searches and copies .cpp files into the Cpp directory, and the second set searches and copies .py files into the Python directory. The -o flag, which is short for -or, is what combines the two sets. It represents the logical OR operator. This -o operator, indicates that the next set of search criteria is an alternative option.

Put another way, when our find command searches the directory, it will grab and copy either .cpp files OR .py files.

You could also add other find criteria by joining them with the -or operator.

5.2. Using Multiple commands on each found file

You may find yourself wanting to invoke many commands with -exec for each found file. How's that? Let's paint a toy scenario: Envision you have many directories, each having multiple types of files. Now. you'd like to list the names of these directories along with how many .py (python) files they contain.

As mentioned before, achieving this task will require joining multiple commands for each item the find command matches. Without further ado, here is one command that could be helpful for this scenario:

$ find . -maxdepth 1 -type d -exec bash -c 'echo "Subdirectory: \"{}\", File Count: $(ls -1 "{}" | grep .py | wc -l)"' \;
directory: ".", File Count: 6
directory: "./backup", File Count: 1
directory: "./Cpp", File Count: 0
directory: "./1topsecretStuff", File Count: 0
directory: "./_bouhannana_project", File Count: 2
directory: "./hi-topsecret_area51", File Count: 1
directory: "./resf-topsecret89vortex - Copy", File Count: 0
directory: "./Python", File Count: 2

Take a gander at what this command does:

  • The find tests grab all directories in the current location.
  • The -maxdepth 1 option instructs find to search only the specified location and not to traverse into subdirectories.
  • The -exec option uses a Bash shell instance to execute multiple commands for each found directory.
  • The bash -c command prints the name of the found directory along with the count of ".py" files.

6. Using -exec interactively

Before wrapping up this article, it's a good idea to mention how you could manually confirm each command invocation on found files.

Often, you might need to create a find -exec command for carrying out a bulk file operation. But you want from find to confirm with you before it proceeds with the execution. In this case, you have the option to use -ok — consider it as the interactive counterpart of -exec. The -ok action works similarly to -exec, but spares you from relying on the interactive option of the specified command, such as with the rm -i command.

So, let's use the earlier rm example but with couple of tweaks: rather than using the -i option of rm, we'll leverage -ok instead:

$ find ./Python -type f -name "*.py" -ok rm  {} \;
< rm ... ./Python/doubling_dispat.py > ? n
< rm ... ./Python/reorder_overbix.py > ? n

The command prompts you for confirmation for each found file. If you type "y", it will proceed and delete the file, if you type "n", it will skip the deletion of that file.

About The Author

Zachary Blackstein

Zachary Blackstein

Zachary, a long-time Linux user since his earliest interactions with computers, is fueled by his passion for programming and data science. He greatly admires writers who can make complex ideas easy to understand, and he consistently strives to excel in that regard. He has written many articles and pre-employment tests on Linux System Administration and various tech-related subjects, and he also enjoys exploring cybersecurity through hacking challenges and bug bounty hunting in his free time.

SHARE

Comments

Please add comments below to provide the author your ideas, appreciation and feedback.

Leave a Reply

Leave a Comment