W dzisiejszym świecie administracji sieci i systemów komputerowych, programowanie staje się nieodzownym narzędziem, które pozwala na automatyzację wielu rutynowych zadań. Python, dzięki swojej prostocie i wszechstronności, zyskał ogromną popularność wśród administratorów. Jego łatwość w nauce i bogata biblioteka standardowa czynią go idealnym językiem do automatyzacji zadań administracyjnych, w tym pracy z plikami. 

Praca z plikami jest jednym z podstawowych zadań, które można zautomatyzować za pomocą Pythona. Może to obejmować odczytywanie plików logów w celu analizy zdarzeń, przetwarzanie plików konfiguracyjnych, czy też zapisywanie wyników zautomatyzowanych skryptów do plików. W tym artykule skupimy się na podstawowych operacjach związanych z otwieraniem, czytaniem i zapisywaniem plików w Pythonie. 

Otwieranie i czytanie plików w Python

Aby rozpocząć pracę z plikami w Pythonie, musimy najpierw otworzyć plik, który chcemy przeczytać. Python oferuje do tego celu wbudowaną funkcję open(). Podstawowa składnia tej funkcji jest następująca: 

file = open('sciezka_do_pliku', 'tryb')  

O co w tym wszystkim chodzi? Parametr ’sciezka_do_pliku’ to ścieżka do pliku, który chcemy otworzyć, a ’tryb’ to sposób otwarcia pliku. Ścieżka do po prostu zmienna lub podany jawnie bezpośrednio ciąg znaków zawierający bezwzględną lub względną ścieżkę do otwieranego pliku. Tryby zaś mówią, jaki to plik (inaczej obsługujemy pliki tekstowe i binarne) i co będziemy chcieli z plikiem zrobić (odczytać, czy może też do niego zapisywać dane). Najczęściej używanymi trybami są: 

  • ’r’ – tryb odczytu (domyślny), pozwala na czytanie zawartości pliku. 
  • ’w’ – tryb zapisu, tworzy nowy plik lub nadpisuje istniejący. 
  • ’a’ – tryb dopisywania, dodaje nowe dane na końcu pliku bez nadpisywania istniejącej zawartości. 
  • ’b’ – tryb binarny, używany do pracy z plikami binarnymi. 

Przykład odczytu całej zawartości pliku tekstowego wygląda następująco: 

with open('example.txt', 'r') as file: 
    content = file.read() 
    print(content)  

Zastosowanie konstrukcji with zapewnia, że plik zostanie prawidłowo zamknięty po zakończeniu operacji, nawet jeśli wystąpi błąd w trakcie odczytu. Funkcja read() odczytuje całą zawartość pliku jako pojedynczy łańcuch znaków. Alternatywnie, możemy odczytywać plik linia po linii za pomocą pętli: 

with open('example.txt', 'r') as file: 
    for line in file: 
        print(line.strip()) 

Funkcja strip() usuwa znaki białe (takie jak spacje i nowe linie) z początku i końca każdej linii, co jest przydatne do czyszczenia danych. Wszystkie użyte funkcje są funkcjami systemowymi wbudowanymi w interpreter języka, zatem nie musimy importować żadnych dodatkowych bibliotek. 

Zapisywanie danych do pliku 

Zapis danych do pliku w Pythonie jest równie prosty jak ich odczyt. Aby zapisać dane do pliku, musimy otworzyć plik w trybie zapisu (’w’) lub dopisywania (’a’). Warto nie mylić tych dwóch trybów – w pierwszym z nich jeżeli wskazany plik istnieje jego zawartość zostanie usunięta i zastąpiona przez nową. W drugim przypadku nowe dane zostaną dodane na końcu pliku. W obu przypadkach, jeżeli wskazany plik nie istnieje, zostanie on utworzony. Poniżej przykładu kodu w obu trybach: 

with open('output.txt', 'w') as file: 
    file.write('To jest przykładowy tekst zapisany do pliku.') 

with open('output.txt', 'a') as file: 
    file.write('\nDodajemy nową linię tekstu.') 

Jest jeszcze jeden warty uwagi a mało znany tryb zwany Exclusive creation. Za jego pomocą otworzymy plik do zapisu, ale tylko jeśli plik nie istnieje. Jeśli plik istnieje, Python zgłosi błąd FileExistsError.

Każdy z tych trybów domyślnie otwiera plik do zapisu jako plik tekstowy. Jeżeli chcemy zapisywać plik formie binarnej musimy uzupełnić atrybut otwarcia o literę ‘b’, na przykład:

with open('output.txt', 'wb') as file: 
    file.write('To jest przykładowy plik zapisany w trybie binarnym.') 

Istnieją też tryby mieszane, które łączą tryby tekstowe z trybami binarnymi oraz operacje zapisu i odczytu. Dzięki nim możemy wykonywać bardziej zaawansowane operacje na plikach, które wymagają zarówno czytania, jak i modyfikowania zawartości pliku bez konieczności zamykania i ponownego otwierania go w innym trybie. Tryby mieszane są szczególnie przydatne w scenariuszach, gdzie dane muszą być dynamicznie aktualizowane lub weryfikowane, a następnie zapisywane z powrotem do tego samego pliku. Tryby mieszane poznamy po tym, że w atrybutach ich otwarcia znajduje się znak ‘+’. Przykładowo:

with open('example.txt', 'r+') as file: 
    content = file.read() 
    file.write('Dopisana zawartość.')  

Powyższe kod otwiera plik do odczytu i zapisu. Jeśli plik nie istnieje, Python zgłosi błąd FileNotFoundError.

Edycja pliku konfiguracyjnego

Połączmy teraz wiedzę w praktyczny przykład.  Rozważmy scenariusz, w którym administrator sieci chce zautomatyzować proces zarządzania plikami konfiguracyjnymi urządzeń sieciowych. Plik konfiguracyjny przechowuje różne ustawienia, takie jak adresy IP, nazwy hostów i inne parametry konfiguracyjne. Administrator chce napisać skrypt, który odczyta bieżące ustawienia, dokona pewnych modyfikacji, a następnie zapisze zaktualizowane ustawienia z powrotem do pliku. 

Załóżmy, że mamy plik konfiguracyjny config.txt o następującej zawartości: 

hostname=router1 
ip=192.168.1.1 
netmask=255.255.255.0 
gateway=192.168.1.254

Chcemy zmienić adres IP urządzenia i zaktualizować plik konfiguracyjny. Oto przykładowy skrypt, którym wykonamy taką czynność: 

config_file = 'config.txt' 
# Function to read configuration 
def read_config(file_path): 
    with open(file_path, 'r') as file: 
        config_data = {} 
        for line in file: 
            line = line.strip() 
            if '=' in line: 
                key, value = line.split('=', 1) 
                config_data[key] = value 
        return config_data 
# Function to write new configuration 
def write_config(file_path, config_data): 
    with open(file_path, 'w') as file: 
        for key, value in config_data.items(): 
            file.write(f'{key}={value}\n') 
# Read current configuration 
config = read_config(config_file) 
# Modify the IP address 
config['ip'] = '192.168.1.100' 
# Save the updated configuration 
write_config(config_file, config) 
print(f'Updated configuration: {config}')

Dużo się tutaj dzieje, więc przeanalizyjmy kolejne bloki kodu tego skryptu. Skrypt, aby był bardziej funkcjonalny, został podzielony na funkcje, które wykonują określone czynności. Dzięki temu zapewnimy modularyzację samego skryptu jak i możliwość powtarzalnego wykonania określonych operacji na innych danych. 

Funkcja read_config przyjmuje ścieżkę do pliku jako argument. Otwiera plik w trybie tylko do odczytu (’r’) za pomocą instrukcji with open(file_path, 'r’) as file. Dzięki temu mamy pewność, że plik zostanie automatycznie zamknięty po zakończeniu operacji. Wewnątrz tej funkcji tworzymy pusty słownik config_data, który będzie przechowywał pary klucz-wartość z pliku konfiguracyjnego. Następnie czytamy plik linia po linii w pętli. Każdą linię oczyszczamy z białych znaków na początku i końcu za pomocą line.strip(). Sprawdzamy, czy linia zawiera znak równości (=). Jeśli tak, dzielimy linię na dwie części: klucz i wartość, używając metody split(’=’, 1). Parametr 1 gwarantuje, że podział nastąpi tylko przy pierwszym znaku równości, co jest ważne, gdyby wartość zawierała znak =. Klucz i wartość są następnie dodawane do słownika config_data. Jeśli linia nie zawiera znaku równości, jest pomijana. Na koniec funkcja zwraca słownik config_data. 

Funkcja write_config również przyjmuje ścieżkę do pliku oraz słownik config_data. Otwiera plik w trybie zapisu (’w’), co powoduje, że plik jest natychmiast opróżniany. Używając instrukcji with open(file_path, 'w’) as file, ponownie mamy pewność, że plik zostanie automatycznie zamknięty po zakończeniu operacji. Wewnątrz tej funkcji przechodzimy przez wszystkie pary klucz-wartość w słowniku config_data za pomocą pętli for key, value in config_data.items(). Dla każdej pary zapisujemy linię w formacie key=value do pliku. 

Po zdefiniowaniu obu funkcji, skrypt odczytuje bieżącą konfigurację za pomocą wywołania read_config(config_file), która zwraca słownik config. Następnie modyfikujemy wartość klucza ip w słowniku na nowy adres IP (’192.168.1.100′). Na koniec zaktualizowana konfiguracja jest zapisywana do pliku konfiguracyjnego za pomocą wywołania write_config(config_file, config). Skrypt kończy się wypisaniem na konsolę komunikatu zaktualizowanej konfiguracji. 

Zadanie domowe 

Przedstawiony przeze mnie skrypt nie jest optymalny. Można w nim parę rzeczy poprawić, w taki sposób że będzie dodana do niego odpowiednia obsługa wyjątków, a także można tą operację wykonać w nieco bardziej optymalny sposób skracając nieznacznie sam kod skryptu. Jako ćwiczenie do samodzielnego wykonania spróbuj wprowadzić modyfikacje takie aby wykorzystać inne tryby otwarcia pliku, dodać obsługę wyjątków, albo spróbuj zakodować tą operację w taki sposób abyśmy w istniejącym pliku edytowali  tylko jedną linię, którą zmieniamy, nie zaś na odpisywali cały plik. Jeżeli chcesz swoimi rozwiązaniami możesz podzielić się w komentarzu lub w wiadomości przesłanej na adres info@wladcysieci.pl. 

Zainteresował Cię ten artykuł i chcesz dowiedzieć się więcej o automatyzacji z wykorzystaniem języka Python lub innych narzędzi?

Koniecznie sprawdź portal Szkoła DevNet (https://szkoladevnet.pl) prowadzony przez Piotra, a także z przygotowanymi przez niego szkoleniami on-line (https://showroute.pl/edu/) między innymi z automatyzacji z wykorzystaniem Python i Ansible.