Итерация внутри каталога для архивирования файлов с помощью python

#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:

У вас проблемы. Решайте отдельно.

  1. Поиск совпадающих имен. Используйте collections.defaultict

  2. Создание 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]) выдает пустую строку.