Poradnik Bash Bushidō cz. IV – historia, skróty klawiszowe i The Fuck
Każdy z nas ma swoją historię. Na szczęście nie będziemy się zajmować trudnymi, zawiłymi i nie zawsze moralnie jednoznacznymi ludzkimi historiami. Naszą historią będzie dzisiaj historia w Bashu, czyli biblioteka oraz plik tekstowy prosto z projektu GNU, z której Bash korzysta. Będziemy sterować zachowaniem tej biblioteki poprzez odpowiednie ustawienia zmiennych w powłoce.
Prawie dwa miesiące przyszło nam czekać na następny artykuł z mojej ulubionej serii Bash Bushidō. Wynika to z prostego faktu, iż artykuły techniczne na naszym blogu piszą administratorzy, architekci i programiści, którzy czasem mają inne zadania.
Po krótkim usprawiedliwieniu – zaczynajmy.
Każdy z nas ma historię
Na szczęście nie będziemy się zajmować trudnymi, zawiłymi i nie zawsze moralnie jednoznacznymi ludzkimi historiami. Naszą historią dzisiaj będzie historia w Bashu, czyli biblioteka oraz plik tekstowy prosto z projektu GNU, z której Bash korzysta. Będziemy sterować zachowaniem tej biblioteki poprzez odpowiednie ustawienia zmiennych w powłoce.
Mimo iż jestem fanem man
, to prawdziwą skarbnicą wiedzy o projektach prowadzonych przez GNU są strony info. Do ich czytania służy narzędzie o tej samej nazwie info
. By zwiększyć naszą wiedzę à propos samej biblioteki historii, możemy wywołać info history
.
Podobnie w celu znalezienia zmiennych używanych przez Basha polecam używanie info bash
. Ze względu na fakt, iż manuale mają płaską strukturę (jedna strona), przy występowaniu odnośników do innych sekcji są one mniej czytelne niż strony info.
Gdzie mamy historię?
Sama historia w Bashu jest po prostu plikiem (jak prawie wszystko w Unixie). By znaleźć plik, do którego historia jest zapisywana, należy wpisać wartość zmiennej HISTFILE
:
[Alex@SpaceShip ~]$ echo $HISTFILE /home/Alex/.bash_history
Jak widać, zmienna HISTFILE jest zmienną automatycznie przypisywaną przez Basha. Aby zmienić miejsce zapisywania historii, zmieniamy wartość zmiennej HISTFILE.
Poniżej przykład z exportem zmiennej.
[Alex@SpaceShip ~]$ bash # Odpalenie basha w bashu :) [Alex@SpaceShip ~]$ export HISTFILE=~/REMOVE_ME_TEST [Alex@SpaceShip ~]$ echo "Ta jedyna ;)" Ta jedyna ;) [Alex@SpaceShip ~]$ exit # Zakończenie podpowłoki exit [Alex@SpaceShip ~]$ cat ~/REMOVE_ME_TEST # export HISTFILE=~/REMOVE_ME_TEST echo "Ta jedyna ;)" exit
Permanentne rozwiązanie tworzymy, dodając export naszej zmiennej HISTFILE do .bashrc
[Alex@SpaceShip ~]$ echo "export HISTFILE=~/.remove_test_hist" >> ~/.bashrc
Najczęściej używane komendy
W celu wyświetlenia najczęściej używanych komend możemy wykonać następującego jednolinijkowca.
[Alex@SpaceShip ~]$ history | awk '{print $2}' | sort | uniq -c | sed "s/^[ \t]*//" | sort -nr | head -10 74 ls 64 vim 59 echo 38 ../xxx.sh 37 cd 32 git 28 bash 23 shellcheck 19 ./xxx.sh 14 history
Dla większości ludzi znających Basha ten oneliner jest zrozumiały. Jedynym miejscem, które może sprawić trudności, jest sed "s/^[ \t]*//"
. Został on skopiowany z internetu napisany w celu usunięcia znajdujących się na początku linii białych znaków. Znaki te są generowane przez uniq -c
.
Skasujmy naszą historię
By wyczyścić historię, możemy wykorzystać history -c
.
[vagrant@localhost ~]$ history 1 w 2 df h 3 uname a ... 70 history [vagrant@localhost ~]$ history c [vagrant@localhost ~]$ history 1 history [vagrant@localhost ~]$
Warto zauważyć, że plik $HISTFILE wciąż będzie zawierał stare wpisy. Historia zapisuje się, gdy kończymy naszą sesję.
Historia z datą
Za format, jak i występowanie daty w pliku historii odpowiada zmienna HISTTIMEFORMAT. W celu rozpoczęcia procesu zapisywania historii z datą wykonania komendy musimy ją odpowiednio ustawić.
export HISTTIMEFORMAT="%Y-%m-%d %T "
Zauważmy, że jeśli poprzednie polecenia nie posiadały swojego czasu wykonania, to za datę tych wpisów zostanie ustawiona data wykonania pierwszego polecenia z ustawioną zmienną historii.
[vagrant@localhost ~]$ export HISTTIMEFORMAT="%Y-%m-%d %T " [vagrant@localhost ~]$ echo 'export HISTTIMEFORMAT="%Y-%m-%d %T "' >> .bashrc [vagrant@localhost ~]$ history 1 2018-07-31 09:58:42 echo "This is great" 2 2018-07-31 09:58:46 df -h 3 2018-07-31 09:58:57 sudo yum update -y 4 2018-07-31 09:59:15 uname -a 5 2018-07-31 09:59:23 cat /etc/grub2.cfg 6 2018-07-31 09:59:25 sudo cat /etc/grub2.cfg 7 2018-07-31 09:59:30 history 8 2018-07-31 09:59:47 export HISTTIMEFORMAT="%Y-%m-%d %T" 9 2018-07-31 10:00:07 echo 'export HISTTIMEFORMAT="%Y-%m-%d %T"' >> .bashrc 10 2018-07-31 10:00:10 history 11 2018-07-31 10:00:19 export HISTTIMEFORMAT="%Y-%m-%d %T " 12 2018-07-31 10:00:23 echo 'export HISTTIMEFORMAT="%Y-%m-%d %T "' >> .bashrc 13 2018-07-31 10:00:25 history
W powyższym przypadku widać kilka wpisów z historią. Chciałbym zaznaczyć, że jednak posiadają one swój rzeczywisty czas wykonania. Wynika to z faktu, iż wydarzyły się w tej samej sesji co ustawienie zmiennej HISTTIMEFORMAT
.
Należy zauważyć, iż spacja na końcu linii HISTTIMEFORMAT
nie jest przypadkowa. Bez niej parametr %T
„zleje się” z wywołaną komendą.
Jeśli posiadamy własne skrypty działające cyklicznie na historii Basha, konieczna może być ich aktualizacja. Przykładowo do znalezienia najczęściej używanych komend użyliśmy wybrania słowa komendy przy pomocy awk '{print $2}
. Teraz będziemy musieli zmienić $2
na $4
.
Ustawienia ignorowania historii
Jak już wspominałem w poprzednich częściach, możemy sprawić, by Bash nie zapisywał części naszych komend, np. jeśli pierwszym znakiem na linii poleceń jest spacja.
W tym celu musimy odpowiednio ustawić zmienną HISTCONTROL
.
HISTCONTROL może mieć następujące wartości:
1. Nieustawiona zmienna lub ustawiona na niepoprawny ciąg znaków – zapisywane jest wszystko.
2. ignorespace – linie zaczynające się od spacji nie są zapisywane.
3. ignoredups – ignoruje duplikaty.
4. erasedups – usuwa poprzednie wpisy, jeśli wystąpiły duplikaty.
5. ignoreboth – ignorespace + ignoredups.
Najczęściej używa się ignoreboth
.
Wpływ zmiennej HISTCONTROL
prezentuje poniższy przykład.
[root@localhost ~]# echo "my_new_pass" | passwd --stdin root Changing password for user root. passwd: all authentication tokens updated successfully. [root@localhost ~]# echo "my_new_pass2" | passwd --stdin root Changing password for user root. passwd: all authentication tokens updated successfully. [root@localhost ~]# history 1 2018-07-31 11:25:55 echo "my_new_pass" | passwd --stdin root 2 2018-07-31 11:26:04 echo "my_new_pass2" | passwd --stdin root 3 2018-07-31 11:26:08 history [root@localhost ~]# echo "export HISTCONTROL=ignoreboth" >> .bashrc [root@localhost ~]# . .bashrc [root@localhost ~]# echo "my_new_pass3" | passwd --stdin root Changing password for user root. passwd: all authentication tokens updated successfully. [root@localhost ~]# history 1 2018-07-31 11:25:55 echo "my_new_pass" | passwd --stdin root 2 2018-07-31 11:26:04 echo "my_new_pass2" | passwd --stdin root 3 2018-07-31 11:26:08 history 4 2018-07-31 11:26:36 echo "export HISTCONTROL=ignoreboth" >> .bashrc 5 2018-07-31 11:26:43 . .bashrc 6 2018-07-31 11:26:53 history
histchars – sterujmy historią
Kolejną ciekawostką, którą chciałbym przedstawić, jest możliwość zmiany domyślnych znaków specjalnych historii poprzez ustawienie zmiennej histchars.
Przy pomocy histchars ustawiamy trzy znaki, które odwołują się kolejno do:
1. Event designator
– domyślnie używamy !
.
2. Szybka podmiana
– domyślnie używany znak to ‘^’. Uruchamiana tylko wtedy, gdy jej znak jest pierwszym znakiem na linii poleceń.
3. Komentarz
– istotne jest tutaj zrozumienie, że chodzi o komentarz dla ekspansji z historii, a nie o zakomentowanie z linii poleceń (parser linii poleceń znajduje komentarze poprzez #
). W związku z tym faktem jest praktycznie bezużyteczna.
Przykładowe wywołania:
[Alex@SpaceShip ~]$ echo "Bash jest super" Bash jest super [Alex@SpaceShip ~]$ !! echo "Bash jest super" Bash jest super [Alex@SpaceShip ~]$ ^Bash^Fish echo "Fish jest super" Fish jest super [Alex@SpaceShip ~]$ # To jest komentarz
Poniżej dokładnie to samo przy zmienionym ustawieniu histchars.
[Alex@SpaceShip ~]$ histchars="+=@" [Alex@SpaceShip ~]$ echo "Bash jest super" Bash jest super [Alex@SpaceShip ~]$ ++ echo "Bash jest super" Bash jest super [Alex@SpaceShip ~]$ =Bash=Fish echo "Fish jest super" Fish jest super [Alex@SpaceShip ~]$ @ To jest komentarz bash: @: command not found...
Poniższy przykład pokazuje także, że rzeczywiście zmiana znaku komentarza dla parsera linii poleceń z #
na @
nie występuje.
Stworzenie własnego skrótu klawiszowego
Jak już wiemy z poprzednich części, za to, jak zachowuje się nasza linia komend, odpowiada biblioteka readline
. W celu ustawienia własnego skrótu klawiszowego użyjemy programu bind.
Jednak, by móc to robić, musimy się dowiedzieć, jakie kody będą wysyłane przez naszą klawiaturę do konsoli. By to sprawdzić, proponuję użyć następującego triku:
ctrl
+ v
potem wcisnąć szukaną kombinację klawiszy (np F9
).
By zobaczyć, za co „pod maską” odpowiada ctrl
+v
, możemy wywołać:
[Alex@SpaceShip ~]$ bind -P | grep '\C-v' display-shell-version can be found on "\C-x\C-v". quoted-insert can be found on "\C-q", "\C-v", "\e[2~".
Dla nas interesująca jest linia „quoted-insert”, co można luźno przetłumaczyć jako „cytowane wstawienie”. Jego sensem jest wstawienie znaku takim, jakim go dostaje linia poleceń (bez interpretowania). Inną nazwą na takie zachowanie konsoli jest „verbatim insert”.
Wiedząc już co pod spodem, możemy stworzyć własny skrót klawiszowy. Poniższy przykład tworzy nam powiązanie dla klawisza F9
. Wykonuje ono komendę „date”.
[Alex@SpaceShip ~]$ # znalezione przy pomocy ctrl-v ^[[20~ [Alex@SpaceShip ~]$ bind '"\e[20~":"date\n"' [Alex@SpaceShip ~]$ date # F9 Tue Jul 31 15:17:53 CEST 2018
Zauważmy zmienione podstawienie – zamiast ^[[20~
użyliśmy \e[20~
.
Innym przykładem może być nadpisanie ctrl
+ q
(jak już wiemy nadmiarowo powiązanego do funkcji quoted-insert
[ctrl
– v
naprawdę nam wystarczy, poza tym jest dużo bardziej oczywistym skrótem]).
[Alex@SpaceShip ~]$ # znalezienie po ctrl + v ^Q [Alex@SpaceShip ~]$ bind '"\C-q":"date\n"` > ^C [Alex@SpaceShip ~]$ bind '"\C-q":"date\n"' [Alex@SpaceShip ~]$ date # ctrl + q Tue Jul 31 15:40:22 CEST 2018 [Alex@SpaceShip ~]$
Jeśli chcielibyśmy dowiązać nasze wywołanie komendy do alt
+ q
, powinniśmy:
[Alex@SpaceShip ~]$ # znalezione po ctrl-v ^[q [Alex@SpaceShip ~]$ bind '"\eq":"date\n"' [Alex@SpaceShip ~]$ date # alt + q Tue Jul 31 15:41:51 CEST 2018
Zmiana zapisu kodu klawisza, który zwróciła nam konsola względem tego, jakiego użyliśmy, wynika z tego, w jaki sposób zinterpretuje go nasza ulubiona biblioteka readline
. Tłumaczy to poniższa tabelka.
< |
Interpretacja |
---|---|
The Escape key. Używany do innych przypisań, takich jak znaki specjalne czy związane z klawiszem Meta (na większości klawiatur alt ). Używamy, gdy przed znakiem (kodem klawisza) mamy ^[ |
|
Reprezentuje przytrzymanie klawisza ctrl . Używamy, gdy przed znakiem (kodem klawisza) mamy ^ |
The Fuck mały pomocnik na linii komend
The Fuck jest projektem stworzonym z myślą o administratorach, którym często zdarzają się literówki. Narzędzie to automatycznie wyszukuje najbliższą poprawną formę i sugeruje możliwe rozwiązanie. Oczywiście zdaję sobie sprawę, że jest to blog firmowy, nie mniej ja takiej nazwy nie wybrałem. Proszę więc o wybaczenie. Po drodze postaram się także spolszczyć nazwę.
The Fuck instalacja na systemach z rodziny Enterprise Linux w wersji 7
fuck
wymaga Pythona w wersji 3.4 lub wyższej. Do instalacji The Fuck
użyjemy zaś menadżera pakietów pythonowych pip
.
By zainstalować na minimalnym wariancie systemu EuroLinux, wywołujemy następujące komendy:
[vagrant@localhost ~]$ sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm [vagrant@localhost ~]$ sudo yum install python34-devel python34-pip gcc [vagrant@localhost ~]$ sudo pip3 install thefuck
No fucks given
Konfiguracja narzędzia fuck
odbywa się poprzez dwukrotne wpisanie fuck
i ponownie zaczytanie konfiguracji powłoki.
[vagrant@localhost ~]$ fuck Seems like fuck alias isn't configured! Please put eval $(thefuck --alias) in your ~/.bashrc and apply changes with source ~/.bashrc or restart your shell. Or run fuck second time for configuring it automatically. More details - https://github.com/nvbn/thefuck#manual-installation [vagrant@localhost ~]$ fuck fuck alias configured successfully! For applying changes run source ~/.bashrc or restart your shell. [vagrant@localhost ~]$ . ~/.bashrc [vagrant@localhost ~]$ fuck No fucks given
Po udanej konfiguracji dostaniemy informację o „no fucks given”, co można przetłumaczyć dwojako: dosłownie jako „OCENZUROWANO” lub „nie znaleziono żadnych niepoprawnych komend” :)
The Fuck drobne spolszczenie
Wielki programista Linus Torvalds, znany między innymi z dyplomatycznego języka i skromności, powiedział:
Nobody actually creates perfect code the first time around, except me. But there's only one of me.
Co można przetłumaczyć w bardzo swobodnym tłumaczeniu.
Nikt w rzeczywistości nie tworzy doskonałego kodu za pierwszym razem, nie licząc mnie. Ale ja jestem jedyny w swoim rodzaju.
Jako że nie jestem Linusem Torvaldsem, a moje ego nie jest wielkości małej planety, to często zdarza mi się popełniać błędy. Czasem sprawiają one, że używam języka i słów, których nie wypada użyć w artykule. Dlatego pozwolę sobie zamienić je na słowo “kurczę”.
W związku z tym skonfigurujemy thefuck
tak, byśmy mogli go używać w polskim kontekście.
[vagrant@localhost ~]$ echo "eval $(thefuck --alias kurcze)" >> ~/.bashrc [vagrant@localhost ~]$ . ~/.bashrc [vagrant@localhost ~]$ kurcze Nothing found
Jak widać, dla naszego własnego aliasu nie jest nam zwracany komunikat `No fucks given`.
[vagrant@localhost ~]$ mkdir new_repo [vagrant@localhost ~]$ cd new_repo/ [vagrant@localhost new_repo]$ git int git: 'int' is not a git command. See 'git --help'. Did you mean this? init [vagrant@localhost new_repo]$ kurcze git init [enter/↑/↓/ctrl+c] Initialized empty Git repository in /home/vagrant/new_repo/.git/
Warto zauważyć, że „the fuck” przy wyświetleniu listy potencjalnych komend do poprawy, pozwala na używanie j
jako ↑
i k
jako ↓
. Identycznie sytuacja ma się z ctrl
+n
i ctrl
+ p
.
Podsumowanie i podziękowania
Wreszcie udało nam się dotrzeć do ustawień historii, które w naszej serii ze względu na długość artykułów odkładaliśmy. Poznaliśmy także ciekawe narzędzie „The Fuck”.
W następnej części omówimy narzędzie ShellCheck, które pomaga nam pisać lepsze skrypty.