返回

Linux 复制目录结构:不用 cp 命令,rsync/find/tar 轻松搞定

Linux

只复制目录结构,不复制文件?安排!

直接上问题:怎样把一个地方的文件夹结构(注意,只要结构,不要文件!),原封不动地搬到另一个地方?我有一个好几TB的文件服务器,想在Linux系统上克隆它的目录结构,这个文件服务器是用CIFS挂载到Linux上的。cp --parents 可以移动文件和它的父目录结构,但是好像没办法完整复制整个目录树?

原因分析

咱得先搞清楚,为什么普通的 cp 命令不行。 cp 命令主要设计来复制文件,或者复制文件和它们的上级目录(通过--parents)。 但是要复制整个,庞大的目录结构, 而不包含任何实际数据, cp 就有点力不从心了。 你也说了,服务器好几TB,要是全复制一遍,那得等到猴年马月?更何况,咱的目的只是要结构。

解决方案,安排!

下面这几个方法,都能帮你解决这个问题:

1. find + mkdir:精细控制,一步到位

这个方法用 find 命令找到所有目录,然后用 mkdir 在目标位置创建对应的目录。非常直接,非常有效。

  • 原理:

    • find 负责找到源目录下的所有目录。
    • mkdir-p 参数,能够创建多级目录,并且如果目录已经存在,也不会报错。
    • 通过巧妙地构造路径和参数来完成目录结构的复制。
  • 代码示例:

    # 假设源目录是 /mnt/cifs/source,目标目录是 /home/user/destination
    find /mnt/cifs/source -type d -print0 | while IFS= read -r -d $'\0' dir; do
      mkdir -p "/home/user/destination/$(echo "$dir" | sed 's|/mnt/cifs/source/||')"
    done
    
  • 代码解读

    • find /mnt/cifs/source -type d -print0
      • /mnt/cifs/source: 源路径
      • -type d:只查找目录。
      • -print0:用 null 字符分隔找到的目录名。这样可以安全地处理包含空格和其他特殊字符的目录名。
  • while IFS= read -r -d $'\0' dir; do ... done:

    • 使用 while 循环处理 find 命令的输出
    • IFS=: 确保 read 不会处理输入中的空格。
    • -r: 告诉read 不转义反斜线。
    • -d $'\0': 设置 read 命令的分隔符为null字符
  • mkdir -p "/home/user/destination/$(echo "$dir" | sed 's|/mnt/cifs/source/||')"

    • -p : 确保父目录已创建
    • /home/user/destination/: 目标目录的前缀
    • $(...): 将执行命令后的结果返回给命令
    • echo "$dir": 输入变量
    • sed 's|/mnt/cifs/source/||': 从目录路径中移除源目录部分,s|A|B|表示使用 B 替换 A.
  • 进阶用法 & 安全建议

    • 如果你对要操作的目录很熟悉,可以用shell的参数展开,获得稍微快一点的速度:

        find /mnt/cifs/source -type d -print0 | while IFS= read -r -d $'\0' dir; do
          mkdir -p "/home/user/destination/${dir#/mnt/cifs/source/}"
        done
      
      • ${dir#/mnt/cifs/source/} 的作用与sed指令的例子相同.
    • 在执行前,最好先用 ls -ld /mnt/cifs/source/* 大概看看目录结构,心里有个数,避免出错。

    • 执行时,一定一定要看清楚源目录和目标目录,别搞反了!

2. rsync:专业选手,功能强大

rsync 本来是用来同步文件和目录的,但是它有一个非常巧妙的用法,可以只复制目录结构。

  • 原理:

    • rsync-d--dirs 选项,可以复制目录,但不递归复制目录下的内容。
    • 搭配上 --include--exclude参数实现我们需要的功能.
    • 我们巧妙地用 --include 包含所有目录,用 --exclude 排除所有文件,就达到了只复制目录结构的目的。
  • 代码示例:

    rsync -av --include='*/' --exclude='*' /mnt/cifs/source/ /home/user/destination/
    
  • 命令解读

  • rsync -av ...:

    • -a: archive 模式,相当于-rlptgoD,会保留各种文件属性。
    • -v: verbose 模式,显示详细信息。
      * --include='*/': 包含所有以 / 结尾的路径,也就是所有目录。
      * --exclude='*': 排除所有其他东西(文件)。
      * /mnt/cifs/source/: 源目录,最后的斜杠不可省略.
      * /home/user/destination/: 目标目录
  • 进阶技巧 & 安全建议:

    • rsync 有一个 --dry-run-n 选项,可以先模拟执行,看看会发生什么,不会真的修改文件。强烈建议在实际执行前先模拟一下。
    rsync -avn --include='*/' --exclude='*' /mnt/cifs/source/ /home/user/destination/
    
    • 如果源目录或目标目录在网络上(比如通过NFS挂载),rsync 也可以很好地工作。

3. tar:打包解包,曲线救国

这个方法稍微绕一点,先把目录结构打包,然后再解包到目标位置。

  • 原理:

    • tar 命令可以创建压缩包,我们可以创建一个只包含目录结构的压缩包。
    • -f -: 表示输出到标准输出(或从标准输入读取)。
      • --null -T - 使用空字符作为分隔符
      • -T: 表示要操作的文件名从文件里读取
    • 在解包的时候,只解压目录结构。
    • 通过 find 指令与 tar 配合可以高效创建仅有结构的包文件,再进行解包。
  • 代码示例:

    # 创建只包含目录结构的压缩包
    find /mnt/cifs/source -type d -print0 | tar --null -T - -cf - | (cd /home/user/destination && tar -xf -)
    
  • 指令解读

    • find /mnt/cifs/source -type d -print0: 与方法1中的指令相同

    • tar --null -T - -cf -:

      • --null -T -:将上一步的结果(使用null分隔)输入tar中.
      • -c: create, 创建一个新的归档文件。
      • -f -: 配合-c使用,这里 - 代表将结果输出到stdout。
    • (cd /home/user/destination && tar -xf -):

      • (...) 开启一个子进程并在子进程内操作.
      • cd /home/user/destination:切换到目标路径下
      • tar -xf -:
      • -x: extract,解压缩归档文件。
      • -f -: 表示从标准输入中读取数据,并解压到当前文件夹下.
  • 安全建议

    • 因为存在中间打包, 所以比上面两种稍微慢一些。

4. 对于 BSD 系统的补充:pax 指令

  • pax是比tar更通用的可移植归档工具,在某些 BSD 家族系统中默认可用. 这种方法类似于方法 3 (tar).

    find /mnt/cifs/source -type d -print0 | pax -rw -'s,.*/,,' -pe /home/user/destination
    
  • 指令解读:

    • find /mnt/cifs/source -type d -print0: 不再解释.

    • pax -rw -'s,.*/,,' -pe /home/user/destination

    • -r: 读取

    • -w: 写入

    • -s: 路径替代,后跟正则表达式

    • -'s,.*/,,': 从路径中移除所有路径,只保留文件夹的名字.

    • -p: 保留原属性,后可跟额外参数.
      * -pe: 保留所有模式和扩展属性

以上,几种方法, 任君选择. 看你喜欢哪个. 一般情况下, rsync 最方便,find + mkdir 最直观, tar比较有趣.