Bash_III

Poradnik Bash Bushidō cz. III – desygnator zdarzeń i desygnator słów

W trzeciej części drogi bashowego wojownika przyjrzymy się „desygnatorowi zdarzeń” oraz jego przyjaciołom: „desygnatorowi słów” i „modyfikatorom”. Na koniec udoskonalimy także klasykę hakera.

W trzeciej części drogi bashowego wojownika przyjrzymy się „desygnatorowi zdarzeń” oraz jego przyjaciołom: „desygnatorowi słów” i „modyfikatorom”. Na koniec udoskonalimy także klasykę hakera.

Ze względu na fakt, iż artykuły z serii Bash Bushido w założeniu nie powinny być zbyt długie, zostałem niejako zmuszony do podzielenia tej części na dwie. W związku z tym dopiero w kolejnym materiale ustawimy historię i przypiszemy własny skrót pod komendę.

Zapraszam :)

Czym jest „!” w linii poleceń?

! w linii poleceń jest znakiem specjalnym, z angielskiego nazwanym event designator, co Google Translate tłumaczy jako oznaczenie zdarzenia. Mówiąc szczerze, tłumaczenie to nie oddaje dobrze natury ! w linii poleceń. Gdy głowiłem się nad tym, jak przetłumaczyć ten zwrot, moja koleżanka spojrzała na mnie wzrokiem pełnym zwątpienia i powiedziała – „Zajrzyj do polskiego manuala?”. Rzeczywiście podręczniki systemowe, tłumaczone przez ludzi skupionych w projekcie manpages-pl, stoją na naprawdę wysokim poziomie. W tym artykule pozwolę sobie je kilkukrotnie zacytować.

Desygnatory zdarzeń (Event Designators)
Desygnator zdarzenia jest odwołaniem do pozycji wiersza poleceń na liście historii. O ile odwołanie nie jest absolutne, zdarzenia są relatywne w stosunku do bieżącej pozycji w liście historii.

„sudo” i „!!”, czyli dobrzy sąsiedzi

Najpowszechniejszym zastosowaniem !, z reguły bez wiedzy czym jest desygnator zdarzenia, jest użycie go z sudo, wraz ze skrótem odpowiedzialnym za powtórzenie ostatniej komendy, czyli !!.

Przykładowe użycie wraz z yumem.

[Alex@SpaceShip ~]$ yum install man-pages-pl
Loaded plugins: langpacks, rhnplugin
*Note* EuroLinux repositories are not listed below. You must run this command as root to access EuroLinux repositories.
You need to be root to perform this command.
[Alex@SpaceShip ~]$ sudo !!
sudo yum install man-pages-pl
Loaded plugins: langpacks, rhnplugin
This system is receiving updates from EuroLinux Network.
...
Installed:
man-pages-pl.noarch 1:0.3-4.el7
Complete!
[Alex@SpaceShip ~]$

Wywołanie komendy ze względu na jej numer w historii

Czasem, zamiast poszukiwać interaktywnie komendy, efektywniej jest wylistować całą historię i przy pomocy wyrażenia regularnego znaleźć interesujący nas zbiór użytych komend.

Poniżej przykład użycia !:

[Alex@SpaceShip ~]$ history | grep grep
1 grep -v bash /etc/passwd | grep -v nologin
2 grep -n root /etc/passwd
101 grep -c false /etc/passwd
202 grep -i ps ~/.bash* | grep -v history
303 grep :$ /etc/passwd
404 grep nologin /etc/passwd
501 history | grep grep
[Alex@SpaceShip ~]$ !404
# Wywoła komende `grep nologin /etc/passwd`

!n – wywołuje n-tą komendę z historii.

Przydatna może być też wiedza, jak wywołać komendę kilka komend wstecz względem obecnie wpisywanej linii w konsoli (ustalonej jako liczba 0). Służy do tego !-n, gdzie n jest liczbą naturalną.

[Alex@SpaceShip ~]$ echo Mars
Mars
[Alex@SpaceShip ~]$ echo Earth
Earth
[Alex@SpaceShip ~]$ !-2
echo Mars
Mars
[Alex@SpaceShip ~]$ !-2
echo Earth
Earth
[Alex@SpaceShip ~]$ !-2
echo Mars
Mars
[Alex@SpaceShip ~]$ !-2
echo Earth
Earth

Jako ciekawostkę należy dodać, iż !! jest tym samym skrótem co !-1.

Wywołanie ostatniej komendy zaczynającej się od zadanego ciągu znaków

W celu wywołania ostatniej komendy zaczynającej się od zadanego ciągu znaków używamy:

!<ciąg znaków>. Poniżej przykład użycia:

[Alex@SpaceShip cat1]$ whoami
Alex
[Alex@SpaceShip cat1]$ who
Alex :0 2018-04-20 09:37 (:0)
...
[Alex@SpaceShip cat1]$ !who
who
Alex :0 2018-04-20 09:37 (:0)
...
[Alex@SpaceShip cat1]$ !whoa
whoami
Alex
[Alex@SpaceShip cat1]$
Word Designator

Oprócz „Desygnatora zdarzeń” istnieje „Desygnator słów”. Pozwolę sobie znów zacytować fragment tłumaczenia z polskiego manuala.

Desygnatory słów (Word Designators)
Desygnatory słów służą do wybierania ze zdarzenia żądanych słów. Dwukropek „:” oddziela określenie zdarzenia od desygnatora słowa. Może być pominięty, jeśli desygnator słowa rozpoczyna się od ^, $, *, - lub %. Słowa numerowane są od początku wiersza, przy czym pierwsze ma numer 0 (zero). Słowa są wstawiane do bieżącego wiersza, rozdzielane pojedynczymi spacjami.

Warto zauważyć oczywisty fakt, iż desygnator słów jest opcjonalny. Gdy dodamy do niego modyfikatory, fakt ten już tak oczywisty nie jest, choć wciąż oczywiście zachodzi.

Przykłady użycia desygnatora słów

Dalsza dokumentacja Basha wprowadza nas w użycie desygnatora słów. Pozwoliłem sobie na zacytowanie jej w formie tabelarycznej:

Desygnator Co robi?
0 (zero) Słowo zerowe. Dla powłoki jest to słowo polecenia.
n n-te słowo.
^ Pierwszy argument. To znaczy, słowo 1.
$ Ostatni Argument
<x-y/td> Zakres słów; \`y’ jest skróconym \`0-y’
* Wszystkie słowa prócz zerowego. Jest to synonim dla \`1-$’. Nie jest błędem użycie * jeśli w zdarzeniu jest tylko jedno słowo;
x* Skrót od x-$
x- Skrót do x-$ podobnie jak x*, ale pomija ostatnie słowo.

Jeśli desygnator słowa podano bez określenia zdarzenia, za zdarzenie przyjmowane jest poprzednie polecenie.

Użycie desygnatora słów prezentują poniższe przykłady:

[Alex@SpaceShip ~]$ echo use printf `use echo\n`
use printf use echo\n
[Alex@SpaceShip ~]$ !!:2* # Można także użyć !:2*
printf `use echo\n`
use echo

Możemy też łączyć wybrane słowa z poprzednich zdarzeń:

[Alex@SpaceShip ~]$ echo I love rock \'n\' roll
I love rock 'n' roll
[Alex@SpaceShip ~]$ echo I love rock \'n\' roll
I love rock 'n' roll
[Alex@SpaceShip ~]$ echo I love rock \'n\' roll
I love rock 'n' roll
[Alex@SpaceShip ~]$ echo I love rock \'n\' roll
I love rock 'n' roll
[Alex@SpaceShip ~]$ echo I love rock \'n\' roll
I love rock 'n' roll
[Alex@SpaceShip ~]$ history | tail -6
583 echo I love rock \'n\' roll
584 echo I love rock \'n\' roll
585 echo I love rock \'n\' roll
586 echo I love rock \'n\' roll
587 echo I love rock \'n\' roll
588 history | tail -6
[Alex@SpaceShip ~]$ echo !583:1 !584:2 !585:3 !586:4 !587:5
I love rock 'n' roll

A także grupować słowa z poprzednich zdarzeń:

[Alex@SpaceShip ~]$ !583:0-3 !584:4*
echo I love rock \'n\' roll
I love rock 'n' roll

Jako ciekawostkę chciałbym dodać, iż istnieje także trzeci zapis odwołujący się do poprzedniej komendy z linii komend. Przykład z jego użyciem:

[Alex@SpaceShip ~]$ yum update
...
You need to be root to perform this command.
[Alex@SpaceShip ~]$ sudo !:0*
sudo yum update
...
No packages marked for update

Wykorzystujemy tutaj fakt, iż desygnator zdarzenia bez argumentu zdarzenia (nr lub skrót dla ostatniej komendy – !) w momencie istnienia desygnatora/ów słów lub używa ostatniego polecenia.

Modyfikatory – dodatkowe operacje na desygnatorze słowa

Po desygnatorze słowa (lub słów), możemy wykonać dodatkową operację, uzyskując jeszcze większą kontrolę nad wywołaniem – za taką operację odpowiadają modyfikatory. Każdy modyfikator jest rozdzielony od poprzedniego dwukropkiem :.

Cytując polskie tłumaczenie podręcznika (manuala) Basha, modyfikator słowa obsługuje następujące argumenty:

column column
h Usuwa końcową składową nazwy pliku, pozostawiając tylko początek.
t Usuwa wszystkie początkowe składowe nazwy pliku, pozostawiając koniec.
r Usuwa kończący przyrostek postaci .xxx, pozostawiając główną część nazwy (basename).
e Usuwa wszystko prócz końcowego przyrostka.
p Wypisuje nowe polecenie, ale go nie wykonuje.
q Cytuje podstawiane słowa, zabezpieczając je przed dalszym podstawianiem.
x Cytuje podstawiane słowa jak q, ale rozbija na słowa w miejscach odstępów i znaków nowej linii.
s Zastępuje nowym pierwsze wystąpienie starego w wierszu zdarzenia. Zamiast / może zostać użyty dowolny ogranicznik. Końcowy ogranicznik jest opcjonalny, jeżeli jest ostatnim znakiem wiersza zdarzenia. Separator może być cytowany w nowym i starym przy pomocy pojedynczego odwrotnego ukośnika. Jeżeli w nowym pojawia się &, to jest zastępowany starym. Pojedynczy odwrotny ukośnik będzie cytował &. Jeżeli stary jest pusty, to ustawiany jest na ostatni podstawiany stary lub, jeśli nie było poprzednich podstawień historii, ostatni łańcuch w wyszukiwaniu !?łańcuch[?].
& Powtarza poprzednie podstawienie.
g Powoduje, że zmiany zostaną zastosowane do całego wiersza zdarzenia. Używany w połączeniu z ‘:s’ (np. ‘:gs/stary/nowy/’) lub ‘:&’. Jeśli użyty z ‘:s’, to zamiast / można posłużyć się dowolnym separatorem, a ostatni separator jest opcjonalny, jeżeli jest ostatnim znakiem wiersza zdarzenia.
G Stosuje następujący po nim modyfikator “s” raz do każdego słowa w wierszu zdarzenia.

Finalnie możemy sobie wyobrazić desygnator zdarzenia jako następujący ciąg.

![nr,!][:desygnator_słów][:operacja_na_słowie_1][:operacja_na_słowie_2]...

Jak już przedtem zauważyliśmy, w przypadku braku wyboru zdarzenia, a zaistnieniu desygnatora słów, Bash używa ostatniej komendy jako wybrane wydarzenie. Identyczna sytuacja zachodzi w przypadku użycia samego modyfikatora. W tym wypadku podstawiane są wszystkie słowa, czyli inaczej rzecz ujmując cała komenda. Ilustruję to drugie wywołanie w poniższych przykładach:

Komendą powtarzaną będzie echo aaa bbb /a/b/c/aaa.txt. W ramach skrócenia zapisu pozwolę sobie nie wywoływać przed każdym pokazowym użyciem:

[Alex@SpaceShip ~]$ !!:s/aaa/bbb # podstawienie jednokrotne
echo bbb bbb /a/b/c/aaa.txt
bbb bbb /a/b/c/aaa.txt

[Alex@SpaceShip ~]$ !:gs/aaa/bbb # podstawienie wielokrotne, zauważmy wykorzystanie domyślnej wartości desygnatora wyrażenia, czyli użycie tylko jednego !
echo bbb bbb /a/b/c/bbb.txt
bbb bbb /a/b/c/bbb.txt

[Alex@SpaceShip ~]$ echo !!:e
echo .txt
.txt
[Alex@SpaceShip ~]$ !!:r
echo aaa bbb /a/b/c/aaa
aaa bbb /a/b/c/aaa

[Alex@SpaceShip ~]$ !!:h
echo aaa bbb /a/b/c
aaa bbb /a/b/c

Podmiana ciągu znaków w ostatniej komendzie – skrótowy zapis

W przypadku popełnienia literówki w ostatniej komendzie, chęci zmiany argumentu lub innego ciągu znaków, możemy użyć następującego podstawienia:

^stary_ciag^nowy_ciag^nowy_ciag_na_końcu_polecenia.

Zapis ten jest specjalnym skrótem umieszczonym w Bashu dla wygody użytkownika. Należy też zauważyć, że podstawienie domyślnie działa tylko dla pierwszego wystąpienia.

Przykładowe użycia:

[Alex@SpaceShip ~]$ dgi google.com | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" | head -1
bash: dgi: command not found...
Similar command is: 'dig'
[Alex@SpaceShip ~]$ ^dgi^dig^
dig google.com | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" | head -1
172.217.16.46

[Alex@SpaceShip ~]$ grep "net.ipv4." /etc/sysctl.conf
...
[Alex@SpaceShip ~]$ ^v4^v6 # Zauważmy brak ostatniego '^'
grep "net.ipv6." /etc/sysctl.conf

[Alex@SpaceShip ~]$ echo "echo printf?\n"
echo printf?\n
[Alex@SpaceShip ~]$ ^echo^printf^:&
printf "printf printf?\n"
printf printf?
...

[Alex@SpaceShip ~]$ echo aaa bbb aaa
aaa bbb aaa
[Alex@SpaceShip ~]$ ^aaa^bbb^ ccc ddd
echo bbb bbb aaa ccc ddd
bbb bbb aaa ccc ddd
[Alex@SpaceShip ~]$

Jak już możemy się domyślać ^stary_ciag^nowy_ciag jest równoważny użyciu ostatniego polecenia wraz z użyciem desygnatora słów wraz z modyfikatorem podstawienia, czyli: !!:s/stary_ciag/nowy_ciag/. Co jak wiemy, jest równoznaczne z !-1:s/stary_ciag/nowy_ciag/.

Jako ciekawostkę należy dodać, iż w przypadku braku następnych modyfikatorów możemy pominąć /. Natomiast w skróconym zapisie podstawienia dla ostatniej komendy w przypadku braku dodatkowych argumentów (słów) możemy pominąć ostatni ^.

Edytowanie zdalnego pliku lokalnie – klasyka hakera

Dla tych z Państwa, którzy nie znają tego tekstu kultury, zachęcam do obejrzenia fragmentu filmu:

https://www.youtube.com/watch?v=wFXLzr86MQ4

Co do samego pojęcia tekstu kultury – dosyć zrozumiale tłumaczy to pojęcie Wikipedia.

Jako wojownicy konsoli, wolimy tworzyć użyteczną „klasykę hakera”, więc zamiast emacsem przez sendmail, użyjemy istniejącego vimem przez ssh.
By edytować zdalny plik przez ssh, używamy następującej konstrukcji:
vim scp://USER@HOST//SCIEZKA/ABSOLUTNA

Dla zdalnej edycji wspierane są protokoły:

  • ftp
  • rcp
  • scp
  • http.

Jakie są zalety takiej edycji?

  • Vim nie musi być zainstalowany na hoście docelowym (jak wiemy, z reguły mamy tylko minimalnego vi)
  • Plik edytujemy na lokalnej maszynie w Vimie, który posiada nasze ulubione wtyczki i naszą, często tworzoną latami konfigurację.

Więcej na temat tej użytecznej sztuczki możemy przeczytać na stronie Vim.wika.com.

Co warto zapamiętać?

Najczęściej stosowaną sztuczką z tego rozdziału jest oczywiście sudo !! nie mniej użycie skrótowego zapisu podstawienia ^stary_ciag^nowy_ciag[^ opcjonalne dodatkowe argumenty] także bywa przydatne. Czy zatem bardziej zaawansowane desygnatory i modyfikatory są niepotrzebne? Cóż, wiele zależy od naszego podejścia i środowiska pracy. W momencie, gdy posiadamy wirtualne terminale z możliwością nieskończonego odwoływania się do ich wyjścia/historii oraz możliwość kopiowania tekstu nawet myszką, modyfikatory i desygnatory tracą znacząco swoją moc. Niemniej, każdy zaawansowany użytkownik powłoki powinien je przynajmniej kojarzyć.

Ostatni trick z Vimem jest moim zdaniem wyjątkowo urodziwy – czysty (prosty w koncepcji) i spełniający swoją bardzo precyzyjną rolę (zupełnie jak Unix). Wymaga jednak od nas oduczenia się nawyku logowania i edycji pliku na rzecz jednego wywołania Vima.

Podziękowania i zapowiedź następnej części

Chciałbym Państwu podziękować za poświecenie swojego czasu na przeczytanie tego artykułu. Jeśli się Państwu spodobał, zachęcam do zapisania się na nasz newsletter oraz do subskrypcji naszego RSS lub polubienia naszych profili w mediach społecznościowych.

W następnej części oprócz zapowiedzianych tematów (ustawienia historii i własne skróty klawiszowe w Bashu), poznamy dwa bardzo przydatne programy, pierwszym z nich będzie The Fuck. ;)

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ść.