Published: 30. 6. 2014   Category: GNU/Linux

Příkazový řádek v bashi jako programátorská kalkulačka

Úvod

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í.

Aritmetické vyhodnocení

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.

Matematické operátory a jejich priority

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.

Zápis čísel

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

Co když potřebuji převést ASCII hodnotu na znak a obráceně?

Převody Unicode

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.

Náhodná čísla

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.

Čísla v pohyblivé čárce ve formátu IEEE 754

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.

Výpočty v plovoucí řadové čárce v bc

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