Python копирование файлов по маске
Недавно появилось некоторое чувство дискомфорта когда я приступаю к работе. Чувство было не то чтобы сильным, но сосредоточиться мешало. Думал, лень. Оказалось, что все чуть сложнее :) Ноуту, за которым я работаю, уже почти 3 года; стоит на нем Mac OS X 10.6.1, но яблок на нем нигде не нарисовано, и система периодического резервного копирования на нем отсутствует как класс. В общем, не было ощущения стабильности и надежности, так что я занялся этим вопросом вплотную. Собственно, далее я опишу результат, который мое подсознание удовлетворил :) Может быть, кому-то что-нибудь будет полезно.
Задача
Что бекапим
- Каталоги с разнообразными исходниками. Некоторые проекты являются Git-репозиториями, соответственно, хочется сохранить репозиторий, а не просто последнюю версию исходников. NB: выполнять make clean или аналоги в конце дня я как-то не привык, так что добавляем требование, что бинарники не копируются.
- Каталоги с конфигами и параметрами.
- Каталоги с TeX-исходниками. На самом деле, можно было бы добавить к первому пункту.
Куда бекапим
- Собственно, файлохранилища. Был выбран бесплатный аккаунт на Dropbox, ибо копировать командой cp – просто и удобно.
- Почта на Gmail. Все равно 7 гигов не используются, да и в надежности пока не было поводов сомневаться.
Какие фичи
- Простота. Основная фича :) Не нужно сложных и больших программ, я сам в состоянии решить какие файлы откуда мне нужно копировать. Проще быть уверенным в корректности работы небольшой программы.
- История изменений. Первая фича, которую хочется – это история изменений, чтобы не ругаться на тему «чем же этот файл мне мешал!»
- Сжатие. Git, конечно, жмет, но т.к. хранить бекапы все равно удобнее в одном файле, так что пускай его дожмет кто-нибудь еще.
- Шифрование Я не сомневаюсь в честности и секъюрности провайдеров интернета и провайдеров хранилищ. Тем не менее, не хочу, чтобы то, что я делаю, передавалось и лежало непонятно где в открытом виде.
Решение
Гугл по моим запросам ничего путного и удовлетворяющего моим запросам не выдал, поэтому пришлось писать самому. Заодно потрогал что есть Python, давно искал подходящий повод.
Копирование
- Список каталогов и файлов для копирования хранится в отдельном файле.
- Для каждого каталога можно указать маски файлов, которые из него нужно копировать. Если маска не указана, копировать все.
- Сохраняется исходная структура каталогов.
- Скрипт копирует (и только копирует) файлы в указанную папку.
- Если в целевой папке файл уже существует, копировать только в случае, если время модификации исходного файла больше.
Скрипт копирования написан на Python и занимает чуть меньше 200 строк (с учетом отступов, комментариев и т.п.). Дополнительно использует один нагугленный модуль для преобразования абсолютных путей в относительные и наоборот.
Использование:
-
Строгий формат файла со списком. Формат строки:
Пример списка (с базовым каталогом ~):
relpath.py был взят отсюда, из первого коммента, и чуть подправлен.
Сжатие и шифрование
Сжатие и шифрование требуют по одной строчке, так что как-то странно было бы их писать на Python. Чем сжимать – дело вкуса. Я предпочитаю 7-zip. Командная строка тут, соответственно, будет простой (имя архива – backup.7z, имя папки – .backup):
Для шифрования выбрал openssl, так как, в теории, почти везде есть и работает из коробки, без необходимости генерации чего бы то ни было. Команда выглядит так:
Соответственно, имя исходного файла backup.7z, имя выходного файла backup.aes256cbc (на случай, если вдруг забуду как зовется алгоритм шифрования). Пароль хранится в файле и может иметь произвольную длину. Лучше, все же, не менее 32 символов.
Отправка
Отправку, из-за потенциального разнообразия способов, сначала хотелось сделать тоже через очень настраиваемый Python-скрипт, но потом я решил, что копировать на небольшое число хостов гораздо проще вручную, а копировать на большое число хостов попахивает паранойей. Поэтому отправка реализована в sh-скрипте. Команда копирования на Dropbox при установленном клиенте интереса не представляет, так что рассмотрим только команду отправки на почту (она хоть чуть реже встречается):
Все вместе
Направления для дальнейшего развития
- Перевести все bash-скрипты на Python.
- Возможно, переключиться на pylzma и встроенную криптографию Python (для кроссплатформенности).
- Сделать формат файла-списка помягче и поприличнее.
Заключение
Я не исключаю возможности, что я изобрел велосипед. Но я потратил на это не так много времени, и получил немного опыта. Хочу отметить, что прежде всего в, кхм, дизайн этой системы закладывалась простота. Я знаю какие файлы и откуда мне надо скопировать. Я знаю что с ними потом делать. Мне нужен скрипт только для того, чтобы все это делалось на автомате. На мой взгляд, свои задачи он выполняет полностью.
Доброго всем утра, вечера, дня и ночи. Начал изучать Python и сразу же столкнулся с задачей копирования директории с условием, чтоб пользователю предлагался выбор - заменить существующий файл или нет. Покопавшись в модулях os, os.path, shutil я такой функции не нашел, сразу скажу в shutil есть функция copytree(), но как я понял она генерирует исключение, а исключения область в которой я еще не твердо стою на ногах, а скорее вообще не стою. Подумав решил написать свою, взяв за основу код той же copytree(), заодно и в Python поупражняться. Собственно возникло пару вопросов:
1) Может я что-то пропустил в os и shutil, с английским у меня не очень, поэтому большая просьба опытным людям - ткните меня носом, если я что то пропустил в упомянутых модулях.
2) Можно ли добиться необходимого результата обработкой исключений, не создавая новую функцию копирования
3)Решил выложить написанную мной функцию здесь, хотелось бы услышать конструктивную критику, замечания, советы по поводу ее реализации.
Использую Python 3.0.1 (в PortablePython)
Копирование файлов с подтверждением замены
1. Нет, стандартные высокоуровневые функции такое не поддерживают - очень неопределен “ответ пользователя”.
2. Вместо рекурсии посмотрите на os.walk
3. Вынесите “функцию-вопрошалку” за пределы вашего копировщика. Передавайте ей исходный путь и назначение. В ответ получайте константы типа “да, делать”, “только в текущей директории”, “все запрещаю и не беспокойте меня” и т.д.
4. Исключения - они несколько не для того. А именно исключения бросаются в случае ошибки, который внешний код не может игнорировать.
Например - open(filename)
если open будет возвращать код ошибки - его обязательно программист обязательно проигнорирует как правило. Особенно если этот open будет вызываться из функции, которая была вызвана уровнем выше и т.д. Да и неудобно так писать. Зато если выбросите OSError типа “файл не существует” - о вас уже не забудут.
По поводу исключений и их правильного применения можно писать долго - так что спрашивайте.
Копирование файлов с подтверждением замены
Андрей, спасибо за ответ.
Сделал функцию-вопрошалку отдельно. Но вот по варианту с os.wolk столкнулся с трудностью. При рекурсивном обходе директорий вот таким кодом:
получается что root - это путь к папке в которой лежит копируемый файл, причем root в себе уже содержит src, т.е root == src + ‘путь с подпапками’. Для того чтобы скопировать файл мне в пути root необходимо src заменить на dst.
Можно ли это сделать каким-либо простым способом (встроенная функция) или же необходимо писать собственную функцию? В os и shutil ничего не нашел. Стал пробовать через модуль re, но пока что не смог составить правильного regexp. Плюс, как я понял, ситуация усугубляется наличием в пути символов ‘\’
shutil has many methods you can use. One of which is:
- Copy the contents of the file named src to a file named dst . Both src and dst need to be the entire filename of the files, including path.
- The destination location must be writable; otherwise, an IOError exception will be raised.
- If dst already exists, it will be replaced.
- Special files such as character or block devices and pipes cannot be copied with this function.
- With copy , src and dst are path names given as str s.
Another shutil method to look at is shutil.copy2() . It's similar but preserves more metadata (e.g. time stamps).
If you use os.path operations, use copy rather than copyfile . copyfile will only accept strings.
@Owen Indeed it can, but the directory has to already exist. By the way, as long as the target directory already exists, dst can either end or not end with a trailing slash, it doesn't matter.
shutil does not actually copy files. There's a big fat warning right at the top of the docs. "this means that file owner and group are lost as well as ACLs. On Mac OS, the resource fork and other metadata are not used. This means that resources will be lost and file type and creator codes will not be correct. On Windows, file owners, ACLs and alternate data streams are not copied."
Function | Copies metadata | Copies permissions | Uses file object | Destination may be directory |
---|---|---|---|---|
shutil.copy | No | Yes | No | Yes |
shutil.copyfile | No | No | No | No |
shutil.copy2 | Yes | Yes | No | Yes |
shutil.copyfileobj | No | No | Yes | No |
copy2(src,dst) is often more useful than copyfile(src,dst) because:
- it allows dst to be a directory (instead of the complete target filename), in which case the basename of src is used for creating the new file;
- it preserves the original modification and access info (mtime and atime) in the file metadata (however, this comes with a slight overhead).
Here is a short example:
I am trying to randomly copy 100k files from 1 million files. copyfile is considerably faster than copy2
In Python, you can copy the files using
1) Copying files using shutil module
shutil.copyfile signature
shutil.copy signature
shutil.copy2 signature
shutil.copyfileobj signature
2) Copying files using os module
os.popen signature
os.system signature
3) Copying files using subprocess module
subprocess.call signature
Using single-string commands is bad coding style (flexibility, reliability and security), instead use ['copy', sourcefile, destfile] syntax wherever possible, especially if the parameters are from user input.
shutil is built-in, no need to provide non-portable alternatives. The answer could be actually improved by removing the system dependent solutions, and after that removal, this answer is just a copy of the existing answers / a copy of the documentation.
os.popen is deprecated for a while now. and check_output doesn't return the status but the output (which is empty in the case of copy/cp )
shutil does not actually copy files. There's a big fat warning right at the top of the docs. "this means that file owner and group are lost as well as ACLs. On Mac OS, the resource fork and other metadata are not used. This means that resources will be lost and file type and creator codes will not be correct. On Windows, file owners, ACLs and alternate data streams are not copied."
You can use one of the copy functions from the shutil package:
Copying a file is a relatively straightforward operation as shown by the examples below, but you should instead use the shutil stdlib module for that.
If you want to copy by filename you could do something like this:
I noticed a while ago that the module is called shutil (singular) and not shutils (plural), and indeed it is in Python 2.3. Nevertheless I leave this function here as an example.
Copy the contents of the file named src to a file named dst. The destination location must be writable; otherwise, an IOError exception will be raised. If dst already exists, it will be replaced. Special files such as character or block devices and pipes cannot be copied with this function. src and dst are path names given as strings.
Take a look at filesys for all the file and directory handling functions available in standard Python modules.
Directory and File copy example - From Tim Golden's Python Stuff:
For small files and using only python built-ins, you can use the following one-liner:
This is not optimal way for applications where the file is too large or when memory is critical, thus Swati's answer should be preferred.
Firstly, I made an exhaustive cheatsheet of shutil methods for your reference.
Secondly, explain methods of copy in exmaples:
- shutil.copyfileobj(fsrc, fdst[, length]) manipulate opened objects
- shutil.copyfile(src, dst, *, follow_symlinks=True) Copy and rename
- shutil.copy() Copy without preseving the metadata
- shutil.copy2() Copy with preseving the metadata
Recursively copy an entire directory tree rooted at src, returning the destination directory
You could use os.system('cp nameoffilegeneratedbyprogram /otherdirectory/')
where rawfile is the name that I had generated inside the program.
This is a Linux only solution
As of Python 3.5 you can do the following for small files (ie: text files, small jpegs):
write_bytes will overwrite whatever was at the destination's location
And then someone uses the code (accidentally or purposefully) on a large file… Using functions from shutil handles all the special cases for you and gives you peace of mind.
For large files, what I did was read the file line by line and read each line into an array. Then, once the array reached a certain size, append it to a new file.
this seems a little redundant since the writer should handle buffering. for l in open('file.txt','r'): output.write(l) should work find; just setup the output stream buffer to your needs. or you can go by the bytes by looping over a try with output.write(read(n)); output.flush() where n is the number of bytes you'd like to write at a time. both of these also don't have an condition to check which is a bonus.
Yes, but I thought that maybe this could be easier to understand because it copies entire lines rather than parts of them (in case we don't know how many bytes each line is).
This is awful. It does unnecessary work for no good reason. It doesn't work for arbitrary files. The copy isn't byte-identical if the input has unusual line endings on systems like Windows. Why do you think that this might be easier to understand than a call to a copy function in shutil ? Even when ignoring shutil , a simple block read/write loop (using unbuffered IO) is straight forward, would be efficient and would make much more sense than this, and thus is surely easier to teach and understand.
Python становится все популярнее благодаря относительной простоте изучения, универсальности и другим преимуществам. Правда, у начинающих разработчиков нередко возникают проблемы при работе с файлами и файловой системой. Просто потому, что они знают не все команды, которые нужно знать.
Эта статья предназначена как раз для начинающих разработчиков. В ней описаны 8 крайне важных команд для работы с файлами, папками и файловой системой в целом. Все примеры из этой статьи размещены в Google Colab Notebook (ссылка на ресурс — в конце статьи).
Показать текущий каталог
Самая простая и вместе с тем одна из самых важных команд для Python-разработчика. Она нужна потому, что чаще всего разработчики имеют дело с относительными путями. Но в некоторых случаях важно знать, где мы находимся.
Относительный путь хорош тем, что работает для всех пользователей, с любыми системами, количеством дисков и так далее.
Так вот, для того чтобы показать текущий каталог, нужна встроенная в Python OS-библиотека:
Ее легко запомнить, так что лучше выучить один раз, чем постоянно гуглить. Это здорово экономит время.
Имейте в виду, что я использую Google Colab, так что путь /content является абсолютным.
Проверяем, существует файл или каталог
Прежде чем задействовать команду по созданию файла или каталога, стоит убедиться, что аналогичных элементов нет. Это поможет избежать ряда ошибок при работе приложения, включая перезапись существующих элементов с данными.
Функция os.path.exists () принимает аргумент строкового типа, который может быть либо именем каталога, либо файлом.
В случае с Google Colab при каждом запуске создается папка sample_data. Давайте проверим, существует ли такой каталог. Для этого подойдет следующий код:
Эта же команда подходит и для работы с файлами:
Если папки или файла нет, команда возвращает false.
Объединение компонентов пути
В предыдущем примере я намеренно использовал слеш "/" для разделителя компонентов пути. В принципе это нормально, но не рекомендуется. Если вы хотите, чтобы ваше приложение было кроссплатформенным, такой вариант не подходит. Так, некоторые старые версии ОС Windows распознают только слеш "\" в качестве разделителя.
Но не переживайте, Python прекрасно решает эту проблему благодаря функции os.path.join (). Давайте перепишем вариант из примера в предыдущем пункте, используя эту функцию:
Создание директории
Ну а теперь самое время создать директорию с именем test_dir внутри рабочей директории. Для этого можно использовать функцию
os.mkdir():
Давайте посмотрим, как это работает на практике.
Если же мы попытаемся создать каталог, который уже существует, то получим исключение.
Именно поэтому рекомендуется всегда проверять наличие каталога с определенным названием перед созданием нового:
Еще один совет по созданию каталогов. Иногда нам нужно создать подкаталоги с уровнем вложенности 2 или более. Если мы все еще используем os.mkdir (), нам нужно будет сделать это несколько раз. В этом случае мы можем использовать os.makedirs (). Эта функция создаст все промежуточные каталоги так же, как флаг mkdir -p в системе Linux:
Вот что получается в результате.
Показываем содержимое директории
Еще одна полезная команда — os.listdir(). Она показывает все содержимое каталога.
Команда отличается от os.walk (), где последний рекурсивно показывает все, что находится «под» каталогом. os.listdir () намного проще в использовании, потому что просто возвращает список содержимого:
В некоторых случаях нужно что-то более продвинутое — например, поиск всех CSV-файлов в каталоге «sample_data». В этом случае самый простой способ — использовать встроенную библиотеку glob:
Перемещение файлов
Самое время попробовать переместить файлы из одной папки в другую. Рекомендованный способ — еще одна встроенная библиотека shutil.
Сейчас попробуем переместить все CSV-файлы из директории «sample_data» в директорию «test_dir». Ниже — пример кода для выполнения этой операции:
Кстати, есть два способа выполнить задуманное. Например, мы можем использовать библиотеку OS, если не хочется импортировать дополнительные библиотеки. Как os.rename, так и os.replace подходят для решения задачи.
Но обе они недостаточно «умные», чтобы позволить перемесить файлы в каталог.
Чтобы все это работало, нужно явно указать имя файла в месте назначения. Ниже — код, который это позволяет сделать:
Здесь функция os.path.basename () предназначена для извлечения имени файла из пути с любым количеством компонентов.
Другая функция, os.replace (), делает то же самое. Но разница в том, что os.replace () не зависит от платформы, тогда как os.rename () будет работать только в системе Unix / Linux.
Еще один минус — в том, что обе функции не поддерживают перемещение файлов из разных файловых систем, в отличие от shutil.
Поэтому я рекомендую использовать shutil.move () для перемещения файлов.
Копирование файлов
Аналогичным образом shutil подходит и для копирования файлов по уже упомянутым причинам.
Если нужно скопировать файл README.md из папки «sample_data» в папку «test_dir», поможет функция shutil.copy():
Удаление файлов и папок
Теперь пришел черед разобраться с процедурой удаления файлов и папок. Нам здесь снова поможет библиотека OS.
Когда нужно удалить файл, нужно воспользоваться командой os.remove():
Если требуется удалить каталог, на помощь приходит os.rmdir():
Однако он может удалить только пустой каталог. На приведенном выше скриншоте видим, что удалить можно лишь каталог level_3. Что если мы хотим рекурсивно удалить каталог level_1? В этом случае зовем на помощь shutil.
Функция shutil.rmtree() сделает все, что нужно:
Пользоваться ею нужно с осторожностью, поскольку она безвозвратно удаляет все содержимое каталога.
Собственно, на этом все. 8 важных операций по работе с файлами и каталогами в среде Python мы знаем. Что касается ссылки, о которой говорилось в анонсе, то вот она — это Google Colab Network с содержимым, готовым к запуску.
Помогите. На диске имеется неизвестное кол-во файлов с одинаковым названием "pic one". Нужно найти их все и сделать копии, но уже с разными последовательными названиями (пример: pic1, pik2, pic3. ) чтобы избежать перезаписи одного и того же файла найденным с аналогичным названием.
- Вопрос задан более года назад
- 501 просмотр
Есть несколько простых правил:
0. Следите за отступами и форматируйте код правильно.
Для питона это самое главное правило. Вы некорректно наставили тегов для оборачивания кода, я вам лишние убрал, но ваши отступы в коде поломаны. К этому нужно относиться внимательно.
Помните, что помимо пробелов бывают табуляции. Табуляция - это один широкий символ, но его ширина нигде не регламентирована, а питон по количеству пустых символов вначале строки понимает её вложенность в коде. Само собой смешение табуляций и пробелов в питоновском коде ведёт к страшной неразберихе и гарантированным ошибкам. Холивару "пробелы против табов" не одна сотня человеко-лет, но есть общее правило, зафиксированное в PEP: используйте 4 пробела в качестве одного отступа. Всегда. Без исключений.
1. Не используйте скобки там, где они не нужны:
В питоне скобки принято использовать для обозначения кортежей. Кортеж формируется запятыми выражении, но скобками ограничивают нужные элементы. В любом случае лишние скобки в питоне - плохой стиль. В вашем случае они не ломают логику, но формируют плохую привычку у вас.
1. Не конкатенируйте пути вручную, как вы это делаете здесь:
f'' + str('\\') + f''
Приведение строки к строке бессмысленно. Могли бы просто написать так: f'\\' , но и это плохая идея, потому что нельзя конкатенировать пути вручную. В разных операционных системах используются разные символы-разделители в путях. В питоне для работы с путями есть модули: os.path (древний как навоз мамонта) и pathlib (новый, объектный, очень удобный).
2. Используйте pathlib для работы с путями.
Изучите эту библиотеку. Вы откроете для себя массу полезных вещей.
3. Используйте оператор with при работе с закрываемыми ресурсами.
Ваш код:
А с использованием pathlib ещё лаконичнее:
Слеши расставятся в правильную сторону сами, как это нужно системе, вам никогда больше об этом не придётся задумываться. Не придётся думать об экранировании слешей, как это сделано у вас в путях. В библиотеке перекрыт оператор деления, через него удобно собирать путь.
А ещё там полно всяких удобностей вроде проверки наличия файла, создания каталогов, удаления.
4. Не смешивайте работу с каталогами и с файлами:
if name in dirs or name in files:
В каких-то случаях, возможно, и есть сходства, ведь в файловой системе каталог - это тоже файл. Но вы не сможете его ни прочитать, как таковой ни записать. По логике вашей программы получается, что, если есть каталог с таким именем и расширением, то вы его будете тоже пытаться читать. на этом ваш код и упадёт.
В pathlib, кстати, есть отличный метод rglob, который рекурсивно итерируется по всем путям, соответствующим маске. Сделать это можно относительно любого каталога:
files = Path('c:').rglob('*.jpg')
найдёт все jpeg-картинки на вашем диске c:.
При этом вы получите объект, по которому можно итерироваться циклом или превратить его в список. Элементы такой последовательности будут тоже типа pathlib.Path.
5. Для решения любой задачи разбейте ее на элементарные части и решайте их по отдельности.
В данном случае логично написать отдельную функцию безопасного копирования одного файла. Эта функция должна проверить есть ли целевое имя файла (если нет, то копирует), какого размера там файл (если такого-же до байта, то нужно проверить хеш и отказаться от копирования при совпадении, если другого, то нужно менять целевое имя или путь). С помощью pathlib можно подставлять расширение и быстро получить все файлы по маске в каталоге. Это позволит, например, быстро понять какой самый последний файл с именем вида 'file_name_00023.jpg' по маске 'file_name_*****.jpg', достать его цифровой хвостик, превратить в целое число, прибавить единицу и снова подставить в имя: Path('file_name_.jpg') .
Читайте также: