Przejdź do treści
Lekcja 17 z 117

Kurs › Moduł 1 › Lekcja 17

Ustawienia Basha pod siebie

⏱ ok. 11 min czytania· moduł 1: Praca w powłoce

Po tej lekcji: kursant rozumie różnicę między zmienną a zmienną środowiskową, ustawia PATH, wie, który plik startowy czyta Bash i kiedy, robi własne aliasy, znak zachęty i ustawia historię.

Wstęp

dwa terminale obok siebie. Lewy szary, $ i nic więcej. Prawy z kolorowym znakiem zachęty, godziną, nazwą serwera i aliasami

Przez szesnaście lekcji pracowałeś w Bashu takim, jaki przyszedł z pudełka. To jak jazda samochodem z fotelem ustawionym przez poprzedniego właściciela, który miał dwa metry wzrostu. Da się, ale po co.

Dziś ustawiamy fotel. A przy okazji rozwiążemy jedną z najczęstszych zagadek: dopisałem coś do .bashrc, a nic się nie zmieniło. Albo zmieniło się tylko w jednym oknie.

Zmienna i zmienna środowiskowa

Zmienna to pudełko z napisem. Napis to nazwa, w środku wartość.

owoc=jablko
echo $owoc
bash -c 'echo "dziecko widzi: [$owoc]"'
jablko
dziecko widzi: []

Nowy Bash, uruchomiony ze starego, pudełka nie widzi. Zwykła zmienna to notatka na Twoim biurku. Kiedy uruchamiasz program, ten program dostaje własne biurko, a Twoja notatka zostaje u Ciebie.

Chcesz, żeby programy, które uruchamiasz, dostały tę wartość? export:

export owoc
bash -c 'echo "dziecko widzi: [$owoc]"'
dziecko widzi: [jablko]

export robi z notatki zmienną środowiskową. To jak teczka, którą każdy nowy pracownik dostaje na start: kopia wszystkich wyeksportowanych pudełek. Kopia, nie oryginał. Jeśli dziecko coś w swojej kopii zmieni, rodzic tego nie zobaczy. Informacja w Linuksie płynie tylko w dół, jak w dobrze zarządzanej korporacji.

Pełną teczkę pokazuje env albo printenv:

env | grep -E '^(HOME|USER|SHELL|LANG|PWD)='
printenv HOME
SHELL=/bin/bash
PWD=/home/eryk
HOME=/home/eryk
LANG=C.UTF-8
USER=eryk
/home/eryk

Zmienne środowiskowe pisze się umownie wielkimi literami. Swoje własne, w skryptach, małymi, żeby przypadkiem nie nadpisać systemowych.

I jeszcze jeden trik. Zmienną można dać tylko jednemu programowi, na jeden raz, wpisując ją przed komendą:

JEZYK=pl bash -c 'echo "jednorazowo: $JEZYK"'; echo "po: [$JEZYK]"
jednorazowo: pl
po: []

Zobaczysz to często, na przykład LANG=C sort plik, żeby sortowanie nie zależało od języka systemu.

PATH: lista miejsc, gdzie szukać programów

Wpisujesz ls. Skąd Bash wie, że chodzi o plik /usr/bin/ls? Ma listę adresów do sprawdzenia, po kolei. Jak kurier, który ma paczkę „dla Kowalskiego” i listę bram: najpierw pyta w pierwszej, potem w drugiej. Pierwszy Kowalski, który się znajdzie, dostaje paczkę.

echo $PATH | tr ':' '\n'
/home/eryk/bin
...
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
/snap/bin

Foldery rozdzielone dwukropkami. tr z lekcji 7 zamienia je na nowe linijki, żeby dało się to przeczytać. Gdzie Bash znajdzie konkretną komendę, mówi type:

type -a ls
type cd
ls is /usr/bin/ls
ls is /bin/ls
cd is a shell builtin

ls jest w dwóch miejscach, bo /bin to drogowskaz do /usr/bin, pamiętasz z lekcji 8. cd w ogóle nie jest plikiem, to umiejętność samego urzędnika.

I teraz własne narzędzia. Robisz folder ~/bin, wrzucasz tam skrypt:

mkdir -p ~/bin
printf '#!/bin/bash\necho "Dzień dobry, $USER"\n' > ~/bin/powitanie
chmod +x ~/bin/powitanie

Na Ubuntu plik ~/.profile sam dopisuje ~/bin na początek PATH, jeśli ten folder istnieje w chwili logowania. Wylogowujesz się, logujesz i:

cd /tmp
powitanie
which powitanie
Dzień dobry, eryk
/home/eryk/bin/powitanie

Działa z dowolnego folderu, jak prawdziwa komenda. Możesz też dopisać folder do PATH sam, zawsze w ten sposób:

export PATH="$HOME/bin:$PATH"

Nowy folder, dwukropek i stary PATH. O tym starym zaraz w typowym błędzie.

Który plik czyta Bash przy starcie

Bash nie czyta wszystkich plików ustawień zawsze. To zależy od tego, jak go uruchomiłeś. Zrobiłem eksperyment: w każdym pliku startowym jedna linijka, która się przedstawia.

bash -l -c true
bash -i -c true
bash -c true
--- login:
[czytam .bash_profile]
--- interaktywna:
[czytam .bashrc]
--- skrypt:
--- login bez .bash_profile:
[czytam .profile]

Rozbierzmy to.

Powłoka logowania, login shell. Startuje, kiedy się logujesz: przez SSH, na konsoli tekstowej, przez su -. Czyta najpierw /etc/profile, wspólny dla wszystkich, a potem pierwszy istniejący z trzech: ~/.bash_profile, ~/.bash_login, ~/.profile. Tylko pierwszy. To jak recepcja hotelu: przy zameldowaniu dostajesz pakiet powitalny raz, na cały pobyt.

Powłoka interaktywna bez logowania. Kolejne okno terminala na pulpicie albo bash wpisany w Bashu. Czyta ~/.bashrc. To codzienna kawa w pokoju: dostajesz ją przy każdym nowym oknie.

Skrypt. Nie czyta żadnego z nich. Dlatego Twoje aliasy w skryptach nie działają.

I teraz ważne: żeby nie trzymać wszystkiego w dwóch miejscach, plik logowania zwykle sam woła .bashrc.

[Ubuntu] Masz ~/.profile, który sprawdza, czy działa Bash, i wczytuje ~/.bashrc, a potem dopisuje ~/bin i ~/.local/bin do PATH. ~/.bash_profile domyślnie nie istnieje. Wspólne ustawienia dla wszystkich są w /etc/profile i /etc/bash.bashrc.

[RHEL] Masz ~/.bash_profile, który wczytuje ~/.bashrc, a ten z kolei wczytuje /etc/bashrc. Na Red Hacie ~/.bashrc dopisuje też do PATH foldery ~/.local/bin i ~/bin.

Zasada praktyczna na obu rodzinach: aliasy, znak zachęty, historia, czyli wszystko, co dotyczy pracy w terminalu, idzie do ~/.bashrc. Zmienne środowiskowe, które mają być ustawione raz po zalogowaniu, do ~/.profile na Ubuntu albo ~/.bash_profile na RHEL. A ustawienia dla wszystkich użytkowników nie w /etc/profile, tylko jako osobny plik .sh w /etc/profile.d/, bo aktualizacja systemu może /etc/profile nadpisać.

Po zmianie ~/.bashrc nie musisz zamykać terminala:

source ~/.bashrc

source, albo krócej sama kropka, . ~/.bashrc, wykonuje plik w bieżącej powłoce, a nie w nowej.

Aliasy: własne skróty

Alias to przezwisko dla komendy. Na Ubuntu kilka dostajesz od razu:

alias
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -alF'
alias ls='ls --color=auto'

Dlatego ll działało na Ubuntu, a na serwerze kolegi nie. To nie komenda, to przezwisko z .bashrc. Własne robisz tak:

alias ..='cd ..'
alias porty='ss -tulpn'
alias aktualizuj='sudo apt update && sudo apt upgrade'

Alias wpisany w terminalu znika po jego zamknięciu. Żeby został, dopisujesz te linijki do ~/.bashrc. Usuwa unalias ll. Sprawdzasz, czym naprawdę jest komenda, przez type ll:

ll is aliased to `ls -alF'

[RHEL] Na Red Hacie root ma w ~/.bashrc aliasy rm, cp i mv z flagą -i, która przed każdym usunięciem albo nadpisaniem pyta „na pewno?”. Miłe, dopóki nie przyzwyczaisz się do tego, a potem na serwerze bez aliasów nie wpiszesz rm * z pełnym spokojem. Alias omijasz, pisząc ukośnik przed komendą, \rm, albo command rm.

PS1: znak zachęty

To, co Bash wypisuje przed kursorem, siedzi w zmiennej PS1. Na Ubuntu wygląda mniej więcej tak:

echo "$PS1"
${debian_chroot:+($debian_chroot)}\u@\h:\w\$ 

Wygląda jak szyfr, ale to tylko kilka znaczników:

  • \u to użytkownik
  • \h to nazwa komputera
  • \w to pełna ścieżka, \W tylko nazwa bieżącego folderu
  • \t to godzina
  • \$ to dolar dla zwykłego użytkownika, a krzyżyk # dla roota

Ten kawałek debian_chroot możesz zignorować, pokazuje się tylko w specjalnym trybie. Spróbujmy własnego:

PS1='[\t] \u@\h \W \$ '
[22:21:36] eryk@vm ~ $ 

A u roota ten sam PS1 daje [22:21:36] root@vm h # . Ten sam przepis, a \$ sam zamienia się w krzyżyk. Dobrze widzieć z daleka, że siedzisz w fotelu dyrektora.

Godzina w znaku zachęty jest bardzo praktyczna: po awarii wiesz, o której wpisałeś komendę, która wszystko zepsuła. Bezcenne przy pisaniu raportu, mniej przy wymyślaniu wymówki.

Kolory dodaje się kodami, które terminal rozumie jako „zmień kolor”. Każdy taki kod musi być w \[ i \], żeby Bash wiedział, że to nie są widoczne litery:

PS1='\[\e[1;32m\]\u@\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]\$ '

Zielony użytkownik i serwer, niebieski folder, jak domyślnie na Ubuntu. Wielu adminów ustawia na serwerach produkcyjnych czerwony znak zachęty. Kolor alarmowy, żeby przed rm jeszcze raz się zastanowić, w którym oknie jesteś.

[RHEL] Na Red Hacie znak zachęty jest ustawiany w /etc/bashrc i domyślnie nie ma kolorów. Zwykle widać w nim nawiasy kwadratowe i samą nazwę bieżącego folderu zamiast pełnej ścieżki.

Historia: HISTSIZE i HISTCONTROL

Strzałka w górę i history pamiętają Twoje komendy. Ile i które, ustawiają trzy zmienne:

echo "HISTSIZE=$HISTSIZE HISTFILESIZE=$HISTFILESIZE HISTCONTROL=$HISTCONTROL"
HISTSIZE=1000 HISTFILESIZE=2000 HISTCONTROL=ignoreboth

HISTSIZE to ile komend pamięta bieżąca sesja, HISTFILESIZE ile trzyma plik ~/.bash_history. Te wartości są z ubuntowego ~/.bashrc. Tysiąc to mało, jeśli lubisz szukać przez Ctrl+R komendy sprzed miesiąca. Wielu adminów daje kilkadziesiąt tysięcy.

HISTCONTROL=ignoreboth to dwie rzeczy naraz: ignoredups, czyli nie zapisuj powtórzeń jedna po drugiej, i ignorespace, czyli nie zapisuj komend zaczynających się od spacji. Test:

echo raz
echo raz
 echo "tajne haslo"
echo dwa
history
    1  HISTCONTROL=ignoreboth
    2  echo raz
    3  echo dwa
    4  history

Drugie echo raz zniknęło jako powtórka, a komenda ze spacją na początku w ogóle nie trafiła do historii. Spacja to czapka niewidka. Przydaje się, kiedy musisz wpisać hasło w komendzie, chociaż najlepiej nie musieć nigdy.

Jak to działa pod spodem

Każdy proces w Linuksie ma swoje środowisko: listę napisów w stylu NAZWA=wartość. Kiedy Bash uruchamia program, jądro przekazuje nowemu procesowi kopię tej listy, ale tylko tego, co wyeksportowane. Możesz ją podejrzeć dla dowolnego procesu w /proc/NUMER/environ, znasz ten folder z lekcji 8.

Pliki startowe to zwykłe skrypty Basha. .bashrc nie jest żadnym specjalnym formatem, tylko listą komend wykonywanych po kolei przy starcie, dokładnie jakbyś je wpisał ręcznie. Dlatego literówka w .bashrc wywala błąd przy otwarciu każdego terminala. Zanim zmienisz .bashrc na serwerze, miej otwartą drugą sesję. Jeśli coś zepsujesz tak, że nowa sesja nie wstanie, stara jest Twoim wyjściem awaryjnym.

Ściąga flag

KomendaFlagaCo robi
exportNAZWA=wartośćustawia zmienną i przekazuje ją programom potomnym
env(bez flag)wypisuje całe środowisko
envNAZWA=x komendauruchamia komendę z dodatkową zmienną
printenvNAZWAwartość jednej zmiennej środowiskowej
type-a nazwawszystkie miejsca, gdzie jest komenda: alias, wbudowana, plik
whichnazwaścieżka do programu w PATH
source / .plikwykonuje plik w bieżącej powłoce
bash-luruchamia powłokę logowania
bash-iuruchamia powłokę interaktywną
aliasnazwa='komenda'tworzy alias, bez argumentów wypisuje wszystkie
unaliasnazwausuwa alias
history-cczyści historię bieżącej sesji
zmiennaHISTSIZE, HISTFILESIZEile komend w sesji i w pliku
zmiennaHISTCONTROL=ignorebothbez powtórzeń i bez komend od spacji
zmiennaPS1wygląd znaku zachęty: \u, \h, \w, \W, \t, \$

⚠ Typowy błąd

Nadpisanie PATH zamiast dopisania.

PATH=~/bin
ls
bash: ls: command not found

Chciałeś dodać swój folder, a zastąpiłeś nim całą listę. Kurier ma teraz jedną bramę do sprawdzenia i każdy Kowalski spoza niej przestał istnieć. Na Ubuntu możesz dodatkowo zobaczyć podpowiedź w stylu Command 'ls' is available in the following places i informację, że folderu nie ma w PATH. To ten sam problem, tylko grzeczniej powiedziany.

Jeśli zrobiłeś to w terminalu, wystarczy go zamknąć i otworzyć nowy. Jeśli wpisałeś to do ~/.bashrc, to nowy terminal też nie pomoże, bo błąd wczytuje się przy każdym starcie. Wtedy programy wołasz pełną ścieżką, na przykład /usr/bin/nano ~/.bashrc, poprawiasz linijkę na:

export PATH="$HOME/bin:$PATH"

i robisz source ~/.bashrc.

✎ Zadanie

  1. Dopisz do ~/.bashrc trzy aliasy: ll, jeśli go nie masz, .. przechodzący folder wyżej i porty pokazujący nasłuchujące porty przez ss -tulpn.
  2. Ustaw HISTSIZE i HISTFILESIZE na 10000, a HISTCONTROL na ignoreboth. Sprawdź, że komenda ze spacją na początku nie trafia do historii.
  3. Ustaw PS1 z godziną, użytkownikiem, nazwą serwera i krótką nazwą folderu.
  4. Zrób folder ~/bin ze skryptem powitanie i spraw, żeby działał z każdego folderu.
  5. Załaduj zmiany bez zamykania terminala i sprawdź type ll oraz type powitanie.
Pokaż rozwiązanie

Wszystko dopisane na koniec ~/.bashrc:

# --- moje ustawienia ---
alias ll='ls -alF'
alias ..='cd ..'
alias porty='ss -tulpn'
HISTSIZE=10000
HISTFILESIZE=10000
HISTCONTROL=ignoreboth
PS1='[\t] \u@\h \W \$ '

Na końcu, bo ubuntowy .bashrc sam ustawia HISTSIZE wyżej w pliku. Plik czyta się od góry do dołu i wygrywa ostatnie słowo, jak w kłótni małżeńskiej. Skrypt powitanie ląduje w ~/bin z chmod +x. Sprawdzenie:

source ~/.bashrc
type ll ..
type powitanie
echo $HISTSIZE $HISTCONTROL
ll is aliased to `ls -alF'
.. is aliased to `cd ..'
powitanie is /home/eryk/bin/powitanie
10000 ignoreboth

[21:21:48] eryk@vm / $ — i znak zachęty z godziną gotowy. Jeśli type powitanie mówi not found, a folder ~/bin zrobiłeś dopiero teraz, to wina kolejności: ~/.profile sprawdza ten folder przy logowaniu. Wyloguj się i zaloguj albo dopisz PATH sam, jak w lekcji.

Co dalej

Masz Basha urządzonego pod siebie. Ale co, jeśli w połowie godzinnej aktualizacji serwera zerwie się Wi-Fi? W następnej lekcji tmux: terminal, który działa dalej, kiedy Ty się rozłączysz, z oknami, panelami i powrotem dokładnie tam, gdzie skończyłeś. I jego starszy kuzyn, screen.