tar / bz2 kompresuje plik usuwając nieskompresowany oryginał

4

Czy jest jakiś sposób, aby zmienić katalog o nazwie dir1 w dir1.tar.bz2 bez zachowania oryginału? Muszę zaoszczędzić miejsce i chcę skompresować niektóre duże pliki, ale nie mam wystarczająco dużo miejsca, aby zachować skompresowaną kopię i oryginał. Czy istnieje sposób na bezpośrednie przekształcenie istniejącego pliku w archiwum?

Gregg Leventhal
źródło

Odpowiedzi:

7

tar nie możesz tego zrobić, ale możesz osiągnąć to, co chcesz:

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

gdzie:

  • find dir1 -depth -print0

    wyświetla listę wszystkich plików i katalogów dir1, wyświetlając zawartość katalogu przed samym katalogiem ( -depth). Zastosowanie -print0(a -0w xargsniżej) jest kluczem do wspierania nazw katalogów i plików ze spacjami .

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

    tworzy archiwum tar i dodaje do niego każdy plik lub katalog. Archiwum tar jest wysyłane na standardowe wyjście z opcją --file -.

  • bzip2 > dir1.tar.bz2

    kompresuje archiwum tar ze standardowego wejścia do pliku o nazwie dir1.tar.bz2.

Wymagana ilość wolnego miejsca na dysku to rozmiar największego skompresowanego pliku,dir1 ponieważ tarpodczas przetwarzania pliku czeka na zakończenie archiwizacji przed usunięciem. Ponieważ tarjest pobierany bzip2przez krótki czas, przed jego tarusunięciem, każdy plik znajduje się w dwóch miejscach: nieskompresowany w systemie plików i skompresowany w środku dir1.tar.bz2.

Byłem ciekawy, jak wykorzystano miejsce na dysku, więc przeprowadziłem ten eksperyment na mojej maszynie Wirtualnej Ubuntu:

  1. Utwórz system plików 1 GB:

    $ 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. Wypełnij system plików 900 1 megabajtowymi plikami:

    $ 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
    

    System plików jest teraz w 98% pełny.

  3. Wykonaj kopię dir1do późniejszej weryfikacji:

    $ cp -a /tmp/mnt/dir1 /tmp/dir1-check
    
  4. Kompresuj 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
    $
    

    Zauważ, że polecenia działały bez błędów „brak miejsca na urządzeniu”.

    dir1został usunięty, dir1.tar.bz2istnieje tylko :

    $ ls /tmp/mnt
    dir1.tar.bz2  lost+found
    
  5. Rozwiń dir1.tar.bz2i porównaj z /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
    $
    

    Kopie dir1i nieskompresowane dir1.tar.bz2są identyczne!

Można to uogólnić w skrypcie:

  1. Utwórz plik o nazwie tarrm(lub dowolną inną nazwę, którą lubisz) z następującymi treściami:

    #!/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 "${dir}.tar" -o -f "${dir}.tar.bz2" ]; then
        echo "$(basename $0): error: '$dir.tar' or '${dir}.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 ${dir}.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 > "${dir}.tar.bz2"
    
    # return status of last executed command
    if [ $? -ne 0 ]; then
        echo "$(basename $0): error while creating '${dir}.tar.bz2'" 1>&2
    fi
    
  2. Spraw, by był wykonywalny:

    chmod a+x tarrm

Skrypt wykonuje podstawowe sprawdzanie błędów: dir1musi istnieć, dir1.tar.bz2i dir1.tarnie powinno istnieć i ma keep tryb. Obsługuje także nazwy katalogów i plików z osadzonymi spacjami.

Przetestowałem skrypt, ale nie mogę zagwarantować, że jest bezbłędny , więc najpierw użyj go w trybie Keep:

./tarrm dir1 --keep

To wywołanie doda dir1do dir1.tar.bz2katalogu, ale go nie usunie.

Jeśli ufasz skryptowi, użyj go w następujący sposób:

./tarrm dir1

Skrypt poinformuje Cię, że dir1zostanie usunięty w trakcie tarowania go:

Tarring and **deleting** 'dir1'

Na przykład:

$ 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/
jaume
źródło
Ciekawe podejście Wydaje się, że zależy to od wystarczającej ilości miejsca na dysku, aby pomieścić zarówno nieskompresowane archiwum tar, jak i prawie w pełni skompresowane archiwum, ponieważ bzip2 (jak również inne narzędzia, o których wiem) tak naprawdę kompresować na miejscu . Może po prostu mógłbyś użyć rury z podpowłoki, aby Ci w tym pomóc?
CVn
Tak, moje proponowane rozwiązanie rzeczywiście wymaga wystarczającej przestrzeni do kompresji dir1.tar. Innym podejściem (znacznie prostsze) byłoby użyć zipzamiast: zip --recurse-paths --move "dir 1.zip" "dir 1". Zredagowałem swoją odpowiedź, aby wspomnieć zip...
jaume
Jak się okazuje, zipnie ma opcji. Przywróciłem pierwotną odpowiedź. zipnie zapewnia tego, czego chce OP (ze strony man): --move Przenieś określone pliki do archiwum zip; w rzeczywistości powoduje to usunięcie docelowych katalogów / plików po utworzeniu określonego archiwum zip. Jeśli katalog zostanie pusty po usunięciu plików, katalog zostanie również usunięty. Nie są usuwane, dopóki zip nie utworzy archiwum bez błędów.
jaume
1
@ MichaelKjörling Dzięki za podpowiedź, zauważyłem, że mogę użyć tar --createzamiast tego, tar --appendwięc ulepszyłem rozwiązanie do potokowania i wysłałem skompresowane wyjście do pliku. Teraz potrzebna ilość wolnego miejsca na dysku to skompresowany rozmiar pliku, dir1który jest największy po skompresowaniu, znacznie mniej niż dir.tar.
jaume
1
Myślę, że czegoś tu brakuje. zmienna „removeir” nie jest używana, a podczas testowania program usuwa tylko pliki, ale nie katalogi. Dodam tylkoif [ "$removedir" == "true" ]; then;rm -rf $dir; fi
mveroone