Команда cp: правильне копіювання папок з файлами *nix
У цій статті будуть розкриті деякі неочевидні речі, пов'язані з використанням макетів при копіюванні, неоднозначна поведінка команди cp при копіюванні, і навіть методи дозволяють коректно копіювати безліч файлів без перепусток і вильотів.
Допустимо, нам потрібно скопіювати все з папки /source в папку /target.
Перше, що спадає на думку це:
cp /source/* /target
Відразу виправимо цю команду на:
cp -a /source/* /target
Ключ -a додасть копіювання всіх атрибутів, прав та додасть рекурсію. Якщо не потрібне точне відтворення прав достатньо ключа -r.
Після копіювання ми виявимо, що скопіювалися не всі файли - були проігноровані файли, що починаються з точки типу:
.profile
.local
.mc
і тому подібні.
Чому так сталося?
Тому що wildcards обробляє shell (bash у типовому випадку). За замовчуванням bash проігнорує всі файли, що починаються з точок, оскільки трактує їх як приховані. Щоб уникнути такої поведінки нам доведеться змінити поведінку bash за допомогою команди:
shopt -s dotglob
Щоб ця зміна поведінки збереглася після перезавантаження, можна зробити файл wildcard.sh з цією командою у папці /etc/profile.d (Можливо у вашому дистрибутиві інша папка).
А якщо в директорії-джерелі немає файлів, то shell не зможе нічого підставити замість зірочки, і копіювання завершиться з помилкою. Проти такої ситуації є опції failglob и nullglob. Нам потрібно виставити failglobяка не дасть команді виконатися. nullglob не підійде, тому що вона рядок з wildcards не знайшли збіги перетворює на порожній рядок (нульової довжини), що для cp викликає помилку.
Однак, якщо в папці тисячі файлів і більше, то від підходу з використанням wildcards варто відмовитися зовсім. Справа в тому що bash розгортає wildcards у дуже довгий командний рядок на кшталт:
cp -a /souce/a /source/b /source/c …… /target
На довжину командного рядка є обмеження, яке ми можемо дізнатися, використовуючи команду:
getconf ARG_MAX
Отримаємо максимальну довжину командного рядка в байтах:
2097152
Або:
xargs --show-limits
Отримаємо щось на кшталт:
….
Maximum length of command we could actually use: 2089314
….
Отже, давайте обходимося зовсім без wildcards.
Давайте просто напишемо
cp -a /source /target
І тут ми зіткнемося з неоднозначністю поведінки cp. Якщо папки /target не існує, ми отримаємо те, що нам потрібно.
Однак, якщо папка target існує, файли будуть скопійовані в папку /target/source.
Не завжди ми можемо видалити заздалегідь папку /target, тому що в ній можуть бути потрібні нам файли і наша мета, допустимо, доповнити файли /target файлами з /source.
Якби папки джерела і приймача називалися однаково, наприклад, ми копіювали б з /source в /home/source, можна було б використовувати команду:
cp -a /source /home
І після копіювання файли /home/source виявилися б доповненими файлами з /source.
Таке ось логічне завдання: ми можемо доповнити файли в директорії-приймачі, якщо папки називаються однаково, але якщо вони відрізняються, то папка-вихідник буде поміщена всередину приймача. Як скопіювати файли з /source в /target за допомогою cp без wildcards?
Щоб обійти це шкідливе обмеження, ми використовуємо неочевидне рішення:
cp -a /source/. /target
Ті хто добре знайомий з DOS і Linux вже все зрозуміли: усередині кожної папки є 2 невидимі папки "." і «..», які є псевдопапками-посиланнями на поточну та вищу директорію.
При копіюванні cp перевіряє існування та намагається створити /target/.
Така директорія існує і це є /target
Файли із /source скопійовані в /target коректно.
Отже, вішаємо в жирну рамочку у своїй пам'яті або на стіні:
cp -a /source/. /target
Поведінка цієї команди однозначна. Все відпрацює без помилок незалежно від того, мільйон у вас файлів або їх немає зовсім.
Висновки
Якщо потрібно скопіювати всі файли з однієї папки в іншу, не використовуємо wildcards, замість них краще використовувати cp у поєднанні з точкою в кінці папки-джерела. Це скопіює всі файли, включаючи приховані та не завалиться за мільйонів файлів або повної відсутності файлів.
Післямова
vmspike запропонував аналогічний за результатом варіант команди: