When we want to copy directories recursively in the Linux command-line, cp -r could be the first command to come up.
We know that dotfiles are treated as hidden files in Linux. Sometimes, when we copy directories recursively, we want to exclude hidden files and directories under the directories.
In this quick tutorial, we’ll learn how to achieve that.
2. Introduction to the Problem
First of all, let’s build up a directory tree as an example:
$ tree -a parentDir parentDir ├── file1 ├── file2 ├── .hidden_dir │ ├── .hidden_sub2 │ ├── sub2_file1 │ └── sub2_file2 ├── .hidden_parent └── sub1 ├── .hidden_sub1 ├── sub1_file1 └── sub1_file2 2 directories, 9 files
We’ve used the handy tree command to present the content under the parentDir directory.
Since the directory and its subdirectories contain hidden files and directories, we’ve passed the -a option to the tree command to show the hidden objects.
Now, let’s copy the parentDir recursively to a new directory using the cp -r command, say parentDir2:
$ cp -r parentDir parentDir2 $ tree -a parentDir2
parentDir2 ├── file1 ├── file2 ├── .hidden_parent ├── sub1 │ ├── .hidden_sub1 │ ├── sub1_file1 │ └── sub1_file2 └── .hidden_dir ├── .hidden_sub2 ├── sub2_file1 └── sub2_file2 2 directories, 9 files
As the output above shows, the directory is successfully copied. However, we’ve copied those hidden files to the new directory as well.
Our goal is to copy directories recursively, excluding hidden directories and files.
Next, let’s see how to solve the problem.
3. Copying Everything Then Removing Hidden Files
One idea may come up to solve the problem: first copying everything recursively and then removing hidden objects from the target directory.
Now, let’s translate this idea into a command and give it a try:
$ cp -r parentDir parentDir2 && find parentDir2 -name '.*' | xargs rm -rf $ tree -a parentDir2 parentDir2 ├── file1 ├── file2 └── sub1 ├── sub1_file1 └── sub1_file2 1 directory, 4 files
As we can see in the tree output, all hidden objects are not in the parentDir2 directory. So the command works for the example.
This approach is straightforward and works for most cases. However, it may have some problems. So next, let’s take a closer look at them.
First, a hidden file or directory can be huge. If this is the case, this approach will unnecessarily copy the large objects and then delete them. That is to say, it may lead to a performance problem.
In a worse case, if the hidden objects are large enough, the target device may not have enough space to store them. Thus, the command may fail.
Second, sometimes, hidden files or directories contain sensitive data. That’s one reason we want to exclude them during copying.
If we copy everything to the target, the sensitive data will be automatically transferred to the target devices. We may think that would be ok since we delete them immediately after the cp command is done.
Once the copy command fails or takes a long time, the sensitive data can stay on an insecure device for a pretty long time. Further, if the target device is a remote filesystem, we don’t know if file-synchronization services are running.
Therefore, if the hidden objects contain sensitive data, this approach may create security holes.
4. Using the rsync Command
rsync is a powerful and convenient file-copying utility. It accepts two valuable options that allow us to include or exclude files during the copy: –include=PATTERN and –exclude=PATTERN.
To skip hidden files and directories, we can pass the “.*” pattern to the –exclude option. We should note that here, the “.*” pattern is not a regex. Instead, it indicates any filename or directory name beginning with a dot.
Now, let’s give it a try:
$ rsync -av --exclude=".*" parentDir/* parentDir3 sending incremental file list created directory parentDir3 file1 file2 sub1/ sub1/sub1_file1 sub1/sub1_file2 sent 315 bytes received 137 bytes 904.00 bytes/sec total size is 0 speedup is 0.00 $ tree -a parentDir3 parentDir3 ├── file1 ├── file2 └── sub1 ├── sub1_file1 └── sub1_file2 1 directory, 4 files
As the output above shows, all hidden objects are not copied to the parentDir3 directory. So, the command does the job.
The rsync command supports the filter option -f to make including/excluding definitions easier.
For example, we can write our command in a shorter form:
rsync -av -f "- .*" parentDir/* parentDir3
In the command above, the -f option defines a filter, and a rule follows. When rsync works, it’s going to apply the filters on files it needs to copy.
A plus “+” indicates “include”, while a minus “-” means “exclude”. Thus, in our command, we exclude all hidden files and directories via “- .*”.
In this article, we’ve addressed how to copy directories recursively, excluding hidden files and directories.
The “copy everything then remove hidden objects” approach is straightforward. However, it may bring performance problems and security flaws.
When rsync is available on the system, we can consider using this file-copying tool to do the job.