Performing actions on search results with the find command

inspect-code

Learn how the Ubuntu/Linux find command can perform actions on each result returned from a search. We'll cover actions carried out by find (itself), and combining find with external programs to help you get productive.

Reading time:
7 min

Not familiar with the find command?

If you are not already familiar with the find command on Ubuntu, you may want to read getting started with the find command. Or, if you would like to know how to find files based on size, owner, group, or access/change/modify time, please read our intermediate look at the find command.

If you manage to get to grips with performing "actions" on the results returned from find (as we show below), you'll have a seriously powerful tool at your disposal.

OK then, lets run you through some of the lesser-known features of this excellent utility.

The default action (-print)

Due to the fact that most people use find to display search results to the screen, the default action performed by find when none is specified, is -print. Note that this is the case for the gnu implementation of the find utility (which is used by Ubuntu and the other Linux distributions), other implementations may require explicit use of "-print" in order to display any results at all.

Omit newline characters with -print0

The default "-print" action includes a newline character. If you specifically don't want this character at the end of each search result, use the -print0 action. It will print a null character rather than a newline character. This is especially useful if you're sending the results of find to another program where the newline may alter the other program's behaviour or simply be superfluous. A simple example will show how all the results are not on separate display lines:

find /var/log -name "\*.log" -print0

And don't forget you can add a 2>/dev/null onto the end in order to ignore error results such as "Permission Denied" (as explained in an[earlier post); for example:

find /var/log -name "\*.log" -print0 2>/dev/null

Customising the display format with -printf

For each result returned by find, you can tweak the format of the text sent to standard output (that is your terminal's display, unless set otherwise) by using the -printf action. This action is used along with a format string specifying the output you would like from the find command. (For a list of what you can use in a format string, please take a look at the man page).

For example, to display a custom format including tabs like this:

\-rw-r--r-- file1.txt 2121 bytes f

You could use:

find . -name "\*.txt" -printf "%M %f \\t %s bytes \\t%y\\n"

In the above format string, %M is the file's permissions, %f is the file's name, %s is the file's size in bytes, %y is the file's type (for example regular, directory, socket etc ...), and the "\t" and "\n" are just tab and newline respectively.

Here is an example of displaying the filename along with access and modification times (for all .conf files under the /etc directory):

find /etc -name "\*.conf" -printf "%f %a, %t\\n"
`Lines from the output of this command would look like this:`
resolv.conf Mon Aug 27 19:52:35.0706744508 2012 , Sat Jun 23 14:01:13.0777581995 2012

In the above format string we're just printing the file's name with %f along with the last access time %a, and the last modification time %t.

As an example of using a custom date time display, along with a more human readable description of the fields such as:

resolv.conf accessed 2012-08-27 7:52:35 pm BST, modified 2012-06-23 2:01:13 pm BST

You could use something like:

find /etc -name "\*.conf" -printf "%f accessed %AF %Ar, modified %TF %Tr\\n"

Here we use %AF (for the date format "2012-08-27") and %Ar (for the time format "7:52:35 pm BST") to display the last access date and time in a different format. We follow the same format for modification times by using %TF and %Tr.

NB: there are a lot of options available for use in find's format string, so use the man page as a reference to see what specifiers are available.

Deleting files

You can have all matching files deleted from your system (provided you have the required permissions) by using the -delete action. For example to delete all .txt files on your system that start with the string "oldStuff", run:

find / -name "oldStuff\*.txt" -delete

Remember to be careful and double check what you are doing when using the "delete" action, otherwise you may delete files you still need!

Executing other commands on your results with -exec

You can instruct find to run another command, using each result from find as a parameter to the other command. One way to do this is by using the -exec command. For example, if you wanted to do an "ls -la" on each .txt file matched by find, you could run:

find . -name "\*.txt" -exec ls -la {} \\;

An alternative way to escape that ; is to enclose it in quotes like so:ells find that the other program's command line arguments have come to an end (it's escaped like ; so the shell doesn't interpret it).

An alternative way to escape that ; is to enclose it in quotes like so:

find . -name "\*.txt" -exec ls -la {} ";"

The two above commands will execute the ls -la command for each result that find matches.

For a large result set, that can use unnecessary resources. A more cpu friendly approach is to use -exec with the + syntax, for example:

find . -name "\*.txt" -exec ls -la {} +

What the above does is actually plug all the results into the command at the same time. So its more efficient than the previous method.

If you want, you can combine more than one -exec together. The contrived example below will do the ls -la and then call myScript.sh:

find . -name "\*.txt" -exec ls -la {} \\; -exec ./myScript.sh {} \\;

Note that you can use the ; syntax for one and the + syntax for the other if you wish because they're different actions:

find . -name "\*.txt" -exec ls -la {} \\; -exec ./myScript.sh {} +

(Note that in the above example, because we've used + syntax for the exec action that calls the script, our script needs to be able to handle all arguments passed at once!).

Executing other commands from target directory with -execdir

The -exec action runs the command from the directory where find was called. If you need to run the command from the directory where the matched file resides, you need to use the -execdir command; for example:

find . -name "\*.txt" -execdir ls -la {} ";"

There is also a + version of -execdir available so you can cut down on cpu usage if need be.

Giving the OK before executing other commands

A special version of -exec called -ok can be used to check for approval before executing the desired command. The user will be prompted for a yes/no response before carrying out or skipping the command as actioned by the user. This is a safer way to carry out the deletion of files in a controlled manner. For example:

find . -name "\*.txt" -ok rm {} \\;

Giving the OK before executing from target directory

The -ok action will run the command from the directory where find was called. To have the command run from the directory where the matched file resides, use the -okdir action instead.

Skip searching specific directories with -prune

In order to tell find to skip certain directories, use the -prune option. By using this option, you can save a lot of wasted cpu cycles by not bothering to search in directories where you know you don't need to.

For example if you want to find all .txt files in or under the current directory but don't want to bother searching a directory called bin, you could use:

find . -name bin -prune -o -name "\*.txt" -print

Note that in this instance, you must include the -print at the end of the command.

If you want to skip more than one directory, just use the "," operator to ensure both parts of the expression are evaluated (we'll be covering how to use the find operators in more detail in a future post), for example.

find . -name somedir -prune , -name bin -prune -o -name "\*.txt" -print

The reason we need to be explicit with "-print" at the end of the above commands is explained below. (This is a bit involved, so skip it if your not interested ...).

As mentioned at the top of this page, when we use find, the default action is -print. This is why we don't have to stick -print on the end of each command (for gnu's version of find) in order to have the results displayed to the screen. This default applies to the whole line, so when the command is made up of more than one expression as is the case above, what actually happens when the default print is shoved in can be shown with parenthesis which are escaped as usual, so as the shell doesn't misinterpret them:

find . \\( -name skipdir1 -prune , -name skipdir2 -prune -o -name "\*.txt" \\) -print # Probably not what you wanted to do!
`When in fact what you probably really want is this:`
find . \\( -name somedir -prune \\) , \\( -name bin -prune \\) -o \\( -name "\*.txt" -print \\)

So because prune returns true when it finds a directory, the result (the dir name) can inadvertently end up in with your results when the default -print gets used!

The bottom line is: be careful that you're printing the results you really want when you combine multiple expressions together.

Do you know any handy "action" examples?

If you use the find command to perform actions, and you know some handy commands that others may find useful, please do share them with us in the comments section below!

Thank you for reading this article.
Please share if you liked it.