1. Overview

In Linux, copying files is easy and straightforward. However, copying specific file types while retaining the directory structure requires a more delicate approach. This can be useful if we want to backup files in a directory structure that’s already familiar to us, for example.

In this tutorial, we’ll explore various commands that we can use to achieve this task.

2. Using the find Command

The find command helps us search for files as well as directories. Typically, this search depends on different criteria like file type, file name, and more.

In this section, we’ll first explore combining find with the cp command. Then, we’ll discuss it in combination with the tar command.

2.1. Combining find With the cp Command

This combination enables us to copy specific file types and, in the process, retain the directory structure.

First, let’s use ls to view the contents of our directory of interest:

$ ls -R source
source:
file_1.txt  file_2.txt  script_1.sh  script_2.sh  source_2

source/source_2:
file_3.txt  file_4.txt

The source directory contains a few files and a subdirectory named source_2. This subdirectory holds two text files.

In this next step, let’s navigate to the source directory and copy all the .txt file types to an empty directory destination. In our example, the destination directory exists at the same directory level as the source directory:

$ find ./ -type f -name "*.txt" -exec cp --parents {} ../destination \;

Let’s break down the command:

  • ./ – represents the directory path to start our search, in this case, the source directory
  • -type f – instructs the find command to only look for files
  • -name “*.txt” – defines the file extension to consider during the search, in this case, .txt
  • -exec cp –parents {} ../destination \; – ensures that the cp command is executed for each file found while keeping the directory structure. The –parents option ensures the files are copied with their original directory structure. Meanwhile, the placeholder {} holds each file found while ../destination is the path to copy the files to

Above, we’ve managed to copy all the text files to the destination directory. At the same time, we retained the directory structure.

Now, let’s view the contents of the destination directory:

$ ls -R ../destination
../destination:
file_1.txt  file_2.txt  source_2

../destination/source_2:
file_3.txt  file_4.txt

From the output above, we see that this directory contains all the text files from the source directory. Most importantly, the directory structure persists.

2.2. Combining find With the tar Command

The tar command is known for creating and extracting archives. However, we can also use it to copy files while retaining the directory structure:

$ find ./ -type f -name "*.txt" -exec tar -cf - {} + | tar -xf - -C ../destination

Let’s understand the above command:

  • find ./ -type f -name “*.txt” – searches for all .txt file types in the current directory
  • -exec tar -cf – {} + – the -exec option executes the tar -cf – {} + for each file found by find. To elaborate, the tar command creates a tar archive with the help of the -c option. Meanwhile, the -f option ensures that the archive is created on the standard output instead of on the file system. {} represents the placeholder for each file to include in the archive. Finally, + instructs find to provide as many files as it can to each execution of tar.
  • |pipes the now-created archive as input to the next tar command
  • tar -xf – -C ../destinationthe tar options (-xf –) instruct tar to extract (-x) the archive data from standard input (-f –). Meanwhile -C ../destination specifies the directory path to copy the extracted data.

In this example, we avoid creating the tar archive on the filesystem.

3. Using the rsync Command

rysnc is a resourceful command line tool for copying files both locally and remotely. Furthermore, we can use it to preserve the directory structure of the files during the copying process. To clarify, we’ll work locally with the same files as the ones in the previous section.

From the terminal, let’s navigate to the source directory and execute our command:

$ rsync -av --include='*/' --include='*.txt' --exclude='*' ./ ../destination
sending incremental file list
./
file_1.txt
file_2.txt
source_2/
source_2/file_3.txt
source_2/file_4.txt
...

This output displays the progress of the copying process. The source_2 directory is also copied since it contains files that we’re interested in.

Let’s break down the command to understand it:

  • -av – the -a option retains file attributes like ownership, permissions, and timestamps, while the -v option displays the progress of the copying process
  • –include=’*/’ – includes every directory in the source directory that contains the defined .txt file type
  • –include=’*.txt’ – includes all the .txt file types in the copy
  • –exclude=’*’ – excludes all other files
  • ./ – represents the directory path to start our search from, in our case, the current working directory
  • ../destination – defines where to copy our files to

At this point, all the .txt files have been copied to the destination directory. Also, the directory structure matches the one in the source directory.

4. Conclusion

In this article, we learned different ways to copy specific file types while keeping the directory structure. First, we discussed using the find command in combination with the cp and tar commands. Then, we implemented a Bash script to automate our task. Afterward, we took a look at the rsync command.

Any of these approaches is suitable depending on our preferences. We can easily create backups of specific file types while maintaining their original format.