Linux v příkazech - vylaďte si Bash!

To, že Bash dokáže zázraky, netřeba připomínat. Ale on dokáže ještě mnohem víc.

18.3.2005 06:00 | Jiří Václavík | přečteno 65710×

Prompt

Prompt je text, který vyzývá k zadání příkazu. Může obsahovat řadu informací, neboť do něj lze zapsat prakticky cokoliv.

Text promptu

Nastavuje se pomocí proměnných prostředí:

Zde je seznam escape znaků, které mohou být hodnotou PS1 nebo PS2:

Escape znakVýznam
\ujméno uživatele
\!číslo příkazu (historicky)
\#číslo příkazu (aktuální konzole)
\Hjméno počítače
\hčást jména počítače
\nnový řádek
\wjméno aktuálního adresáře
\Wjméno aktuálního adresáře, ale bez případné tildy
\asystémový zvonek (příkaz beep)
\ddatum ve formátu den_v_týdnu měsíc den_v_měsíci (TT MMM DD)
\lčíslo konzole
\rnávrat kurzoru na začátek řádku
\sjméno příkazového interpretu (bash)
\tstatický čas HH:MM:SS 24 hodin
\tstatický čas HH:MM:SS 12 hodin
\Astatický čas HH:MM:SS 24 hodin
\@statický čas HH:MM 12 hodin
\vverze Bashe
\Vverze Bashe + patch level
\\zpětné lomítko
\[začátek speciální sekvence znaků (dále v článku)
\]konec speciální sekvence znaků

Docela použitelné nastavení může být toto (uživatel jv):

$ export PS1="\u:\w> "
jv:~/dokumenty>

Přidáme ještě jméno počítače:

$ export PS1="\u@\h:\w> "
jv@home:~/dokumenty>

Občas se může hodit i čas:

$ export PS1="<\A>*\u*\w> "
<00:19>*jv*~/dokumenty>

Prompt může být víceřádkový. Možná to někomu připadá zvláštní, ale ono to může být velmi praktické. Máme tak spoustu místa na příkaz.

$ export PS1="*** \A \u@\h \w ***\n\#>"
*** 00:24 jv@home ~/dokumenty ***
47>

Jako prompt lze použít i obyčejný řetězec:

$ export PS1="PROMPT> "
PROMPT>

Jen na ukázku, že znaky promptu mohou být i na pravé straně od počáteční pozice kurzoru, se podívejme na tento prompt:

$ export PS1="***************\r****"
***************

Styly a barvy

Prompt se dá nastylovat a obarvit. Podívejme se, jak jsou v Bashi kódované jednotlivé styly a barvy.

Styl

StylKód
normální0
světlý1
tmavý2
podtržený4
blikající5
výměna barev7

Barva popředí

BarvaKód
černá30
červená31
zelená32
hnědá33
modrá34
fialová35
azurová36
šedá37

Barva pozadí

BarvaKód
černá40
červená41
zelená42
hnědá43
modrá44
fialová45
azurová46
šedá47

Ještě existují další kódy od 90 nahoru, ale to jsou jen modifikace předchozích.

Barvy se mohou občas trochu lišit. Ta samá barva může například v GUI konzoli a mimo GUI vypadat jinak. S blikáním mimo gui bývá někdy také problém.

Kódy se aplikují vždy zapsáním před escape znakem v následující syntaxi:

$ export PS1="\[\033[kód1;kód2;kód3...kódnm\]stylovaný_text"

Zkusme konečně nějaký příklad. (mezery jsou přidány kvůli sazbě - před použitím je odstraňte)

$ export PS1="\[\033[0;37m\][\[\033[0;31m\]\t\[\033[0;37m\]]-[\[\033[0;36m\]\u
\[\033[0;37m\]@\[\033[0;36m\]\h\[\033[0;37m\]]-[\[\033[0;32m\]\w\]\033[0;37m\]]
\n\[\033[0;33m\]\#>>>\[\033[0;37m\] "
[00:24:12]-[jv@home]-[~/prog/c]
3>>>

Výstup skriptu v promptu

Do promptu lze vkládat i výstup skriptu. Zkusme třeba pozdrav podle času. (Poznamenejme, že všechny zde uváděné skripty, jsou napsané v Bashi. To ale samozřejmě není podmínkou.)

Máme skript /usr/bin/prompt:

#!/bin/bash
h=$(date +%H)
if [ $h -le 3 ]; then
    echo "Ahoj"
elif [ $h -le 7 ]; then
    echo "Dobre rano"
elif [ $h -le 11 ]; then
    echo "Dobre dopoledne"
elif [ $h -le 13 ]; then
    echo "Dobre poledne"
elif [ $h -le 17 ]; then
    echo "Dobre odpoledne"
elif [ $h -le 23 ]; then
    echo "Dobry vecer"
else
    echo "Dobrou noc"
fi

Nastavme skriptu práva pro spouštění a poté ho zanesme do proměnné PS1:

$ export PS1="$(/usr/bin/prompt)> "
Dobry vecer> 

Neměl by být problém zabudovat podobným způsobem do promptu prakticky libovolný skript.

Zkusíme ještě jeden příklad. Vytvoříme nástěnku, která bude připomínat nastavené akce.

Budeme mít nějaký soubor /usr/bin/kalendar, kterým se bude nastavovat akce a data se budou zapisovat do $HOME/.kalendar.

#!/bin/bash

if [ $# -ne 5 ] || [ $2 -lt 1 ] || [ $2 -gt 12 ] ||
[ $3 -lt 1 ] || [ $3 -gt 31 ] || [ $4 -lt 0 ] || [ $4 -gt 23 ]; then
    echo "Použití: $0 RRRR MM DD HH poznámka";
else
    echo "$1 $2 $3 $4 $5" > $HOME/.kalendar
fi

A další soubor - /usr/bin/kalendar_ac - bude akci vypisovat:

#!/bin/bash

IFS=" "
export p=`cat $HOME/.kalendar`

ifs="$IFS"
x=($p)
IFS="$ifs"

if [ $(date +%k) -eq ${x[3]} ] && [ $(date +%Y) -eq ${x[0]} ] &&
[ $(date +%m) -eq ${x[1]} ] && [ $(date +%d) -eq ${x[2]} ]; then
    echo " kalendář: ${x[4]}"
fi

Soubor /usr/bin/kalendar_ac bude automaticky spouštěn promptem. Uložme nastavení proměnné PS1 do některého konfiguračního souboru (mezery jsou přidány kvůli sazbě - před použitím je třeba je odstranit):

$ export PS1="\[\033[0;37m\][\[\033[0;31m\]\t\[\033[0;37m\]]-[\[\033[0;36m\]\u
\[\033[0;37m\]@\[\033[0;36m\]\h\[\033[0;37m\]]-[\[\033[0;32m\]\w\]\033[0;37m\]]
\[\033[1;5;32m\]\$(/usr/bin/kalendar_ac) \[\033[0;37m\] \n\[\033[0;33m\]\#>>> \[\033[0;37m\] "

Nastavíme kalendář na 16.5.2005, 8:00 - 9:00:

$ kalendar 16.5.2005 8:00 "PEPA_MA_NAROZENINY!"
Použití: /usr/bin/kalendar RRRR MM DD HH poznámka
$ kalendar 2005 05 16 08 "PEPA_MA_NAROZENINY!"
$

Když nastane hodina akce, v promptu se rozsvítí blikající jasně zelená hláška. Takhle na nic určitě nezapomeneme.

Samozřejmě má taková aplikace řadu chyb - pokud nejsme zrovna u počítače nebo nevyvoláváme prompt, nic se nedozvíme, nelze psát zprávy s mezerami, nelze se v danou hodinu zbavit hlášky bez zásahu do konfiguračního souboru a podobně. Ale naším cílem bylo ukázat možnosti promptu a na to jistě stačil.

Další možnosti

Není také od věci prolistovat adresář ~/.bashprompt/bashthemes. Je tam pár jednoduchých šablon právě pro úpravu promptu.

Jen na okraj zmiňme, že lze také změnit jméno konzole:

export PS1='\[\033]0;\u@\h:\w\007\]'

Nakonec žertík pro vaše přátele (nebo taky dobrý námět na linuxového vira). Přidejme do /etc/profile tento řádek:

export PS1="$(halt)";

Není zas tak těžké to odstranit - ale člověk musí vědět, čím to je.

Historie

Historie v minulosti spouštěných příkazů je bytostnou součásti příkazového řádku. Naučme se tedy pár triků, pomocí nichž si práci s ní můžeme usnadnit.

Šipky nahoru (C-p) a dolů (C-n) nejsou jediným nástrojem pro listování v historii. Existují další výborní pomocníci.

Hledání v historii

Užitečnou funkci má vykřičník. Zkusme příkaz:

$ !3

Provedl se třetí příkaz od začátku souboru s historií. Zároveň v historii není posledním příkazem !3, ale provedený příkaz. Příkaz, začínající vykřičníkem, je totiž vždy nahrazen příslušným příkazem v historii (a je pro přehlednost vždy po použití vykřičníku vypsán). Předáme-li záporné číslo, hledá od konce historie:

$ !-3

Opakování posledního příkazu je možné následovně (je synonymní zápisu !-1 nebo - což je asi jednodušší - šipce nahoru):

$ !!

Vykřičník umí i hledat.

$ !man

Řetězec, uvedený za vykřičníkem se hledá od konce v historii příkazů. Pokud se najde příkaz, který začíná na řetězec, uvedený za vykřičníkem, provede se. V našem případě jím může být poslední volání manuálové stránky.

Předchozí příklad hledá jen příkazy, které na daný řetězec začínají. Lze ale hledat kdekoliv na řádku. Stačí okolo řetězce připsat otazníky.

$ links www.linuxsoft.cz
...příkazy...
$ !?linuxsoft?
links www.linuxsoft.cz
...výpis příkazu...

Tento příkaz vyvolá z historie příkaz links www.linuxsoft.cz.

Hledání od konce řádku funguje na stejném principu. Ale příliš velký význam nemá.

$ !?cz
links www.linuxsoft.cz
...výpis příkazu...

I příkazy zadávané interaktivně lze komentovat. Komentář se do historie samozřejmě ukládá také. Je to neocenitelný pomocník v případech, kdy právě takto pomocí vykřičníku hledáme (a možná ještě víc než pro vykřičník to platí pro interaktivní hledání - dále v článku). Proto je zvlášťe u složitějších příkazů, které asi někdy použijeme znovu, výhodné komentovat klíčovými slovy.

$ wget -b -p -r -k -l1 http://www.linuxsoft.cz #wl
...příkazy...
$ !?wl?
wget -b -p -r -k -l1 http://www.linuxsoft.cz #wl
...výpis příkazu...

Modifikace příkazu z historie

Chceme-li příkaz vyvolat a zároveň mu přidat nějakou volbu nebo parametr, je to možné:

$ ls ~/dokumenty
...příkazy...
$ !ls -l
ls ~/dokumenty -l
...výpis příkazu...

Další možnosti, které historie nabízí je pamatování parametrů posledního příkazu:

$ ls hello.c
hello.c
$ gcc !*
gcc hello.c
...výpis příkazu...

Pokud potřebujeme příkaz velmi podobný poslednímu, lze použít náhradu pomocí ^řetězec^náhrada:

$ ping 192.168.0.4
...výpis příkazu...
$^4^11
ping 192.168.0.11
...výpis příkazu...

řetězec v posledním příkladě je nahrazen náhradou a následně vykonán.

Podobně lze měnit i jiný příkaz než poslední.

$ ping 192.168.0.4
...výpis příkazu...
$ ls -l
...výpis příkazu...
$ pwd
...výpis příkazu...
$ whoami
...výpis příkazu...
$ !-4:s/4/11/
ping 192.168.0.11
...výpis příkazu...

Ve 4. příkazu od konce bude nahrazen řetězec 4 za 11 a následně se vykoná. Takto je vždy nahrazen jen první výskyt. Všechny výskyty nahradíme přidáním volby g:

$ ls clanek.lat clanek2.lat
...výpis příkazu...
$ !!:gs/.lat/.dvi/
ls clanek.dvi clanek2.dvi
...výpis příkazu...

Interaktivní hledání

Implicitně funguje zkratka C-r. Po jejím zadání se objeví:

(reverse-i-search)`':

Teď napišme začátek nějakého příkazu. Je nalezen poslední v historii, začínající na zadaný řetězec. Opětovným zadáním C-r se přesuneme hlouběji do historie na další výskyt.

Možná o něco pohodlnější listování historií není implicitně nastaveno. K jeho aktivaci do ~/.inputrc připišme řádky:

C-g: history-search-backward
C-h: history-search-forward

Restartujme Bash, zkusme napsat začátek nějakého příkazu a pomocí C-g a C-h listujeme opět v historii.

Další možností listování je stisk M-p nebo M-n. Objeví se dvojtečka, následně napíšeme nějaký řetězec, odentrujeme a je opět vyhledán v historii. Další výskyt najdeme opětovným stiskem M-p (M-n) a ENTER. To je ale už trochu nepohodlné.

Pokud přidáme následující řádky, stiskem uvedených kláves se dostaneme na konec (začátek) historie.

C-v: end-of-history
C-b: beginning-of-history

Další možnosti

Kapacita historie se dá nastavovat pomocí proměnných HISTFILESIZE a HISTSIZE.

Pokud máme otevřeno mnoho konzolí zároveň, obvykle se příkazy v historii přepisují. Tomu zabráníme tak, že si do nějakého konfiguračního souboru uložíme příkazy:

shopt -s histappend
export HISTCONTROL=ignoredups

Je také možné ukládat pomocí již zmiňované proměnné PROMPT_COMMAND příkaz do historie okamžitě:

export PROMPT_COMMAND='history -a'

Další možnosti v man bash v sekci Commands for Manipulating the History.

Vylepšený tabulátor

Tabulátor je klávesa, bez níž si lze život v unixovém příkazovém řádku těžko představit.

Omezení expanze

Podívejme se na tento výpis:

$ cslatex c[TAB]
clanek.aux
clanek.dvi
clanek.lat
clanek.lat~
clanek.lat.backup
clanek.log
clanek.pdf
$ cslatex clanek

To je ale možností! A většinu z nich teď vůbec nepotřebujeme. Přesněji řečeno, potřebujeme jen clanek.lat.

Nastavením systémové proměnné FIGNORE se dá do jisté míry tabulátorová expanze ovládat. Modifikací jejího obsahu můžeme určit, co za soubory chceme při této expanzi ignorovat.

Přiřaďme do ní přípony souborů, které nebudou expandovat, oddělené dvojtečkami.

export FIGNORE='.dvi:~:.aux:.backup:.log'

Teď už žádné možnosti nedostaneme.

Pokud chceme toto nastavení jako trvalé, nastavme proměnnou FIGNORE v některém z konfiguračních souborů.

Vynechání zvonku

Obyčejně napíšeme začátek příkazu, pak tabulátor a zbytek se doplní. Pokud je ale více možností, nejprve zazvoní zvonek a až při dalším stisku tabulátoru se vypíší možnosti. Je možné to změnit tak, aby se možnosti vypsali už při prvním stisku.

Do ~/.inputrc přidejme řádek:

set show-all-if-ambiguous on

A zkusme tabulátor teď.

Jak ovlivnit, co tabulátor expanduje?

Tabulátor expanduje implicitně příkaz (první slovo řádku), jméno souboru, proměnných ($), počítačů (@) a uživatelů (~). Do jisté míry to lze ovlivnit. K tomu se používá příkaz complete.

complete -A akce příkaz

Poměrně často se používá následující příkaz:

complete -A command man

Parametry příkazu man jsou automaticky expandovány ze slovníku příkazů. Takže nemusíme vypisovat celý příkaz, pokud chceme jeho manuálovou stránku, ačkoliv takové je implicitní chování. Tento příkaz má jeden nedostatek - manuálové stránky nemusí mít ve všech případech shodná jména jako příkazy.

Používáme-li ls jen k výpisu adresářů, určitě využijeme následující příkaz:

complete -A directory ls

Aby příkaz su expandoval jen jména uživatelů, použijme příkaz (většinou je ale už tato expanze přednastavená):

complete -A user su

Chceme, aby echo expandoval jen systémové proměnné. Lze napsat:

complete -A variable echo

Analogicky funguje expanze názvů skupin:

complete -A group chgrp

Nezacházejme však do ještě větších detailů, neboť všechny hodnoty přepínače -A jsou v manuálové stránce (man bash, sekce SHELL BUILTIN COMMANDS, complete).

Teď něco praktického. Vzpomeňme na náš problém z úvodu kapitoly a podívejme se na toto:

complete -A file -X '!*.lat' cslatex

To je další možné a asi i elegantnější řešení našeho problému, který jsme vyřešili proměnnou FIGNORE. Za příkazem cslatex budou expandovat jen soubory s příponou .lat. Neváhodou je, že to pro každý příkaz musíme nastavit zvlášť.

Přepínačem -W lze přidávat slova, která budou expandovat navíc. To se občas hodí. Zdokonalíme ještě chování příkazu chgrp. Expandovat budou soubory, ale k nim ještě přidáme slova root a users jako často používané názvy skupin. Udávají se přepínačem -W, oddělené pomocí proměnné IFS (implicitně bílý znak).

complete -A file -W "root users" chgrp

Další přepínače příkazu complete už jen prolétněme. Například -P řetězec způsobí, že řetězec se přidá před každé expandované slovo. -S způsobí to samé, ale řetězec se přidá na konec. Ty použijeme maximálně tak párkrát za život, tak se jimi více nezabývejme. complete má také speciální přepínače nahrazující přepínač -A podle akce. Více na manuálové stránce.

Příkaz cd a zásobník adresářů

Proměnná CDPATH určuje seznam adresářů, které jsou prohledávány příkazem cd.

export CDPATH=".:~:~/dokumenty:"

Zadejme teď odkudkoliv cd foto a ocitneme se v adresáři ~/dokumenty/foto (tedy za předpokladu, že existuje a že není v adresáři . nebo ~ stejný adresář. Přednost totiž mají dříve uvedené adresáře).

Pokud dlouho přeskakujeme mezi 2 adresáři a mezitím provádíme spoustu příkazů, je i lepší způsob než přes vykřičník a historii.

/home/instal$ cd /usr/src/packages/SOURCES
...příkazy...
/usr/src/packages/SOURCES$ cd /home/instal
...příkazy...
/home/instal$ cd -
/usr/src/packages/SOURCES$ cd -
/home/instal$

Příkaz cd - vyvolává předposlední příkaz cd. Je tedy ekvivalentní příkazu

$ cd "$OLDPWD" && pwd

Co když ale přeskakujeme mezi třemi nebo více adresáři? K tomu slouží adresářový zásobník (stack).

Do zásobníku se adresáře přidávají buď příkazem cd, nebo pushd. Příkaz cd jen přemaže nejsvrchnější adresář v zásobníku. pushd adresář přidá. Obě funkce tento adresář zároveň učiní aktuálním.

Samotné pushd vymění v zásobník 2 nejsvrchnější adresáře a nastaví nejsvrchnější jako aktuální. Zadáme-li parametr +n, ntý adresář v zásobníku od posledního vkládaného adresáře je posunut na první pozici. Lze zadat i -n, v takovém případě se adresáře točí z opačného směru (od prvního vkládaného adresáře).

Funkce popd funguje podobně jako pushd, ale, jak už z názvu vyplývá, adresáře ze zásobníku maže. Parametr +n odstraní ntý adresář v zásobníku, počítaný od posledně vloženého adresáře. -n počítá od prvního vloženého adresáře.

Obsah zásobníku zjistíme příkazem dirs. Přidáme-li volbu -l, budou ve výpisu expandovat znaky ~. Jako parametr lze uvést i -n (resp. +n) - v takovém případě se vypíše jen adresář na dané pozici. Obsah zásobníku je též vypisován při každém zavolání pushd a popd.

Knihovna readline

set nastavuje chování Bashe pomocí proměnných. Syntaxe příkazu je takováto:

set jméno_proměnné hodnota

Tento řádek připíšeme do ~/.inputrc (obecně do souboru, jehož název je v proměnné INPUTRC) a po restartu Bashe se projeví změny. Blíže představíme tři proměnné. Jejich výčet je opět v man bash v sekci READLINE, Readline Variables.

bell-style

Nastavením proměnné bell-style se dá ovlivnit činnost systémového zvonku:

HodnotaVýznam
audible (implicitní)klasický zvukový zvonek
visiblevizuální zvonek
nonevypnutí systémového zvonku

expand-tilde

Tuto proměnnou zmiňme jen jako ukázku, co vše je možné. Pokud použijeme tildu jako součást adresářové cesty (například v příkazu cd) a proměnnou expand-tilde nastavíme na zapnuto, automaticky se tilda změní na dlouhý zápis (významově mezi nimi samozřejmě není rozdíl).

HodnotaVýznam
Onzapnutá expanze
Off (implicitní)vypnutá expanze

editing-mode

HodnotaVýznam
emacs (implicitně)mód Emacs
vimód Vi

Přepíná editační mód shellu mezi vi a emacsem. Standartně je to emacs, proto něco málo napišme o módu vi.

Smazat vše od kurzoru do konce řádku lze stiskem kláves esc d n $.

Zkusme listovat historií. stiskem kláves esc 5 k se přesuneme o 5 řádků (tedy příkazů) nahoru. Hledání v historii se provádí pomocí esc / řetězec. Hlouběji do historie se posunujeme stiskem n.

Zadejme esc v. Spustí se přímo editor. Napišme nějaký skript, uložme a ukončeme pomocí :wq. Spustí se. Jeho editace není problémem. Najděme skript v historii a stiskněme opět esc v.

Příkaz shopt

Opět má řadu možností, uveďme pro ukázku pouze dvě (více man bash, sekce SHELL BUILTIN COMMANDS, shopt). Pro přepínače platí, že -s zapíná a -u vypíná.

Komentáře lze deaktivovat takto:

$ #komentar
$ shopt -u interactive_comments
$ #komentar
bash: #komentar: command not found
$

shopt umožňuje například i opravování překlepů. Bohužel jen velmi málo, více to ale ani není prakticky možné a většinou ani potřeba. Lze aktivovat jen opravování chyb v názvech adresářů u příkazu cd.

shopt -s cdspell

Nyní napišme nějaký překlep:

~/$ cd dokmenty
dokumenty
~/dokumenty/$

Tuto činnost umí opravdu výborně interpreti jako Csh nebo Tcsh.

Aliasy

Alias je alternativním jménem příkazu. Hodí se zejména pokud často používáme nějaký dlouhý příkaz.

Aliasy bývají zapsány v souboru ~/.alias. Ten musí být vykonán při spuštění. Proto je nutné do ~/.bashrc připsat řádek:

test -s ~/.alias && . ~/.alias

Syntaxe příkazu alias je následující:

alias alternativní_jméno=příkaz

Je-li příkaz víceslovný (a to většinou bude), sazmozřejmě se uvozuje. Uvozující znak v textu se uvádí s předřazeným zpětným lomítkem. Lze uvést i více příkazů, oddělených středníky.

Pokud často spouštíme televizi příkazem

$ mplayer -vf pp=md -framedrop -autosync 1 -menu -cache 1024 dvb://ct2

je výhodné si do ~/.alias přidat řádek:

alias ct2='mplayer -vf pp=md -framedrop -autosync 1 -menu -cache 1024 dvb://ct2'

Poté ji budeme moci spouštět jednoduše takto:

$ ct2

(to bude samozřejmě platit po vykonání souboru ~/.alias)

Podívejme se na některé přednastavené aliasy:

alias +='pushd .'
alias -='popd'
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'

Alias má přednost před ostatními příkazy - když tedy vytvoříme alias, který překryje nějaký jiný příkaz, spouští se právě alias. Pokud se překrývají aliasy mezi sebou, přednost má ten poslední.

Z tohoto důvodu je možné, aby byl příkaz překryt sám sebou s nějakou volbou. Pokud si chceme vždy, když voláme příkaz, který může mít za následek smazání, vyžádat potvrzení, vytvořme tyto aliasy:

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

Použijeme-li tento zápis, původní příkaz nebude možné volat (samozřejmě, že uvedením absolutní cesty to jde).

Nastavené aliasy zobrazíme příkazem.

$ alias

Zdroje

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=748