1. Overview

File copying is a common file operation when we work with the Linux command-line. Usually, we’ll use the cp command to copy files.

However, the cp command won’t work if the target directory doesn’t exist.

In this tutorial, we’ll discuss how to create the non-existing target directory automatically when we copy files.

2. Introduction to the Problem

To understand the problem clearly, let’s see an example of copying a file to a non-existing target directory:

$ cp $HOME/.vimrc /tmp/test/one/non-exist/backup/dir/myVimrc 
cp: cannot create regular file '/tmp/test/one/non-exist/backup/dir/myVimrc': No such file or directory

In the example above, we attempted to copy the .vimrc file from the current user’s home directory to a target directory /tmp/test/one/non-exist/backup/dir/.

The cp command refuses to do that because the target directory doesn’t exist.

This happens pretty often when we copy files:

  • First, we execute the cp command
  • Then, we see the error message and realize, “Oh, I forgot to create the directory.”
  • We execute the mkdir command to create the target directory
  • Finally, we execute the cp command once again

Obviously, this is inefficient. In this tutorial, we’re going to create the non-existing target directory automatically when we copy files.

We’ll address three approaches to achieve this goal:

  • Combining the mkdir and the cp commands
  • Writing a simple shell function to wrap the cp command
  • Using the install command

3. Combining the mkdir and the cp Commands

The mkdir command was born to create directories. It has a -p option to create parent directories we need. Moreover, it reports no error if the target directory exists already.

Also, we know that we can connect two commands using the && operator, such as cmd1 && cmd2. In this way, cmd2 will be started only if cmd1 has been executed successfully (with exit code = 0).

Therefore, we can connect the mkdir and cp commands with && so that we can create the target directory automatically:

$ mkdir -p /tmp/test/one/non-exist/backup/dir && cp $HOME/.vimrc /tmp/test/one/non-exist/backup/dir/myVimrc

$ ls -l /tmp/test/one/non-exist/backup/dir/myVimrc
-rw-r--r-- 1 kent kent 3.4K Mar 19 14:50 /tmp/test/one/non-exist/backup/dir/myVimrc

As the output above shows, we created the target directory and copied the file to it in one shot.

However, we may also notice that we have to type the long path /tmp/test/one/non-exist/backup/dir twice. It’s inconvenient, particularly when the directory doesn’t exist, and the shell’s tab completion won’t help us either.

Now, it’s time to introduce the special Bash variable $_The underscore variable $_ expands to the last argument of the last command executed.

Next, let’s see how to use this special variable to make typing our command easier:

$ mkdir -p /tmp/test/one/non-exist/backup/dir && cp $HOME/.vimrc $_/myVimrc

$ ls -l /tmp/test/one/non-exist/backup/dir/myVimrc
-rw-r--r-- 1 kent kent 3.4K Mar 19 15:41 /tmp/test/one/non-exist/backup/dir/myVimrc

In this example, we use the special $_ variable in the cp command.

When we execute the cp command, the last executed command is the mkdir command in front of the &&.

Further, the last argument of the mkdir command is the target directory, which is exactly the argument we don’t want to type once again.

Our mkdir and cp combination is easier to use now. However, it still isn’t perfect.

Usually, when we’re about to copy files, we type something like cp sourceFile targetFile.

But with our combination, we have to type mkdir first, then comes the target directory without the target file, then the && operator, and the cp command with the special $_ variable.

Wouldn’t it be nice if we could achieve our goal more naturally?

Let’s figure it out in the next section.

4. Writing a Simple Bash Function

In the previous section, we’ve learned we can combine mkdir -p and cp to automatically create the target directory when we copy files.

But we cannot naturally type the command like cp sourceFile targetFile. To solve that problem, we can wrap the combination in a Bash function:

CP() {
    mkdir -p $(dirname "$2") && cp "$1" "$2"
}

In the code snippet above, we declare a function CP. It can have two arguments, $1 and $2. The $1 argument is the source file we want to copy, while the $2 argument stores the target path.

We use command substitution $(dirname “$2”) to extract the target directory and pass it to the mkdir -p command.

Now let’s save the function in a file func.sh, source the file, and test if it works as we expect:

$ source func.sh 
$ CP $HOME/.vimrc /tmp/test/one/non-exist/backup/dir/myVimrc

$ ls -l /tmp/test/one/non-exist/backup/dir/myVimrc
-rw-r--r-- 1 kent kent 3464 Mar 19 17:08 /tmp/test/one/non-exist/backup/dir/myVimrc

As we can see in the output above, the target directory has been created automatically, and the file has been copied as well.

We can put the function in our $HOME/.bashrc file to source it automatically every time we log in. In this way, we can use the CP “command” to copy files without having to take care of the “target directory doesn’t exist” problem separately.

5. Using the install Command

The install command is a member of the GNU coreutils package. Therefore, it is available on all Linux distros.

We can use the install command to copy files and set file attributes.

Similar to the cp command, the install command supports various options. However, we won’t dive into this command’s usage and make this into a tutorial of the install command.

We want to automatically create the non-existing target directory during file copying. This is exactly what the -D option is for:

$ install -D $HOME/.vimrc /tmp/test/one/non-exist/backup/dir/myVimrc

$ ls -l /tmp/test/one/non-exist/backup/dir/myVimrc
-rwxr-xr-x 1 kent kent 3464 Mar 19 17:40 /tmp/test/one/non-exist/backup/dir/myVimrc

Yes! It works as we expected. The directory has been created, and the file has been copied, too.

However, if we check the ls -l output above and compare it to earlier outputs carefully, we see some difference even though the myVimrc file is copied from the same source:

-rw-r--r-- 1 kent kent ... # <-- file copied by the cp command
-rwxr-xr-x 1 kent kent ... # <-- file copied by the install -D command

Why was the file copied by the install command set to be executable automatically?

This is because the file copied by the install command will have the permission “-rwxr-xr-x” (0755) by default. We can set the target file’s permission using the -m option:

$ install -D -m 644 $HOME/.vimrc /tmp/test/one/non-exist/backup/dir/myVimrc

$ ls -l /tmp/test/one/non-exist/backup/dir/myVimrc
-rw-r--r-- 1 kent kent 3464 Mar 19 17:54 /tmp/test/one/non-exist/backup/dir/myVimrc

Now, the copied file has the permission bits 644.

6. Conclusion

The cp command will abort if the target directory doesn’t exist. Sometimes, this brings inconvenience when we work with files in the Linux command line.

In this article, we’ve addressed three techniques to create the non-existing target directory automatically during file copying:

  • mkdir -p && cp
  • A simple Bash function wrapping the combination above
  • install -D (-m)

When we use the install command, we should keep in mind that it’ll set 0755 as the copied file’s permission. We should use the -m option to set the permission if we’re not happy with the default permission.