?

Log in

No account? Create an account
Previous Entry —>другу Next Entry
Про розгрібання файлів in Unix way
root
dmytrish
Недавно в моєму фейсбуку почалась досить гаряча суперечка про те, що краще, mc чи bash. Аргументи за mc були про швидкість і м’язову пам’ять (хоч при нормальному володінні клавіатурою м’язова пам’ять на башівські команди може бути нічим не гірше), я ж захищав можливо повільніший, але набагато більш потужний підхід командного рядка.

Зараз я просто хочу розібрати приклад того, що стає можливим при правильному жонглюванні можливостями юніксового командного рядка.

Свою бібліотеку електронної літератури я з деякого часу тримаю у Дропбоксі, але тим не менш у мене часто розводяться офлайнові звалища літератури, і так на робочому комп’ютері самозародилась тека ~/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 уже б давно поставив чергову приблуду із бледжеком і шлюхами рекламою і вірусами. З другого боку, це призводить до певного лудитства: там, де у Віндоус виникає ринок рішень для видалення копій файлів, під юніксами лише сміються і квітне буйним цвітом DIY. Що ж, друге мені більше до душі.

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


  • 1
Здається, можна find . -name *.pdf -exec md5sum {} \; | sort | uniq -d -w 32

Edited at 2014-08-26 02:52 pm (UTC)

Це просто знаходження дублікатів в поточній директорії.

Мені ж треба саме добуток і різниця множин файлів під двома різними директоріями.

Ed: І на жаль, у comm опції -w32 немає, тобі б обійшлось без md5-файлів.

Edited at 2014-08-26 03:32 pm (UTC)

чому це? знаходить дублікати з усіх директорій, які є в поточній директорії. Можна find . замінити на find ~/ для твого випадку.

Не зрозумів, у кого немає опції -w 32.

У утиліти comm, яка порівнює два файла і може показувати спільні рядки або рядки, які специфічні для якогось із них.

А взагалі да, такий рецепт також працює:


mkdir tmp && cd tmp
ln -s ~/public/Dropbox .
ln -s ~/downloads/Books .
find -L . -name \*pdf -exec md5sum '{}' \; | sort | uniq -d -w 32 | | sed 's/.\{34\}//' | xargs -d'\n' rm -v

— але це покладається на те, що Books сортується перед Dropbox, втім, можна було б просто назвати лінки в правильному порядку.

Edited at 2014-08-26 04:02 pm (UTC)

Господи. Ти ще українець. Це просто свято :-)

дякувати богу, не москаль

  • 1