递归复制文件夹,不包括某些文件夹


197

我正在尝试编写一个简单的bash脚本,该脚本会将一个文件夹的所有内容(包括隐藏文件和文件夹)复制到另一个文件夹中,但是我想排除某些特定的文件夹。我怎样才能做到这一点?


1
我想像发现。-name *通过管道传递到grep / v“ exclude-pattern”以过滤不需要的内容,然后通过管道传递给cp进行复制。
i_am_jorf'2

1
我试图做类似的事情,但无法弄清楚如何在管道上使用cp
trobrock 2010年

1
这可能应该交给超级用户。您要查找的命令是xargs。您还可以执行类似两个通过管道连接的焦油的操作。
Kyle Butt 2010年

1
也许是晚了,但它并不能准确回答问题,但是这里有个提示:如果您只想排除目录的直接子级,则可以利用bash模式匹配,例如cp -R !(dir1|dir2) path/to/destination
Boris D. Teoharov

1
请注意,!(dir1|dir2)需要extglob打开图案(shopt -s extglob将其打开)。
Boris D. Teoharov 2014年

Answers:


334

使用rsync:

rsync -av --exclude='path1/to/exclude' --exclude='path2/to/exclude' source destination

请注意,使用sourcesource/是不同的。斜杠表示将文件夹的内容复制source到中destination。如果没有斜杠,则表示将文件夹复制source到中destination

或者,如果要排除的目录(或文件)很多,则可以使用--exclude-from=FILE,其中FILE是包含要排除的文件或目录的文件的名称。

--exclude 也可能包含通配符,例如 --exclude=*/.svn*


10
我建议添加--dry-run以检查要复制的文件。
loretoparisi

1
@AmokHuginnsson-您正在使用什么系统?我所知的所有主流Linux发行版都默认包含Rsync,包括RHEL,CentOS,Debian和Ubuntu,而且我相信FreeBSD中也包含Rsync。
Siliconrockstar

1
对于RHEL衍生发行版:yum install rsync,或基于Debian的发行版:apt-get install rsync。除非您是基于绝对的硬件来构建服务器,否则这不是问题。默认情况下,rsync也安装在我的Amazon EC2盒子以及ZeroLag和RackSpace的盒子上。
Siliconrockstar

2
rsync似乎比cp极其慢?至少这是我的经验。
Kojo

2
例如忽略git dir:rsync -av --exclude='.git/' ../old-repo/ .
nycynik '17

40

与管道一起使用焦油。

cd /source_directory
tar cf - --exclude=dir_to_exclude . | (cd /destination && tar xvf - )

您甚至可以跨ssh使用此技术。


这种方法不必要地先将目标源放映(并排除归档中的特定目录),然后再将其放到目标上。不建议!
Wouter Donders

4
@Waldheri你错了。这是最好的解决方案。它完全符合OP的要求,并且可以在大多数* nix操作系统的默认安装中使用。去皮和去皮是在没有文件系统伪影的情况下即时完成的(在内存中),此tar + untar的成本可以忽略不计。
AmokHuginnsson,2016年

@WouterDonders Tar是最小的开销。它不应用压缩。
Kyle Butt

9

您可以使用find-prune选项。

来自的示例man find

       cd /源目录
       找 。-name .snapshot -prune -o \(\!-name *〜-print0 \)|
       cpio -pmd0 /目标目录

       此命令将/ source-dir的内容复制到/ dest-dir,但省略
       名为.snapshot的文件和目录(及其中的任何内容)。它也是
       省略名称以〜结尾的文件或目录,但不删除其内容
       帐篷。构造-prune -o \(... -print0 \)很常见。的
       这里的想法是-prune之前的表达式匹配的是
       被修剪。但是,-prune操作本身返回true,因此,
       以下-o确保仅对右侧求值
       那些没有被修剪的目录(被修剪的内容
       目录甚至都没有访问过,因此它们的内容无关紧要)。
       -o右侧的表达式仅在括号中
       为了清楚。它强调-print0操作仅发生
       对于没有修剪的东西。因为
       测试之间的默认“和”条件比-o绑定更紧密,这
       仍然是默认值,但是括号有助于显示正在发生的情况
       上。

直接从联机帮助页中找到高度相关的示例的道具。
David M

确实看起来不错!在线文档中也提供了此功能。不幸的是cpio,尚未为MSYS2打包。
underscore_d

3

您可以将tar与--exclude选项一起使用,然后将其解压缩到目标位置。例如

cd /source_directory
tar cvf test.tar --exclude=dir_to_exclude *
mv test.tar /destination 
cd /destination  
tar xvf test.tar

有关更多信息,请参见tar的手册页。


2

类似于Jeff的想法(未经测试):

find . -name * -print0 | grep -v "exclude" | xargs -0 -I {} cp -a {} destination/

抱歉,但是我真的不明白为什么有5个人在未经测试的情况下对它进行了投票,并且似乎无法通过简单的测试进行操作:我在的一个子目录中尝试了此操作,/usr/share/icons并立即获得find: paths must precede expression: 22x22后者是其中一个子目录的位置。我的命令是find . -name * -print0 | grep -v "scalable" | xargs -0 -I {} cp -a {} /z/test/((当然,我在MSYS2上,所以确实在/mingw64/share/icons/Adwaita,但我看不到这是MSYS2的错))
underscore_d

0
EXCLUDE="foo bar blah jah"                                                                             
DEST=$1

for i in *
do
    for x in $EXCLUDE
    do  
        if [ $x != $i ]; then
            cp -a $i $DEST
        fi  
    done
done

未经测试...


这是不正确的。几个问题:如所写,它将多次复制一个不应该排除的文件(要排除的项目数,在这种情况下为4)。即使您确实尝试复制排除列表中的第一项'foo',当您到达x = bar并且我仍然是foo时,它仍将被复制。如果您坚持不使用预先存在的工具(例如rsync)来执行此操作,请将副本移至“ for x in ...”循环之外的if语句,并使“ for x ...”循环更改if(true)复制文件。这将阻止您多次复制。
Eric Bringley '18

0

受@SteveLazaridis答案的启发,该方法可能会失败,这是POSIX shell函数-只需将其复制并粘贴到以cpxyout 命名的文件中$PATH并使其可执行(chmod a+x cpr)。[现在在我的GitLab中维护了源。

#!/bin/sh

# usage: cpx [-n|--dry-run] "from_path" "to_path" "newline_separated_exclude_list"
# limitations: only excludes from "from_path", not it's subdirectories

cpx() {
# run in subshell to avoid collisions
  (_CopyWithExclude "$@")
}

_CopyWithExclude() {
  case "$1" in
    -n|--dry-run) { DryRun='echo'; shift; } ;;
  esac

  from="$1"
  to="$2"
  exclude="$3"

  $DryRun mkdir -p "$to"

  if [ -z "$exclude" ]; then
      cp "$from" "$to"
      return
  fi

  ls -A1 "$from" \
    | while IFS= read -r f; do
        unset excluded
        if [ -n "$exclude" ]; then
          for x in $(printf "$exclude"); do
          if [ "$f" = "$x" ]; then
              excluded=1
              break
          fi
          done
        fi
        f="${f#$from/}"
        if [ -z "$excluded" ]; then
          $DryRun cp -R "$f" "$to"
        else
          [ -n "$DryRun" ] && echo "skip '$f'"
        fi
      done
}

# Do not execute if being sourced
[ "${0#*cpx}" != "$0" ] && cpx "$@"

用法示例

EXCLUDE="
.git
my_secret_stuff
"
cpr "$HOME/my_stuff" "/media/usb" "$EXCLUDE"

说某人的回答“将会失败”而没有解释问题出在哪里以及如何解决这一问题似乎无济于事
underscore_d

@underscore_d:是的,事后看来,尤其是我现在不记得失败了的地方:-(
go2null

多种情况:(1)它多次复制文件,(2)逻辑仍然复制要排除的文件。使用i = foo遍历循环:对于其他任何文件,例如i = test.txt,它将被复制3次而不是4次。
Eric Bringley '18

1
感谢@EricBringley澄清了Steve回答的缺点。(他确实说这未经测试。)
go2null
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.