【问题标题】:Unix Shell scripting for copying files and creating directory用于复制文件和创建目录的 Unix Shell 脚本
【发布时间】:2009-01-30 08:32:02
【问题描述】:

我有一个源目录,例如 /my/source/directory/ 和一个目标目录,例如 /my/dest/directory/,我想用一些镜像约束。

  • 我想将满足 find 命令某些条件的文件,例如 -ctime -2(小于 2 天)复制到 dest 目录以进行镜像
  • 我想包含一些前缀,以便我知道它来自哪里,例如 /source/directory
  • 我想用绝对路径来做这一切,所以它不依赖于我从哪个目录运行
  • 我想没有 cd 命令也是一种好习惯。
  • 如果子目录不存在,我希望创建子目录

所以

/my/source/directory/1/foo.txt -> /my/dest/directory/source/directory/1/foo.txt
/my/source/directory/2/3/bar.txt -> /my/dest/directory/source/directory/2/3/bar.txt

我已经将以下命令行组合在一起,但看起来有点难看,有人可以做得更好吗?

find /my/source/directory -ctime -2 -type f -printf "%P\n" | xargs -IFILE rsync -avR /my/./source/directory/FILE /my/dest/directory/

如果您认为我应该自己添加此命令行作为答案,请发表评论,我不想贪婪地获得声誉。

【问题讨论】:

  • 请让您的答案成为答案。有什么难看的?
  • 它不使用空终止符,所以它会在名称中包含空格和换行符的文件上失败?

标签: shell scripting directory mirroring


【解决方案1】:

这与一个(封闭的)问题非常相似:Bash scripting copying files without overwriting。我给出的答案引用了其他答案中提到的“find | cpio”解决方案(减去时间标准,但这就是“相似”和“相同”之间的区别),并且还概述了使用 GNU 'tar' 的解决方案。

ctime

当我在 Solaris 上进行测试时,GNU tar 和 (Solaris) cpio 都无法保留 ctime 设置;实际上,我不确定是否有任何方法可以做到这一点。例如,touch 命令可以设置 atime 或 mtime 或两者,但不能设置 ctime。 utime() 系统调用也只接受 mtime 或 atime 值;它不处理 ctime。所以,我相信,如果你找到了保留 ctime 的解决方案,那么该解决方案很可能是特定于平台的。 (奇怪的例子:破解磁盘设备并编辑 inode 中的数据 - 不可移植,需要提升权限。)不过,重读这个问题,我发现“保留 ctime”不是要求的一部分(呸);它只是文件是否被复制的标准。

chdir

我认为 'cd' 操作是必要的 - 但是它们可以完全本地化到脚本或命令行,如引用的问题和下面的命令行所示,其中第二个假设 GNU tar .

(cd /my; find source/directory -ctime -2 | cpio -pvdm /my/dest/directory)

(cd /my; find source/directory -ctime -2 | tar -cf - -F - ) |
    (cd /my/dest/directory; tar -xf -)

不使用chdir()(又名cd),您需要专门的工具或选项来处理动态的路径名操作。

带有空格、换行符等的名称

正如 Adam Hawes 所指出的,GNU 特定的“find -print0”和“xargs -0”非常强大和有效。有趣的是,GNU cpio 有一个选项来处理来自“find -print0”的输出,即“--null”或其缩写形式“-0”。因此,使用 GNU find 和 GNU cpio,安全命令是:

(cd /my; find source/directory -ctime -2 -print0 |
    cpio -pvdm0 /my/dest/directory)

注意:这不会覆盖备份目录下已有的文件。为此,将-u 添加到cpio 命令中。

同样,GNU tar 支持 --null(显然没有 -0 短格式),也可以使用:

(cd /my; find source/directory -ctime -2 -print0 | tar -cf - -F - --null ) |
    (cd /my/dest/directory; tar -xf -)

GNU 对带有空终止符的文件名的处理非常聪明,也是一项有价值的创新(尽管我最近才意识到这一点,感谢 SO;它在 GNU tar 中已经存在至少十年了)。

【讨论】:

  • OP 说“镜像”所以cpio 需要-u。我不记得 GNU utils 不支持 -0 选项的时候。这样的命令行工具就像不安全的剃须刀。很容易射中自己的脚。很好的答案!
  • 嗯...我去查看了 1999 年的 GNU tar 1.13 源代码,它就在那里。也许“最近”的意思是“我最近才意识到这件事——谢谢你,所以”。
【解决方案2】:

你可以尝试 cpio 使用 copy-pass 模式,-p。我通常使用它来覆盖所有(-u),创建目录(-d),并保持修改时间(-m)。

find myfiles | cpio -pmud target-dir

请记住,find 应该生成相对路径名,这不符合您的绝对路径标准。这种感冒当然可以用cd来“解决”,你也不喜欢(为什么不呢?)

(cd mypath; find myfiles | cpio ... )

括号将生成一个子shell,并将状态更改(即目录切换)保持在本地。您还可以定义一个小程序来抽象出“丑陋”。

【讨论】:

  • 我一直对脚本中的 cd 感到有点不自在。也许这只是过于紧张,但我总是喜欢在任何上下文中工作并且对状态没有影响的命令。不过,我不是一个 guru shell scriptet,这可能是不合理的
  • 这就是我把它放在括号中的原因,这会产生一个子shell,因此在括号之外不会有副作用。我会补充...
【解决方案3】:

如果您使用 find ,请始终使用 -print0 并通过 xargs -0 管道输出;几乎总是。如果您使用 find 的默认换行符终止符输出,则名称中包含空格的第一个文件将破坏脚本。

我同意所有其他发帖者的观点 - 如果可以,请使用 cpio 或 tar。它会做你想做的事并省去麻烦。

【讨论】:

    【解决方案4】:

    另一种方法是使用 tar, (cd $SOURCE; tar cf - .) | (cd $DESTINATION; tar xf -)

    编辑: 啊,我错过了有关保留 CTIME 的部分。我相信 tar 的大多数实现都会保留 mtime,但如果保留 ctime 很关键,那么 cpio 确实是唯一的方法。

    此外,一些 tar 实现(GNU tar 就是其中之一)可以根据 atime 和 mtime 选择要包含的文件,尽管似乎不是 ctime。

    【讨论】:

    • 不错的答案,我没想到 tar。
    • 实际上,这并不管理条件并且基于文件时间
    • @vatine:幸运的是,它不是保留 ctime,而是简单地根据 ctime 进行选择。
    【解决方案5】:
    #!/bin/sh
    
    SRC=/my/source/directory
    DST=/my/dest/directory
    
    for i in $(find $SRC -ctime -2 -type f) ; do
        SUBDST=$DST$(dirname $i)
        mkdir -p $SUBDST
        cp -p $i $SUBDST
    done
    

    而且我想,既然你想包含“它来自哪里”,那么你将使用不同的源目录。该脚本可以修改为将源目录作为参数,只需将 SRC=/my/source/directory 替换为 SRC=$1

    编辑:删除多余的 if 语句。

    当文件名有空格时不起作用。

    【讨论】:

    • 如果目录已经存在,我认为 mkdir -p 会成功,所以 if 有点多余。您可以将它与存在测试结合使用,以确保它不是文件。
    • 文件名或目录名中的任何空格或特殊字符都会导致此问题。在脚本中总是引用这些变量;例如mkdir -p "$SUBDST"。最好像其他人建议的那样只使用 find 和 cpio 而不是重新创建非圆形轮子。
    • 引用根本没有帮助,因为这完全是关于 for 循环 (IFS) 的分隔符。我同意在这种情况下最好使用 cpio,但是如果你想实现基于文件名的自定义逻辑,而不是像 cpio 那样复制文件呢?
    【解决方案6】:

    !/usr/bin/sh

    复制具有相同目录结构的文件的脚本"

    echo "请输入源目录的完整路径(以/开头,以/结尾):" 读取路径

    echo " 请输入目标位置的完整路径(以 / 开头,以 / 结尾):" 读取路径

    si=echo "$spath" | awk -F/ '{print NF-1}'

    find $spath -type f -print 中的 fname 做 cdir=echo $fname | awk -F/ '{ for (i='$si'; i<NF; i++) printf "%s/", $i; printf "\n"; }' 如果 [ $cdir ];然后 如果 [ ! -d "$dpath$cdir" ];然后 mkdir -p $dpath$cdir 菲 菲

    cp $fname $dpath$cdir

    完成

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多