1. Overview

Sometimes, we find ourselves in situations where we need to copy files with specific extensions to another directory. For example, we may have to copy only .js and .html files to a given location for backup or processing. Thankfully, the find command makes this task a breeze, enabling us to pinpoint the necessary files, then automatically copy them with the cp command.

In this tutorial, we’ll walk through the steps for using the find command to locate and copy files with specific extensions.

2. Finding Files by Extension

The find command provides us with the ability to use logical expressions and operators to search for files based on different criteria. We may join multiple tests together with these operators to make our search more specific and streamlined.

However before we dive into the discussion, let’s take a quick look at our current directory:

$ ls
1file.html  2file.html  3file.html  bouhannana_dir.js/
1file.js    2file.js    3file.js    smara_dir.html/

The output of ls shows a mixture of files with the extensions .js and .html, as well as directories with names ending with .js and .html.

2.1. Dealing With Logical Expressions

Suppose we want to list all files with the .js extension. We can simply combine the -name test with the -type f test using the logical AND operator. In this case, we explicitly specify this operator using the -a flag (short for -and):

$ find -type f -and -name "*.js"
./1file.js
./2file.js
./3file.js

The command lists only items that are of type f (which stands for file) and have filenames ending with .js. Notably, the find command automatically applies the logical AND operator (-a) to all tests by default. This means executing the previous command without the -and operator yields the same result.

On the other hand, we can use the logical operator OR to join tests. In contrast to -a, we should always explicitly specify the -o flag (short for -or). So, using the same tests, let’s introduce -o instead of -a and see the results:

$ find -type f -or -name "*.js"
./1file.html
./1file.js
./2file.html
./2file.js
./3file.html
./3file.js
./bouhannana_dir.js

The above command locates items that are either of type f or have a name that ends with .js. So naturally, find includes the bouhannana_dir.js directory because it has a name ending with .js, and also files with .html extensions.

2.2. Combining Both AND and OR Operators

After becoming familiar with the -a and -o operators, constructing a find command to locate files with different extensions may seem like a no-brainer task. However, this can lead to unexpected or unwanted results without properly understanding how find deals with expressions.

Let’s check an example of this sort:

$ find -type f -name "*.html" -o -name "*.js"
./1file.html
./1file.js
./2file.html
./2file.js
./3file.html
./3file.js
./bouhannana_dir.js

Obviously, this isn’t what we want since the bouhannana_dir.js directory is in the output. The -o operator (logical OR) above joins two expressions together, the -type f -name “*.html” and -name “*.js”. Meaning, find searches for items with type file and the .html extension, OR items that end with .js.

We can simply avoid this by adding another type to the right operand of the -o logical operator:

$ find -type f -name "*.html" -o -type f -name "*.js"
./1file.html
./1file.js
./2file.html
./2file.js
./3file.html
./3file.js

However, for more precise and concise expressions, it’s better to group our expressions with parenthesis:

$ find -type f '(' -name "*.html" -o -name "*.js" ')'
./1file.html
./1file.js
./2file.html
./2file.js
./3file.html
./3file.js

Parentheses are special characters to the shell as well. So, we should always wrap them with single quotes () or double quotes () or simply use backslashes \( …\) to escape them.

2.3. Using Regular Expressions

Many editions of the find command support the -regex test which locates filenames based on regular expression matching.

Let’s capture the same files with the previous extensions using regex:

$ find -type f -regex ".*\.\(html\|js\)"
./1file.html
./1file.js
./2file.html
./2file.js
./3file.html
./3file.js

Same as before, find located only files with either .html or .js extensions. We can break the pattern into three parts:

  • .* matches any character zero or more times
  • \. matches a single dot
  • \(html\|js\) matches either html or js at the end of the filename

In this case, the vertical bar character separates alternatives.

We can actually argue that the use of regex is more efficient than the native operators of the find command.

3. Copying Located Files Automatically

If we want to automatically copy any found files to a specific directory, we can use two methods. We could execute the cp command either through the -exec action of the find command, or through the xargs command.

Let’s copy the files that match our condition with -exec:

$ find -type f -regex ".*\.\(html\|js\)" -exec cp -v -t smara_dir.html {} \;

The above -exec action executes cp to copy each found file to the directory specified with the -t option. Moreover, the {} symbol is a placeholder that represents each file found by the find command.

Similarly, we can use the xargs command to pass each file as input to the cp command:

$ find -type f -regex ".*\.\(html\|js\)" | xargs -I {} cp -t smara_dir.html {}

Unlike our previous example, here, we use xargs to handle executing the cp command on the files captured by find. In this case, xargs lets us specify a placeholder with the -I option (short for -replace), meaning we can use characters or strings other than the usual {}.

4. Conclusion

In this article, we looked at how to use the find command to locate and copy files with specific extensions.

We learned how to combine logical operators and expressions to search for files based on various criteria, including type, name, extension, or regular expressions. Finally, we saw how to copy the files that match our search to another directory automatically using -exec and xargs.