Зараз я просто хочу розібрати приклад того, що стає можливим при правильному жонглюванні можливостями юніксового командного рядка.
Свою бібліотеку електронної літератури я з деякого часу тримаю у Дропбоксі, але тим не менш у мене часто розводяться офлайнові звалища літератури, і так на робочому комп’ютері самозародилась тека
~/downloads/Books/
, в якій розвелось немало копій того, що уже лежить в ~/public/Dropbox
. Завдання полягає у тому, щоб:1) з’ясувати, які pdf-файли однакові і там, і там (тоді можна знищити зайві копії в локальному гадюшнику);
2) з’ясувати, які pdf-файли є лише локально (тоді можна їх скопом скопіювати в дропбокс);
Отже, мій рецепт складаєтсья із восьми юніксових команд.
Перш за все, треба визначитись, по чому будемо порівнювати файли. Можна по імені, але це спосіб ненадійний. Можна по розміру, і розмір досить часто достатньо унікальний, але це все не the right thing. Спосіб по контенту, через md5-суму, оптимальний. Отже, знайдемо всі файли із розширенням pdf та обчислимо їх md5-суми. Перше робиться через
$ find . -name \*.pdf
— це обходить поточну директорію і рекурсивно заходить у всі піддиректорії і випльовує в stdout список шляхів до знайдених файлів. Далі на цей список можна натравити утиліту
md5sum
, яка для кожного шляху припише на початку md5-суму цього файла:$ find -name \*.pdf | xargs md5sum
Упс, маленька проблема: в іменах багатьох файлів є пробіли і
xargs
сприймає частини імені як окремі імена. Але це вирішується просто (якщо знаєш як, а я знаю):$ find . -name \*.pdf -print0 | xargs -0 md5sum
Тепер збережемо цей список у файлик, а, і бажано сортованим:
$ find . -name \*.pdf -print0 | xargs -0 md5sum | sort >~/local.list
. local.list
тепер виглядає приблизно так:
$ head local.list
06d5655e20ec3ad03310acd45ca9be0c ./RTL8110S_8169S_DataSheet_1.3.pdf
0a0248a0ff31c57be0e15b70476ac296 ./x86/319440.pdf
0a128ffef667adf59eb1470073fb1f72 ./comp/GoF_DesignPatterns-ElementsOfReusableObject-OrientedSoftware(RUS).pdf
0c72b8a86c99e026227cd10c112658c2 ./lambda/Category Theory for Computer Science.pdf
0d2bc07089d8551cd966948b80df1720 ./plai-2007-04-26.pdf
1199867a45d58064c9bca65e20174060 ./lisp/The.Joy.of.Clojure.pdf
15161ab42e87c4ccb589f14c10c0bde6 ./comp/pci21.pdf
15941548d3437c8ccf5a2b17e51374a1 ./cpp/1999-02 const T vs T const.pdf
1668a523f8afa33e18ba4254a486bf26 ./cpp/Alexandresku_ModernCppDesign.pdf
16bfc681fa6aab6218412c416c2a6326 ./linux/ldd3_pdf/ch10.pdf
Застосовуємо той самий рецепт у директорії
~/public/Dropbox
і пишемо сортований по md5 список у файлик ~/dbox.list
. Тепер можна порівняти ці списки за допомогою утиліти join
(про яку знав): $ join dbox.list local.list
— видасть рядки, перше слово в яких (тобто md5-сума) спільне для обох файлів. Але це не дає способу отримати логічну різницю файлів; в мануалі ж
join
є посилання на мануал по comm
, яка може видавати логічну різницю, але порівнює лише по цілому рядку. Ну що ж, неприємно, але не смертельно, зробимо копії файлів .list, в яких містяться лише md5-суми:$ awk '{ print $1 }' <dbox.list >dbox.md5 # і так само local.md5
Список md5, які присутні лише в local.md5:
$ comm -13 dbox.md5 local.md5
Список md5, які присутні в обох .md5:
$ comm -12 dbox.md5 local.md5
Для того, щоб видалити локальні копії уже присутніх у дропбоксі файлів, треба пройтись по другому списку і для кожного md5 знайти ім’я файла із
local.list
. Знайдемо імена файлів для кожної суми і запишемо в ~/to_delete.list
:$ comm -12 dbox.md5 local.md5 >to_delete.md5
— спочатку збережемо
to_delete.md5
.Шукаємо в
local.list
(жаль, циклу по рядках не уникнути, але він спокійно поміщається в один рядок і після деякої практики пишеться просто), обрізаємо md5 на початку рядків (sed-вираз страшний для сторонньої людини, але достатньо поширений, щоб його можна було виписати на автоматі) і запишемо список файлів в to_delete.list
:$ while read line; do grep $line local.list | sed "s/$line //"; done <~/to_delete.md5 >to_delete.list
Перевіримо, чи правильно xargs сприйме список (довелось додати опцію
-d'\n'
, щоб імена файлів розділялись перенесенням рядка):$ xargs -d'\n' file <~/to_delete.list
Гаразд, можна видаляти:
$ xargs -d'\n' rm -v <~/to_delete.list
Отже, повний рецепт:
$ find ~/downloads/Books -name \*.pdf -print0 | xargs -0 md5sum | sort >local.list
$ find ~/public/Dropbox -name \*.pdf -print0 | xargs -0 md5sum | sort >dbox.list
$ awk '{ print $1 }' <dbox.list >dbox.md5
$ awk '{ print $1 }' <local.list >local.md5
$ comm -12 dbox.md5 local.md5 >to_delete.md5
$ while read line; do grep $line local.list | sed "s/$line //"; done <to_delete.md5 >to_delete.list
$ xargs -d'\n' rm -v <to_delete.list # it's better to be verbose
Гаразд, тепер пересунемо всі локальні pdf, які залишились, до
~/public/Dropbox/books/misc
, де зможемо їх пізніше розсортувати (так, для цього уже краще «візуальний» файловий менеджер, наприклад, чудовий КДЕшний Dolphin):
$ find ~/downloads/Books -name \*.pdf -exec mv '{}' ~/public/Dropbox/books/misc/ \;
Підсумок: 8 команд і трохи експериментування, які автоматично розгребли близько двохсот файлів (замість нудотно одноманітного — зате швидкого! — клацання в mc), а на додачу цей рецепт тривіально записується скриптом на майбутнє. Користувач Windows уже б давно поставив чергову приблуду із
P.S.:
У коментах підказали цікавий спосіб:
$ mkdir tmp && cd tmp
$ ln -s ~/downloads/Books a # deletion from what comes first in lexicographic order!
$ ln -s ~/public/Dropbox b
$ find -L . -name \*pdf -exec md5sum '{}' \; | sort | uniq -d -w 32 | sed 's/.\{34\}//' | xargs -d'\n' rm -v
P.P.S:
Але, звичайно, без DIY все стає набагато простіше (thnx to Alexander Yakushev):
$ sudo $PACKAGE_MANAGER install fdupes
$ fdupes -r -d ~/dir1 ~/dir2