#!/usr/bin/env python3 """ Format .tar jest dość starą metodą przechowywania wielu plików w jednej paczce. Na tyle starą, że jej nazwa to akronim "Tape ARchive", czyli Archiwum Taśmowe! To dość prosty format – polega on na sklejeniu archiwizowanych plików ze sobą. Każdy plik poprzedzony jest nagłówkiem oraz dopełniony do najbliższej wielokrotności 512 bajtów zerami. Dokładniejsze informacje są powszechnie dostępne w sieci, np.: * https://en.wikipedia.org/wiki/Tar_%28computing%29 * https://wiki.osdev.org/USTAR W szczególności interesują nas w nagłówku bajty: * 0-99: nazwa pliku, dopełniona znakami ASCII NUL ('\x00') * 124-134: długość pliku, niestety ze względów historycznych w postaci liczby w formacie ósemkowym, której cyfry zapisane są jako ciąg ASCII. Po 512 bajtach nagłówka zaczyna się właściwa treść pliku, która jest następnie – jak wspomniano – dopełniana zerami do najbliższej wielokrotności 512 bajtów, po czym ewentualnie zaczyna się nagłówek kolejnego pliku. Poniższy kod wyodrębnia pliki z podanego archiwum .tar. Dla uproszczenia ignorujemy pozostałe informacje zawarte w nagłówku (np. uprawnienia dostępu do pliku i jego właściciela) oraz zakładamy, że archiwum zawiera tylko i wyłącznie zwyczajne pliki (nie ma katalogów ani linków). Przed wyodrębnieniem każdego pliku program sprawdza czy plik ten już istnieje i pyta czy nadpisać. Aby przetestować program, można utworzyć jakieś własne archiwum: tar -cf archiwum.tar lista plikow oddzielonych spacjami """ import sys import math def untar(nazwa_archiwum, nadpisywanie = "--ask"): with open(nazwa_archiwum,'rb') as archiwum: offset = 0 #przesunięcie względem początku, zwiększa się dla kolejnych plików while True: #decode zmienia dane binarne na string, .rstrip usuwa znaki NUL z końca nazwa_pliku = archiwum.read(100).decode().rstrip("\x00") if nazwa_pliku == "": break #jeżeli już nie ma nic więcej do wypakowania to będzie puste archiwum.seek(124+offset) #skaczemy do miejsca z długością wypakowywanego pliku #długość pliku jest zapisana jako string z cyframi jej zapisu ósemkowego #wystarczy odpowiednie użycie int, by Python przekonwertował to na zwykły dziesiętny integer dlugosc_pliku = int(archiwum.read(11),8) print(nazwa_pliku, dlugosc_pliku) archiwum.seek(512+offset) #skaczemy do właściwej treści pliku tresc_pliku = archiwum.read(dlugosc_pliku) #czytamy ją #zaokrąglamy w górę do najbliższej wielokrotności 512 #offset = 512*(archiwum.tell()/512).__ceil__() #moglibyśmy w zasadzie nie importować math offset = 512*math.ceil(archiwum.tell()/512) #ale tak wygląda bardziej przejrzyście archiwum.seek(offset) #skaczemy do początku nagłówka kolejnego pliku try: #sprawdzamy czy plik już czasem nie istnieje with open(nazwa_pliku,'xb') as plik: #jeśli nie, to nie ma błędu plik.write(tresc_pliku) except FileExistsError: #jeśli istnieje, to metoda otwierania 'x' wyrzuci błąd if (nadpisywanie == "--overwrite" or nadpisywanie == "--ask" and input("Plik istnieje! Czy nadpisać? (T)")[0].upper() == "T"): with open(nazwa_pliku,'wb') as plik: #jeśli tak, to otwórz do nadpisywania plik.write(tresc_pliku) elif nadpisywanie == "--keep-old-files": print("Wybrano opcję --keep-old-files. Pozostawiono istniejący plik.") #użycie powyższej funkcji if len(sys.argv) == 3: if sys.argv[2] not in {"--ask","--overwrite","--keep-old-files"}: print("Błędna opcja: '"+sys.argv[2]+"'. Wychodzę!") else: untar(sys.argv[1],sys.argv[2]) else: untar(sys.argv[1])