Pull to refresh
7
0
Иван Цыганенко@thsiganenko

User

Send message

В какой-то момент понял, что "решение" однострочников, может быть очень увлекательной задачей, ну и не смог удержаться, чтобы не интерпретировать вместо 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 лет назад, ее уже тогда было очень сложно найти. Есть в некоторых университетских библиотеках, но для этого нужен к ним доступ. Насколько я понимаю, в электронном виде данная книга не издавалась вообще.

На сайте издательства "Диалектика" удалось найти "Введение", оглавление и даже файлы примеров из книги https://www.dialektika.com/books/oldpages/S_Unix.html.

Вот прямо сейчас взял 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Кб

PS C:\Users\penguin\Desktop\jdupes_test> ls .\source\


    Каталог: C:\Users\penguin\Desktop\jdupes_test\source


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        31.12.2023     21:33         131072 blob01
-a----        31.12.2023     21:33         131072 blob02
-a----        31.12.2023     21:33         131072 blob03
-a----        31.12.2023     21:33         131072 blob04
-a----        31.12.2023     21:33         131072 blob05
-a----        31.12.2023     21:33         131072 blob06
-a----        31.12.2023     21:33         131072 blob07
-a----        31.12.2023     21:33         131072 blob08
-a----        31.12.2023     21:33         131072 blob09

PS C:\Users\penguin\Desktop\jdupes_test> ls .\target\


    Каталог: C:\Users\penguin\Desktop\jdupes_test\target


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        31.12.2023     21:34         131072 blob_target_01
-a----        31.12.2023     21:34         131072 blob_target_02
-a----        31.12.2023     21:34         131072 blob_target_03
-a----        31.12.2023     21:34         131072 blob_target_04
-a----        31.12.2023     21:34         131072 blob_target_05
-a----        31.12.2023     21:34         131072 blob_target_06
-a----        31.12.2023     21:34         131072 blob_target_07
-a----        31.12.2023     21:34         131072 blob_target_08
-a----        31.12.2023     21:34         131072 blob_target_09

Далее из каталога source в каталог target были скопированы первые 5 файлов:

PS C:\Users\penguin\Desktop\jdupes_test> ls .\target\


    Каталог: C:\Users\penguin\Desktop\jdupes_test\target


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        31.12.2023     21:33         131072 blob01
-a----        31.12.2023     21:33         131072 blob02
-a----        31.12.2023     21:33         131072 blob03
-a----        31.12.2023     21:33         131072 blob04
-a----        31.12.2023     21:33         131072 blob05
-a----        31.12.2023     21:34         131072 blob_target_01
-a----        31.12.2023     21:34         131072 blob_target_02
-a----        31.12.2023     21:34         131072 blob_target_03
-a----        31.12.2023     21:34         131072 blob_target_04
-a----        31.12.2023     21:34         131072 blob_target_05
-a----        31.12.2023     21:34         131072 blob_target_06
-a----        31.12.2023     21:34         131072 blob_target_07
-a----        31.12.2023     21:34         131072 blob_target_08
-a----        31.12.2023     21:34         131072 blob_target_09

Скачиваем архив с утилитой в удобное место и распаковываем его (в моем случае это Рабочий стол)

PS C:\Users\penguin\Desktop\jdupes-1.27.3-win64> ls


    Каталог: C:\Users\penguin\Desktop\jdupes-1.27.3-win64


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
------        27.08.2023     10:45          19698 CHANGES.txt
------        27.08.2023     10:46         119808 jdupes-barebones.exe
------        27.08.2023     10:46         195584 jdupes-loud.exe
------        27.08.2023     10:46         162816 jdupes-lowmem.exe
------        27.08.2023     10:46         179712 jdupes.exe
------        27.08.2023     10:45           1101 LICENSE.txt
------        27.08.2023     10:45          34235 README.md

Далее запускаем jdupes

PS C:\Users\penguin\Desktop\jdupes-1.27.3-win64> .\jdupes.exe C:\Users\penguin\Desktop\jdupes_test\source C:\Users\penguin\Desktop\jdupes_test\target

Scanning: 23 files, 2 items (in 3 specified)
C:\Users\penguin\Desktop\jdupes_test\source\blob05
C:\Users\penguin\Desktop\jdupes_test\target\blob05

C:\Users\penguin\Desktop\jdupes_test\source\blob04
C:\Users\penguin\Desktop\jdupes_test\target\blob04

C:\Users\penguin\Desktop\jdupes_test\source\blob03
C:\Users\penguin\Desktop\jdupes_test\target\blob03

C:\Users\penguin\Desktop\jdupes_test\source\blob02
C:\Users\penguin\Desktop\jdupes_test\target\blob02

C:\Users\penguin\Desktop\jdupes_test\source\blob01
C:\Users\penguin\Desktop\jdupes_test\target\blob01

В выводе видим ожидаемые дубликаты файлов. На данный момент они не удалены, нам только показывают найденное.

Чтобы удалить нужно воспользоваться ключом -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, при этом будет оставлен первый файл в выводе дубликатов.

PS C:\Users\penguin\Desktop\jdupes-1.27.3-win64> .\jdupes.exe --delete --no-prompt C:\Users\penguin\Desktop\jdupes_test\source C:\Users\penguin\Desktop\jdupes_test\target

Scanning: 23 files, 2 items (in 3 specified)

   [+] C:\Users\penguin\Desktop\jdupes_test\source\blob05
   [-] C:\Users\penguin\Desktop\jdupes_test\target\blob05


   [+] C:\Users\penguin\Desktop\jdupes_test\source\blob04
   [-] C:\Users\penguin\Desktop\jdupes_test\target\blob04


   [+] C:\Users\penguin\Desktop\jdupes_test\source\blob03
   [-] C:\Users\penguin\Desktop\jdupes_test\target\blob03


   [+] C:\Users\penguin\Desktop\jdupes_test\source\blob02
   [-] C:\Users\penguin\Desktop\jdupes_test\target\blob02


   [+] C:\Users\penguin\Desktop\jdupes_test\source\blob01
   [-] C:\Users\penguin\Desktop\jdupes_test\target\blob01

Итоговый вид каталогов source и target

PS C:\Users\penguin\Desktop\jdupes-1.27.3-win64> ls C:\Users\penguin\Desktop\jdupes_test\source\


    Каталог: C:\Users\penguin\Desktop\jdupes_test\source


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        31.12.2023     21:33         131072 blob01
-a----        31.12.2023     21:33         131072 blob02
-a----        31.12.2023     21:33         131072 blob03
-a----        31.12.2023     21:33         131072 blob04
-a----        31.12.2023     21:33         131072 blob05
-a----        31.12.2023     21:33         131072 blob06
-a----        31.12.2023     21:33         131072 blob07
-a----        31.12.2023     21:33         131072 blob08
-a----        31.12.2023     21:33         131072 blob09

PS C:\Users\penguin\Desktop\jdupes-1.27.3-win64> ls C:\Users\penguin\Desktop\jdupes_test\source\


    Каталог: C:\Users\penguin\Desktop\jdupes_test\source


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        31.12.2023     21:33         131072 blob01
-a----        31.12.2023     21:33         131072 blob02
-a----        31.12.2023     21:33         131072 blob03
-a----        31.12.2023     21:33         131072 blob04
-a----        31.12.2023     21:33         131072 blob05
-a----        31.12.2023     21:33         131072 blob06
-a----        31.12.2023     21:33         131072 blob07
-a----        31.12.2023     21:33         131072 blob08
-a----        31.12.2023     21:33         131072 blob09

Хотелось бы понять, а много ли файлов в тестовых каталогах? Потому что исполнение приложение происходит очень быстро, то есть с момента запуска до момента завершения проходит всего 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. То есть сеанс работы может выглядеть следующим образом:

$ # Удаляем дубликаты
$ jdupes --delete --no-prompt --recurse source target

$ # Удаляем пустые каталоги  
$ find target/ -type d -empty -delete

И еще раз хочу обратить внимание на то, что задумкой статьи было показать способ начинающим разработчикам, сам процесс разработки, и продемонстрировать возможные "грабли", на которые мне, если присмотреться к комментариям, удалось не просто наступить, а прям станцевать.

Спасибо за ваше предложение! Но, если честно, то сейчас, после обсуждения и выявления фундаментального изъяна в алгоритме, который при определении идентичности файлов полагается только на алгоритм 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:

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, то есть «Проще просить прощения, чем разрешения»).

Так же, есть обсуждение на StackOverflow "What is the EAFP principle in Python?"

Опять же не исключено, что в данном случае я неправильно применил данный подход. Есть повод вернуться и подумать над этим.

Что касается изменения размеров считываемого блока, то нет менять его не пробовал. Если быть честным, то мысль была поэкспериментировать с этим, но тут нужно каким-то образом измерять результаты изменений, а это, мягко говоря, не очень просто.

Когда я пробовал тестировать данный скрипт в 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 уже отличается. При этом размер файлов также совпадает, так что даже проверки на совпадение размера файлов в дополнении к контрольной сумме может быть недостаточно!

$ mkdir test && cd test

$ wget http://web.archive.org/web/20071226014140/http://www.cits.rub.de/imperia/md/content/magnus/order.ps
$ wget http://web.archive.org/web/20071226014140/http://www.cits.rub.de/imperia/md/content/magnus/letter_of_rec.ps

$ ls -l
total 8
-rwxrwxrwx 1 user user 2029 Jun 19  2007 letter_of_rec.ps
-rwxrwxrwx 1 user user 2029 Jun 19  2007 order.ps

$ md5sum *
a25f7f0b29ee0b3968c860738533a4b9  letter_of_rec.ps
a25f7f0b29ee0b3968c860738533a4b9  order.ps

$ sha1sum *
07835fdd04c9afd283046bd30a362a6516b7e216  letter_of_rec.ps
3548db4d0af8fd2f1dbe02288575e8f9f539bfa6  order.ps

$ sha256sum *
de4e4c6e2b94e95a3c5bd72a9a6af29bc5f83bf759325d9921943a6fc08ea245  letter_of_rec.ps
077046dd66015e05c3e03a43a6e4de129038e0701de5a4103fc7ed91c3782d06  order.ps

$ diff --binary letter_of_rec.ps order.ps
Binary files letter_of_rec.ps and order.ps differ

$ stat *
  File: letter_of_rec.ps
  Size: 2029            Blocks: 8          IO Block: 4096   regular file
Device: eh/14d  Inode: 35184372088972066  Links: 1
Access: (0777/-rwxrwxrwx)  Uid: ( 1000/ user)   Gid: ( 1000/ user)
Access: 2023-12-17 03:40:20.897605000 +0500
Modify: 2007-06-19 14:01:09.000000000 +0600
Change: 2023-12-17 03:37:36.616898300 +0500
 Birth: -
  File: order.ps
  Size: 2029            Blocks: 8          IO Block: 4096   regular file
Device: eh/14d  Inode: 59672695062770586  Links: 1
Access: (0777/-rwxrwxrwx)  Uid: ( 1000/ user)   Gid: ( 1000/ user)
Access: 2023-12-17 03:40:20.897605000 +0500
Modify: 2007-06-19 14:01:09.000000000 +0600
Change: 2023-12-17 03:37:28.041841500 +0500
 Birth: -

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

Можно, конечно, заменить алгоритм подсчета контрольной суммы на другой, но, получается, чтобы окончательно исключить возможность удаления различающихся файлов, имеющих одинаковую контрольную сумму из-за коллизии, можно только после побайтового сравнения файлов.

Спасибо за уточнение терминологии! В предлагаемом решении для вычисления контрольной суммы был использован именно алгоритм MD5. Я исходил из того, что CRC это не конкретный алгоритм, а термин описывающий множество алгоритмов, в которые как раз входят MD5, SHA-1 или SHA-256 и т.д.

1

Information

Rating
Does not participate
Location
Россия
Date of birth
Registered
Activity