Как удалить файл одновременно с добавлением в архив?
Учитывая контекст, я буду интерпретировать этот вопрос как:
Как удалить данные с диска сразу после его чтения, до того, как будет прочитан весь файл, чтобы было достаточно места для преобразованного файла.
Преобразование может быть любым, что вы хотите сделать с данными: сжатие, шифрование и т. Д.
Ответ таков:
<$file gzip | dd bs=$buffer iflag=fullblock of=$file conv=notrunc
Вкратце: прочитайте данные, бросьте их в gzip (или что вы хотите с ними делать), буферизируйте вывод, чтобы мы наверняка прочитали больше, чем записали, и записали его обратно в файл. Это версия, которая красивее и показывает результат при запуске:
cat "$file" \
| pv -cN 'bytes read from file' \
| gzip \
| pv -cN 'bytes received from compressor' \
| dd bs=$buffer iflag=fullblock 2>/dev/null \
| pv -cN 'bytes written back to file' \
| dd of="$file" conv=notrunc 2>/dev/null
Я пройдусь по ней, строка за строкой:
cat "$file"
читает файл, который вы хотите сжать. Это бесполезное использование cat (UUOC), так как следующая часть, pv, также может читать файл, но я считаю, что это красивее.
Он передает его по каналу, pv
который показывает информацию о прогрессе ( -cN
говорит, что «используйте какой-то [c] ursor» и дайте ему [N] ame).
Те каналы, в gzip
которые явно выполняется сжатие (чтение из stdin, вывод в stdout).
Это трубы в другое pv
(представление трубы).
Это трубы в dd bs=$buffer iflag=fullblock
. $buffer
Переменный является число, что - то вроде 50 мегабайта. Тем не менее, это много оперативной памяти, которую вы хотите выделить для безопасной обработки вашего файла (в качестве точки данных, буфер объемом 50 МБ для файла объемом 2 ГБ вполне подойдет). iflag=fullblock
Говорит dd
дочитать до $buffer
байт перед тем конвейер через. В начале gzip напишет заголовок, поэтому выходные данные gzip окажутся в этой dd
строке. Затем dd
будет ждать, пока у него не будет достаточно данных, прежде чем передать его, и поэтому ввод может читать дальше. Кроме того, если у вас есть несжимаемые части, выходной файл может быть больше, чем входной файл. Этот буфер гарантирует, что до $buffer
байтов это не проблема.
Затем мы переходим в другую строку представления канала и, наконец, в нашу dd
строку вывода . Эта строка имеет of
(выходной файл) и conv=notrunc
указывает, где notrunc
говорит dd
не обрезать (удалить) выходной файл перед записью. Таким образом, если у вас есть 500 байтов A
и вы пишете 3 байта B
, файл будет BBBAAAAA...
(вместо того, чтобы быть замененным на BBB
).
Я не покрывал 2>/dev/null
части, и они не нужны. Они просто приводят в порядок вывод, подавляя сообщение dd
«Я закончил и написал это много байтов». Обратная косая черта в конце каждой строки ( \
) заставляет bash рассматривать все это как одну большую команду, которая соединяет друг с другом.
Вот полный скрипт для более легкого использования. К счастью, я положил его в папку «gz-in-place». Затем я понял аббревиатуру, которую я сделал: GZIP: GNU ZIP на месте. Итак, я представляю, GZIP.sh:
#!/usr/bin/env bash
### Settings
# Buffer is how many bytes to buffer before writing back to the original file.
# It is meant to prevent the gzip header from overwriting data, and in case
# there are parts that are uncompressible where the compressor might exceed
# the original filesize. In these cases, the buffer will help prevent damage.
buffer=$((1024*1024*50)) # 50 MiB
# You will need something that can work in stream mode from stdin to stdout.
compressor="gzip"
# For gzip, you might want to pass -9 for better compression. The default is
# (typically?) 6.
compressorargs=""
### End of settings
# FYI I'm aware of the UUOC but it's prettier this way
if [ $# -ne 1 ] || [ "x$1" == "x-h" ] || [ "x$1" == "x--help" ]; then
cat << EOF
Usage: $0 filename
Where 'filename' is the file to compress in-place.
NO GUARANTEES ARE GIVEN THAT THIS WILL WORK!
Only operate on data that you have backups of.
(But you always back up important data anyway, right?)
See the source for more settings, such as buffer size (more is safer) and
compression level.
The only non-standard dependency is pv, though you could take it out
with no adverse effects, other than having no info about progress.
EOF
exit 1;
fi;
b=$(($buffer/1024/1024));
echo "Progressing '$1' with ${b}MiB buffer...";
echo "Note: I have no means of detecting this, but if you see the 'bytes read from";
echo "file' exceed 'bytes written back to file', your file is now garbage.";
echo "";
cat "$1" \
| pv -cN 'bytes read from file' \
| $compressor $compressorargs \
| pv -cN 'bytes received from compressor' \
| dd bs=$buffer iflag=fullblock 2>/dev/null \
| pv -cN 'bytes written back to file' \
| dd of="$1" conv=notrunc 2>/dev/null
echo "Done!";
Я чувствую, что добавляю еще одну строку буферизации перед gzip, чтобы предотвратить слишком dd
длинную запись, когда строка буферизации очищается, но только с буфером 50 МБ и 1900 МБ /dev/urandom
данных, похоже, все равно уже работает (md5sums совпал после распаковки). Достаточно хорошее соотношение для меня.
Другим улучшением было бы обнаружение слишком длинного письма, но я не вижу, как это сделать, не удаляя красоту вещи и не создавая много сложности. На этом этапе вы могли бы просто сделать его полноценной программой на Python, которая делает все правильно (с отказоустойчивыми файлами для предотвращения уничтожения данных).