#python #loops
#python #циклы
Вопрос:
Мне нужно выполнить итерацию по папке и найти каждый экземпляр, где имена файлов идентичны (за исключением расширения), а затем заархивировать (предпочтительно с использованием tarfile) каждый из них в один файл.
Итак, у меня есть 5 файлов с именем: «example1», каждый с разными расширениями файлов. Мне нужно заархивировать их вместе и вывести как «example1.tar» или что-то подобное.
Это было бы достаточно просто с помощью простого цикла for, такого как:
tar = tarfile.open(‘example1.tar’,»w»)
для вывода в glob (‘example1 *’):
tar.add (вывод)
tar.close()
однако существует 300 файлов «примеров», и мне нужно выполнить итерацию по каждому из них и связанным с ними 5 файлам, чтобы заставить это работать. Это выше моего понимания. Любой совет приветствуется.
Ответ №1:
Шаблон, который вы описываете, обобщается на MapReduce. Я нашел простую реализацию MapReduce онлайн, из которой еще более простая версия:
def map_reduce(data, mapper, reducer):
d = {}
for elem in data:
key, value = mapper(elem)
d.setdefault(key, []).append(value)
for key, grp in d.items():
d[key] = reducer(key, grp)
return d
Вы хотите сгруппировать все файлы по их имени без расширения, которое вы можете получить из os.path.splitext(fname)[0]
. Затем вы хотите создать архив из каждой группы с помощью tarfile
модуля. В коде это:
import os
import tarfile
def make_tar(basename, files):
tar = tarfile.open(basename '.tar', 'w')
for f in files:
tar.add(f)
tar.close()
map_reduce(os.listdir('.'),
lambda x: (os.path.splitext(x)[0], x),
make_tar)
Редактировать: Если вы хотите группировать файлы по-разному, вам просто нужно изменить второй аргумент на map_reduce
. Приведенный выше код группирует файлы, которые имеют одинаковое значение для выражения os.path.splitext(x)[0]
. Таким образом, чтобы сгруппировать по имени базового файла с удаленными всеми расширениями, вы могли бы заменить это выражение на strip_all_ext(x)
и добавить:
def strip_all_ext(path):
head, tail = os.path.split(path)
basename = tail.split(os.extsep)[0]
return os.path.join(head, basename)
Комментарии:
1. в любом случае, чтобы изменить этот код или использовать os.path.extsep, чтобы разделить несколько расширений одного файла. например ‘foobar.aux.xml ‘
Ответ №2:
Вы могли бы сделать это:
- перечислите все файлы в каталоге
- создайте словарь, в котором базовое имя является ключом, а все расширения являются значениями
- затем выполнить проверку всех файлов по ключу словаря
Что-то вроде этого:
import os
import tarfile
from collections import defaultdict
myfiles = os.listdir(".") # List of all files
totar = defaultdict(list)
# now fill the defaultdict with entries; basename as keys, extensions as values
for name in myfiles:
base, ext = os.path.splitext(name)
totar[base].append(ext)
# iterate through all the basenames
for base in totar:
files = [base ext for ext in totar[base]]
# now tar all the files in the list "files"
tar = tarfile.open(base ".tar", "w")
for item in files:
tar.add(item)
tar.close()
Ответ №3:
У вас проблемы. Решайте отдельно.
-
Поиск совпадающих имен. Используйте
collections.defaultict
-
Создание tar-файлов после того, как вы найдете подходящие имена. Вы довольно хорошо это рассмотрели.
Итак. Сначала решите проблему 1.
Используйте glob
, чтобы получить все имена. Используйте os.path.basename
, чтобы разделить путь и базовое имя. Используйте os.path.splitext
для разделения имени и расширения.
Словарь списков можно использовать для сохранения всех файлов с одинаковым именем.
Это то, что вы делаете в части 1?
Часть 2 помещает файлы в архивы tar. Для этого у вас есть большая часть необходимого кода.
Ответ №4:
Попробуйте использовать модуль glob:http://docs.python.org/library/glob.html
Ответ №5:
#! /usr/bin/env python
import os
import tarfile
tarfiles = {}
for f in os.listdir ('files'):
prefix = f [:f.rfind ('.') ]
if prefix in tarfiles: tarfiles [prefix] = [f]
else: tarfiles [prefix] = [f]
for k, v in tarfiles.items ():
tf = tarfile.open ('%s.tar.gz' % k, 'w:gz')
for f in v: tf.addfile (tarfile.TarInfo (f), file ('files/%s' % f) )
tf.close ()
Комментарии:
1. @Hyperboreus: -1 …
f = 'fubar'; prefix = f [:f.rfind ('.') ]
выдает'fuba'
… используйтеos.path.splitext()
2. @Hyboreus: пока вы этим занимаетесь, уберите уродливые пробелы перед
[
в обращениях к фрагментам и dict и(
в вызовах функций3. @Hyperboreus: — спасибо за вашу помощь. При использовании приведенного выше кода я получал .tar каждого файла вместо каждого уникального имени файла? Мысли? @John Machin: не уверен насчет вашей ссылки os.path.splitext().
4. @KennyC: Все дело в использовании os.path.splitext() для удаления расширения (если таковое имеется) в конце пути, что является правильным решением и используется в 3 ответах. Если расширение отсутствует, оно вернет входные данные без изменений. Однако трюковой код, используемый @Hyboreus, ЗАВЕРШАЕТСЯ ОШИБКОЙ; он удаляет последний символ (fubar -> fuba).
5. @KennyC: Не принимая во внимание имена файлов без точек (моя ошибка, но другие уже указали, как это сделать правильно), скрипт упаковывает tar.gz файлы группируют файлы по их имени. Вот пример вывода:
Ответ №6:
import os
import tarfile
allfiles = {}
for filename in os.listdir("."):
basename = '.'.join (filename.split(".")[:-1] )
if not basename in all_files:
allfiles[basename] = [filename]
else:
allfiles[basename].append(filename)
for basename, filenames in allfiles.items():
if len(filenames) < 2:
continue
tardata = tarfile.open(basename ".tar", "w")
for filename in filenames:
tardata.add(filename)
tardata.close()
Комментарии:
1. -1
Use os.path.splitext()
—'.'.join ('fubar'.split(".")[:-1])
выдает пустую строку.