Как рекурсивно скопировать каталог, используя жесткие ссылки для каждого файла

52

Я хочу создать «копию» дерева каталогов, где каждый файл является жесткой ссылкой на исходный файл

Пример: у меня есть структура каталогов:

dirA/
dirA/file1
dirA/x/
dirA/x/file2
dirA/y/
dirA/y/file3

Вот ожидаемый результат, «копия» дерева каталогов, где каждый файл является жесткой ссылкой на исходный файл:

dirB/            #  normal directory
dirB/file1       #  hardlink to dirA/file1
dirB/x/          #  normal directory
dirB/x/file2     #  hardlink to dirA/x/file2
dirB/y/          #  normal directory
dirB/y/file3     #  hardlink to dirA/y/file3
Гудмундур Орн
источник

Ответы:

50

В Linux (точнее, с GNU и busyboxреализациями, cpкоторые обычно встречаются в системах с Linux в качестве ядра) и недавней FreeBSD, вот как:

cp -al dirA dirB

Для более портативного решения см. Ответ с использованием pax и cpio от Стефана Шазеласа

Гудмундур Орн
источник
Обратите внимание, что, как paxи во FreeBSD, cp -aне содержит жестких ссылок.
Стефан Шазелас
Имейте в виду, что жесткие ссылки не работают между отдельными монтируемыми файловыми системами.
Дэйв
24

POSIXly, вы бы использовали paxв режиме чтения + записи с -lопцией:

pax -rwlpe -s /A/B/ dirA .

( -peсохраняет все возможные атрибуты файлов (в данном случае только каталоги), которые копируются, как cpэто -aделает GNU ).

Теперь, хотя и стандартная , эта команда не обязательно очень переносима .

Во-первых, многие системы на основе GNU / Linux не включают paxпо умолчанию (хотя это не дополнительная утилита POSIX).

Затем ряд ошибок и несоответствий с несколькими реализациями вызывают ряд проблем с этим кодом.

  • из-за ошибки Solaris 10 pax(по крайней мере) не работает при использовании -rwlв сочетании с -s. По какой-то причине кажется, что применяется замена как к исходному, так и к скопированному пути. Так что выше, он будет пытаться сделать некоторые link("dirB/file", "dirB/file")вместо link("dirA/file", "dirB/file").
  • во FreeBSD paxне создает жестких ссылок для файлов типа symlink (поведение, разрешенное POSIX). Кроме того, он также применяет подстановку к целям символических ссылок (поведение, не допускаемое POSIX). Так, например, если есть foo -> AAсимволическая ссылка dirA, она станет foo -> BAвнутри dirB.

Кроме того, если вы хотите сделать то же самое, но с произвольными путями к файлам, содержимое которых хранится в $srcи $dst, важно понимать, что pax -rwl -- "$src" "$dst"создает полную структуру каталогов $srcвнутри $dst(которая должна существовать и быть каталогом). Например, если $srcесть foo/bar, то $dst/foo/barсоздается.

Если вместо этого вы хотите $dstбыть копией $src, проще всего сделать это следующим образом:

absolute_dst=$(umask 077 && mkdir -p -- "$dst" && cd -P -- "$dst" && pwd -P) &&
(cd -P -- "$src" && pax -rwlpe . "$absolute_dst")

(который также будет работать вокруг большинства проблем, упомянутых выше, но потерпит неудачу, если абсолютный путь $dstоканчивается символами новой строки).

Теперь это не поможет в системах GNU / Linux, где их нет pax.

Интересно отметить , что paxбыла создана POSIX для слияния в себе черты tarи cpioкоманд.

cpioявляется исторической командой Unix (с 1977 года), в отличие от изобретения POSIX, и есть реализация GNU (а не paxодна). Таким образом, даже если это больше не стандартная команда (хотя она была в SUSv2), она все еще очень распространена, и есть основной набор функций, на которые вы обычно можете положиться.

Эквивалент pax -rwlбудет cpio -pl. Тем не мение:

  1. cpio принимает список входного файла в stdin, в отличие от аргументов (разделитель новой строки, что означает, что имена файлов с символами новой строки не поддерживаются)
  2. Все файлы должны быть указаны (как правило, вы передаете его вывод find( findи cpioбыли разработаны совместно теми же людьми)).
  3. метаданные не сохраняются (некоторые cpioреализации имеют опции для сохранения некоторых, но ничего переносимого).

Так что с cpio:

absolute_dst=$(umask 077 && mkdir -p -- "$dst" && cd -P -- "$dst" && pwd -P) &&
(cd -P -- "$src" && find . | cpio -pl "$absolute_dst")
Стефан Шазелас
источник
Кажется, что -s / A / B / относится к моему примеру. Как бы вы сделали это, если бы имя исходного каталога и имя целевого каталога были переменными $ sourcedir и $ targettdir?
Гудмундур Орн
@GudmundurOrn, см. Редактировать.
Стефан Шазелас
Я запускаю эту команду в OS X и просто получаю сообщение об ошибке «pax: Невозможно связать файл ./a.txt с самим собой». Я использовал вашу команду буквально, просто заменив исходный каталог на фактическое имя, оставив / A / B и последнюю точку как есть. Я что-то неправильно понимаю?
ДБ
@db, -s /A/Bзаменяется Aна Bтак, что dirAстановится dirB. Если имя вашего исходного каталога не имеет A, тогда это скопирует (свяжет) это по себе. Смотрите также остальную часть ответа для возможных лучших подходов.
Стефан Шазелас
6

Краткий ответ:

cd $source_folder
pax -rwlpe . $dest_folder
lkraider
источник
2

В случае, если вы ищете эту функцию копирования с жесткими ссылками для создания снимков или резервных копий (полностью или частично) ваших файлов rsnapshot.

Janis
источник
1
Это интересно. Но я полагаю, что жесткие ссылки являются хорошим механизмом моментального снимка, если файлы не будут изменены. Правильно?
Гудмундур Орн
@Gudmundur Orn; Это верно. Инструмент, упомянутый в моем ответе, создаст новый снимок так, чтобы файлы были уникальными; т.е. существующие (неизмененные) файлы будут созданы как жесткие ссылки, а новые файлы (или измененные версии существующих файлов) будут созданы как новые файлы. Следовательно, у вас будет наименьшая избыточность.
Янис
0

Ответ @ gudmundur-orn верен, но если вы работаете в BtrFS в Linux, вам cp a --reflink=auto dirA dirBнужно сделать то же самое, с той разницей, что файлы на самом деле разные, и изменение одного не меняет другого. Вы можете добиться в основном того же с cp -cMac на APFS ( autoполная копия будет сделана, если это невозможно, -cне удастся).

Любая файловая система COW должна быть в состоянии сделать это, но производители не согласовали стандартную опцию командной строки.

rbanffy
источник