NFsec Logo

Zapewnienie maksymalnej kompresji plików

04/09/2009 w Hacks & Scripts Brak komentarzy.  (artykuł nr 145, ilość słów: 780)

P

rzedstawiony skrypt jest tłumaczeniem “Ensuring Maximally Compressed Files” z książki “Wicked Cool Shell Scripts” autorstwa Dave’a Taylor’a udostępnionym on-line (Skrypt #38) na stronie http://www.intuitive.com/wicked/. Do tłumaczenia zostało dodane także parę informacji od tłumacza.

   Jak widzieliśmy w skrypcie #37, większość implementacji Uniksa zawiera więcej niż jedną metodę kompresji, ale to na użytkowniku spoczywa ciężar ustalenia, która z nich jest najlepsza. Typowym zachowaniem jest to, że użytkownik przyzwyczaja się do pracy tylko z jednym programem do kompresji, nie wiedząc nawet, że lepsze wyniki może osiągnąć za pomocą innego programu. Większe tego zagmatwanie powoduje to, że niektóre pliki lepiej kompresują się przy jednym algorytmie, a inne przy innym, a nie można się o tym inaczej przekonać jak za pomocą eksperymentów.

Logicznym rozwiązaniem wydaje się skrypt, który kompresuje pliki za pomocą każdego z narzędzi, a następnie wybiera jako najlepszy najmniejszy plik wynikowy. A potrafi to zrobić skrypt bestcompress. Nawiasem mówiąc, jeden z moich ulubionych w tej książce.

Kod:

#!/bin/sh

# bestcompress - po wprowadzeniu pliku próbuje go skompresować wszystkimi
# dostępnymi narzędziami kompresującymi, zachowując najmniejszy skompresowany
# plik i informując o rezultatach użytkownika. Jeśli nie podamy flagi -a,
# w strumieniu wejściowym pomija skompresowane pliki.

Z="compress"
gz="gzip" 
bz="bzip2"
Zout="/tmp/bestcompress.$$.Z"
gzout="/tmp/bestcompress.$$.gz"
bzout="/tmp/bestcompress.$$.bz"
skipcompressed=1

if [ "$1" = "-a" ] ; then
  skipcompressed=0  ; shift
fi

if [ $# -eq 0 ]; then
  echo "Usage: $0 [-a] file or files to optimally compress" >&2; exit 1
fi

trap "/bin/rm -f $Zout $gzout $bzout" EXIT

for name 
do 
  if [ ! -f "$name" ] ; then 
    echo "$0: file $name not found. Skipped." >&2
    continue
  fi

  if [ "$(echo $name | egrep '(\.Z$|\.gz$|\.bz2$)')" != "" ] ; then
    if [ $skipcompressed -eq 1 ] ; then
      echo "Skipped file ${name}: it's already compressed." 
      continue
    else
      echo "Warning: Trying to double-compress $name" 
    fi
  fi

  $Z  < "$name" > $Zout  &
  $gz < "$name" > $gzout &
  $bz < "$name" > $bzout &
  
  wait	# run compressions in parallel for speed. Wait until all are done

  smallest="$(ls -l "$name" $Zout $gzout $bzout | \
     awk '{print $5"="NR}' | sort -n | cut -d= -f2 | head -1)"

  case "$smallest" in
     1 ) echo "No space savings by compressing $name. Left as-is."
	 ;;
     2 ) echo Best compression is with compress. File renamed ${name}.Z
         mv $Zout "${name}.Z" ; rm -f "$name"
	 ;;
     3 ) echo Best compression is with gzip. File renamed ${name}.gz
	 mv $gzout "${name}.gz" ; rm -f "$name"
	 ;;
     4 ) echo Best compression is with bzip2. File renamed ${name}.bz2
	 mv $bzout "${name}.bz2" ; rm -f "$name"
  esac

done

exit 0

Jak to działa:

Najciekawszą linią tego skryptu jest:

smallest="$(ls -l "$name" $Zout $gzout $bzout | awk '{print $5"="NR}' | sort -n | cut -d= -f2 | head -1)"

W wierszu tym ls generuje rozmiar każdego pliku (oryginalny i trzech plików skompresowanych, w znanym porządku), za pomocą awk wycina same rozmiary plików, sortuje je numerycznie i generuje numer wiersza z najmniejszym plikiem wynikowym. Jeśli wszystkie skompresowane wersje są większe niż plik pierwotny, wynikiem jest 1 i towarzyszy mu wyświetlenie odpowiedniego komunikatu. W przeciwnym przypadku funkcja smallest wskaże, kto z trójki compress, gzip lub bzip2 najlepiej wykonał zadanie. Wówczas pozostaje przeniesienie odpowiedniego pliku do bieżącego katalogu i usunięciu pierwotnego pliku.

Inna technika w tym skrypcie godna przyjrzeniu się to:

$Z < "$name" > $Zout &
$gz < "$name" > $gzout &
$bz < "$name" > $bzout &
wait

Trzy wywołania kompresji są wykonywane równolegle, poprzez użycie końcowego znaku &, w celu umieszczenia każdego procesu we własnej podpowłoce, a następnie mamy wywołanie polecenia wait, które zatrzymuje skrypt, aż wszystkie wywołania zostaną zakończone. Na komputerze z jednym procesorem będzie można odczuć spadek wydajności, ale na maszynie wieloprocesorowej zadanie powinno zostać rozłożone i wykonane znacznie szybciej.

Uruchamianie skryptu:

Skrypt ten należy wywoływać, podając listę plików do skompresowania. Jeśli niektóre z nich są już skompresowane, a chcemy skompresować je jeszcze bardziej, używamy flagi -a; w przeciwnym razie zostaną one pominięte.

Rezultaty:
Najlepszym przykładem pokazania działania tego skryptu jest przy pomocy pliku wymagającego kompresji:

$ ls -l alice.txt
-rw-r--r-- 1 taylor staff 154872 Dec 4 2002 alice.txt

Skrypt ukrywa proces kompresowania pliku każdym z trzech narzędzi do kompresji, wyświetlając tylko rezultaty:

$ bestcompress alice.txt Best compression is with compress. File renamed alice.txt.Z

Widać, że nowy plik jest znacznie mniejszy:

$ ls -l alice.txt.Z
-rw-r--r-- 1 taylor wheel 66287 Jul 7 17:31 alice.txt.Z

Więcej informacji: Shell Script, awk –usage, man compress, gzip, bzip2

Kategorie K a t e g o r i e : Hacks & Scripts

Tagi T a g i : , , , ,

Komentowanie tego wpisu jest zablokowane.