Spis treści:

Podstawy Pythona: Bezpłatny kurs dla wszystkich poziomów ➞ Tworzenie 4 Imponujące projekty i interakcja z ekspertami. Dowiedz się, jakie umiejętności możesz opanować!
Dowiedz się więcejKilka dni temu przygotowywałem materiały do wirtualnej lekcji, która skupiała się na zmiennych środowiskowych w systemie operacyjnym Linux. Mowa o zmiennych takich jak PATH, PS1 i TERM, które są znane każdemu, kto kiedykolwiek korzystał z wiersza poleceń.
Czasami studenci pytają mnie: „Czy ktoś tego w ogóle potrzebuje w dzisiejszych czasach?”. I mogę śmiało odpowiedzieć, że tak! Zmienne środowiskowe to podstawowe narzędzie do konfigurowania aplikacji w dowolnym systemie Unix. Dotyczy to zarówno laptopów z systemem macOS, jak i serwerów w chmurze lub kontenerów Kubernetes.
Aplikacja dwunastoczynnikowa, znakomity manifest tworzenia skalowalnych aplikacji internetowych, w tym mikrousług, jasno i jednoznacznie wyraża ideę w sekcji poświęconej konfiguracjom: konieczne jest odizolowanie konfiguracji od kodu i przechowywanie ustawień w zmiennych środowiskowych.

Dyrektor techniczny w Tabby, firmie fintech, i twórca kursów testowania języka Python w Skillbox.
W 2000 roku po raz pierwszy zobaczyłem czarny ekran FreeBSD 4.0 i od tego momentu zakochałem się w konsoli Unix od pierwszego wejrzenia.
Definicja i istota zmiennych środowiskowych
Zmienne środowiskowe to zbiór wartości, które regulują parametry i funkcjonalność systemu operacyjnego, a także programów w nim działających. Zmienne te są zorganizowane w pary klucz-wartość i przechowywane w pamięci RAM, co ułatwia interakcję z aplikacjami. Większość publikacji na ten temat ogranicza się jednak do tych definicji, nie zagłębiając się w szczegóły ich implementacji.
Należy pamiętać, że nasze postrzeganie funkcjonowania każdej technologii jest modelem mentalnym, który wymaga dostosowania i udoskonalenia. Najskuteczniej można to zrobić, cofając się do początkowych etapów jej rozwoju, kiedy koncepcja dopiero zaczynała nabierać kształtu i nie została jeszcze nasycona różnymi ulepszeniami. Przeanalizujmy, kiedy pojawiły się zmienne środowiskowe i jak ewoluowały do dziś.
Zmienne środowiskowe, jakie znamy dzisiaj, zaczęły istnieć w 1979 roku wraz z siódmą wersją systemu operacyjnego Unix (Unix V7). Analizując genezę systemów operacyjnych typu Unix, zauważymy, że system ten zajmuje fundamentalne miejsce w historii, wyznaczając początek ewolucji takich systemów operacyjnych jak Linux, macOS i FreeBSD. Jednym z komputerów, na których działała ta wersja Uniksa, był PDP-11.

Minęło sporo czasu od rozwoju zmiennych środowiskowych. Ponad czterdzieści lat! Jakie były cele autorów, gdy tworzyli koncepcję zmiennych środowiskowych i samego środowiska? Jak istotne jest to dzisiaj? Jak dokładnie zostało wdrożone to rozwiązanie? Jeśli dotarłeś aż tutaj i jesteś ciekaw odpowiedzi na te pytania, proponuję, abyśmy je wspólnie zbadali!
Istota i znaczenie zmiennych środowiskowych
Najpierw przeanalizujmy koncepcję konfiguracji i spróbujmy zrozumieć jej pochodzenie. Pomoże nam w tym John, fikcyjny pracownik laboratorium DEC. Cześć, John!
DEC to firma znana z rozwoju komputerów, w szczególności PDP-11 i innych technologii. John posiadał terminal Teletype Model 33, który widać na powyższym obrazku. Był łatwy i wygodny w obsłudze: John po prostu wpisał polecenie „cat” w terminalu, aby uruchomić plik wykonywalny. PDP-11 drukował dane wyjściowe aplikacji z prędkością 100 słów na minutę, linijka po linijce, przypominając maszynę do pisania. Chociaż był dość głośny, proces przebiegał szybko.
Wszystko działało bez zarzutu, ale czas leci. Wkrótce DEC wprowadził terminal wideo VT52, a jeden z nich trafił w ręce Johna.

Nowy terminal różni się od Jego poprzednik – zamiast papieru, wyposażony jest w ekran! Spowodowało to zmianę liczby wyświetlanych znaków: Teletype Model 33 mógł wyświetlać 72 znaki w wierszu, podczas gdy VT52 obsługiwał 80 znaków. Tych parametrów nie można zmienić: Teletype Model 33 działa jak maszyna do pisania, a VT52 może wyświetlać tylko znaki ASCII i tylko w jednym rozmiarze.
Jednak wersja programu cat, której używał John, pozwalała na wyświetlanie tylko 72 znaków w wierszu. Konieczne byłoby opracowanie nowych wersji aplikacji obsługujących VT52. Okazuje się więc, że różne terminale wymagałyby dwóch wersji programu cat, różniących się jedynie jednym drobnym ustawieniem? To nieefektywne, zwłaszcza biorąc pod uwagę, że liczba modeli urządzeń może nadal rosnąć. Ale cat nie ma możliwości określenia, z którym terminalem aktualnie pracuje John.
Najoczywistszym sposobem przekazania mu tej informacji jest określenie parametru odpowiadającego terminalowi używanemu podczas uruchamiania aplikacji. Na przykład, można wpisać cat TERM=dumb dla terminala Teletype Model 33 lub cat TERM=vt52 dla nowoczesnych monitorów. John omówił tę kwestię z Dennisem Ritchiem i wkrótce wydano nową wersję cat, która obsługiwała linie o dowolnej długości. Teraz to polecenie może być używane zarówno na starych, jak i nowych urządzeniach, po prostu określając odpowiedni parametr.
Rozdzielenie konfiguracji na parametry aplikacji i środowiska było ważnym krokiem, który pozwolił nam na bardziej elastyczne zarządzanie ustawieniami. Zrozumieliśmy już, że parametryzacja aplikacji pozwala nam korzystać z jednego pliku w różnych sytuacjach. Pojawia się jednak pytanie: dlaczego zdecydowaliśmy się na oddzielenie konfiguracji dla aplikacji i dla środowiska?
W tym czasie wiele aplikacji było już opracowanych w systemie Unix. Każda z nich wymagała implementacji obsługi zmiennej TERM, która wskazywała konkretne środowisko, z którym wchodziła w interakcję. Jednak ręczne określanie TERM w parametrach jest zbędne. Optymalnym rozwiązaniem byłoby jednorazowe skonfigurowanie środowiska, co pozwoliłoby Unixowi automatycznie dostarczać odpowiednie parametry dla wszystkich uruchomionych aplikacji. To właśnie ten mechanizm, polegający na rozdzieleniu parametrów między ustawienia środowiskowe i specyficzne dla aplikacji, został zaimplementowany w systemie Unix V7.
Przeniesienie niektórych parametrów do środowiska eliminuje potrzebę indywidualnej konfiguracji dla każdej aplikacji.
Koncepcja jest jasna, dzięki, John!
Przejście do źródła: Podręcznik środowiska
Jak wyglądała konfiguracja środowiska we wczesnych wersjach systemu Unix? Aby odpowiedzieć na to pytanie, sięgnijmy do książki The UNIX Time Sharing System — UNIX Programmer's Manual — Seventh Edition, Volume 1A, opublikowanej w styczniu 1979 roku. Sekcja poświęcona powłoce, na stronie 161, zawiera informacje o środowisku.
Podsumowując treść:
- Środowisko to zestaw par „klucz” i „wartość”, który jest przekazywany programowi podczas jego wykonywania, podobnie jak standardowy zestaw argumentów.
- Aby wprowadzić zmienne środowiskowe, użyj polecenia export.
- Polecenia wykonywane z poziomu powłoki współdzielą wspólne środowisko.
- Powłoka tworzy parametry na podstawie zmiennych środowiskowych.
Wszystko działa podobnie do tego, jak działa w nowoczesnych systemach operacyjnych typu Unix. Dokumentacja wspomina również o zmiennych środowiskowych, takich jak PATH, HOME, TERM i innych, znanych nam ze współczesnego programowania.
Z perspektywy użytkownika podejście do zarządzania zmiennymi środowiskowymi zostało opracowane na samym początku istnienia Uniksa i od tamtej pory pozostaje niezmienione.
W tym momencie zdałem sobie sprawę, że to ćwiczenie z pewnością nie znajdzie się w materiałach lekcji online, ale nie mogłem przerwać mojego „śledztwa”. Chciałem dowiedzieć się, gdzie dokładnie to środowisko jest przechowywane w systemie Unix.
Przestrzeń pod maską samochodu
Aby zrozumieć, co zostało zaimplementowane w systemie Unix, aby środowisko działało, ponownie sięgam do dokumentacji. Linki podane w sekcji poświęconej powłoce kierują nas do sekcji ENVIRON (5) — numer sekcji w dokumentacji jest oznaczony liczbą w nawiasach.
Dokumentacja jest przedstawiona w skrócie. Environ to zmienna globalna, która jest tworzona po wykonaniu polecenia EXEC (2). Ponieważ jest to zmienna globalna, informacje o środowisku są przechowywane w pamięci RAM procesu. W kontekście systemu operacyjnego proces jest uważany za aktywny plik wykonywalny.
Czym jest wywołanie EXEC i jak tworzy ono Environ w pamięci aplikacji?
Wywołanie EXEC jest wykonywane, gdy użytkownik działa w powłoce. Kiedy użytkownik decyduje się na uruchomienie programu, wprowadza ścieżkę do jego pliku wykonywalnego i pozostawia dalsze działania powłoce. Jednak powłoka nie inicjuje również uruchomienia aplikacji samodzielnie — przekazuje kontrolę funkcji EXEC, dostarczając jej parametry niezbędne do uruchomienia programu, a także ustawienia związane z zarządzaniem procesami w systemie operacyjnym Unix.
Jak dokładnie Unix obsługuje przetwarzanie w trybie EXEC? Zajrzyjmy do dokumentacji. Po uruchomieniu programu w C system operacyjny inicjuje funkcję main, przekazując jej argumenty main (argc, argv, envp). Wartość argc wskazuje liczbę przekazanych argumentów, a argv to tablica wskaźników do tych argumentów. Z kolei envp jest wskaźnikiem odnoszącym się do środowiska procesu nadrzędnego. W tym przypadku będzie to powłoka z jej zmiennymi.
Jednak przed uruchomieniem procesu głównego main tworzona jest kopia środowiska reprezentowana przez zmienną envp. Kopia ta będzie dostępna dla aplikacji poprzez zmienną globalną environ. Powłoka generuje listę zmiennych, a sam system operacyjny tworzy jej kopię w pamięci nowego procesu.
W ten sposób nowy proces otrzymuje duplikat środowiska swojego procesu nadrzędnego. Ta kopia pozwala każdej aplikacji zmieniać swoje ustawienia bez obawy o uszkodzenie działania innych aplikacji. Jeśli nowy proces zdecyduje się uruchomić inną aplikację, zostanie utworzony proces potomny, który odziedziczy kopię środowiska tylko od procesu nadrzędnego, bez względu na poprzednie procesy.
Unix wywołał obsługę zmiennych środowiskowych, dzięki czemu każda aplikacja otrzymuje swoją własną, indywidualną kopię środowiska procesu nadrzędnego, przechowywaną w swojej pamięci.
Koncepcja środowiska została po raz pierwszy wprowadzona w siódmej wersji Uniksa, o czym świadczy brak wzmianki o niej w podręcznikach do szóstej wersji. Dokumentacja wywołania systemowego EXEC (2) wymienia tylko dwa parametry: argc i argv, natomiast parametr envp nie jest wspomniany, co wskazuje, że koncepcja środowiska jeszcze wówczas nie istniała.
Oprócz podręcznika V7 – „Annotated Excerpts from the Programmer's Handbook” – można znaleźć wzmiankę o tym, że znana nam powłoka (powłoka Bourne'a) została opracowana specjalnie dla siódmej wersji, zastępując poprzednią. Bez tej powłoki praca w tym środowisku byłaby znacznie mniej komfortowa.
Jak zmieniło się środowisko od tego czasu?
Mamy zatem wszystkie niezbędne odpowiedzi, ale chciałbym wiedzieć, jak wygląda sytuacja ze środowiskiem w nowoczesnych wersjach Linuksa. Być może zaszły jakieś zmiany w sposobie jego zarządzania?
Testuję na systemie Linux działającym w kontenerze Docker i używam do tego standardowych narzędzi programistycznych. W szczególności korzystam z narzędzia Strace do monitorowania wywołań systemowych i GDB do analizy zawartości pamięci procesu.
Zanim przejdę do omówienia wywołań, chciałbym najpierw nakreślić, co dokładnie będę rozważał. Jako punkt wyjścia wybiorę wywołanie exec(). Dlaczego akurat to wywołanie? Powodem jest to, że standardy POSIX istnieją od 1988 roku i regulują strukturę wywołań systemowych.
Unix V7, o którym mówiłem, został wydany wcześniej, ale standardy POSIX były w dużej mierze inspirowane systemami uniksopodobnymi. Dlatego Linux w dużej mierze jest zgodny ze standardami POSIX, co pozwala nam szukać podobnych wywołań w tym systemie operacyjnym.
Tym razem nie muszę konsultować dokumentacji online. Mogę użyć polecenia man, które zawiera informacje o tym, jak używać innych poleceń.
W kontenerze Ubuntu uruchamiamy polecenie man exec, aby uzyskać dostęp do podręcznika. W systemie operacyjnym Linux, exec to szeroka rodzina wywołań systemowych, obejmująca również globalną zmienną environ oraz możliwość przekazywania środowiska za pomocą wskaźnika envp. Aby jednak śledzić żądane wywołanie za pomocą strace, należy znać jego nazwę, a zgodnie z man exec jest to wywołanie execve. Dzięki standardom POSIX mechanizmy zarządzania środowiskiem w Linuksie są znacząco podobne do tych zaimplementowanych w systemie Unix V7.
Uruchommy polecenie cat w powłoce Bash, aby zapoznać się z jego parametrami wywołania.
Trzecim argumentem jest tablica zmiennych wskazywana przez wskaźnik 0xaaaaadacacb50. Ciekawe, co jest przechowywane w tej tablicy? Aby uzyskać informacje, uruchom ponownie strace z opcją -v.
Jako trzeci argument ponownie napotykamy znane zmienne środowiskowe, takie jak PID, HOSTNAME, HOME itd. Zasadniczo proces ten nie różni się zbytnio od korzystania z funkcji exec() w siódmej wersji Uniksa. Jedyną zmianą jest konieczność zliczania argumentów.
Kontynuujmy naszą eksplorację. Informacje o wywołaniu nie zawierają żadnych wskazówek, gdzie znaleźć zmienne środowiskowe w pamięci aktywnego procesu. Jest to jednak ciekawe, ponieważ to właśnie tam znajduje się środowisko, o którym tak często wspominamy.
Mam informację – adres wskaźnika ze strace to 0xaaaaadacacb50. Sprawdźmy, co znajduje się pod tym adresem w pamięci bash:
Wow, to jest dokładnie ten znacznik, który sygnalizuje start środowiska procesu bash. Możemy sprawdzić adresy tych znaczników i odkryć, że przechowują one nasze zmienne środowiskowe:
Wygląda na to, że czas na przerwę.
Zamiast wniosków
Zaczynałem karierę pracując w konsoli, a zmienne środowiskowe stały się dla mnie znajomym i prostym narzędziem. Byłem zdumiony, że ich zastosowanie pozostało niezmienione przez tak długi czas.
Chociaż wczesne wersje Uniksa poszły w zapomnienie, stanowiły one podstawę nowoczesnych systemów operacyjnych, takich jak macOS i Linux, które można znaleźć na większości komputerów. Zrozumienie tego dziedzictwa wymaga zainteresowania, dostępu do starych instrukcji i wiedzy, gdzie szukać informacji.
Chciałbym podziękować tym, którzy pracują nad zachowaniem i publikacją dokumentacji starszych wersji programów. Bez ich wysiłków nie byłbym w stanie przeprowadzić tego krótkiego badania.
Aby poznać więcej fascynujących faktów na temat kodowania, dołącz do naszego kanału na Telegramie. Z przyjemnością zobaczymy Cię wśród naszych subskrybentów!
Przeczytaj również:
- Polecenia klawiszowe i skróty klawiaturowe do pracy w terminalu Linux, a także w systemach Unix, macOS i FreeBSD.
- Test: Jak dobrze rozumiesz architekturę procesora?
- Używanie powłoki Bash do przyspieszenia zadań na komputerze może znacznie zwiększyć Twoją produktywność. Istnieje kilka metod, które pomogą Ci osiągnąć ten cel.
Po pierwsze, opanowanie poleceń terminala to podstawowy krok. Znajomość podstawowych poleceń, takich jak ls, cd, cp, mv i rm, pozwoli Ci szybko nawigować między katalogami, kopiować i usuwać pliki bez konieczności korzystania z interfejsów graficznych.
Po drugie, używanie skryptów powłoki Bash może znacznie uprościć i zautomatyzować rutynowe zadania. Utworzenie skryptu wykonującego sekwencję poleceń pozwoli zaoszczędzić czas i zmniejszyć prawdopodobieństwo wystąpienia błędów.
Trzecim ważnym aspektem jest użycie aliasów. Aliasy pozwalają na tworzenie krótkich i wygodnych poleceń dla często używanych poleceń, co przyspiesza proces realizacji zadań.
Warto również zwrócić uwagę na użycie potoków i przekierowań. Pozwoli to na łączenie kilku poleceń, przekazując wynik jednego polecenia jako dane wejściowe do drugiego, co usprawnia pracę.
Nie zapomnij o użyciu zmiennych i funkcji. Zmienne pomogą Ci przechowywać dane tymczasowe, a funkcje pozwolą Ci uporządkować kod i w razie potrzeby go ponownie wykorzystać.
Dodatkowo możesz zapoznać się z różnymi narzędziami i programami użytkowymi, takimi jak `grep`, `awk`, `sed`, które znacząco rozszerzają funkcjonalność Basha i sprawiają, że przetwarzanie danych tekstowych jest wygodniejsze i szybsze.
Na koniec nie powinieneś zaniedbywać możliwości korzystania z menedżerów pakietów w celu zainstalowania nowych narzędzi, które mogą uzupełnić Twoją pracę w Bashu i poprawić ogólną wydajność.
Postępując zgodnie z tymi wskazówkami, możesz wydajniej wykonywać zadania na komputerze za pomocą powłoki Bash.

