LINUX.ORG.RU

Борьба с нехорошими символами в именах файлов

 ,


1

2

Уважаемые форумчане! Требуется ваша помощь!

Хочу разобраться с файловым хранилищем, удалить дубликаты файлов. Написал программу обходящую дерево директорий. Застрял на этапе вычисления хэшей, дело в том, что имена файлов содержат не только алфавитные символы и цифры, а также пробелы и спецсимволы. Сначала Python останавливался на первом же проблемном файле, позже я дописал try / except, и теперь он доходит до конца, пропуская проблемные файлы, но меня это не устраивает!

Язык: Python 3

#! /usr/bin/env python3

import subprocess

try:
    hash = subprocess.check_output('md5sum -- "{}"'.format(file), universal_newlines=True, shell=True)[:32]

except Exception:
    print('Проблемный файл {}'.format(file))

С hashlib пока не получается, проблем много создаёт, поэтому пока пришлось воспользоваться стандартной утилитой md5sum. Может подскажете заодно как решить проблему с применением hashlib.

Давай лучше скажи что у тебя с hashlib не получилось.
То что ты сейчас делаешь, можно назвать гейством.

itn ★★★
()
Ответ на: комментарий от itn

То что ты сейчас делаешь, можно назвать гейством.

Как? Гейством? :) Это от какого слова?

Давай лучше скажи что у тебя с hashlib не получилось.

Я давно писал с использованием hashlib, поэтому сейчас по памяти отвечу, могу ошибаться. Вроде он неправильные суммы давал, не сходились они с тем же md5sum, да и пока не привык я к Python3, с его необычностью, имеется ввиду многословность (как мне показалось)

tonchikp
() автор топика
#!/usr/bin/env python3

from hashlib import md5

def get_cache(filename):
    with open(filename, "rb") as f:
        data = f.read()
    return md5(data).hexdigest()

try:
    hash = get_cache(file)
except Exception:
    print('Проблемный файл {}'.format(file))

P. S. Лучше после шебанга пробел не ставить. Не помню точно, почему — вроде на каких-то юниксах (старых и коммерческих, но всё же) может не работать.

P. P. S. Если файлы большие, можно считывать не целиком, а частично, вызывая update() у md5().

Psych218 ★★★★★
()
Последнее исправление: Psych218 (всего исправлений: 2)
Ответ на: комментарий от tonchikp

УМВР

#!/usr/bin/env python3

import sys
import hashlib


def main():
    with open(sys.argv[1], 'rb') as fd:
        md5 = hashlib.md5()
        while True:
            buf = fd.read(4096)
            if not buf:
                break
            md5.update(buf)
        print(md5.hexdigest())


if __name__ == '__main__':
    main()



Примени его к проблемному файлу.

itn ★★★
()
Ответ на: комментарий от Psych218

Psych218, вот смотрю на Ваш код, здорово! Вы нашли компромисс. Вроде и всё соблюдено, но и не «многословно». Приходит в голову вопрос, а почему другие подходы смотрятся громоздко?

tonchikp
() автор топика
Ответ на: комментарий от Psych218

P. S. Лучше после шебанга пробел не ставить. Не помню точно, почему — вроде на каких-то юниксах (старых и коммерческих, но всё же) может не работать.

Спасибо, а это применимо ко всем языкам (bash, perl) или к Python только?

tonchikp
() автор топика
Ответ на: УМВР от itn

Примени его к проблемному файлу.

Хорошо, спасибо. Попробую, надеюсь всё получится

tonchikp
() автор топика

Не надо использовать shell=True с subprocess. Это бессмысленно, сложно и небезопасно, даже официальный мануал не рекомендует так делать.

Так проще и надёжнее:

#!/usr/bin/env python3

import subprocess,sys
file = sys.argv[1]
try:
    hash = subprocess.check_output(['md5sum','--',file],universal_newlines=True)[:32]

except Exception:
    print('Проблемный файл {}'.format(file))

print(file,':',hash)

# touch "wat time it is? five o'clock"
# ./tonchikp.py wat\ time\ it\ is\?\ five\ o\'clock 
wat time it is? five o'clock : d41d8cd98f00b204e9800998ecf8427e
legolegs ★★★★★
()
Ответ на: комментарий от legolegs

Так проще и надёжнее

Проще и надёжнее — использовать родной питоновский md5, который даст тот же результат, только быстрее и без выкрутасов. Хотя если говорить не конкретно про md5, а вообще про вызов внешних команд (когда это оправдано), то да, shell=True лучше избегать.

Psych218 ★★★★★
()
except Exception:
    print('Проблемный файл {}'.format(file))

Зашибись диагностика. Обязательно выводи в лог эксепшн.

true_admin ★★★★★
()
Ответ на: комментарий от Psych218

Наркоман. Из-за таких как ты питон и считается тормозным говном. Сейчас проверил на двух 4Гб файлах - твой вариант мало того что жрёт память, так ещё и медленнее на треть.

legolegs ★★★★★
()

емнип, в имени файла, при работе с utf-8, не может быть «проблемных символов», ну окромя разве что /, который служит для специального дела. так что да, нужно разобраться с hashlib и не заниматься хренью с оутсорсингом.

anonymous
()
Ответ на: комментарий от legolegs

Читай P.P.S. Вариант я привёл самый простой по коду, чтобы человеку легче было разобраться, подходит для мелких файлов. Для больших надо читать кусками. Ниже itn собственно этот вариант и привёл — более правильный, который не жрёт столько памяти.

Psych218 ★★★★★
()
Ответ на: комментарий от Psych218

Как на питоне хеш не считай, а всё равно получается тормозной велосипед:

time cat "$FNAME" > /dev/null; time md5sum "$FNAME"; time ./md5sum.py "$FNAME"

real    0m1.300s
user    0m0.001s
sys     0m1.288s
2861c4f7a9caf211519b8746f024fadf  велосипеды_на_питоне.1080p.mkv

real    0m6.886s
user    0m5.589s
sys     0m1.199s
2861c4f7a9caf211519b8746f024fadf

real    0m8.545s
user    0m6.937s
sys     0m1.507s

Итого: вариант с вызовом утилиты 1) проще 2) понятнее 3) быстрее

legolegs ★★★★★
()
Ответ на: комментарий от legolegs

Не надо использовать shell=True с subprocess. Это бессмысленно, сложно и небезопасно

И чего ты боишься в данном контексте? Что попадется файл с названием «& rm -Rf /» ?

Siado ★★★★★
()
Ответ на: комментарий от legolegs

вариант с вызовом утилиты 1) проще

Нет.

2) понятнее

Точно нет.

3) быстрее

И тоже нет. Код:

/tmp/test.py 1  79,27s user 12,48s system 24% cpu 6:12,79 total
/tmp/test.py 2  80,84s user 11,97s system 24% cpu 6:14,68 total

Вариант без сторонней утилиты на 2 секунды быстрее оказался на 103 файлах, являющихся играми от GOG (объём разнообразный — от 50 мб до 7 ГБ). 2 секунды это в данном случае ничто, конечно, но никак не с вызовом внешней утилиты быстрее.

Psych218 ★★★★★
()
Ответ на: комментарий от Siado

И чего ты боишься в данном контексте? Что попадется файл с названием «& rm -Rf /» ?

/ запрещён в названия файлов. Но в именах могут быть и кавычки всех сортов и * и ? многое. А главное, в этой возне нет никакого смысла: нет никакого профита в том, чтобы сначала склеить аргументы в строку в питоне, а затем поручить (неявно вызываемому) шеллу строку распарсить на аргументы и передать в позиксовый exec(), если можно сразу из питона воспользоваться рекомендованным способом и передавать в subprocess список аргументов не имея проблем с кавычками, пробелами и файлами lol"; rm -rf "..

legolegs ★★★★★
()
Ответ на: комментарий от Psych218

1) проще

Нет.

одна строка проще, чем 8 (9 если считать от балды выбранный буфсайз)

2) понятнее

Точно нет.

Лол. Ну добавь обработку ошибок тогда, раз тебе там слишком понятно.

3) быстрее

И тоже нет. Код:

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

legolegs ★★★★★
()
Ответ на: комментарий от legolegs

Кеш должен быть либо прогрет сразу либо очищен перед каждым тестом, я это добросовестно делал, а ты нет.

Я очищал кэш перед каждым тестом так:

sudo sync; echo 3 | sudo tee /proc/sys/vm/drop_caches 

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

2 секунды — это ничто, да. Это действительно может быть и погрешностью измерений (хотя ещё раз запустил, результут тот же). Но вариант с внешней утилитой в любом случае точно не быстрее. Не намного медленнее — да, можно даже сказать, что в пределах погрешности. Но не быстрее. Ты же перед этим по сути замерил разницу в вызове md5sum и запуске питоновского интерпретатора. То, что он стартует медленно, ни для кого не секрет. На одном файлике мало смысла тестить.

P. S. Быстрее всего будет это делать, конечно, в столько потоков, сколько ядер у процессора. В обоих случаях, что с внешней утилитой, что с родным hashlib.md5() это даст прирост производительности почти во столько же раз, сколько ядер и есть.

Psych218 ★★★★★
()
Последнее исправление: Psych218 (всего исправлений: 2)
Ответ на: комментарий от legolegs

Не надо использовать shell=True с subprocess. Это бессмысленно, сложно и небезопасно, даже официальный мануал не рекомендует так делать.

Объясните, почему? Без него не работает, хотя не знаю, что он делает честно говоря. Если это ненужное только рад буду

tonchikp
() автор топика
Ответ на: комментарий от Psych218

а вообще про вызов внешних команд (когда это оправдано), то да, shell=True лучше избегать

Почему? Для чего он вообще служит? Объясните пожалуйста

tonchikp
() автор топика
Ответ на: комментарий от Psych218

2 секунды - это, наверно, 106 вызовов md5sum. Хотя не должен он так медленно пускаться, из кеша-то. С find -exec {} + может быть быстрее, хотя действительно нет смысла это считать. Я признаю, что достаточно аккуратно изобретённый велосипед на питоне может быть не сильно плох, я только не понимаю, зачем его изобретать?

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

legolegs ★★★★★
()
Ответ на: комментарий от true_admin

Зашибись диагностика

Боитесь что файл обидится, что его назвали «проблемным»? :)) Пусть знает! :)

tonchikp
() автор топика
Ответ на: комментарий от tonchikp

Существует POSIX-вызов exec(), который принимает список/массив аргументов и передаёт его ядру. Существует также шелл (оболочка), который позволяет человеку ввести программу через пробелы аргументы, разбивает эту строку на список/массив и передаёт в exec(). Питоновская subprocess работает двумя путями: либо получает аргументы списком, как я тут показал Борьба с нехорошими символами в именах файлов (комментарий) либо получает строку и передаёт её шеллу ,а он передаёт в exec(). Но чтобы шелл понимал, какие пробелы разделяю аргументы, а какие есть часть имён файлов есть всякие правила закавычивания, экранирования и т.п. Так вот это в твоей задаче не нужно, шелл и связанный с ним головняк - лишнее звено.

legolegs ★★★★★
()
Ответ на: комментарий от legolegs

Спасибо! К сожалению сначала недостаточно внимательно посмотрел на Ваше решение с [], поэтому не увидел разницы

tonchikp
() автор топика
Ответ на: комментарий от tonchikp

Боитесь что файл обидится, что его назвали «проблемным»?

не, не в этом дело. Просто по твоей диагностике непонятно что за проблема с файлом. Может, у тебя прав не хватает, может, питон где-то внутрях падает итп. Надо хотя бы except Exception as e: print(«problem with processing file {}: {}».format(path_to_file, e))

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

Надо хотя бы except Exception as e: print(«problem with processing file {}: {}».format(path_to_file, e))

Хорошо, спасибо

tonchikp
() автор топика

удалить дубликаты файлов

Package: fdupes
Version: 1.51-1
Description: identifies duplicate files within given directories
 FDupes uses md5sums and then a byte by byte comparison to find
 duplicate files within a set of directories. It has several useful
 options including recursion.

Package: rdfind
Version: 1.3.4-2
Description: find duplicate files utility
 rdfind is a program to find duplicate files and optionally list, delete
 them or replace them with symlinks or hard links.  It is a command
 line program written in c++, which has proven to be pretty quick compared
 to its alternatives.

python

Вдоль.

anonymous
()
Ответ на: комментарий от anonymous

Package: fdupes

Гибкости нет в этой поделке. Узкая область применимости.

Mirage1_
()
Ответ на: УМВР от itn

Решил воспользоваться Вашим кодом, немного изменив:

def md5_file(file):
	md5 = hashlib.md5()
	with open(file, mode='rb') as file2:

		for part in file2.read(4096):
			md5.update(part)

	return md5.hexdigest()

Пробовал с mode='rb', выдаёт:

TypeError: object supporting the buffer API required

Пробовал с mode='r', выдаёт:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 66: invalid start byte

Подскажите пожалуйста что не так? (Python3)

tonchikp
() автор топика
Ответ на: комментарий от tonchikp

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

Deleted
()
Ответ на: комментарий от Deleted

А не проще поправить? Если знаете правильный вариант

tonchikp
() автор топика
Ответ на: комментарий от tonchikp

Это не так очевидно но тут итератор всё портит.
Не делай так.

Если всё по полочкам разложить то получив строку 'qweqwe' он разбивает на последовательность «якобы символов» конвертируя их в целое число.

Тоесть вся логика работы меняется полностью.

itn ★★★
()
Ответ на: комментарий от itn

А какое изменение относительно Вашего кода имеется ввиду? (1) Заменил while на for (2) Убрал лямбду с итератором (на других форумах предлагали лямбду) (3) Убрал if с break

tonchikp
() автор топика
Ответ на: комментарий от tonchikp

(1).
(3) это выход из бесконечного цикла.

Не в моём ни в твоём примере нет лямбд.

Проблема в том (без обид) что ты не совсем понимаешь как устроен сам питон. Полистай книжку Лутца, там очень подробно и на пальцах всё это описано.

Также если тебе чёт не понятно есть docs.python.org. Если мне надо что-то освежить я сразу иду туда.

itn ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.