Как рекурсивно скопировать папку идемпотентным способом, используя cp?

46

Когда я использую

cp -R inputFolder outputFolder

результат зависит от контекста :

  1. если outputFolderон не существует, он будет создан и путь клонированной папки будет outputFolder.
  2. если outputFolderсуществует, то созданный клон будетoutputFolder/inputFolder

Это ужасно , потому что я хочу создать какой-нибудь сценарий установки, и если пользователь запустит его дважды по ошибке, он создаст outputFolderпервый раз, а затем при втором запуске все вещи будут созданы снова outputFolder/inputFolder.

  • Я хочу всегда первое поведение: создать клона рядом с оригиналом (в качестве родного брата).
  • Я хочу использовать, cpчтобы быть портативным (например, MINGW не rsyncпоставляется)
  • Я проверил, cp -R --parentsно это воссоздает путь до дерева каталогов (так что клон не будет, outputFolderно some/path/outputFolder)
  • --remove-destinationили --updateв случае 2 ничего не изменить, все же вещи копируются вoutputFolder/inputFolder

Есть ли способ сделать это без предварительной проверки на наличие outputFolder(если папка не существует, то ...) или с помощью rm -rf outputFolder?

Каков согласованный, переносимый способ UNIX сделать это?

jakub.g
источник
2
Некоторые из этих опций не POSIX, поэтому те, которые вы пробовали, не очень переносимы. Если вы можете жить с небольшим недостатком мобильности, почему бы не использовать rsync?
Муру
1
Если вам не нужно использовать cp, конвейерная передача между двумя tarкомандами - хороший надежный способ копирования деревьев.
R ..
@R .. можешь привести пример? @ MURU Я хотел бы, чтобы это работало в MINGW, который не был rsyncотправлен.
jakub.g
С GNU tar tar -C input -cf - . | tar -C output -xf -работает. -Cменяет рабочий каталог, но это не стандартная опция, поэтому, если она не поддерживается, для начала вам нужно запустить два tars в правильных рабочих каталогах, например. ( cd input && tar -cf - . ) | ( cd output && tar -xf - )Однако, если вы имеете дело с Windows, я бы просто использовал ответ roaima.
R ..

Ответы:

54

Используйте это вместо:

cp -R inputFolder/. outputFolder

Это работает точно так же, как, скажем, cp -R aaa/bbb cccработает: если cccне существует, то он создается как копия bbbи его содержимое; но если он cccуже существует, он ccc/bbbсоздается как копия bbbи его содержимое.

Практически в любом случае bbbэто приводит к нежелательному поведению, которое вы отметили в своем Вопросе. Тем не менее, в этой конкретной ситуации bbbэто справедливо ., так и aaa/bbbесть aaa/., что, в свою очередь, действительно справедливо, aaaно под другим именем. Итак, у нас есть два сценария:

  1. ccc не существует:

    Команда cp -R aaa/. cccозначает «создавать cccи копировать содержимое aaa/.в ccc/., т.е. копировать aaaв ccc.

  2. ccc существует:

    Команда cp -R aaa/. cccозначает «скопировать содержимое aaa/.в ccc/., т. Е. Скопировать aaaв ccc.

roaima
источник
3
Да, это новый для меня, но, похоже, работает! Это где-нибудь задокументировано?
Ильмари Каронен
1
Я проверил inputFolder/.вместо, inputFolderи это действительно работает для меня на Windows / Git Bash. (Это не работает, однако, только с трейлингом /, мне нужна также точка после косой черты). Я дал +1, потому что это интересно, хотя, вероятно, это слишком сложно, чтобы использовать его в публичном коде :)
jakub.g
@IlmariJaronen это не должно быть задокументировано явно. Следуйте объяснениям, и вы увидите, как оно просто «следует правилам».
Ройма
18

Не копируйте папку, только скопируйте содержимое:

## Create the target directory. The -p suppresses error messages
## if the directory already exists
mkdir -p outputFolder

## Copy the contents recursively, this will not recreate the parent
cp -R inputfolder/* outputfolder/

Таким образом, вы оба гарантируете, что целевой каталог создается при первом запуске скрипта, и избегаете проблемы при его запуске во второй раз.

Крис Даун очень правильно указывает, что в bash это будет пропускать файлы, имя которых начинается с a .. Чтобы избежать этого, вы можете запустить shopt -s dotglobперед запуском команды выше.

И -pдляmkdir и -Rдляcp определяются POSIX , так это должно быть совершенно портативным.

Тердон
источник
6
Примечание: это не будет копировать файлы, начинающиеся с .. В Bash вы можете использовать dotglobдля этого.
Крис Даун
2
Обратите внимание, что этот метод может завершиться ошибкой, если число записей каталога во входной папке велико, поскольку расширение глобуса может превышать максимальную длину командной строки.
Тим Каттс
11

Попробуйте -Tвариант cp. Это существует в GNU coreutils cpверсии 8.22; это не может быть портативным за пределами этого.

Том Хант
источник
1
Да, похоже, это именно то, что я хотел: cp -T( cp --no-target-directory). unix.stackexchange.com/questions/94831/… К сожалению, моя версия cpнамного старше.
jakub.g 9.09.15
1
+1 Использовал это только сегодня. -uотличный вариант, чтобы сделать его ленивым.
PSkocik 10.09.15
4

Вы также можете использовать rsync:

rsync -uav inputFolder/ outputFolder/

(обратите внимание на косые черты, особенно после первого)

Ned64
источник
0

На мой взгляд, нет ничего проще и портативнее, чем rm -rf outputFolder. Так что я всегда придерживаюсь этого. Я понимаю, что ваш вопрос отличается, но я думаю, что это лучшая практика.

dashohoxha
источник
Это терпит неудачу, если были определенные разрешения или владения на цели, которые должны были быть сохранены. Или если это была символическая ссылка.
Ройма
1
Вопрос хочет, чтобы результат cpбыл одинаковым как в том случае, когда каталог не существует, так и в том случае, если он существует. Так о чем ты говоришь?
Дашохокша
Команда, cpзаданная OP, не сохраняет разрешения (или временные метки).
Ройма
0

Вы можете использовать -tопцию cpкоманды как:

cp -R inputFolder -t outputFolder

теперь, если целевой папки не существует, она выдаст ошибку:

cp: failed to access ‘outputFolder’: No such file or directory

Команда note выше скопирует inputFolderвместе со своим содержимым (а не только с содержимым внутри него)

если вы хотите скопировать только его содержимое, inputFolderэто становится немного сложнее (поскольку вы должны быть осторожны при использовании глобализации оболочки при использовании звездочки *)

cp -R  -t outputFolder/ -- inputFolder/*

теперь, если целевой папки не существует, она выдаст ошибку:

cp: failed to access ‘outputFolder’: No such file or directory

работает с cp (GNU coreutils) 8.23

Эдвард Торвальдс
источник