Рекурсивно копировать папку, исключая некоторые папки

197

Я пытаюсь написать простой сценарий bash, который будет копировать все содержимое папки, включая скрытые файлы и папки, в другую папку, но я хочу исключить некоторые конкретные папки. Как я мог этого добиться?

trobrock
источник
1
Я представляю что-то вроде поиска. -name * передается в grep / v "exclude-pattern", чтобы отфильтровать ненужные файлы, а затем передается в cp, чтобы сделать копию.
i_am_jorf
1
Я пытался сделать что - то подобное, но не мог понять, как использовать ф с трубкой
trobrock
1
Это, вероятно, следует перейти к супер пользователя. Команда, которую вы ищете, это xargs. Вы также можете сделать что-то вроде двух смол, соединенных трубкой.
Кайл Батт
1
Может быть, уже поздно, и он не дает точного ответа на вопрос, но вот совет: если вы хотите исключить только непосредственных дочерних элементов каталога, вы можете воспользоваться преимуществами сопоставления шаблонов bash, например,cp -R !(dir1|dir2) path/to/destination
Борис Д. Теохаров
1
Обратите внимание, что !(dir1|dir2)шаблон extglobдолжен быть включен ( shopt -s extglobчтобы включить его).
Борис Дмитриевич Теохаров

Ответы:

334

Используйте rsync:

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

Обратите внимание, что использование sourceи source/разные. Конечный слеш означает копирование содержимого папки sourceв destination. Без косой черты это означает, что скопируйте папку sourceв destination.

В качестве альтернативы, если у вас есть много каталогов (или файлов) для исключения, вы можете использовать --exclude-from=FILE, где FILE- имя файла, содержащего файлы или каталоги, которые нужно исключить.

--exclude может также содержать символы подстановки, такие как --exclude=*/.svn*

Калеб Педерсон
источник
10
Я предлагаю добавить --dry-run, чтобы проверить, какие файлы будут скопированы.
Лоретопариси
1
@AmokHuginnsson - Какие системы вы используете? Rsync включен по умолчанию во все известные мне основные дистрибутивы Linux, включая RHEL, CentOS, Debian и Ubuntu, и я верю, что он также во FreeBSD.
SiliconRockstar
1
Для дистрибутивов, производных от RHEL: yum install rsync или для выпусков на основе Debian: apt-get install rsync. Если вы не строите свой сервер на основе собственного оборудования, это не проблема. rsync по умолчанию устанавливается на мои блоки Amazon EC2, а также на мои блоки ZeroLag и RackSpace.
silicrockstar
2
Rsync кажется очень медленным по сравнению с cp? По крайней мере, это был мой опыт.
Кожо
2
Например, чтобы игнорировать git dir:rsync -av --exclude='.git/' ../old-repo/ .
nycynik
40

Используйте смолу вместе с трубкой.

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

Вы можете даже использовать эту технику через ssh.

Кайл Батт
источник
При таком подходе вначале копируется целевой источник (и исключаются отдельные каталоги в архиве), а затем распаковывается его в целевой. Не рекомендуется!
Воутер Дондерс
4
@ Вальдхери, ты не прав. Это лучшее решение. Он выполняет именно то, что запрашивал OP, и работает по умолчанию при установке большинства * nix-подобных ОС. Тарирование и распаковка выполняются на лету без артефактов файловой системы (в памяти), стоимость этого tar + untar незначительна.
AmokHuginnsson
@WouterDonders Tar - минимальные накладные расходы. Сжатие не применяется.
Кайл Батт
9

Вы можете использовать findс -pruneопцией.

Пример из man find:

       cd / source-dir
       найти . -name .snapshot -prune -o \ (\! -name * ~ -print0 \) |
       cpio -pmd0 / dest-dir

       Эта команда копирует содержимое / source-dir в / dest-dir, но пропускает
       файлы и каталоги с именем .snapshot (и все, что в них). Это также
       пропускает файлы или каталоги, чье имя оканчивается на ~, но не их
       палатки. Конструкция -prune -o \ (... -print0 \) довольно распространена. 
       Идея в том, что выражение перед -prune соответствует вещам, которые
       быть обрезанным. Однако само действие -prune возвращает true, поэтому
       следующий -o гарантирует, что правая часть вычисляется только для
       те каталоги, которые не были сокращены (содержимое сокращенного
       каталоги даже не посещаются, поэтому их содержимое не имеет значения).
       Выражение справа от -o приведено только в скобках
       для ясности. Подчеркивается, что действие -print0 выполняется только
       за вещи, к которым не было применено -прун. Поскольку
       условие `` и 'между тестами связывает более тесно, чем -o, это
       в любом случае по умолчанию, но скобки помогают показать, что происходит
       на.
Приостановлено до дальнейшего уведомления.
источник
Реквизиты для поиска очень актуального примера прямо из man-страницы.
Дэвид М
Выглядит действительно хорошо! Это также доступно в онлайн-документах . К сожалению, 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 для получения дополнительной информации

ghostdog74
источник
2

Похоже на идею Джеффа (не проверено):

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), переместите копию в оператор if вне цикла «for x in ...» и сделайте так, чтобы цикл «for x ...» изменил логический оператор в Копия if (true). Это остановит вас от копирования несколько раз.
Эрик Брингли,
0

Вдохновленный ответом @ SteveLazaridis, который потерпит неудачу, вот функция оболочки POSIX - просто скопируйте и вставьте файл с именем cpxв yout $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"
go2null
источник
Кажется бесполезным говорить, что чей-то ответ "потерпит неудачу", не объяснив, что с ним не так и как вы это исправите ...
underscore_d
@underscore_d: правда, задним числом, особенно если я не могу вспомнить, что не удалось :-(
go2null
Несколько вещей: (1) он копирует файлы несколько раз и (2) логика все еще копирует файлы для исключения. Выполните циклы, используя i = foo: он будет скопирован 3 раза вместо 4 для любого другого файла, например, i = test.txt.
Эрик Брингли,
1
Спасибо @EricBringley за разъяснение недостатков ответа Стива. (Он все же сказал, что это было непроверено .)
go2null