В какой-то момент понял, что "решение" однострочников, может быть очень увлекательной задачей, ну и не смог удержаться, чтобы не интерпретировать вместо AI предложенный в статье однострочник. Хотя я прекрасно понимаю, что он приведен исключительно для демонстрации идеи, какую помощь может оказать AI системному администратору.
В целом, кажется ничего особо сложного в нем нет, да и AI с задачей справился и описал, что потенциально может делать данная конструкция. Вот только если ее попробовать запустить... То окажется, что это нерабочий однострочник!
Первая проблема. seq 5 1 - не выведет ничего. Поскольку по умолчанию если передать два аргумента, то первый будет восприниматься как начало диапазона, а второй как окончание, без шага -1, той идей, что задумывал автор, не получится. Если, конечно, он ожидал получить вывод 5 4 3 2 1.
Вторая проблема. date +%Y-%m-%dT%H:%M -d '$i min ago' - не сработает, как ожидается, а выдаст сообщение об ошибке, так как внутри апострофов, в которые заключена конструкция '$i min ago', не происходит подстановка значений переменных.
Третья проблема. После обработки фильтром sed 's/|$//', на выходе получится строка вида 2024-11-29T12:092024-11-29T12:102024-11-29T12:112024-11-29T12:122024-11-29T12:13, которая навряд ли найдется в файле лога, хотя может быть это то, что и было задумало, кто знает.
При этом AI на данные недочеты внимания не обращает, так что все же для целей обучения, как раз новичкам и стажерам при разборе того, что было когда-то кем-то написано, как мне кажется, лучше использовать свой "естественный" интеллект
Все действительно выполняется в виртуальном окружении программы GNS3, так как физическое оборудование в домашних условиях использовать достаточно затруднительно.
Если немного расширить скриншоты, которые приложены к статье, то интерфейс клиентского приложения выглядит следующим образом.
Если брать книги посвященные GNU/Linux, то основная цель это узнать как можно больше о системе, что включает в себя как вопросы уровня пользователя, так и вопросы администрирования. И вот уже в рамках администрирования составить представление о том, как система устроена. Потому что без понимания устройства очень сложно заниматься устранением неисправностей и отладкой, да и настроить систему максимально эффективно без понимания внутренностей навряд ли получится.
Ну а что касается методов, то я одновременно с чтение составляю что-то вроде конспекта, отмечая для себя важные моменты и то, что хотелось бы запомнить на будущее. В прошлом году начал для этого использовать Obsidian, но пока сказать насколько это эффективно не могу. Удобно то, что можно делать связи между книгами. Например, если брать systemd, то есть заметка про systemd, и есть связи этой заметки, которые указывают на кусочки конспектов из разных книг. Плюс в Obsidian отличная система поиска.
Спасибо за ваше мнений и еще одну рекомендацию! Xочу уточнить, я правильно понял, что вы упомянули книгу Андрея Михайловича Робачевского "Операционная система Unix"?
Опять же, быстрый поиск показал, что ее издавали в последний раз в 2010 году, так что достать ее тоже не просто.
Операционная система UNIX. / Робачевский А.М., Немнюгин С.А., Стесик О.Л. — 2-е изд., перераб. и доп. — СПб.: БХВ-Петербург, 2010. — 656 с.: ил. ISBN 978-5-94157-538-1
Пока в моей библиотеке не много завершенных книг на тему Linux, но наибольшее впечатление произвели две:
Дмитрий Владимирович Кетов "Внутреннее устройство Linux", совместно с записями лекций, который доступны его YouTube канале Dmitry Ketov. У меня она есть во 2-ом издании 2021 года, но на сайте издательства сейчас уже доступно 3-е издание, не сочтите за рекламу "Внутреннее устройство Linux, 3 изд.".
Брайан Керниган, Роб Пайк "UNIX. Программное окружение", в переводе с английского 2003 года издания (ISBN 5-93286-029-4)
Ну и, как уже отмечал в комментариях ранее, сейчас читаю Эви Немет "Unix и Linux: руководство системного администратора", добрался до третьей главы, пока впечатления приятные.
Спасибо за ваше дополнение! Мне тоже временами казалось, что некоторые фразы выглядят странно, даже по меркам машинного перевода, ну и в некоторых местах возникают сложности в понимании текста из-за очень специфического перевода терминов. Нет под рукой оригинала, чтобы привести пример, но в некоторых моментах прям ощущается, что определение указывает на совершенно другую сущность, а не ту, что указана.
Но помимо самого перевода у меня возник вопрос к методике автора изложения информации в книге. Много внимания уделяется темам простым, и очень поверхностный обзор того, что действительном может быть интересно опытным пользователям.
Быстрый поиск в сети показал что перевод 2-го издания книги Джеймса С. Армстронга "Секреты UNIX" в печатном виде был выпущен издательским домом "Вильямс" в 2000 году, ISBN 5-8459-0068-9 (оригинал: Unix Secrets 2nd Edition by James C. Armstrong, Publisher: John Wiley & Sons Inc; 2nd edition (January 1, 1999) ISBN-13: 978-0764533204).
После этого книга не переиздавалась. И судя по тому, что писали на форумах 20 лет назад, ее уже тогда было очень сложно найти. Есть в некоторых университетских библиотеках, но для этого нужен к ним доступ. Насколько я понимаю, в электронном виде данная книга не издавалась вообще.
Вот прямо сейчас взял 5-е издание Эви Немет "Unix и Linux: руководство системного администратора" и получаю удовольствие от чтения. Жаль автора этой книги уже нет с нами. Судя по биографии была удивительным человеком.
Unix и Linux: руководство системного администратора / Немет Эви, Снайдер Гарт, Хейн Трент, Уэйли Бен, Макни Дэн, 5-е изд.: Пер. с англ. - СПб. : ООО "Диалектика", 2020. - 1168 с. : ил. - Парал. тит. англ. ISBN 978-5-907144-10-1
Вообще, если вам нужно удалить дубликаты, то имеет смысл воспользоваться приложением jdupes, которое упоминал @yarolig в первом комментарии. Это приложение доступно для операционной системы Windows jdupes-1.27.3-win64.zip
Пример использования утилиты jdupes
Для тестирования утилиты было создано два каталога source и target, эти каталоги были наполнены 10 файлами по 128Кб
В выводе видим ожидаемые дубликаты файлов. На данный момент они не удалены, нам только показывают найденное.
Чтобы удалить нужно воспользоваться ключом -d или --delete, в этом режиме на каждый найденный дубликат будет задан вопрос, какой именно файл удалить
PS C:\Users\penguin\Desktop\jdupes-1.27.3-win64> .\jdupes.exe --delete C:\Users\penguin\Desktop\jdupes_test\source C:\Users\penguin\Desktop\jdupes_test\target
Scanning: 23 files, 2 items (in 3 specified)
[1] C:\Users\penguin\Desktop\jdupes_test\source\blob05
[2] C:\Users\penguin\Desktop\jdupes_test\target\blob05
Set 1 of 5: keep which files? (1 - 2, [a]ll, [n]one, [l]ink all):
Если нет желание отвечать на данные вопросы, то можно воспользоваться ключом -N или --no-prompt, при этом будет оставлен первый файл в выводе дубликатов.
Хотелось бы понять, а много ли файлов в тестовых каталогах? Потому что исполнение приложение происходит очень быстро, то есть с момента запуска до момента завершения проходит всего 4 сотых секунды.
Предлагаю добавить отладочное логирование, чтобы посмотреть более детально на ход исполнения приложения. Чтобы не возиться с "заплатками", предлагаю полные текст приложения:
Текст приложения с отладочным выводом
#!/usr/bin/env python3
"""Application to remove duplicates and empty directories"""
import argparse
import sys
import logging
import os
import hashlib
from pathlib import Path
from typing import List, Optional
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(formatter)
logger.addHandler(handler)
READING_BLOCK_SIZE = 4096
def calculate_crc(path: Path) -> Optional[str]:
"""Returns CRC file for the path"""
crc = hashlib.md5()
try:
with open(path, 'rb') as fd:
while True:
chunk = fd.read(READING_BLOCK_SIZE)
if not chunk:
break
crc.update(chunk)
except OSError as err:
logger.error("Processing attempt: %s failed with error: %s", path, err)
return None
return crc.hexdigest()
def create_set_of_crc(source: Path) -> set:
"""Return a set of files hashes for the path"""
logger.info('Directory tree traversal %s started', source)
paths = [Path(root, filename)
for root, __, files in os.walk(source, topdown=False)
for filename in files]
logger.debug('In directore %s was found %s files', source, len(paths))
logger.info('Checksums calculation for a set of paths has begun')
hashes = set(calculate_crc(path) for path in paths)
logger.dedug('Checksums for %s paths were calculated')
if None in hashes:
hashes.remove(None)
return hashes
def remove_duplicates(target: Path, hashes: set):
"""Remove file duplicates and empty dirs by the path"""
for root, folders, files in os.walk(target, topdown=False):
for filename in files:
target_file = Path(root, filename)
crc = calculate_crc(target_file)
if crc in hashes:
try:
os.remove(target_file)
logger.info('File %s was removed', target_file)
except OSError:
logger.error('Deleting of file %s failed', target_file)
for folder in folders:
target_folder = Path(root, folder)
try:
os.rmdir(target_folder)
logger.info('Empty dir %s was removed', target_folder)
except OSError:
pass
def main(source: Path, targets: List[Path]) -> int:
logger.info('Creating set of hashes for "%s" has begun', source)
hashes = create_set_of_crc(source)
for target in targets:
logger.info('Processing of the target directory %s has begun', target)
if source.resolve() == target.resolve():
logger.error('The source and target directory are the same: %s',
source.resolve())
continue
remove_duplicates(target, hashes)
logger.info('Removing duplicates of the directory %s has ended', target)
return 0
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('source', type=Path,
help='path to the directory with sample files')
parser.add_argument('targets', type=Path, nargs='+',
help='paths to directories from which duplicates need to be removed')
args = parser.parse_args()
exit_code = main(args.source, args.targets)
sys.exit(exit_code)
Из предоставленного стектрейса видно, что ошибка происходит в одном из модулей пакета spyder-kernels. С данным пакетом мне никогда не приходилось встречаться.
Знакомство с исходным текстом данного пакета в репозитории spyder-ide/spyder-kernels выявило, что данная ошибка выбрасывается в одном месте модуля spyder_kernels/customize/code_runner.py при вызове метода _exec_file() экземпляра класса SpyderCodeRunner на строке 272 при следующей проверке:
if args is not None and not isinstance(args, str):
raise TypeError("expected a character buffer object")
Получается что args не является значением None и в тоже время это не строка, которая ожидается в данном месте.
Данный метод вызывается из метода runfile экземпляра класса SpyderCodeRunner и в качестве параметром ему передается args, который получен в результате вызова метода _parse_runfile_argstring() экземпляра класса SpyderCodeRunner, далее в процессе стали появляться методы и декораторы с замечательным словом в названии magic, и стало понятно, что эту цепочку вызовов мне уже не осилить.
Вообще смущает один момент, если из первой строки runfile('C:/Python_Temp/Dublicate/Dublicate.py', ['-h', 'c:/1', 'c:/2'], wdir='C:/Python_Temp/Dublicate') считать что ['-h', 'c:/1', 'c:/2'] это аргументы, которые передается скрипту при его вызове, то в этом месте ожидается не список, в смысле тип list, а именно строка, то есть тип str, потому что в итоге оно попадает в функцию shlex.split.
В общем, насколько я понимаю, до вызова и исполнения предложенного мной в статье кода дело даже не дошло.
Да, приложение, предложенном варианте, не принимает во внимание ни имя файла, ни иные метаданные файла, а опирается только на результат преобразования содержимого файла в контрольную сумму с помощью алгоритма MD5. Иными словами, если обобщить и упростить, то файлы сравниваются по содержимому. Но не стоит забывать о существовании вероятности коллизии при использовании данного подхода.
Действительно, в требованиях это не указано, что именование файлов не должно иметь значение, поскольку опять же это требование воспринималось как само собой разумеющееся, что, судя по вашему вопросу, таковым не является.
Еще раз хочу поблагодарить за комментарий! Решил повнимательнее посмотреть на утилиту jdupes, обнаружил, что в репозитории Ubuntu 23.04 доступна версия 1.21.3-1. Оказалось, что она делает ровно тоже, что было описано в статье, за исключением удаления пустых каталогов, но это можно решить с помощью утилиты find. То есть сеанс работы может выглядеть следующим образом:
И еще раз хочу обратить внимание на то, что задумкой статьи было показать способ начинающим разработчикам, сам процесс разработки, и продемонстрировать возможные "грабли", на которые мне, если присмотреться к комментариям, удалось не просто наступить, а прям станцевать.
Спасибо за ваше предложение! Но, если честно, то сейчас, после обсуждения и выявления фундаментального изъяна в алгоритме, который при определении идентичности файлов полагается только на алгоритм MD5, я считаю, хорошо, что исходный текст приложения находится в тексте статьи, и вероятность заметить предупреждение о потенциальной ошибке выше, чем если бы он был выложен на GitHub.
Хотя, предложенный вариант, пусть с оговорками и ограничениями, может быть использован для решения практических задач, все же не имеет смысла создавать дополнительные возможности и развивать приложение, пока не будет устранен описанный выше недостаток.
И еще раз хотел бы упомянуть, что целью статьи не являлось создание самого эффективного способа решения обозначенной задачи, а показать способ начинающим разработчикам, как можно начать процесс разработки.
А вообще у меня есть вариант скрипта для одной из задач, который как раз хранит файл с вычисленными значениями MD5, и при добавлении новых файлов обновляется, это позволяет существенно экономить время работы. Есть вариант, в котором эти процессы разделены, PowerShell скрипт считает контрольные суммы и экспортирует их в файл формата CSV, а скрипт на Python уже на основании этого файла производит обработку.
Прошу меня простить, за резкий тон данного ответа, но я утверждаю, что приложение работает так, как и должно. В приложении действительно есть потенциальная ошибка, связанная с коллизиями при использовании алгоритма MD5, о чем указано в дополнении к статье, но в остальном все работает ровно так как задумано и описано.
Почему вы решили, что если бы вы не закомментировали вызов os.rmdir, то были бы удалены не пустые каталоги?
Могу предположить, что вы исходите из того, что после того, как вы закомментировали вызов os.rmdir, в выводе вы увидели строки "Empty dir /some/path/to/dir was removed"?
Если это так, то смею вас уверить, в случае если вызов os.rmdir будет вызван с передачей в качества аргумента непустого каталога, то будет выброшено исключение, и строка logger.info(Empty dir %s was removed) просто не будет выполнена.
os.rmdir(path, *, dir_fd=None)
Remove (delete) the directory path. If the directory does not exist
or is not empty, a FileNotFoundError or an OSError is raised respectively.
In order to remove whole directory trees, shutil.rmtree() can be used.
Поэтому хотел бы попросить вас, создать тестовое окружение, то есть дерево каталогов с исходными файлами, дерево каталогов откуда вы хотите удалить дубликаты, при этом чтобы часть файлов в подкаталогах была уникальна, и запустить приложение в том виде, в котором оно предлагается, без правок, и убедиться в результате работы.
И было бы очень хорошо, если бы вы сообщили результаты своего нового тестирования в ответном комментарии.
Соглашусь с вами, что просто отсылка на конференцию выглядит слабовато. Тогда позвольте сослаться на книгу: Седер Наоми / Python. Экспресс-курс. 3-е изд. — СПб.: Питер, 2019. — 480 с.: ил. — (Серия «Библиотека программиста»). ISBN 978-5-4461-0908-1
Об авторе Наоми Седер, автор третьего издания книги, занимается программированием на различных языках в течение почти 30 лет. Она работала системным администратором Linux, преподавателем программирования, разработчиком и системным архитектором. Она начала использовать Python в 2001 году, и с тех пор преподавала Python пользователям всех уровней, от 12-летних до профессионалов. Она рассказывает о Python и о достоинствах Python-сообщества каждому, кто готов слушать. В настоящее время Наоми руководит группой разработки для Dick Blick Art Materials и является председателем Python Software Foundation
14.2. Исключения в Python Подход к обработке ошибок в Python в целом отличается от подхода в таких языках, как, скажем, Java. Эти языки по возможности стараются выявить как можно больше возможных ошибок заранее, поскольку обработка исключений после их возникновения может обойтись достаточно дорого. Такой стиль описан в первой части этой главы; иногда он обозначается сокращением LBYL (Look Before You Leap, то есть «Смотри, прежде чем прыгать»).
С другой стороны, Python скорее полагается на то, что исключения будут обработаны после их возникновения. И хотя такой подход может показаться рискованным, при разумном использовании исключений код получается менее громоздким и лучше читается, а ошибки обрабатываются только в случае их возникновения. Подход Python к обработке ошибок часто описывается сокращением EAFP (Easier to Ask Forgiveness than Permission, то есть «Проще просить прощения, чем разрешения»).
Опять же не исключено, что в данном случае я неправильно применил данный подход. Есть повод вернуться и подумать над этим.
Что касается изменения размеров считываемого блока, то нет менять его не пробовал. Если быть честным, то мысль была поэкспериментировать с этим, но тут нужно каким-то образом измерять результаты изменений, а это, мягко говоря, не очень просто.
Когда я пробовал тестировать данный скрипт в Windows 10 22H2, я столкнулся с какой-то "магией" кэширования, причем даже не смог найти где именно оно происходит, когда при первом запуске приложение работает 90 секунд, а вот повторный запуск завершается за 1,5 секунды и дает тот же самый результат.
Спасибо за комментарий. Отсутствие вывода причины, которое вызвало исключение, а только сам факт исключения, действительно было не лучшим решением, тем более, что исправить это практически ничего не стоит, например, можно сделать следующим образом:
try:
os.remove(target_file)
logger.info('File %s was removed', target_file)
except OSError as error:
logger.error('Deleting of file %s failed, reason: %s', target_file, error)
Что касается использования исключений, как инструмента в рамках нормального потока исполнения, а не обработки действительно исключительных ситуаций, как отмечено в тексте статьи, является очень и очень спорным решением. Хотя подход "проще просить прощения, чем разрешения", мне встретился на одной из международных Python конференций.
Опять же, вы обратили внимание на то, что показывает разницу между скриптом, написанным для личного использования, и продуктовую разработку. Использовать исключения для нормального потока выполнения в продуктовой разработке, скорее можно отнести к "антипатернам".
Выбор иного способа обработки дерева каталогов при удалении, то есть одновременный обход, вычисление контрольной суммы и удаление, в первую очередь был обусловлен желанием показать, что так тоже возможно. То есть в рамках одного примера, показать разные способы решения задачи.
Безусловно вы правы в том, что можно описать обобщенную функцию обхода дерева каталогов, и передавать ей в качестве параметра функцию, которая будет выполняться в зависимости от того, на каком этапе выполнения находится программа. Но это может оказаться сложнее для понимания, и такое решение было бы скорее интересно для опытных разработчиков.
Ну и последнее. Список путей, который формируется, появился в процессе попыток сократить время выполнения приложения за счет использования ProcessPoolExecutor и ThreadPoolExecutor. Вычисление контрольных сумму можно распараллелить, а вот как распараллелить обход дерева каталогов? Поэтому сначала выполняем то, что можно сделать только последовательно, собираем результат, а потом выполняем то, что можно делать одновременно.
Спасибо за комментарий! Благодаря вам засомневался в достаточности вычисления только контрольной суммы с помощью алгоритма MD5, и пошел искать информацию по этому вопросу.
В статье "Cautions (or why it’s hard to write a dupefinder)" (прим. статья на английском), есть раздел "Collision Robustness", где показан практический пример того, когда для двух разных файлов контрольная сумма MD5 совпадает, а вот SHA1 уже отличается. При этом размер файлов также совпадает, так что даже проверки на совпадение размера файлов в дополнении к контрольной сумме может быть недостаточно!
Пусть даже, как я уже отметил в одном комментарии, целью статьи не являлось создание самого эффективного способа решения обозначенной задачи, а задача лишь использовалось для демонстрации процесса разработки, но наличие такого уровня ошибки, делают приложение потенциально опасным.
Можно, конечно, заменить алгоритм подсчета контрольной суммы на другой, но, получается, чтобы окончательно исключить возможность удаления различающихся файлов, имеющих одинаковую контрольную сумму из-за коллизии, можно только после побайтового сравнения файлов.
Спасибо за уточнение терминологии! В предлагаемом решении для вычисления контрольной суммы был использован именно алгоритм MD5. Я исходил из того, что CRC это не конкретный алгоритм, а термин описывающий множество алгоритмов, в которые как раз входят MD5, SHA-1 или SHA-256 и т.д.
В какой-то момент понял, что "решение" однострочников, может быть очень увлекательной задачей, ну и не смог удержаться, чтобы не интерпретировать вместо AI предложенный в статье однострочник. Хотя я прекрасно понимаю, что он приведен исключительно для демонстрации идеи, какую помощь может оказать AI системному администратору.
В целом, кажется ничего особо сложного в нем нет, да и AI с задачей справился и описал, что потенциально может делать данная конструкция. Вот только если ее попробовать запустить... То окажется, что это нерабочий однострочник!
Первая проблема.
seq 5 1- не выведет ничего. Поскольку по умолчанию если передать два аргумента, то первый будет восприниматься как начало диапазона, а второй как окончание, без шага-1, той идей, что задумывал автор, не получится. Если, конечно, он ожидал получить вывод5 4 3 2 1.Вторая проблема.
date +%Y-%m-%dT%H:%M -d '$i min ago'- не сработает, как ожидается, а выдаст сообщение об ошибке, так как внутри апострофов, в которые заключена конструкция'$i min ago', не происходит подстановка значений переменных.Третья проблема. После обработки фильтром
sed 's/|$//', на выходе получится строка вида2024-11-29T12:092024-11-29T12:102024-11-29T12:112024-11-29T12:122024-11-29T12:13, которая навряд ли найдется в файле лога, хотя может быть это то, что и было задумало, кто знает.При этом AI на данные недочеты внимания не обращает, так что все же для целей обучения, как раз новичкам и стажерам при разборе того, что было когда-то кем-то написано, как мне кажется, лучше использовать свой "естественный" интеллект
Вы абсолютно правы! Именно из-за визуального сходства на схеме, ее так и называют.
Все действительно выполняется в виртуальном окружении программы GNS3, так как физическое оборудование в домашних условиях использовать достаточно затруднительно.
Если немного расширить скриншоты, которые приложены к статье, то интерфейс клиентского приложения выглядит следующим образом.
Если брать книги посвященные GNU/Linux, то основная цель это узнать как можно больше о системе, что включает в себя как вопросы уровня пользователя, так и вопросы администрирования. И вот уже в рамках администрирования составить представление о том, как система устроена. Потому что без понимания устройства очень сложно заниматься устранением неисправностей и отладкой, да и настроить систему максимально эффективно без понимания внутренностей навряд ли получится.
Ну а что касается методов, то я одновременно с чтение составляю что-то вроде конспекта, отмечая для себя важные моменты и то, что хотелось бы запомнить на будущее. В прошлом году начал для этого использовать Obsidian, но пока сказать насколько это эффективно не могу. Удобно то, что можно делать связи между книгами. Например, если брать
systemd, то есть заметка проsystemd, и есть связи этой заметки, которые указывают на кусочки конспектов из разных книг. Плюс в Obsidian отличная система поиска.Спасибо за ваше мнений и еще одну рекомендацию! Xочу уточнить, я правильно понял, что вы упомянули книгу Андрея Михайловича Робачевского "Операционная система Unix"?
Опять же, быстрый поиск показал, что ее издавали в последний раз в 2010 году, так что достать ее тоже не просто.
Пока в моей библиотеке не много завершенных книг на тему Linux, но наибольшее впечатление произвели две:
Дмитрий Владимирович Кетов "Внутреннее устройство Linux", совместно с записями лекций, который доступны его YouTube канале Dmitry Ketov. У меня она есть во 2-ом издании 2021 года, но на сайте издательства сейчас уже доступно 3-е издание, не сочтите за рекламу "Внутреннее устройство Linux, 3 изд.".
Брайан Керниган, Роб Пайк "UNIX. Программное окружение", в переводе с английского 2003 года издания (ISBN 5-93286-029-4)
Ну и, как уже отмечал в комментариях ранее, сейчас читаю Эви Немет "Unix и Linux: руководство системного администратора", добрался до третьей главы, пока впечатления приятные.
Спасибо за ваше дополнение! Мне тоже временами казалось, что некоторые фразы выглядят странно, даже по меркам машинного перевода, ну и в некоторых местах возникают сложности в понимании текста из-за очень специфического перевода терминов. Нет под рукой оригинала, чтобы привести пример, но в некоторых моментах прям ощущается, что определение указывает на совершенно другую сущность, а не ту, что указана.
Но помимо самого перевода у меня возник вопрос к методике автора изложения информации в книге. Много внимания уделяется темам простым, и очень поверхностный обзор того, что действительном может быть интересно опытным пользователям.
Напоминает историю про то, как нарисовать сову.
Спасибо за Вашу рекомендацию!
Быстрый поиск в сети показал что перевод 2-го издания книги Джеймса С. Армстронга "Секреты UNIX" в печатном виде был выпущен издательским домом "Вильямс" в 2000 году, ISBN 5-8459-0068-9 (оригинал: Unix Secrets 2nd Edition by James C. Armstrong, Publisher: John Wiley & Sons Inc; 2nd edition (January 1, 1999) ISBN-13: 978-0764533204).
После этого книга не переиздавалась. И судя по тому, что писали на форумах 20 лет назад, ее уже тогда было очень сложно найти. Есть в некоторых университетских библиотеках, но для этого нужен к ним доступ. Насколько я понимаю, в электронном виде данная книга не издавалась вообще.
На сайте издательства "Диалектика" удалось найти "Введение", оглавление и даже файлы примеров из книги https://www.dialektika.com/books/oldpages/S_Unix.html.
Вот прямо сейчас взял 5-е издание Эви Немет "Unix и Linux: руководство системного администратора" и получаю удовольствие от чтения. Жаль автора этой книги уже нет с нами. Судя по биографии была удивительным человеком.
Вообще, если вам нужно удалить дубликаты, то имеет смысл воспользоваться приложением
jdupes, которое упоминал @yarolig в первом комментарии. Это приложение доступно для операционной системы Windows jdupes-1.27.3-win64.zipПример использования утилиты jdupes
Для тестирования утилиты было создано два каталога
sourceиtarget, эти каталоги были наполнены 10 файлами по 128КбДалее из каталога
sourceв каталогtargetбыли скопированы первые 5 файлов:Скачиваем архив с утилитой в удобное место и распаковываем его (в моем случае это Рабочий стол)
Далее запускаем
jdupesВ выводе видим ожидаемые дубликаты файлов. На данный момент они не удалены, нам только показывают найденное.
Чтобы удалить нужно воспользоваться ключом
-dили--delete, в этом режиме на каждый найденный дубликат будет задан вопрос, какой именно файл удалитьЕсли нет желание отвечать на данные вопросы, то можно воспользоваться ключом
-Nили--no-prompt, при этом будет оставлен первый файл в выводе дубликатов.Итоговый вид каталогов
sourceиtargetХотелось бы понять, а много ли файлов в тестовых каталогах? Потому что исполнение приложение происходит очень быстро, то есть с момента запуска до момента завершения проходит всего 4 сотых секунды.
Предлагаю добавить отладочное логирование, чтобы посмотреть более детально на ход исполнения приложения. Чтобы не возиться с "заплатками", предлагаю полные текст приложения:
Текст приложения с отладочным выводом
Из предоставленного стектрейса видно, что ошибка происходит в одном из модулей пакета spyder-kernels. С данным пакетом мне никогда не приходилось встречаться.
Знакомство с исходным текстом данного пакета в репозитории spyder-ide/spyder-kernels выявило, что данная ошибка выбрасывается в одном месте модуля
spyder_kernels/customize/code_runner.pyпри вызове метода_exec_file()экземпляра классаSpyderCodeRunnerна строке 272 при следующей проверке:Получается что
argsне является значениемNoneи в тоже время это не строка, которая ожидается в данном месте.Данный метод вызывается из метода
runfileэкземпляра классаSpyderCodeRunnerи в качестве параметром ему передаетсяargs, который получен в результате вызова метода_parse_runfile_argstring()экземпляра классаSpyderCodeRunner, далее в процессе стали появляться методы и декораторы с замечательным словом в названииmagic, и стало понятно, что эту цепочку вызовов мне уже не осилить.Вообще смущает один момент, если из первой строки
runfile('C:/Python_Temp/Dublicate/Dublicate.py', ['-h', 'c:/1', 'c:/2'], wdir='C:/Python_Temp/Dublicate')считать что['-h', 'c:/1', 'c:/2']это аргументы, которые передается скрипту при его вызове, то в этом месте ожидается не список, в смысле типlist, а именно строка, то есть типstr, потому что в итоге оно попадает в функцию shlex.split.В общем, насколько я понимаю, до вызова и исполнения предложенного мной в статье кода дело даже не дошло.
Очень жаль, что не смог помочь!
Да, приложение, предложенном варианте, не принимает во внимание ни имя файла, ни иные метаданные файла, а опирается только на результат преобразования содержимого файла в контрольную сумму с помощью алгоритма MD5. Иными словами, если обобщить и упростить, то файлы сравниваются по содержимому. Но не стоит забывать о существовании вероятности коллизии при использовании данного подхода.
Действительно, в требованиях это не указано, что именование файлов не должно иметь значение, поскольку опять же это требование воспринималось как само собой разумеющееся, что, судя по вашему вопросу, таковым не является.
Еще раз хочу поблагодарить за комментарий! Решил повнимательнее посмотреть на утилиту
jdupes, обнаружил, что в репозитории Ubuntu 23.04 доступна версия 1.21.3-1. Оказалось, что она делает ровно тоже, что было описано в статье, за исключением удаления пустых каталогов, но это можно решить с помощью утилитыfind. То есть сеанс работы может выглядеть следующим образом:И еще раз хочу обратить внимание на то, что задумкой статьи было показать способ начинающим разработчикам, сам процесс разработки, и продемонстрировать возможные "грабли", на которые мне, если присмотреться к комментариям, удалось не просто наступить, а прям станцевать.
Спасибо за ваше предложение! Но, если честно, то сейчас, после обсуждения и выявления фундаментального изъяна в алгоритме, который при определении идентичности файлов полагается только на алгоритм MD5, я считаю, хорошо, что исходный текст приложения находится в тексте статьи, и вероятность заметить предупреждение о потенциальной ошибке выше, чем если бы он был выложен на GitHub.
Хотя, предложенный вариант, пусть с оговорками и ограничениями, может быть использован для решения практических задач, все же не имеет смысла создавать дополнительные возможности и развивать приложение, пока не будет устранен описанный выше недостаток.
И еще раз хотел бы упомянуть, что целью статьи не являлось создание самого эффективного способа решения обозначенной задачи, а показать способ начинающим разработчикам, как можно начать процесс разработки.
А вообще у меня есть вариант скрипта для одной из задач, который как раз хранит файл с вычисленными значениями MD5, и при добавлении новых файлов обновляется, это позволяет существенно экономить время работы. Есть вариант, в котором эти процессы разделены, PowerShell скрипт считает контрольные суммы и экспортирует их в файл формата
CSV, а скрипт на Python уже на основании этого файла производит обработку.Прошу меня простить, за резкий тон данного ответа, но я утверждаю, что приложение работает так, как и должно. В приложении действительно есть потенциальная ошибка, связанная с коллизиями при использовании алгоритма MD5, о чем указано в дополнении к статье, но в остальном все работает ровно так как задумано и описано.
Почему вы решили, что если бы вы не закомментировали вызов
os.rmdir, то были бы удалены не пустые каталоги?Могу предположить, что вы исходите из того, что после того, как вы закомментировали вызов
os.rmdir, в выводе вы увидели строки"Empty dir /some/path/to/dir was removed"?Если это так, то смею вас уверить, в случае если вызов
os.rmdirбудет вызван с передачей в качества аргумента непустого каталога, то будет выброшено исключение, и строкаlogger.info(Empty dir %s was removed)просто не будет выполнена.Ну и чтобы не быть голословным, ссылка на документацию https://docs.python.org/3/library/os.html?highlight=os rmdir#os.rmdir, в которой явно указано, что если каталог не пустой, то будет выброшено исключение
OSError:Поэтому хотел бы попросить вас, создать тестовое окружение, то есть дерево каталогов с исходными файлами, дерево каталогов откуда вы хотите удалить дубликаты, при этом чтобы часть файлов в подкаталогах была уникальна, и запустить приложение в том виде, в котором оно предлагается, без правок, и убедиться в результате работы.
И было бы очень хорошо, если бы вы сообщили результаты своего нового тестирования в ответном комментарии.
Соглашусь с вами, что просто отсылка на конференцию выглядит слабовато. Тогда позвольте сослаться на книгу: Седер Наоми / Python. Экспресс-курс. 3-е изд. — СПб.: Питер, 2019. — 480 с.: ил. — (Серия «Библиотека программиста»). ISBN 978-5-4461-0908-1
Так же, есть обсуждение на StackOverflow "What is the EAFP principle in Python?"
Опять же не исключено, что в данном случае я неправильно применил данный подход. Есть повод вернуться и подумать над этим.
Что касается изменения размеров считываемого блока, то нет менять его не пробовал. Если быть честным, то мысль была поэкспериментировать с этим, но тут нужно каким-то образом измерять результаты изменений, а это, мягко говоря, не очень просто.
Когда я пробовал тестировать данный скрипт в Windows 10 22H2, я столкнулся с какой-то "магией" кэширования, причем даже не смог найти где именно оно происходит, когда при первом запуске приложение работает 90 секунд, а вот повторный запуск завершается за 1,5 секунды и дает тот же самый результат.
Спасибо за комментарий. Отсутствие вывода причины, которое вызвало исключение, а только сам факт исключения, действительно было не лучшим решением, тем более, что исправить это практически ничего не стоит, например, можно сделать следующим образом:
Что касается использования исключений, как инструмента в рамках нормального потока исполнения, а не обработки действительно исключительных ситуаций, как отмечено в тексте статьи, является очень и очень спорным решением. Хотя подход "проще просить прощения, чем разрешения", мне встретился на одной из международных Python конференций.
Опять же, вы обратили внимание на то, что показывает разницу между скриптом, написанным для личного использования, и продуктовую разработку. Использовать исключения для нормального потока выполнения в продуктовой разработке, скорее можно отнести к "антипатернам".
Выбор иного способа обработки дерева каталогов при удалении, то есть одновременный обход, вычисление контрольной суммы и удаление, в первую очередь был обусловлен желанием показать, что так тоже возможно. То есть в рамках одного примера, показать разные способы решения задачи.
Безусловно вы правы в том, что можно описать обобщенную функцию обхода дерева каталогов, и передавать ей в качестве параметра функцию, которая будет выполняться в зависимости от того, на каком этапе выполнения находится программа. Но это может оказаться сложнее для понимания, и такое решение было бы скорее интересно для опытных разработчиков.
Ну и последнее. Список путей, который формируется, появился в процессе попыток сократить время выполнения приложения за счет использования
ProcessPoolExecutorиThreadPoolExecutor. Вычисление контрольных сумму можно распараллелить, а вот как распараллелить обход дерева каталогов? Поэтому сначала выполняем то, что можно сделать только последовательно, собираем результат, а потом выполняем то, что можно делать одновременно.Спасибо за комментарий! Благодаря вам засомневался в достаточности вычисления только контрольной суммы с помощью алгоритма MD5, и пошел искать информацию по этому вопросу.
В статье "Cautions (or why it’s hard to write a dupefinder)" (прим. статья на английском), есть раздел "Collision Robustness", где показан практический пример того, когда для двух разных файлов контрольная сумма MD5 совпадает, а вот SHA1 уже отличается. При этом размер файлов также совпадает, так что даже проверки на совпадение размера файлов в дополнении к контрольной сумме может быть недостаточно!
Пусть даже, как я уже отметил в одном комментарии, целью статьи не являлось создание самого эффективного способа решения обозначенной задачи, а задача лишь использовалось для демонстрации процесса разработки, но наличие такого уровня ошибки, делают приложение потенциально опасным.
Можно, конечно, заменить алгоритм подсчета контрольной суммы на другой, но, получается, чтобы окончательно исключить возможность удаления различающихся файлов, имеющих одинаковую контрольную сумму из-за коллизии, можно только после побайтового сравнения файлов.
Спасибо за уточнение терминологии! В предлагаемом решении для вычисления контрольной суммы был использован именно алгоритм MD5. Я исходил из того, что CRC это не конкретный алгоритм, а термин описывающий множество алгоритмов, в которые как раз входят MD5, SHA-1 или SHA-256 и т.д.