tar / bz2 сжимает файл, удаляя несжатый оригинал

5480
Gregg Leventhal

Есть ли способ превратить каталог с именем dir1 в dir1.tar.bz2 без сохранения оригинала? Мне нужно сэкономить место и сжать несколько больших файлов, но у меня недостаточно места для хранения сжатой копии и оригинала. Есть ли способ напрямую преобразовать существующий файл в архив?

4

1 ответ на вопрос

7
jaume

tar can't do that, but you can achieve what you want with:

find dir1 -depth -print0 | xargs -0 tar --create --no-recursion --remove-file --file - | bzip2 > dir1.tar.bz2

where:

  • find dir1 -depth -print0

    lists all files and directories in dir1, listing the directory contents before the directory itself (-depth). The use of -print0 (and -0 in xargs below) is the key to supporting directory and file names with embedded spaces.

  • xargs -0 tar --create --no-recursion --remove-file --file -

    creates a tar archive and adds every file or directory to it. The tar archive is sent to standard output with option --file -.

  • bzip2 > dir1.tar.bz2

    compresses the tar archive from standard input to a file called dir1.tar.bz2.

The amount of free disk space needed is the size of the largest compressed file in dir1 because tar, when processing a file, waits until archiving is complete before deleting it. Since tar is piped to bzip2, for a short moment, before tar removes it, every file resides in two places: uncompressed in the filesystem and compressed inside dir1.tar.bz2.

I was curious to see how disk space was used so I made this experiment on my Ubuntu VM:

  1. Create a 1 GB filesystem:

    $ dd if=/dev/zero of=/tmp/1gb bs=1M count=1024 $ losetup /dev/loop0 /tmp/1gb $ mkfs.ext3 /dev/loop0 $ sudo mount /dev/loop0 /tmp/mnt $ df -h Filesystem Size Used Avail Use% Mounted on /dev/loop0 1008M 34M 924M 4% /tmp/mnt 
  2. Fill the filesystem with 900 1 megabyte-files:

    $ chown jaume /tmp/mnt $ mkdir /tmp/mnt/dir1 $ for (( i=0; i<900; i++ )); do dd if=/dev/urandom of=/tmp/mnt/dir1/file$i bs=1M count=1; done $ chown -R jaume /tmp/mnt $ df -h Filesystem Size Used Avail Use% Mounted on /dev/loop0 1008M 937M 20M 98% /tmp/mnt 

    The filesystem is now 98% full.

  3. Make a copy of dir1 for later verification:

    $ cp -a /tmp/mnt/dir1 /tmp/dir1-check 
  4. Compress dir1:

    $ ls /tmp/mnt dir1 lost+found $ find /tmp/mnt/dir1 -depth -print0 | xargs -0 tar --create --no-recursion --remove-file --file - | bzip2 > /tmp/mnt/dir1.tar.bz2 $ 

    Note that the commands ran without any 'no space left on device' errors.

    dir1 was removed, only dir1.tar.bz2 exists:

    $ ls /tmp/mnt dir1.tar.bz2 lost+found 
  5. Expand dir1.tar.bz2 and compare to /tmp/dir1-check:

    $ tar --extract --file dir1.tar.bz2 --bzip2 --directory /tmp $ diff -s /tmp/dir1 /tmp/dir1-check (...) Files /tmp/dir1/file97 and /tmp/dir1-check/file97 are identical Files /tmp/dir1/file98 and /tmp/dir1-check/file98 are identical Files /tmp/dir1/file99 and /tmp/dir1-check/file99 are identical $ 

    Copy of dir1 and uncompressed dir1.tar.bz2 are identical!

This can be generalized in a script:

  1. Create a file called tarrm (or any other name of your liking) with these contents:

    #!/bin/bash # This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. # You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. # dir is first argument dir="$1" # check dir exists if [ ! -d "$dir" ]; then echo "$(basename $0): error: '$dir' doesn't exist" 1>&2 exit 1 fi # check if tar file exists if [ -f "$.tar" -o -f "$.tar.bz2" ]; then echo "$(basename $0): error: '$dir.tar' or '$.tar.bz2' already exist" 1>&2 exit 1 fi # --keep is second argument if [ "X$2" == "X--keep" ]; then # keep mode removefile="" echo " Tarring '$dir'" else removefile="--remove-file" echo " Tarring and **deleting** '$dir'" fi # normalize directory name (for example, /home/jaume//// is a legal directory name, but will break $.tar.bz2 - it needs to be converted to /home/jaume) dir=$(dirname "$dir")/$(basename "$dir") # create compressed tar archive and delete files after adding them to it find "$dir" -depth -print0 | xargs -0 tar --create --no-recursion $removefile --file - | bzip2 > "$.tar.bz2" # return status of last executed command if [ $? -ne 0 ]; then echo "$(basename $0): error while creating '$.tar.bz2'" 1>&2 fi 
  2. Make it executable:

    chmod a+x tarrm

The script does some basic error checking: dir1 must exist, dir1.tar.bz2 and dir1.tar shouldn't exist and has a keep mode. It also supports directory and file names with embedded spaces.

I've tested the script but can't guarantee it is flawless, so first use it in keep mode:

./tarrm dir1 --keep

This invocation will add dir1 to dir1.tar.bz2 but won't delete the directory.

When you trust the script use it like this:

./tarrm dir1

The script will inform you that dir1 will be deleted in the process of tarring it:

Tarring and **deleting** 'dir1'

For example:

$ ls -lF total 4 drwxrwxr-x 3 jaume jaume 4096 2013-10-11 11:00 dir 1/ $ find "dir 1" dir 1 dir 1/subdir 1 dir 1/subdir 1/file 1 dir 1/file 1 $ /tmp/tarrm dir\ 1/ Tarring and **deleting** 'dir 1/' $ echo $? 0 $ ls -lF total 4 -rw-rw-r-- 1 jaume jaume 181 2013-10-11 11:00 dir 1.tar.bz2 $ tar --list --file dir\ 1.tar.bz2 dir 1/subdir 1/file 1 dir 1/subdir 1/ dir 1/file 1 dir 1/ 
Интересный подход. Кажется, что это зависит от того, достаточно ли места на диске для хранения как несжатого архива tar, так и почти полностью сжатого архива, хотя bzip2 (как и другие известные мне инструменты) на самом деле не сжать * на месте *. Может быть, просто может быть, вы могли бы использовать трубу из подоболочки, чтобы помочь с этим? a CVn 10 лет назад 0
Да, моему предложенному решению действительно нужно достаточно места для сжатия `dir1.tar`. Другой (гораздо более простой) подход будет использовать вместо этого `zip`:` zip --recurse-paths --move "dir 1.zip" "dir 1" `. Я отредактировал свой ответ, упомянув `zip` ... jaume 10 лет назад 0
Ну, как оказалось, «zip» - не вариант. Я откатился на первоначальный ответ. `zip` не предоставляет то, что хочет OP (со страницы руководства): * - move Переместить указанные файлы в zip-архив; фактически это удаляет целевые каталоги / файлы ** после создания ** указанного архива zip. Если каталог становится пустым после удаления файлов, каталог также удаляется. ** Удаление не производится до тех пор, пока zip не создал архив без ошибок. *** jaume 10 лет назад 0
Впечатляющее решение. Я предполагаю, что оставшийся вопрос - сколько свободного места требуется для выполнения этой операции. Требуется ли оригинальный файл + архив или меньше? Gregg Leventhal 10 лет назад 0
@ MichaelKjörling Спасибо за подсказку, я заметил, что могу использовать «tar --create» вместо «tar --append», поэтому я улучшил решение для конвейерной передачи и отправил сжатый вывод в файл. Теперь требуемое количество свободного дискового пространства - это сжатый размер файла в `dir1`, который является наибольшим после сжатия, намного меньше, чем` dir.tar`. jaume 10 лет назад 1
@GreggLeventhal Я улучшил решение, и теперь необходимый объем свободного дискового пространства равен сжатому размеру файла в `dir1`, который является наибольшим после сжатия. Я сделал тест с файловой системой, заполненной на 98%, и работал безотказно. jaume 10 лет назад 0
Я сам еще не тестировал его, но награждаю вас ответом о том, сколько времени и усилий вы потратили. Вы должны поместить этот скрипт на язык более высокого уровня, такой как Python, и открыть его. Спасибо за вашу работу! Gregg Leventhal 10 лет назад 0
Я очень ценю ваши комментарии, Грегг. Мой Python (и PHP, Perl и т. Д. В этом отношении) довольно плох, поэтому я не думаю, что переписываю сценарий, но я добавил стандартную инструкцию разрешения копирования GPL. Убедитесь, что у вас есть резервная копия ваших данных, прежде чем тестировать скрипт. jaume 10 лет назад 0
Я бы подтвердил это снова, если бы мог, но, увы, я уже использовал свое выделение голосов на этот конкретный ответ ... a CVn 10 лет назад 0
Я думаю, что-то здесь не хватает. переменная «removeir» не используется, и при тестировании эта программа удаляет только файлы, но не каталоги. Я просто добавлю `if [" $ removeir "==" true "]; затем; rm -rf $ dir; fi` mveroone 10 лет назад 1
Вы правы, спасибо за указание на это и за ваше предложение. Я проверю сценарий позже и отредактирую его. jaume 10 лет назад 0
@Kwaio В своих тестах я обнаружил, что переменная `removeir` не нужна, поэтому я удалил ее. Каталог удаляется, если `--keep` не указан, интересно, почему он удалил только файлы в вашем тесте. Я запустил tarrm на OS X 10.9.1. jaume 10 лет назад 0
Я использую RHEL3 с ядром 2.4.17 и coreutils 4.5.3. это может изменить вещи. В любом случае, спасибо за скрипт, файлы занимают больше всего места. mveroone 10 лет назад 0

Похожие вопросы