Contents

复制并创建目标目录(如果不存在)

1. 概述

当我们使用 Linux 命令行时,文件复制是一种常见的文件操作。通常,我们会使用cp 命令来复制文件。

但是, 如果目标目录不存在, cp命令将不起作用。

在本教程中,我们将讨论如何在复制文件时自动创建不存在的目标目录。

2. 问题介绍

为了清楚地理解问题,让我们看一个将文件复制到不存在的目标目录的示例:

$ 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

在上面的示例中,我们尝试将 .vimrc文件从当前用户的主目录复制到目标目录*/tmp/test/one/non-exist/backup/dir/*。

cp命令拒绝这样 做,因为目标目录不存在。

当我们复制文件时,这种情况经常发生:

  • 首先,我们执行cp命令
  • 然后,我们看到错误消息并意识到,“哦,我忘记创建目录了。”
  • 我们执行mkdir 命令创建目标目录
  • 最后,我们再次执行cp命令

显然,这是低效的。在本教程中,我们将在复制文件时自动创建不存在的目标目录。

我们将介绍实现这一目标的三种方法:

  • 结合 mkdircp 命令
  • 编写一个简单的shell函数来包装cp命令
  • 使用 install 命令

3. 结合mkdir和 cp命令

mkdir命令是为创建目录而生的。 它有一个*-p*选项来创建我们需要的父目录。此外,如果目标目录已经存在,它不会报告错误。

此外,我们知道我们可以使用&& 运算符连接两个命令,例如cmd1 && cmd2。这样,只有当cmd1已成功执行(退出代码 = 0)时, 才会启动cmd2

因此,我们可以将mkdircp命令用*&&*连接起来, 这样我们就可以自动创建目标目录:

$ 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

如上面的输出所示,我们创建了目标目录并将文件一次性复制到其中。

但是,我们也可能注意到我们必须输入长路径*/tmp/test/one/non-exist/backup/dir*两次。这很不方便,特别是当目录不存在时,shell 的 tab 补全也对我们没有帮助。

现在,是时候介绍特殊的 Bash 变量*$_ *了。 下划线变量$_扩展为执行的最后一个命令的最后一个参数。

接下来,让我们看看如何使用这个特殊变量来简化我们的命令输入:

$ 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

在这个例子中,我们在cp命令中使用了特殊的*$_*变量。

当我们执行cp命令时,最后执行的命令是*&&前面的mkdir*命令。

此外,  mkdir命令的最后一个参数是目标目录,这正是我们不想再次键入的参数。

我们的mkdir和 cp组合现在更易于使用。然而,它仍然不完美。

通常,当我们要复制文件时,我们会键入类似*cp sourceFile targetFile *的内容。

但是使用我们的组合,我们必须先键入 mkdir,然后是没有目标文件的目标目录,然后是*&&运算符,以及带有特殊$_变量的cp*命令。

如果我们能更自然地实现我们的目标不是很好吗?

让我们在下一节中弄清楚。

4. 编写一个简单的 Bash 函数

在上一节中,我们了解到我们可以结合 mkdir -p和 cp在复制文件时自动创建目标目录。

但是我们不能自然地键入像cp sourceFile targetFile这样的命令。为了解决这个问题,我们可以将组合包装在一个 Bash 函数中:

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

在上面的代码片段中,我们声明了一个函数CP。它可以有两个参数,$1和*$2*。$1参数是我们要复制的源文件,而$ 2参数存储目标路径。

我们使用命令替换 $(dirname “$2”)来提取目标目录并将其传递给mkdir -p命令。

现在让我们将函数保存在文件func.sh中,获取文件,并测试它是否按预期工作:

$ 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

正如我们在上面的输出中看到的,目标目录已自动创建,文件也已被复制。

*我们可以把这个函数放在我们的$HOME/.bashrc文件中,以便每次登录时自动获取它。**这样,我们可以使用CP*命令来复制文件,而不必关心“目标目录”存在的问题。

5.使用install命令

** install命令是*GNU coreutils 包的成员。因此,它适用于所有 Linux 发行版。*

我们可以使用 install命令来复制文件和设置文件属性。

cp命令类似,install命令支持各种选项。但是,我们不会深入研究此命令的用法,并将其作为安装命令的教程。

我们希望在文件复制期间自动创建不存在的目标目录。这正是*-D*选项的用途:

$ 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

是的!它按我们的预期工作。目录已创建,文件也已复制。

但是,如果我们检查上面的ls -l输出并将其与之前的输出仔细比较,我们会发现一些差异,即使myVimrc文件是从同一源复制的:

-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

为什么安装命令复制的文件设置为自动可执行?

*这是因为install命令复制的文件默认具有“ -rwxr-xr-x ”(0755)权限。*我们可以使用-m选项设置目标文件的权限:

$ 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

现在,复制的文件具有权限位644