Last modified: August 24, 2024

This article is written in: 🇵🇱

Informacje o systemie operacyjnym

Praca z systemem operacyjnym jest nieodłączną częścią tworzenia aplikacji i skryptów w Pythonie. Moduł os z biblioteki standardowej dostarcza bogaty zestaw funkcji, które pozwalają na interakcję z systemem operacyjnym w sposób przenośny i niezależny od platformy. Dzięki temu możemy uzyskiwać informacje o bieżącym środowisku, manipulować plikami i katalogami, a nawet uruchamiać procesy.

Dzięki funkcjom modułu os możemy tworzyć kod, który będzie działać podobnie na różnych systemach operacyjnych, takich jak Linux, Windows czy macOS. To duże udogodnienie, ponieważ nie musimy dostosowywać poleceń do określonej platformy. Wystarczy, że skorzystamy z uniwersalnych metod os, a Python „pod spodem” wykona odpowiednie czynności w zależności od środowiska.

Ustalanie rodzaju systemu operacyjnego

Czasami potrzebujemy dostosować działanie naszego programu w zależności od systemu operacyjnego, na którym jest uruchamiany. Aby poznać podstawową nazwę systemu, możemy skorzystać z atrybutu os.name:

import os

print(os.name)

Jeśli uruchomimy ten kod, otrzymamy jedno z następujących wyjść, w zależności od systemu:

Otrzymana wartość daje ogólne pojęcie o rodzaju systemu, ale często potrzebujemy bardziej szczegółowych informacji. W tym celu możemy użyć modułu platform:

import platform

print(platform.system())    # Nazwa systemu operacyjnego
print(platform.release())   # Wersja systemu
print(platform.version())   # Szczegółowa wersja systemu
print(platform.machine())   # Architektura sprzętowa

Przykładowe wyniki mogą wyglądać następująco:

Linux:

Windows:

Dzięki tym informacjom możemy precyzyjnie dostosować działanie naszego programu do środowiska użytkownika. Na przykład, jeśli stwierdzimy, że pracujemy na Windowsie, możemy użyć innych ścieżek do plików niż na Linuxie, czy też wykonać określone polecenia shellowe.

Manipulacja systemem plików

Operacje na plikach i katalogach są kluczowe w wielu aplikacjach. Moduł os oferuje funkcje, które umożliwiają zarządzanie systemem plików w sposób przenośny między różnymi systemami operacyjnymi.

Jest to bardzo ważne w kontekście kompatybilności projektów – używając metod os, nie musimy martwić się o różnice w separatorach ścieżek pomiędzy Windows a Unix/Linux (które używają innych znaków w ścieżkach). W efekcie piszemy kod bardziej uniwersalny i mniej podatny na błędy zależne od platformy.

Listowanie zawartości katalogu

Aby wyświetlić listę plików i folderów w określonym katalogu, używamy funkcji os.listdir():

import os

zawartosc = os.listdir('.')
print("Zawartość bieżącego katalogu:")
for element in zawartosc:
    print(element)

Jeśli w bieżącym katalogu znajdują się pliki dokument.txt, skrypt.py oraz folder dane, wynik będzie:

Zawartość bieżącego katalogu:
dokument.txt
skrypt.py
dane

W ten sposób szybko i prosto możemy zobaczyć, co znajduje się w danej lokalizacji. Metoda listdir() nie rozróżnia jednak plików od katalogów – zwraca wszystkie nazwy obiektów znajdujących się w katalogu.

Tworzenie nowego katalogu

Do utworzenia nowego katalogu służy funkcja os.mkdir():

import os

os.mkdir('nowy_folder')
print("Utworzono katalog 'nowy_folder'.")

Po uruchomieniu tego kodu w bieżącym katalogu pojawi się nowy folder o nazwie nowy_folder.

Dzięki takim operacjom mamy pełną kontrolę nad strukturą naszych plików i katalogów – możemy dynamicznie tworzyć foldery, w których będziemy zapisywać dane, logi czy raporty. Pamiętajmy, że jeśli folder już istnieje, wywołanie os.mkdir() spowoduje błąd. Aby uniknąć tego, możemy najpierw sprawdzić, czy folder już nie istnieje, używając os.path.exists().

Usuwanie pliku lub katalogu

Aby usunąć plik, korzystamy z funkcji os.remove():

import os

os.remove('dokument.txt')
print("Usunięto plik 'dokument.txt'.")

Jeśli chcemy usunąć pusty katalog, używamy os.rmdir():

import os

os.rmdir('nowy_folder')
print("Usunięto katalog 'nowy_folder'.")

Uwaga: os.rmdir() usuwa tylko puste katalogi. Jeśli katalog zawiera pliki lub podkatalogi, musimy użyć modułu shutil i funkcji shutil.rmtree().

Podobnie jak przy tworzeniu katalogów, operacje usuwania należy wykonywać ostrożnie. Przed wywołaniem os.remove() czy shutil.rmtree() upewnijmy się, że faktycznie chcemy usunąć wskazane elementy, bo cofnięcie takiej operacji może być trudne lub niemożliwe (zwłaszcza gdy dane nie są nigdzie zbackupowane).

Łączenie ścieżek w sposób przenośny

Przy łączeniu fragmentów ścieżek warto użyć funkcji os.path.join(), która automatycznie uwzględnia separator odpowiedni dla danego systemu operacyjnego:

import os

sciezka = os.path.join('katalog', 'podkatalog', 'plik.txt')
print(f"Ścieżka do pliku: {sciezka}")

Na systemie Windows wynik będzie wyglądał jak katalog\podkatalog\plik.txt, a na Unix/Linux katalog/podkatalog/plik.txt.

To rozwiązanie jest absolutnie kluczowe dla transportowalności kodu, ponieważ w przeciwnym wypadku musielibyśmy tworzyć warunkową logikę do obsługi różnych rodzajów separatorów. Warto od samego początku przyzwyczaić się do używania os.path.join(), zwłaszcza w wieloosobowych projektach, aby uniknąć problemów z kompatybilnością.

Sprawdzanie istnienia pliku lub katalogu

Możemy sprawdzić, czy dana ścieżka istnieje oraz czy jest plikiem lub katalogiem:

import os

sciezka = 'dane'

if os.path.exists(sciezka):
    if os.path.isfile(sciezka):
        print(f"'{sciezka}' jest plikiem.")
    elif os.path.isdir(sciezka):
        print(f"'{sciezka}' jest katalogiem.")
else:
    print(f"'{sciezka}' nie istnieje.")

Jeśli dane jest katalogiem, otrzymamy:

'dane' jest katalogiem.

Warto rozróżniać te funkcje, gdyż pozwalają nam bezpiecznie wykonywać kolejne kroki na ścieżkach, np. najpierw sprawdzić, czy plik istnieje, zanim spróbujemy go otworzyć. Dzięki temu unikamy nieoczekiwanych błędów lub wyjątków w czasie działania programu.

Informacje o użytkownikach

W systemach Unix/Linux możemy uzyskać informacje o użytkownikach i grupach za pomocą modułu pwd. Jest to szczególnie przydatne, gdy chcemy dostosować działanie programu do konkretnego użytkownika.

Typowym przykładem użycia jest sytuacja, w której skrypt powinien zapisywać dane wyłącznie w katalogu domowym bieżącego użytkownika, a my chcemy pobrać nazwę tego użytkownika czy lokalizację jego katalogu home. Moduł pwd uprości nam te zadania.

Pobieranie informacji o bieżącym użytkowniku

import os
import pwd

user_info = pwd.getpwuid(os.getuid())

print(f"Nazwa użytkownika: {user_info.pw_name}")
print(f"ID użytkownika: {user_info.pw_uid}")
print(f"ID grupy: {user_info.pw_gid}")
print(f"Katalog domowy: {user_info.pw_dir}")

Przykładowe wyjście:

Nazwa użytkownika: jan

ID użytkownika: 1000

ID grupy: 1000

Katalog domowy: /home/jan

Dzięki takiej wiedzy skrypt może np. automatycznie zapisywać raporty w /home/jan/raporty albo sprawdzić, czy dany użytkownik ma uprawnienia do uruchomienia pewnych funkcji w systemie.

Pobieranie informacji o grupie użytkownika

Aby uzyskać informacje o grupie, możemy użyć modułu grp:

import grp

group_info = grp.getgrgid(user_info.pw_gid)

print(f"Nazwa grupy: {group_info.gr_name}")
print(f"ID grupy: {group_info.gr_gid}")
print(f"Członkowie grupy: {', '.join(group_info.gr_mem)}")

Jeśli grupa jan ma dodatkowych członków, zostaną oni wymienieni w wyniku.

Taka kontrola nad użytkownikami i grupami pozwala np. na pisanie skryptów administracyjnych, które wykonują różne akcje zależnie od uprawnień danej osoby. Zazwyczaj tego typu operacje są bardziej zaawansowane i wymagają uruchamiania skryptu z odpowiednimi prawami (np. jako administrator), dlatego trzeba pamiętać o kwestiach bezpieczeństwa.

Informacje o dyskach

Monitorowanie dostępnego miejsca na dysku jest istotne w aplikacjach, które przechowują lub przetwarzają duże ilości danych. Możemy uzyskać te informacje za pomocą funkcji os.statvfs().

Dzięki temu unikniemy sytuacji, w której skrypt zaczyna zapisywać pliki na dysku i nagle napotyka brak miejsca, co może prowadzić do utraty danych lub nieoczekiwanych błędów. Możemy na przykład zaplanować działania prewencyjne, jeśli wolnego miejsca jest mniej niż określony próg.

Sprawdzanie pojemności i wolnego miejsca na dysku

import os

statvfs = os.statvfs('/')

# Rozmiar bloku systemu plików
rozmiar_bloku = statvfs.f_frsize

# Całkowita liczba bloków
liczba_blokow = statvfs.f_blocks

# Liczba wolnych bloków
wolne_bloki = statvfs.f_bavail

# Obliczenia
calkowita_pojemnosc = rozmiar_bloku * liczba_blokow
wolne_miejsce = rozmiar_bloku * wolne_bloki

print(f"Całkowita pojemność dysku: {calkowita_pojemnosc / (1024**3):.2f} GB")
print(f"Wolne miejsce na dysku: {wolne_miejsce / (1024**3):.2f} GB")

Jeśli dysk ma 500 GB, a wolne jest 200 GB, wynik będzie:

Całkowita pojemność dysku: 500.00 GB

Wolne miejsce na dysku: 200.00 GB

Tego rodzaju informacja pozwala nam elastycznie zarządzać danymi i reagować, gdy zasoby systemowe zaczynają się kończyć. Zanim zostanie w pełni zapełniony, aplikacja może np. automatycznie przenieść nieużywane pliki w inne miejsce lub powiadomić administratora o potrzebie powiększenia przestrzeni.

Informacje o procesorze

Poznanie parametrów procesora może być przydatne przy optymalizacji wydajności aplikacji. Niektóre zadania da się rozproszyć na wiele rdzeni, co pozwala znacznie przyspieszyć obliczenia.

Liczba rdzeni procesora

Moduł os udostępnia funkcję cpu_count():

import os

liczba_rdzeni = os.cpu_count()
print(f"Liczba rdzeni procesora: {liczba_rdzeni}")

Przykładowy wynik:

Liczba rdzeni procesora: 8

Dzięki temu możemy na przykład dostosować liczbę wątków lub procesów, które nasz program będzie używał, by optymalnie wykorzystać dostępną moc obliczeniową. W środowisku DevOps i przy przetwarzaniu równoległym ta wiedza jest szczególnie cenna.

Szczegółowe informacje o częstotliwości procesora

Aby uzyskać informacje o częstotliwości procesora, możemy skorzystać z modułu psutil. Pamiętajmy jednak, że wymaga on instalacji (pip install psutil).

import psutil

cpu_freq = psutil.cpu_freq()

print(f"Bieżąca częstotliwość procesora: {cpu_freq.current:.2f} MHz")
print(f"Maksymalna częstotliwość procesora: {cpu_freq.max:.2f} MHz")

Jeśli procesor działa z częstotliwością 2.20 GHz, a maksymalna częstotliwość to 3.50 GHz, wynik będzie:

Bieżąca częstotliwość procesora: 2200.00 MHz

Maksymalna częstotliwość procesora: 3500.00 MHz

Takie dane bywają wykorzystywane np. w menedżerach zadań, narzędziach monitorujących czy w aplikacjach optymalizujących działanie systemu, które mogą np. zmniejszać obciążenie procesora przy braku potrzeby pracy na najwyższych częstotliwościach.

Zmienne środowiskowe

Zmienne środowiskowe są używane do przechowywania informacji konfiguracyjnych systemu operacyjnego i aplikacji. Mogą to być m.in. ścieżki do różnych katalogów systemowych, klucze dostępu do serwisów zewnętrznych lub adresy serwerów baz danych.

Dzięki możliwości definiowania zmiennych środowiskowych możemy łatwo parametryzować nasz program, bez konieczności zmiany kodu źródłowego. Jest to szczególnie wygodne w środowiskach ciągłej integracji i konteneryzacji (np. Docker), gdzie w zależności od potrzeb podajemy różne wartości zmiennych środowiskowych.

Odczytywanie zmiennych środowiskowych

Możemy uzyskać dostęp do zmiennych środowiskowych za pomocą os.environ, które działa jak słownik:

import os

shell = os.environ.get('SHELL')
print(f"Używana powłoka: {shell}")

Jeśli używamy powłoki Bash na systemie Unix/Linux, wynik będzie:

Używana powłoka: /bin/bash

Odczytując zmienne środowiskowe, możemy w prosty sposób dopasować działanie programu do środowiska, w którym aktualnie działa. Przykładowo w systemach Windows może nie istnieć zmienna SHELL, więc os.environ.get('SHELL') zwróci wartość None.

Ustawianie zmiennych środowiskowych

Możemy ustawić nową zmienną środowiskową w bieżącym procesie:

import os

os.environ['MOJA_ZMIENNA'] = 'wartość'
print(f"MOJA_ZMIENNA: {os.environ.get('MOJA_ZMIENNA')}")

Wynik:

MOJA_ZMIENNA: wartość

Warto pamiętać, że zmiany wprowadzone w os.environ są widoczne tylko w bieżącym procesie i nie są zachowywane po zakończeniu programu.

Innymi słowy, jeśli po uruchomieniu skryptu wpiszemy w terminalu echo $MOJA_ZMIENNA, najprawdopodobniej nic nie zobaczymy. Aby zmienna środowiskowa była widoczna globalnie, należałoby ją ustawić bezpośrednio w powłoce lub w pliku konfiguracyjnym systemu.

Użycie zmiennych środowiskowych w aplikacji

Zmienne środowiskowe są często wykorzystywane do przechowywania danych konfiguracyjnych, takich jak klucze API czy dane dostępowe do bazy danych.

import os

api_key = os.environ.get('API_KEY')
if api_key:
    print("Klucz API został pobrany.")
else:
    print("Brak klucza API. Ustaw zmienną środowiskową 'API_KEY'.")

Jeśli zmienna API_KEY nie jest ustawiona, program poinformuje nas o tym, co ułatwia zarządzanie konfiguracją.

Ten wzorzec jest bardzo popularny w środowiskach produkcyjnych, gdzie klucze i hasła nie powinny znajdować się w kodzie, tylko w dedykowanym rozwiązaniu do zarządzania sekretami, który przekazuje je jako zmienne środowiskowe. Dzięki temu nie musimy obawiać się, że ktoś podejrzy hasła w repozytorium kodu.

Spis Treści

    Informacje o systemie operacyjnym
    1. Ustalanie rodzaju systemu operacyjnego
    2. Manipulacja systemem plików
      1. Listowanie zawartości katalogu
      2. Tworzenie nowego katalogu
      3. Usuwanie pliku lub katalogu
      4. Łączenie ścieżek w sposób przenośny
      5. Sprawdzanie istnienia pliku lub katalogu
    3. Informacje o użytkownikach
      1. Pobieranie informacji o bieżącym użytkowniku
      2. Pobieranie informacji o grupie użytkownika
    4. Informacje o dyskach
      1. Sprawdzanie pojemności i wolnego miejsca na dysku
    5. Informacje o procesorze
      1. Liczba rdzeni procesora
      2. Szczegółowe informacje o częstotliwości procesora
    6. Zmienne środowiskowe
      1. Odczytywanie zmiennych środowiskowych
      2. Ustawianie zmiennych środowiskowych
      3. Użycie zmiennych środowiskowych w aplikacji