Bash_Bushido

Poradnik Bash Bushidō cz. I – podstawowe komendy

Ponieważ poprzedni artykuł o Bashu okazał się bardzo poczytny, postanowiłem stworzyć mini serię artykułów o mniej lub bardziej znanych poleceniach i funkcjach wbudowanych w basha, czy przydatnych skrótach.

Ponieważ poprzedni artykuł o bashu cieszył się dużą popularnością, postanowiłem stworzyć mini serię artykułów o mniej lub bardziej znanych poleceniach i funkcjach wbudowanych w basha, czy przydatnych skrótach.

Nazwa Bushidō z japońskiego oznacza drogę wojownika, zbiór wartości i praw, jakimi powinien kierować się prawdziwy wojownik. Artykuły tej serii dedykowane są więc wojownikom konsoli.

Nie będę ukrywał, że bezpośrednią inspiracją była dla mnie książka Command Line Kung Fu. Zawiera ona znaczną część materiału, który tutaj zaprezentuję. Postaram się jednak na dodanie od siebie jak największej wartości. Szczególnie że pomimo swoich walorów często są w niej pomijane ważne aspekty, takie jak „dlaczego warto coś zastosować”. Na dodatek zawiera mnóstwo oczywistości, które dla nawet początkującego użytkownika nic nie wnoszą. Całość uzupełniły ciekawsze informacje wykopane z różnych miejsc internetowych oraz z mojego najnowszego źródła wiedzy – „Linux Command Line and Shell Scripting Bible”.

Ze względu na bardzo obszerny materiał i charakter tego artykułu, proszę o wyrozumiałość, że prezentowane porady są tylko wyborem autora.

yes, yes, yes!

Unix to niezwykle ciekawy system. Prawie 20 lat temu Terry Lambert, jeden z developerów FreeBSD, napisał o nim wspaniałą rzecz:

If you aim the gun at your foot and pull the trigger, it's UNIX's job to ensure reliable delivery of the bullet to where you aimed the gun (in this case, Mr. Foot).

Co w bardzo swobodnym tłumaczeniu oznacza:

Jeśli wycelujesz broń w swoją stopę i pociągniesz za spust, zadaniem Unixa jest niezawodna dostawa pocisku tam, gdzie wycelowałeś broń (w tym przypadku Panią Stopę).

Całość można znaleźć w archiwach freebsd.hackers na Google Groups.

Zgodnie z 3 zasadą sudo „With great power comes great responsibility”, administratorzy celowo zaczytują konfigurację, która włącza interaktywne operacje na plikach.

W EuroLinuxie (oraz innych pochodnych Enterprise Linuxa) 6 i 7 w /root/.bashrc znajdziemy:

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

Dla przykładu, w przypadku kasowania będziemy pytani o każdy plik/folder, który chcemy skasować. Na szczęście da się to obejść przełącznikiem -f lub --force. Niestety nie wszystkie komendy posiadają takie udogodnienia. W takim wypadku przydatny jest program yes. Komenda yes przekaże wybrany przez nas ciąg znaków (string), jeśli nie podamy żadnego argumentu, to będzie przekazywać po prostu „yes”.

Ogólny przykład użycia.
yes | komenda pytająca
yes moja odpowiedź | komenda pytająca

W omawianym przypadku komendy kasowania zamiast przełącznika -f możemy użyć:

 # Tajny skrypt kopiący bitcoiny
yes | rm -r /*

„Yes” można użyć np. do zmiany hasła. W celu uzyskania bezpiecznego losowego hasła użyliśmy programu pwgen dostępnego w repozytorium EPEL.

my_pass=$(pwgen -s 20 1)
yes $my_pass | passwd user

Zauważmy, że dodaliśmy spację przed komendą yes. O tym, dlaczego tak zrobiliśmy i kiedy taka operacja ma sens, dowiemy się w następnej części Bash Bushido. Jeśli nie chcesz jej przegapić, to rozważ, proszę subskrypcję naszego newslettera.

Chciałbym się też rozprawić z nieprawidłowością, którą znalazłem w internecie. Mianowicie pusta komenda yes nie jest wcale dobrym sposobem na obciążenie jednego wątku procesora. Do testowania naszego systemu służą inne narzędzie, przykładowe benchmarki systemu można znaleźć na naszym blogu.

tee – podział wyjścia na dwa lub więcej

Zdarza się czasem, iż chcemy zapisać wynik do pliku, a równocześnie mieć na niego podgląd w czasie rzeczywistym. Służy do tego komenda tee.

Jej przykładowe użycie wygląda następująco:

./start_app_server.sh | tee log-$(date +%F-%T).log

Wyjście komendy startującej jakiś serwer aplikacji (np. EuroAP) będzie wtedy wyświetlać się na wyjście terminala i równocześnie zapisywać się w zależnym od czasu pliku log.

W tym momencie może paść pytanie, dlaczego w takim razie nie wykonamy tego inaczej i jeśli podane rozwiązanie nie jest złe, to po co w ogóle używać tee?

my_log=log-$(date +%F-%T).log
./start_app_server.sh > $my_log & tail -f $my_log

Jeżeli założymy, że log.log nie będzie za długi (między wywołaniem start_app a tail -f minie bardzo niewiele czasu, więc wciąż możemy dostać kompletny log), to z pozoru nie ma sensu używać tee. Ta komenda pozwala nam jednak na więcej, mianowicie na zapis wyników cząstkowych w potokach (pipes) komend.

Wyobraźmy sobie następujący przykład:

echo 'Poprzednie wyniki' >> final_sum;
my_grep1='grep -v "^#"'
my_grep2='grep -i "IP:"'
my_grep3='grep "8\.8\.8\.8"'
cat duzy_plik | $my_grep1 | tee grep1.log | $my_grep2 | tee grep2.log | $my_grep3 | tee -a final_ip final_sum
# dalsze polecenia

Dzięki komendzie tee możemy porównać wyniki cząstkowe po poszczególnych grepach. Jest to prosty, bardzo szybki i elegancki sposób na osiągniecie tego celu. Zauważmy także, że ostatnie wywołanie zapisuje z dodawaniem (-a – append) do dwóch plików: final_ip (w założeniu nowy plik) i final_sum, który w domyśle miałby zawierać także wyniki poprzednich poleceń.

script – czyli zapiszmy naszą konsolę

Często zdarza się, że wprowadzamy zmiany na serwerze, które następnie chcielibyśmy umieścić w dokumentacji. Schemat, z którym się spotkałem w kilku organizacjach, dla których mieliśmy okazje wprowadzać nasze rozwiązania, był z natury nieoptymalny. Administrator na początku wprowadzał zmiany, po czym wywoływał te same komendy lub przeglądał historie. Dużo prościej działa się na spisanej sesji. Takie spisywanie sesji oferuje program script.

[Alex@SpaceShip el7_tmp]$ script
Script started, file is typescript
[Alex@SpaceShip el7_tmp]$ logout
[Alex@SpaceShip el7_tmp]$ exit
exit
Script done, file is typescript
[Alex@SpaceShip el7_tmp]$ # Tutaj powróciliśmy do 'normalnej' konsoli
[Alex@SpaceShip el7_tmp]$ cat typescript
Script started on Sun 25 Feb 2018 07:08:20 PM CET
[Alex@SpaceShip el7_tmp]$ exit
exit

Script done on Sun 25 Feb 2018 07:08:22 PM CET
[Alex@SpaceShip el7_tmp]$

Oczywiście istnieją też inne rozwiązania – np. Terminator, popularny program do zarządzania wieloma terminalami, z którego zresztą pochodzą screeny prezentowanych na blogu komend, posiada możliwość logowania. Jest to bardzo podobna funkcjonalność, z tym zastrzeżeniem, że potrzebujemy najpierw tego programu i, co oczywiste, środowiska graficznego, co nie zawsze jest możliwe.

Jako ciekawostkę dodam, iż w ramach EuroLinux Professional Services mieliśmy okazję stworzyć rozwiązanie, które w podobny sposób zapisuje aktywność użytkowników na serwerze.

cat jako edytor tekstu?

Na pierwszym roku studiów miałem okazje być jeszcze naiwnym dobrym studentem i regularnie uczęszczałem na wszystkie możliwe zajęcia oraz prawie żadne każde wykłady. Poznałem wiele miejskich legend, a raczej akademickich legend. Jedna z nich dotyczy prowadzącego, którego stopień roztargnienia nie był odwrotnie proporcjonalny do wiedzy eksperckiej w swojej dziedzinie. Miał on na zajęciach z systemów operacyjnych uruchomić program cat bez żadnego argumentu. Jako że nie znał magicznej kombinacji klawiszy ctrl+d – czyli wywołanie znaku końca pliku (EOF), to zadzwonił do administratora, by ten mu zdalnie zresetował maszynę. Całą sytuację miał skwitować stwierdzeniem: „Na komendę »cat« należy uważać, bo wywołana bez parametru zawiesza komputer”. Jest to jedna z tych anegdot, które chyba każdy student I roku słyszał. Kiedyś podzieliłem się nią z moim starszym o dobre 15 lat wykładowcą, zresztą bardzo cenionym ekspertem w swojej dziedzinie, na co odpowiedział mi – „Tak! To było na moim roku”. Jak widać, każdy z nas ma kolegę, który znał kogoś rok wyżej, kto zna człowieka, który był na tych zajęciach ;)

Cat można używać do pisania do pliku. W tym celu wykonajmy:

$cat > cat_out.txt
some text <ctrl^d>

O ile w przypadku pisania ręcznego jest to nieporęczne (brak możliwości edycji poprzedniej linii), to w przypadku skryptów, możliwość zapisania pliku tekstowego bywa przydatna. Poniżej zilustruję prosty przykład dodania repozytorium na bazie wersji systemu zapisanej w pliku /etc/system-release.

FULL=$( cat /etc/system-release | grep -o "[0-9].[0-9]" )
VER=$(echo $FULL | cut -f1 -d.)
cat << EOF > /etc/yum.repos.d/my-base.repo
[my-base]
name=my base repo \$releasever - \$basearch
baseurl=http://host:/my_repo/${VER}/
enabled=1
gpgcheck=0
EOF

Przy używaniu cata do tworzenia pliku należy pamiętać, by nie zrobić błędu, jakim jest wcięcie. Zdarzyło się, iż jeden z programistów przeprowadzając inspekcję kodu, zdecydował, zresztą poniekąd słusznie, iż brak wcięcia jest rażącym błędem. Uśmiechnięty przy aprobacie kolegów (zatwierdzony pull request (PR) do repozytorium) poprawił skrypt. Gdy kilka godzin później skrypty CI aplikacji przestały działać, kilka osób zachodziło w głowę, dlaczego najnowsze zmiany w aplikacji zepsuły testy. Problemem nie okazała się bynajmniej aplikacja, lecz „ulepszone” pliki konfiguracyjne, które nie były już poprawnie odczytywane przez narzędzie. Poniżej przedstawiam przykład takiego błędu na tym samym przykładzie.

FULL=$( cat /etc/system-release | grep -o "[0-9].[0-9]" )
VER=$(echo $FULL | cut -f1 -d.)
if true; then
    cat << EOF > /etc/yum.repos.d/my-base.repo
    [my-base]
    name=my base repo \$releasever - \$basearch
    baseurl=http://host:/my_repo/${VER}/
    enabled=1
    gpgcheck=0
EOF
fi

Zauważmy, że EOF musi być na początku linii, inaczej nie zostanie poprawnie zinterpretowany. Jednak po takiej kreacji pliku my-base.repo yum nie jest w stanie zaczytać konfiguracji i odmawia nam posłuszeństwa.

Bonus szybkie udostępnianie zawartości folderu w Pythonie

Zdarza się czasem, iż potrzebujemy udostępnić wybrane pliki w sieci lokalnej.
Przydatny bywa wtedy moduł pythona SimpleHttpServer lub http.server, w zależności od posiadanej wersji interpretera języka Python.

W Pythonie 2 używamy:

python2 -m SimpleHTTPServer

Dla Pythona w wersji 3 używamy:

python3 -m http.server

W ten sposób udostępniamy bieżący folder na porcie 8000 na wszystkich interfejsach (0.0.0.0).

W celu udostępniania na innym procie (8080), używamy:

python2 -m SimpleHTTPServer 8080 # PORT 8080 na 0.0.0.0
python3 -m http.server 8080 # PORT 8080 na 0.0.0.0

Dla modułu http.server (python3) możemy także podać interfejs:

python3 -m http.server 8080 -b 10.10.1.123 # PORT 8080 na 10.10.1.123

Zauważmy, że nawet jeśli otwieramy usługę na porcie nieuprzywilejowanym (> 1024), nie zwalnia nas to ze stworzenia reguły lub wyłączenia firewalla w celu dopuszczenia ruchu do usługi.

Zapowiedź następnej części

W następnej części skupimy się na historii wykonywanych operacji. Znajdziemy w niej między innymi sposób na niezapisywanie wykonywanych operacji, ponownie zajrzymy do biblioteki readline i poznamy kolejną garść ciekawostek.

Autorzy

Artykuły na blogu są pisane przez osoby z zespołu EuroLinux. 80% treści zawdzięczamy naszym developerom, pozostałą część przygotowuje dział sprzedaży lub marketingu. Dokładamy starań, żeby treści były jak najlepsze merytorycznie i językowo, ale nie jesteśmy nieomylni. Jeśli zauważysz coś wartego poprawienia lub wyjaśnienia, będziemy wdzięczni za wiadomość.