Копирование деревьев каталогов

kopirovanie derevev katalogov Законченные системные программы

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

Это ограничение касается не только копий на компакт-дисках. Я столкнулся с подобной проблемой, когда попытался создать резервную копию жесткого диска моего ноутбука на другом жестком диске, — операция копирования, инициированная перетаскиванием файлов мышью, прекращалась, как только встречался файл со слишком длинным или необычным именем (обычно среди сохраненных веб-страниц). Последние полчаса, потраченные на копирование, оказывались потраченными впустую — я был, мягко говоря, расстроен!

Возможно, в Windows существует какая-нибудь волшебная настройка, позволяющая справиться с этой особенностью, но я перестал ее разыскивать, когда понял, что проще написать сценарий копирования на языке Python. Сценарий cpall.py, представленный в примере 6.10, реализует один из возможных способов копирования. С его помощью я могу управлять действиями, которые выполняются при обнаружении проблемных файлов, например, пропустить файл с помощью обработчика исключения. Кроме того, этот инструмент работает и на других платформах с тем же интерфейсом и таким же результатом. По крайней мере, мне кажется, что потратить несколько минут и написать на языке Python переносимый и многократно используемый сценарий для решения некоторой задачи более выгодно, чем искать решения, работающие только на одной платформе (если они вообще есть).

Пример 6.10. PP4E\System\Filetools\cpall.py

############################################################################## Порядок использования: “python cpall.py dirFrom dirTo”.

Рекурсивно копирует дерево каталогов. Действует подобно команде Unixcpr dirFrom/* dirTo”, предполагая, что оба аргумента dirFrom и dirTo являются именами каталогов.

Был написан с целью обойти фатальные ошибки при копировании файлов перетаскиванием мышью в Windows (когда встреча первого же проблемного файла вызывает прекращение операции копирования) и обеспечить возможность реализации более специализированных операций копирования на языке Python.

##############################################################################

import os, sys

maxfileload = 1000000

blksize = 1024 * 500

def copyfile(pathFrom, pathTo, maxfileload=maxfileload):

Копирует один файл из pathFrom в pathTo, байт в байт; использует двоичный режим для подавления операций кодирования/декодирования и преобразований символов конца строки

if os.path.getsize(pathFrom) <= maxfileload:

bytesFrom = open(pathFrom,’rb’).read() # маленький файл читать целиком open(pathTo, ‘wb’).write(bytesFrom)

else:

fileFrom = open(pathFrom, ‘ rb’) # большие файлы по частям

fileTo = open(pathTo, ‘wb’) # режим b для обоих файлов

while True:

bytesFrom = fileFrom.read(blksize) # прочитать очередной блок

if not bytesFrom: break # пустой после последнего блока

fileTo.write(bytesFrom)

def copytree(dirFrom, dirTo, verbose=0):

Копирует содержимое dirFrom и вложенных подкаталогов в dirTo, возвращает счетчики (files, dirs);

для представления имен каталогов, недекодируемых на других платформах, может потребоваться использовать переменные типа bytes;

в Unix может потребоваться выполнять дополнительные проверки типов файлов, чтобы пропускать ссылки, файлы fifo и так далее.

fcount = dcount = 0

for filename in os.listdir(dirFrom): # для файлов/каталогов

pathFrom = os.path.join(dirFrom, filename)

pathTo = os.path.join(dirTo, filename) # расширить оба пути

if not os.path.isdir(pathFrom): # скопировать простые файлы

try:

if verbose > 1: print(‘copying’, pathFrom, ‘to’, pathTo) copyfile(pathFrom, pathTo)

fcount += 1

except:

print(‘Error copying’, pathFrom, ‘to’, pathTo, ‘—skipped’)

print(sys.exc_info()[0], sys.exc_info()[1])

else:

if verbose: print(‘copying dir’, pathFrom, ‘to’, pathTo)

try:

os.mkdir(pathTo) # создать новый подкаталог

below = copytree(pathFrom, pathTo) # спуск в подкаталоги

fcount += below[0]

#

увеличить счетчики

dcount += below[1]

dcount += 1

#

подкаталогов

except:

print(‘Error creating’, pathTo, ‘—skipped’)

print(sys.exc_info()[0], sys.exc_info()[1])

return (fcount, dcount)

def getargs():

Извлекает и проверяет аргументы с именами каталогов, по умолчанию возвращает None в случае ошибки

try:

dirFrom, dirTo = sys.argv[1:]

except:

print(‘Usage error: cpall.py dirFrom dirTo’) else:

if not os.path.isdir(dirFrom):

print(‘Error: dirFrom is not a directory’)

elif not os.path.exists(dirTo):

os.mkdir(dirTo)

print(‘Note: dirTo was created’)

return (dirFrom, dirTo) else:

print(‘Warning: dirTo already exists’)

if hasattr(os.path, ‘samefile’):

same = os.path.samefile(dirFrom, dirTo) else:

same = os.path.abspath(dirFrom) == os.path.abspath(dirTo) if same:

print(‘Error: dirFrom same as dirTo’)

else:

return (dirFrom, dirTo)

if __name__ == ‘__main__’:

import time

dirstuple = getargs() if dirstuple:

print(‘Copying…’)

start = time.clock()

fcount, dcount = copytree(*dirstuple) print(‘Copied’, fcount, ‘files,’, dcount, ‘directories’, end=’ ‘) print(‘in’, time.clock() start, ‘seconds’)

В этом сценарии реализована собственная логика рекурсивного обхода дерева каталогов, в ходе которого запоминаются пути каталогов источника и приемника. На каждом уровне она копирует простые файлы, создает каталоги в целевом пути и производит рекурсивный спуск в подкаталоги с расширением путей «из» и «в» на один уровень. Эту задачу можно запрограммировать и другими способами (например, в процессе обхода можно изменять текущий рабочий каталог с помощью функции os.chdir или использовать решение на основе функции os.walk, замещая пути «из» и «в» по мере их обхода), но на практике вполне достаточно использовать прием расширения имен каталогов при спуске.

Обратите внимание на повторно используемую в этом сценарии функцию copyfile — на тот случай, если потребуется копировать файлы размером в несколько гигабайтов, она, исходя из размера файла, решает, читать ли файл целиком или по частям (напомню, что при вызове без аргументов метода read файла он загружает весь файл в строку, находящуюся в памяти). Мы выбрали достаточно большие размеры для читаемых целиком файлов и для блоков, потому что чем больший объем мы будем читать за один присест, тем быстрее будет работать сценарий. Это решение гораздо эффективнее, чем могло бы показаться на первый взгляд, — строки, остающиеся в памяти после последней операции чтения, будут утилизироваться сборщиком мусора, и освободившаяся память будет повторно использована последующими операциями. Здесь мы снова используем двоичный режим доступа к файлам, чтобы подавить кодирование/декодирование содержимого файлов и преобразование символов конца строки — в дереве каталогов могут находиться файлы самых разных типов.

Заметьте также, что сценарий при необходимости создает целевой каталог, и перед началом копирования предполагает, что он пуст, — удалите целевой каталог перед копированием нового дерева с тем же именем, иначе к дереву результата могут присоединиться старые файлы (мы могли бы автоматически очищать целевой каталог перед копированием, но это не всегда бывает желательно). Кроме того, данный сценарий пытается определить — не являются ли исходный и конечный каталоги одним и тем же каталогом. В Unix-подобных системах, где есть такие диковины, как ссылки, функция os.path.samefile проделывает более сложную работу, чем простое сравнение абсолютных имен файлов (разные имена файлов могут означать один и тот же файл).

Ниже приводится пример копирования большого дерева примеров книги в Windows (на протяжении этой главы я буду использовать дерево с примерами к предыдущему изданию). При запуске сценарию необходимо указать имена исходного и целевого каталогов, перенаправить вывод сценария в файл, если возникает слишком много ошибок, чтобы можно было прочитать все сообщения о них сразу (например, > output. txt), и при необходимости выполнить команду оболочки rmr или rmdir /S (или аналогичную для соответствующей платформы), чтобы сначала удалить целевой каталог:

C:\\PP4E\System\Filetools> rmdir /S copytemp copytemp, Are you sure (Y/N)? y

C:\\PP4E\System\Filetools> cpall.py C:\temp\PP3E\Examples copytemp

Note: dirTo was created

Copying

Copied 1430 files, 185 directories in 10.4470980971 seconds

C:\\PP4E\System\Filetools> fc /B copytemp\PP3E\Launcher.py

C:\temp\PP3E\Examples\PP3E\Launcher.py

Comparing files COPYTEMP\PP3E\Launcher.py and C:\TEMP\PP3E\EXAMPLES\PP3E\ LAUNCHER.PY

FC: no differences encountered

Вы можете воспользоваться аргументом verbose функции копирования, чтобы проследить, как протекает процесс копирования. Когда я работал над этим изданием в 2010 году, в этом примере за 10 секунд было скопировано дерево каталогов, содержащее 1430 файлов и 185 подкаталогов, — на моем удручающе медлительном нетбуке (для получения системного времени была использована встроенная функция time.clock). У вас аналогичная операция может выполняться быстрее или медленнее. Во всяком случае, это не хуже, чем самые лучшие результаты хронометража, полученные мной при перетаскивании каталогов мышью на этом же компьютере.

Каким же образом этот сценарий справляется с проблемными файлами на CD с резервными копиями? Секрет заключается в том, что он перехватывает и игнорирует исключения и продолжает обход. Чтобы скопировать с CD все хорошие файлы, я просто выполняю команду такого вида:

C:\\PP4E\System\Filetools> python cpall.py G:\Examples C:\PP3E\Examples

Поскольку на моем компьютере, работающем под управлением Windows, привод CD доступен как диск «G:», эта команда эквивалентна копированию путем перетаскивания элемента, находящегося в папке верхнего уровня на компакт-диске, за исключением того, что сценарий Python восстанавливается после возникающих ошибок и копирует остальные файлы. В случае ошибки копирования он выводит сообщение в стандартный поток вывода и продолжает работу. При копировании большого количества файлов, вероятно, будет удобнее перенаправить стандартный вывод сценария в файл, чтобы позднее его можно было детально исследовать.

Вообще говоря, сценарию cpall можно передать любой абсолютный путь к каталогу на вашем компьютере, даже такой, который обозначает устройство, например привод CD. Для выполнения сценария в Linux можно обратиться к приводу CD, указав такой каталог, как /dev/cdrom. После копирования дерева каталогов таким способом у вас может появиться желание проверить получившийся результат. Чтобы увидеть, как это делается, перейдем к следующему примеру.

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Каталог сайтов Всего.ру
Оцените статью
Секреты программирования
Добавить комментарий