2025, Oct 31 21:47

Как побайтно синхронизировать два бинарных файла в Python

Рабочий способ побайтной синхронизации бинарных файлов в Python: сравнение, правки в bytearray, запись и truncate. Почему запись в цикле часто не срабатывает.

Когда нужно выровнять содержимое двух двоичных файлов .SET побайтно, желание «залатать» различия прямо на месте понятно. В этом случае задача заключалась в том, чтобы скопировать отличающиеся байты из файла SET исправного GPS‑приёмника в файл приёмника, застрявшего в цикле загрузки. Первый вариант на Python без ошибок отработал в Windows 11, но целевой файл так и не изменился. Решение упиралось во время и способ записи данных.

Постановка задачи и неудачный пример

Идея была сравнить два файла и по ходу итерации сразу записывать отличия в целевой файл:

from itertools import zip_longest

counter = 0

with open(r"C:\Users\jimal\Desktop\APP2\mgnShell.set", "r+b") as bad_fp, open(r"E:\APP\mgnShell.set", "rb") as good_fp:
bad_blob = bad_fp.read()
bad_bytes = bytearray(bad_blob)
good_blob = good_fp.read()
good_bytes = bytearray(good_blob)
for b_bad, b_good in zip_longest(bad_bytes, good_bytes):
counter += 1
if b_bad != b_good:
bad_fp.seek(bad_bytes.index(b_bad))
if b_good == None:
bad_fp.write(bytes(0x00))
else:
bad_fp.write(bytes(b_good))

print(f"Done! Count was {counter}")

Что пошло не так на самом деле

Суть проблемы была не в правах ОС и не в атрибутах папки. Загвоздка — в стратегии записи. Запись внутри цикла идёт вразрез с логикой операции. Сначала нужно собрать весь набор изменений, и лишь затем выполнить запись. Иными словами: обновляем bytearray в памяти, а по завершении цикла записываем готовый буфер на диск.

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

Рабочий подход

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

counter = 0

with open(r"C:\Users\jimal\Desktop\APP2\mgnShell.set", "rb+") as dst_fp, open(r"E:\APP\mgnShell.set", "rb") as src_fp:
dst_raw = dst_fp.read()
dst_arr = bytearray(dst_raw)
src_raw = src_fp.read()
src_arr = bytearray(src_raw)
for b_dst, b_src, idx in zip(dst_arr, src_arr, range(len(dst_arr))):
counter += 1
if b_dst != b_src:
dst_arr[idx] = b_src
data_out = dst_arr[0:len(src_arr)]
dst_fp.seek(0)
dst_fp.write(data_out)
dst_fp.truncate()

print(f"Done! Count was {counter}")

Почему это важно

Правки бинарных файлов не прощают мелочей. Малейшая ошибка во времени записи или управлении смещениями приводит к отсутствию изменений или к частичной записи, которая тихо не достигает цели. Сбор всех модификаций в памяти с последующей единовременной фиксацией даёт чёткую границу: сначала считаем различия, затем сохраняем. Это соответствует простой ментальной модели — «прочитать, преобразовать, записать», — которую легче проверять и понимать.

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

Итоги

Для побайтной синхронизации двух файлов вычисляйте все изменения в памяти, а в конце выполняйте одну запись. Перейдите в начало, запишите подготовленный буфер и обрежьте файл, чтобы его длина совпала с источником. Если нужна лишь идентичность содержимого, подумайте о прямой замене. А при диагностике делайте невидимое видимым: добавляйте печать значений, чтобы проверять предположения о данных и потоке управления.

Статья основана на вопросе с StackOverflow от jacob malu и ответе от jacob malu.