1. Overview

We know we can copy a directory recursively using the cp command and the -R option. In this way, the source directory, together with all the files under it, will be copied to the destination.

Sometimes, we don’t want to copy all the files from the source directory. Instead, we only want to copy the empty directory structure to the target directory.

In this tutorial, we’re going to address how to achieve that.

2. Introduction to the Problem

To understand the problem clearly, let’s have a look at a directory example rootDir:

$ tree -a rootDir 
rootDir
├── dir1
│   ├── 1_level.file
│   └── dir1_1
│       └── 1_1_level.file
├── dir2
│   └── 2_level.file
├── dir3
│   └── dir3_1
│       ├── 3_1_level.file
│       └── dir3_1_1
│           └── 3_1_1_level.file
└── root_level.file

6 directories, 6 files

In the rootDir directory, we have different levels of subdirectories. Also, six files are located in the directories.

Our goal is to copy the empty directory structure of the rootDir directory to another location so that in the target directory, we have:

rootDir
├── dir1
│   └── dir1_1
├── dir2
└── dir3
    └── dir3_1
        └── dir3_1_1

A straightforward idea may come up to solve the problem: First, we copy everything under rootDir recursively to the target directory and then remove all the files.

This can solve the problem. However, if the source directory has many files or contains large files, copying can take a very long time. We’re looking for more efficient solutions to the problem.

In this tutorial, we’re going to address three approaches to solve this problem:

  • Using the tree command and the xargs command
  • Using the find command
  • Using the rsync command

Next, let’s see how to copy the directory structure in detail.

3. Using the tree Command and the xargs Command

If we want to clone an empty directory structure, we can do it in two steps:

  1. Getting all directory paths we want to create
  2. Taking the directory paths as the input, using the mkdir -p to create those directories

Next, let’s see how to do the two steps using the tree and xargs commands.

3.1. Getting All Directory Paths

As we’ve seen in the previous section, the tree command can print contents in a directory in a tree-like format, which is pretty convenient to read.

Moreover, we can pass some option combinations to the command to output all the directory paths.

First, let’s enter the parent of our rootDir directory and print all the directory paths under it:

$ tree -dfi --noreport rootDir
rootDir
rootDir/dir1
rootDir/dir1/dir1_1
rootDir/dir2
rootDir/dir3
rootDir/dir3/dir3_1
rootDir/dir3/dir3_1/dir3_1_1

Next, let’s walk through the options we passed to the tree command and understand what they mean:

  • -d: We ask the tree command to print directories only
  • -f: The tree command will print the full path for us
  • -i: Usually, tree outputs in a tree format. In other words, lines are well indented in the output. However, this option will prevent tree from printing indentation lines
  • –noreport: This flag suppresses the summary report at the end of the output, such as “x directories, y files

3.2. Creating Directories

Now, we have paths of all the directories under our source directory: rootDir. 

The next step would be straightforward: pass each path to the mkdir -p command and create those directories under our target directory.

Let’s say we want to clone the rootDir structure under /tmp/test. Let’s see how to get it done:

$ tree -dfi --noreport rootDir | xargs -I{} mkdir -p "/tmp/test/{}"

$ cd /tmp/test
$ tree -a rootDir 
rootDir
├── dir1
│   └── dir1_1
├── dir2
└── dir3
    └── dir3_1
        └── dir3_1_1

6 directories, 0 files

As the example above shows, we pipe the output of the tree command to the xargs command to pass each directory path to the mkdir command.

Since we execute this command under the parent directory of our source rootDir directory, we need to add our target directory “/tmp/test” as the prefix of each path read by the xargs command.

After we execute the command, we verify it using the tree command again. However, this time we use the -a option to tell the tree command to print all contents, including files.

The output shows that we’ve cloned the clean rootDir directory structure under /tmp/test without any files. That is to say, we’ve solved the problem.

4. Using the find Command

We’ve learned to use the tree command to get all directory paths under the source directory. Similarly, we can also get those paths using the find command:

$ find rootDir -type d
rootDir
rootDir/dir3
rootDir/dir3/dir3_1
rootDir/dir3/dir3_1/dir3_1_1
rootDir/dir1
rootDir/dir1/dir1_1
rootDir/dir2

We can pipe the find output above to the xargs command to create the directories, as we’ve done with the tree output:

$ find rootDir -type d | xargs -I{} mkdir -p "/tmp/test/{}"

$ cd /tmp/test
$ tree -a rootDir 
rootDir
├── dir1
│   └── dir1_1
├── dir2
└── dir3
    └── dir3_1
        └── dir3_1_1

6 directories, 0 files

Alternatively, we can also use the find command’s -exec argument to execute the mkdir -p command:

$ find rootDir -type d -exec mkdir -p "/tmp/test/{}" \;
$ cd /tmp/test
$ tree -a rootDir
rootDir
├── dir1
│   └── dir1_1
├── dir2
└── dir3
    └── dir3_1
        └── dir3_1_1

6 directories, 0 files

Each found directory path will fill the placeholder “*{}*” in the mkdir command. Also, *the ending “\;” is required*. It is the command delimiter of the -exec option.

5. Using the rsync Command

The rsync command is a powerful command-line file synchronization and copying utility.

Finally, let’s see how to clone a clean directory structure using the rsync command:

$ rsync -av -f"+ */" -f"- *" "/path/to/the/source/rootDir" "/tmp/test"
sending incremental file list
rootDir/
rootDir/dir1/
rootDir/dir1/dir1_1/
rootDir/dir2/
rootDir/dir3/
rootDir/dir3/dir3_1/
rootDir/dir3/dir3_1/dir3_1_1/

sent 258 bytes  received 44 bytes  604.00 bytes/sec
total size is 0  speedup is 0.00

$ cd /tmp/test
$ tree -a rootDir 
rootDir
├── dir1
│   └── dir1_1
├── dir2
└── dir3
    └── dir3_1
        └── dir3_1_1

6 directories, 0 files

The options we passed to the rsync command look a bit obscure. Next, let’s go through them quickly.

The -a option asks rsync to do an “archive” copying. In other words, the copying will be done recursively and with file permission preserved. Next, the -v option means verbose. With this option, we can see a detailed log of what the rsync command has done.

Next, the tricky part -f”+ */” -f”- *” comes.

Each -f option defines a filter, and a filter rule follows a filter. When rsync works, it’s going to apply the filters on files it wants to copy.

A plus “+” indicates “include”, while a minus “-” means “exclude”. In our command, we include all directories “+ */” but exclude all files “- *”. Thus, the rsync command will only copy directories under the source directory and skip all files.

6. Conclusion

In this article, we’ve discussed how to clone a directory structure without copying files under it.

We’ve learned three different ways to achieve that through examples.