Občas se stává, že ve skriptech je nutné provést nějakou matematickou operaci. A kromě toho bash sám a další std. příkazy dokáží fungovat jako rychlá programátorská kalkulačka a převodník číselných soustav.
Bash pracuje pouze s celými čísly, jejichž datový typ je signed long, u mě na 64bitové Fedoře, je to pak od –263 do 263–1. Čísla jsou uložená v doplňkovém kódu, takže v hexa do vypadá od 0x8000000000000000 do 0x7fffffffffffffff, takže pozor na přetečení.
K vyvolání matematické operace, pak můžeme použít:
V prvním případě pak je výraz vyhodnocen a interpretován jako příkaz, parametr. V druhém je výraz vyhodnocen a návratová hodnota $? je nastavena na false (1) pokud je výsledná hodnota nulová a nebo true (0), pokud je nenulová.
Příklady:
# echo $[1+1] 2 # $[2+2] bash: 4: command not found... # ((0*1024)) ; echo $? 1 # ((2+2)) ; echo $? 0 # echo $((355/113)) $[ 355 % 133 ] 3 89 # i=10; ((i+=20, i=i-31)); echo $i -1
Osobně rád používám zápis v hranatých závorkách oproti dvojitým kulatým, protože je to kratší, a i když se tenhle zápis v dokumentaci neuvádí zatím se mi nestalo, že by v některé z běžných verzí bashe nefungoval.
K proměnným v matematickém režimu můžeme přistupovat bez uvozujícího dolaru, takže zápis $((x)) a $(($x)) vrátí stejnou hodnotu pokud $x obsahuje platnou hodnotu.
Mat. operátory pocházejí z jazyka C a i jejich priorita je stejná. Výjimkou je operátor pro mocnění **, který pochází z Fortranu, ale našel si cestu i do dalších jazyků. V následující tabulce mají výše uvedené operátory vyšší prioritu.
id++ id-- | postinkrementace, postdekrementace |
++id --id | preinkrementace, predekrementace |
- + | unární mínus a plus |
! ~ | logická a bitová negace |
** | umocňování, a**b=ab |
* / % | násobení, dělení, zbytek po dělení |
+ - | sčítání, odčítání |
<< >> | levý a pravý bitový posun |
<= >= < > | porovnání |
== != | rovná se a nerovná se |
& | bitové AND |
^ | bitové XOR |
| | bitové OR |
&& | logické AND |
|| | logické OR |
expr?expr:expr | ternární operátor |
= *= /= %= += -= <<= >>= &= ^= |= | přiřazení |
expr1 , expr2 | čárka |
A teď několik příkladů:
# echo $((1<<4 + 1<<5))
1024
# a=1; b=2; echo $[++a+++b]
5
# let "i = 2 * 3 ** 5"; echo $i
486
Přestože bitové posuny jsou často využity jako násobení a dělení mocninou 2 je jejich pririta nižší než u sčítání, pokud se to ještě zapíše stejně záludně jako v prvním případě, výsledek 48 je špatně. Výrazy v příkazu let je nutné psát do uvozovek, bez nich by se znak * rozvinul na seznam souborů v pracovním adresáři a dochází i k dalším chybám.
Bash používá normálně desítkovou soustavu, ale v mat. režimu lze zapisovat běžným způsobem i osmičkově a šestnáctkově. Osmičková soustava má prefixovou nulu a šestnáctková 0x. U ostatních číselných soustav je nutné uvést základ soustavy před znak #. Lze tak zapsat číslo až o základu 32.
# echo $[0xffff] 65535 # echo $[2#101010101010] 2730 # echo $echo $[018] bash: 018: value too great for base (error token is "018") # echo $[0777] 511
Díky výše uvedenému zápisu tedy snadno převedeme libovolné číslo na desítkové, opačně je to lehce namáhavější, musíme k tomu použít externí utilitky. První z nich je printf známá především ze std. knihovny jazyka C a její formátovací sekvence pro osmičkovou %o a šestnáctkovou soustavu %x, při zápise %#o resp. %#x printf vypíše i prefixy těchto čísel.
# printf "%#x\n" 65535 0xffff # printf "%o\n" $[2#101111111] 577
Pokud potřebujeme libovolný převod o základech 2 až 16 musíme použít bc, pozor ten vyžaduje zápis cifer velkými písmeny!
# echo "obase=2; 255" | bc 11111111 # echo "ibase=16; FFFF" | bc 65535
Pro převod do dvojkové soustavy perlisti používají následující one-liner:
# perl -e 'printf "%#b\n", 255' 0b11111111
Malé řecké písmenko alfa (α) je v UTF-8 reprezentováno jako 0xCE 0xB1 a v UTF-16 jako 0x03B1, s těmito hodnotami si v shellu poradíme následovně:
Pro opačný převod znak na UTF-8 použijte hexdump:
# echo -n "α" | hexdump -C 00000000 ce b1 |..| 00000002
Volba -n pro echo potlačí výpis konce řádku, aby se nám jeho hodnota 0x0A zbytečně nepletla na výstup.
Bash nabízí proměnnou $RANDOM, které generuje celá čísla v intervalu 0 až 32767, pokud do RANDOM uložím hodnotu, bere se jako počáteční semínko. Nevzpomínám si, že bych RANDOM použil v seriózním skriptu, ovšem ptákovin se s ním dá užít spousty:
P=(' ' █ ░ ▒ ▓);while :;do printf "\e[$[RANDOM%LINES+1];$[RANDOM%COLUMNS+1]f${P[$RANDOM%5]}";done
Pro generování čísel v menším rozsahu se pak používá zbytek po dělení. Takže čísla 0 až 99 získám pomocí zbytku po dělení 100: $((RANDOM % 100)), atd.
Při zkoumání různých hexdumpů paměti nebo binárních souborů, kde jsou uložena čísla v plovoucí řadové čárce se hodí převádět mezi jejich IEEE 754 reprezentací a desítkovým číslem.
IEEE 754-1985 používá tři přesnosti: single (32bit.), double (64bit.) a extended (80bit.), v C je to float, double a long double.
# gdb --batch -ex "print/x (float *) ( (float) 1234.567 )" $1 = 0x449a5224 # gdb --batch -ex "print/f (float *) 0x449a5224" $1 = 1234.56689
Nepodařilo se mi přijít jak to udělat v gdb i pro ostatní typy, takže pro ostatní formáty už si člověk musí poradit s jinými nástroji, např. v pythonu to jde následovně:
# python -c 'import struct; print "%#x" % struct.unpack("I", struct.pack("f", 1234.567))' 0x449a5225 # python -c 'import struct; print "%#x" % struct.unpack("Q", struct.pack("d", 1234.567))' 0x40934a449ba5e354 # python -c 'import struct; print "%f" % struct.unpack("d", struct.pack("Q", 0x4094b2449ba5e354))' 1324.567000 # python -c 'import struct; print "%f" % struct.unpack("f", struct.pack("I", 0x449a5225 ))' 1234.566895
Pro hrátky s binárními daty a snadné přetypování se v pythonu (a podobně i v perlu), používají struktury, do nich se zabalí data v daném formátu: f float, d double, Q unsigned long int (8 bytů), I unsigned int (4 byty). Pro přetypování je pak rozbalím a vypíšu na obrazovku v daném formátu.
Všimněte si známé záludnosti formátů s plovoucí řadovou čárko a to mírně rozdílné přesnosti v různě řešených implementacích.
Kalkulátor s libovolnou přesností bc slouží k výpočtům s desetinou čárkou, resp. tečkou. Na některých systémech je to jen nadstavba pro převod syntaxe do reverzní polské notaca a výpočty řeší dc, ale v Linuxu to tak nemusí nutně být. Samotné bc je i interpretem jazyka podobného C, takže se v něm toho dá udělat spousta. Počet cifer za desetinou tečkou ovlivňuje parametr scale, je potřeba ho zadat hlavně u dělení. Volba bc -l zpřístupní knihovnu matematických funkcí s(x) sin(x), c(x) cos(x), a(x)arctg(x), kde x se zadává v radiánech; l(x) přirozený logaritmus ln(x), e(x) exp(x), j(n,x) Besselova funkce řádun.
# echo "scale=10; 355 / 113" | bc 3.1415929203 # echo "scale=10; (1+1/10000)^10000" | bc 2.7181459268 # echo "3.5 * 785.498" | bc 2749.243 #bc <<< "2340 * 1.22"
2854.80 #bc -l <<< "scale=10; 4*a(1)"
3.1415926532