Как безопасно конвертировать файл.gz в файл.xz
У меня есть несколько огромных файлов, которые в настоящее время разархивированы, и я бы хотел их сохранить. Я хочу настроить скрипт для этого, но я хочу быть осторожным, чтобы не потерять данные, то есть я никогда не должен удалять версию gzipped, если версия xz не была определенно создана правильно. Поскольку это большие файлы, я бы также предпочел не разархивировать файл на диск. Я думал о трубе set -o pipefail; gzip -dc file.gz | xz > file.xz && rm file.gz
может быть близко к тому, что я хочу. Какой правильный способ сделать это? Гарантируется ли это перехват всех сбоев, произошедших до удаления окончательного файла?
1 ответ
Добавление суммы SHA1 (которая математически гарантирует до невероятно высокой степени уверенности в том, что файлы совпадают, когда совпадают хэши, и не совпадают хэши, когда файлы не совпадают), добавляет меру целостности данных для защиты от случаев, когда дисковая подсистема могла сделать (тихую) ошибку во время записи. Безмолвная коррупция редка, но коварна, когда это происходит.
Конечно, вы все равно могли бы получить ошибочные результаты, если у вас возникли случайные ошибки при чтении, но в этом случае суммы не будут совпадать в любом случае, с чрезвычайно высокой степенью достоверности. Другими словами, если система повреждена (либо ОЗУ, либо диск выдает неправильные биты / перевернутые биты / поврежденные данные), тогда произойдет сбой, если просто &&
может быть успешным, и шансы этого добраться до rm
строки с поврежденными данными исчезающе малы (так как большинство ошибок имеют тенденцию повреждать данные случайным образом, шансы случайного изменения, вызывающего столкновение хеша в SHA1 во время обратного чтения, невероятно крошечные.)
#!/bin/bash
set -e
set -o pipefail
ORIGSUM=$(gzip -dc file.gz | tee >(xz > file.xz) | sha1sum)
NEWSUM=$(unxz -c file.xz | sha1sum)
if [ "${ORIGSUM}" = "${NEWSUM}" ]; then rm file.gz; fi
set -e
заставляет скрипт оболочки завершиться, как только любая строка скрипта возвращает ненулевой код выхода.
Затем мы используем tee
команда, чтобы скопировать распакованный вывод файла в оба xz
компрессор, и к sha1sum
программа. sha1sum
вычисляет сумму SHA1 исходных данных, содержащихся в архиве gzip, временно распаковывая их в программу sha1sum, которая считывает данные для вычисления суммы и затем отбрасывает данные. Используя tee
нам нужно только заплатить процессор разархивировать файл один раз.
Затем мы выполняем дополнительный вычислительно-дорогой шаг (для супер-дополнительной проверки) и удаляем сжатие xz для файла (временно, в поток) и направляем его в sha1sum, чтобы получить сумму SHA1 нашего "нового файла".
Затем мы сравниваем две суммы, и если они не равны строки, или если одна или обе из них имеют нулевую длину, мы либо получим ошибку сценария (которая завершается благодаря set -e
), или файл не будет удален. Вы можете реализовать else
предложение для удобной обработки ошибок, если вы хотите, но этот существенный скрипт как есть, будет чрезвычайно безопасным, хотя и не очень информативным для пользователя, выполняющего команду в интерактивном режиме.
В конце концов, file.gz
будет только несвязанным, если и только если несжатое содержимое file.gz
а также file.xz
точно совпадают в момент времени, когда хэши были вычислены, с астрономически высокой степенью достоверности (вероятность того, что что-то не так, будет не такой, как 1 в 1 с 300 нулями после него). В этот момент вам нужно беспокоиться только о повреждении данных после выхода из этого скрипта.;)
Спектакль
Этот сценарий будет работать почти с той же скоростью, что и исходный сценарий в вопросе, за исключением части, которая выполняется unxz
, К счастью, распаковка из LZMA происходит чрезвычайно быстро, почти так же быстро, как обычная Zip, и примерно на порядок быстрее, чем сжатие в LZMA. Если у вас быстрый ЦП, а файлы достаточно малы, это не должно увеличивать время выполнения сценария, но если вы цените целостность данных, а не производительность, это очевидный выигрыш.
Кредит, где кредит должен
Этот ответ на StackOverflow помог мне существенно написать этот скрипт.