Tämä viha goto käsky juontuu basic kieleen ja sen puutteisiin. goto käskyä käyttäen voi tulla tapauksia jossa goto käsky joutuu väärään paikkaan esim versionhallinan takia. Esim parin kolmen vuoden taakaa muistan Applen koodeissa ssl bugin johtuneen juurikin silloin kun käytettiin goto käskyä eikä esim aaltosulkeita.
Voihan olla että esim kernelin syvimmissä c kielen rakenteissa on hyödyllistä joskus käyttää goto käskyä. Tai joissakin bash scripteissä. Mutta esim c++ koodeissa, goto käskyä ei tulisi käyttää.
Enpä tiennytkään ja kiva että kerroit. Sillä aina uudet tiedot oikaisee käsityksiä.
**
Edellinen oli tarinaa muuttujista. Puhutaanpa muuttujista toisesta näkökulmasta:
Bash tuntee vain tekstijonot. Kuitenkin tulkille voi antaa ohjeita kuinka sen pitää tekstijono tulkita silloin kun se pitäisi tunnistaa numeroksi. Bash osaa tulkita tekstijonon ainoastaan etumerkillisenä kokonaislukuna alueelta -9223372036854775808-9223372036854775807; mikäli arvo on näiden alueiden ulkopuolella niin tulkinta antaa väärän tuloksen eikä asiasta edes urputeta. Mikäli luvussa on desimaalipiste niin välittämättä declare:sta muiodostetaan tekstijono - vasta kun tekstijonoa yritetään käyttää bash:in omissa matemaattisissa laskuissa se aiheuttaa virheen samoinkuin mikätahansa muukin aakkonen. Sanotaan ettei bashin muuttujilla ole tyyppiä - no sanotaan niin, mutta jokatapauksessa bash:in voi määrätä seuraaviin tulkintoihin kirjoittamalla skriptin alkuun:
declare a ; määrittelee että a on etumerkillinen kokonaisluku väliltä: -9223372036854775808-9223372036854775807
declare -i a ; määrittelee että a on kokonaisluku samalta lukualueelta: näennäisesti ihan sama.
- kuitenkin:"declare -i a; a=1+2; echo $a" tulostaa 3. Kuntaas: "declare a; a=1+2; echo $a" tulostaa 1+2
declare -r a ; määrittelee että muuttujan a voi ainoastaan lukea. Siis käytännössä määrittely on: "declare -r a=1" ja jos myöhemmin määritelee arvon uudestaan aiheuttaa se virheen.
declare -a muuttuja ; luo muuttujan joka tulkitaan matriisiksi
declare -A muuttuja ; luo muuttujan joka tulkitaan assosiatiiviseksi matriisiksi. Nimenomaan pitää huomioida että tulkitaan - itse matriisi on aivan samanlainen.
declare -f muuttuja ; luo muuttujan joka tulkitaan funktioksi
declare -x muuttuja ; luo muuttujan joka siirtyy ilmanmuuta myös "lapsi-prosessien" käyttöön.
declare a=373 ; muuttujalle voidan antaa arvo samalla kun se määritellään
declare -ira a ; samallakertaa voidaan antaa useampiakin määreitä.
- mitään määrittelyjä ei tarvitse tehdä, sillä bash on kova olettamaan.
- jos määritetään a=0.001 niin a ei ole numero vaan tekstijono.
- funktioissa voi määritellä että muuttuja tunnetaan vain funktion sisällä, määrittely on: local muuttuja
- kaikki bashin muuttujat ovat globaalialueella elikä ne tunnetaan kaikkialla. Tämä saa naurettavaan valoon sen huomautuksen jonka useimmat esittävät: bash:in funktiot eivät osaa palauttaa arvoja. Eihän niiden tarvitsekaan palauttaa sillä arvothan ovat muuttuneet muutenkin - puute se on sittenkin mutta sen kanssa voi elää. Globaalialueella toimiminen ei aiheuta ristiriita-tilanteita massiivisissakaan skripteissä mikäli skriptaaja osaa nimetä muuttujat oikein.function summaa { echo summa=$( eval echo \$\{$1\[\*\]\} | tr ' ' + | bc -l) ;}; mat=({1..100000}); time summaa mat
- bash-skripteissä voi laskea myös desimaaliluvuilla, mutta itse bash ei laskemiseen sovellu vaan laskut on tarkoitus suorittaa funktiossa nimeltään bc; esimerkiksi: echo 0.001+0.002 | bc tai toisella tavalla: bc <<< 0.001+0.002
- ei merkitse mitää ovatko bc:n syöttöluvut numeroita tai tekstijonoja kunhan niissä on vain numeroita, mahdollisesti molemmilla luvuilla etumerkit ja mahdollisesti yksi desimaalipiste.
- muuten bc:ssä numeroiden määrää ei mitenkään rajoiteta vaan muuttujassa saa kokonaisosassa olla numeroita vaikka ziljoona, sitten desimaalipiste ja taas ziljoona numeroa desimaaliosassa. Samoin käytettävissä on funktiokirjasto, joten bash:issa matematiikka on helppoa. Ei niin että bc olisi hyvä matematiikkaohjelma, mutta helppo se on noissa yksinkertaisissa laskuissa.
- sekä tavallisen että assosiatiivisen matriisin sisäinen esitys on samanlainen; kyse on siis kuinka matriisit tulkitaan:
- tavallinen matriisi:
määrittely: declare -a mat1; mat1[1]=1; mat1[2]=2; mat1[3]=3 koneen muistissa on: declare -a mat1=([1]="1" [2]="2" [3]="3" )
- assosiatiivinen matriisi:
määrittely: declare -A mat2; mat2[1]=1; mat2[2]=2; mat2[3]=3 koneen muistissa on: declare -A mat2=([1]="1" [2]="2" [3]="3" )
- siis koneen muistissa kumpikin määrittely on melkein samanlainen
- assosiatiivisen matriisin osoitteet voivat kyllä olla tekstiäkin ja silloinhan tavallinen matriisi ei edes toimisi, mutta esitystapa olisi silloinkin melkein samanlainen.
- hexa-luvuille ei ole declarea. Hexa-alueen laskutoimitukset esimerkiksi : echo $((0xF + 0xF)) joka tulostaa 30 - ilmeisesti kääntää esin hexat desimaalialueelle, suorittaa laskutoimitukset ja tulostaa aina desimaalisena.
- oktaaliluville ei ole declarea. Oktaalialueen laskutoimitukset esimerkiksi: echo $((8#100)) joka tulostaa 64 - ilmeisesti kääntää esin hexat desimaalialueelle, suorittaa laskutoimitukset ja tulostaa aina desimaalisena.
- bc voi suorittaa laskut mielivaltaisessa lukujärjestelmässä ja tulostaa myös mielivaltaisessa lukujärjestelmässä: echo "ibase=8; obase=2;11" | bc
**
Koska bash on niin tekstijono-suuntautunut ovat siinä useimmat datan-käsittely-käskytkin tarkoitettu tekstijonoille - mutta toisaalta numeroitakin voi käsitellä tekstijonoina ja joskus siinä on järkeäkin:
muuttuja=apua; echo ${muuttuja^} # muuta ensimmäinen suuri kirjain suureksi
muuttuja=apua; echo ${muuttuja^^} # muuta kaikki kirjaimet suuriksi
muuttuja=APUA; echo ${muuttuja,} # muuta ensimmäinen kirjain pieneksi
muuttuja=APUA; echo ${muuttuja,,} # muuta kaikki kirjaimet pieneksi
muuttuja=APUA; echo ${muuttuja~} # muuta ensimmäinen kirjan toisentyyppiseksi kuin se oli
muuttuja=APUA; echo ${muuttuja~~} # muuta kaikki kirjaimet toisentyyppiseksi kuin ne olivat
muuttuja=appuva; echo ${muuttuja/p/k} # muuta ensimmäinen p-kirjan k-kirjaimeksi - toimii kirjainryhmillekin ja mikäli on määrätty ryhmä niin yksi ei kelpaa - ryhmät voivat olla erisuuria eli tämä on super-tr
muuttuja=appuva; echo ${muuttuja//p/k} # muuta kaikki p-kirjaimet k-kirjaimiksi - toimii kirjainryhmillekin ja mikäli on määrätty ryhmä niin yksi ei kelpaa - ryhmät voivat olla erisuuria eli tämä on super-tr
- ja vastaavia loppumattomiin, väsyin jo katsellessani
**
Bash:in matematiikka toimii vain kokonaisluvuille ja on muutenkin niin vikaherkkää että sitä kannattaa käyttää hyvin harkiten. Salahaudat poislukien: on se ainakin nopeaa - bash:iksi tarkoitan:
echo $((1+1)) tulostaa 2 elikä summan. - * ja / toimivat myös.
echo $((5%2)) tulostaa 1 eli jakojäännöksen
echo $((5**2)) tulostaa 25 elikä potenssiin korotuksen
echo $((5^2)) tulostaa 7 elikä binääri-lukujen 101 ja 10 bitwise-XOR:in; siis kyseessä ei ole potenssiin korotus
echo $((2|4)) tulostaa 6 elikä binääri-lukujen 10 ja 100 bitwise-OR:in. Muutkin loogiset operaattorit tunnetaan.
a=1; echo $((~a)) tulostaa 2. Negaatiostahan tässä kyse on mutta tulokseen lisätään jostasin syystä aina 1. option-base hommeli vaikka bash:issa ei sitä ole?
a=1; echo $((a<<=2)) tulostaa 4 elikä a:n binääri-esityksen siirrto 2 paikkaa vasemmalle ja tulos muutettu desimaaliluvuksi
- voi laskutoimituksia laittaa samaan useampiakin: echo $((5**2-8/2)) tulostaa 21
- sulutkin otetaan huomioon: echo $(((2+2)*3)) tulostaa 12 kuntaas: echo $((2+(2*3))) tulostaa 8
- muuten nopein tapa laskea kertoma on: echo $(($(seq -s* 20))) # kylläkin vain luvuista alle 20
Bash toimii kuitenkin loistavasti desimaaliluvuilla rajoittamattoman monen desimaalin laskutarkkuuden omaavan ohjelman bc avulla jolla on kytkimellä -l käytettävissään funktiokirjasto. Esimerkiksi:
echo 0.001+0.002 | bc tai toisin: bc <<< 0.001+0.002 # normaalit laskutoimitukset; samat kuin bash:issakin
echo "for (i = 4.00; i < 5.00; i += 0.01) i" | bc # desimaalinen looppi
echo 1.0000002 '>' 1.0000001 | bc # desimaaliluvut vertaillaan bc:ssä. Tai luvut aina viisainta verrata bc:llä sillä bc toimii yhtähyvin myös kokonaisluvuille.
echo $(echo "s(4*a(1)*90/180)" | bc -l) # 90-asteen sini. Siksi kaava on niin inhoittavan näköinen kun bc toimii radiaaneissa ja ihmiset haluaa toimia asteissa.
**
Koska loopit ovat bash:in pahiksia niin aloin etsiä niille korvaavaa toimintaa: tässä tapauksessa oli kyse pino-rakenteen tutkiminen: inhoitti ihan esittää lopputulos jossa pinon hoitamiseen kului 20ms ja looppeihin 700ms.
Tässä esitetty kertoo jotenkin kuinka "loopittomia looppeja" tehdään. Menetelmä saattaa johtaa johonkin: vaikka bash on jo melkein kuollut eikä tämmöiset enää auta mitään, niin sikäli asia on mielenkiintoinen että muut tulkkaavat kielet taitaa jäädä jalkoihin.
- "looppi" on nopea: 11 mikrosekuntia per kierros ja jokaisella kierroksella suoritetaan tässä yksi funktiokutsu ja matriisi-operaatio. Liian hyvää ollakseen totta?
function lisää_perään () { mat+=("$@") ;}; unset mat; $(seq -s' 'lisää_perään' ' 10000 | sed 's/^[0-9]*//'); echo ${mat[*]//lisää_perään/}
**
Nämä loopittomat ratkaisut ovat todella nopeita, mutta meidät kaikki on opetettu tekemään bash-skriptit siten että ne mählivät kaikkialla. Ja kun on opetettu väärin niin vain harvoin pystyy heittämään yhden väärin opitun pois, ja ennenkuin skriptit toimivat nopeasti ne täytyy kaikki karsia pois.
Tämä loopiton skripti laskee summan matriisista jonka nimi mainitaan. Kun matriisin nimen vaihtaa niin mitään muuta ei tarvitse muuttaa.
- vaikka puhutaan summasta niin monet muutkin matematiikka-operaatiot ovat mahdollisia. Muodostettavan matriisin "koolla ei ole ylärajaa", se voi olla vaikka satatuhat jäseninen kuten ({1..100000}). Matriisi muodostetaan ainoastaan testausta varten - matriisi voidaan yhtähyvin määritellä myös: mat=(6*6 -1*6^2 .00000000000000000000000000000000000000000001)
siis desimaalien luvulla ei ole mitään rajaa ja jäsenet voivat olla ratkaisemattomassa muodossa - myös sinit sun logaritmit tunnetaan mutta tutustu bc-hen ennen kuin käytät niitä.
function summaa { echo summa=$( eval echo \$\{$1\[\*\]\} | tr ' ' + | bc -l) ;}; mat=({1..100000}); time summaa mat
- time:n antama suoritusaika on noin .14 sekuntia ja se vaihtelee senverran kun skripteillä yleensäkin. Mutta ajettaessa skripti päätteessä ensimmäisen kerran kestää noin 1.8 sekuntia sellaista aikaa jolloin ei tunnu tapahtuvan mitään. Aika kuluu mat-matriisin täyttämiseen. Kun matriisi on kerran täytetty sen uudelleenmuodostaminen melkein-samanlaisena on huomattavasti nopeampaa joten ajettaessa skripti myöhemmin samassa päätteessä tuo 1.8 sekuntia vähenee .2 sekuntiin. Mikäli skriptiä ajetaan kymmeniä kertoja alkaa matriisin uudelleenmuodostusaika taas kasvaa.
- "skriptin" voi kopioida foorumilta Ubuntun päätteeseen ja painaa enter.
Inhottavamman näköinen versio poistaa turhat etu-plussat, muuttaa pi:n arvoksi 3.14... ja muuttaa e:n arvoksi 2.71... silloin kun on kyse luonnollisen lukujärjestelmän kantaluvusta ja muutoin eksponenttimuotoon:
function summaa { echo summa=$( eval echo \$\{$1\[\*\]\} | sed 's/^+//;s/ /+/g;s/pi/4*a(1)/g;s/\be\b/e(1)/g;s/\Be\B/*10^/g' | bc -l) ;}; mat=(e+2*pi 1e3); time summaa mat
**
Siitä tämä BASH on mukava, että kukaan ei voi koskaan tehdä sitä parasta versiota - en minä etkä sinä. Toisin ilmaistuna: ainoastaan kun pysyy hiljaa ei puhu potaskaa. Siis ei ole mitään paineita: teet mitä vaan niin pieleen menee.
Taas uusi versio loopittomasta bash-skriptistä matriisin jäsenen arvoa vastaavan osoitteen etsimiseksi. Skripti on montakertaa nopeampi kuin aikaisemmat ja se toimii aina jokseenkin yhtänopeasti.
- matriisin nimi voi olla mikävaan ja samassa skriptissä voidaan kutsua hakua eri paikoista - ja kokonaisluvuilla, desimaaliluvuilla tai sanoilla muodostetuista matriiseista - sanoissa saa olla välilyöntejäkin.
BASH:issa matriisin jäsenten osoitteet voivat pomppia sinnetänne jättäen osoitteita väliinkin. Ja matriiseissa yleensäkin samaa arvoa voi löytyä monestakin eri osoitteesta. Lisäksi bash:issa matriisin ensimmäinen numero-osoite voi olla mikähyvänsä positiivinen kokonaisluku - ja assosiatiivisessa matriisissa osoite voi olla tekstiäkin. Näiden ominaisuuksien vuoksi eivät "normaalit" skriptit toimi hyvin etsittäessä jäsenen arvoa vastaavaa osoitetta.
#!/bin/bash
function arvoa_vastaava_osoite () { osoitelista=($(declare -p | grep dec.*$1= | grep -Po \[[[:alnum:]]*\]=\""$2"\" | grep -Po '[[:alnum:]]*(?=])')); }
unset mat # unset tuhoaa matriisin, mutta myös tiedon siitä oliko se tavallinen vaiko assosiatiivinen. Tämä siis hyväksyy myös assosiatiiviset matriisit.
unset osoitelista
mat=($(seq 10000000 | awk '{print rand()}')) # luodaan kymmenmiljoonainen matriisi noin kuusinumeroisista satunnaisluvuista.
arvoa_vastaava_osoite mat 0.500489
echo etsittävä: .500489' löytyi seuraavista osoitteista:'
echo -e ${osoitelista[*]/#/\\n} | sed 1d
echo; echo ja varmistukseksi:
for (( n=0; n<${#osoitelista[@]}; n++ )); do echo osoitteessa:${osoitelista[@]:$n:1}' on arvo:'${mat[${osoitelista[@]:$n:1}]}; done
- tuon kymmenmiljoonaisen matriisin luominen satunnaisluvuista kestää noin 12 sekuntia ja etsiminen sieltä 3.5 sekuntia elikä 0.35mikrosekuntia osoitteelta . Osoitteitahan tulee monta sillä laskettaessa 6-numeroinen satunnaislukuluku 10 miljoonakertaa saadaan sama luku yleensä 6-14 kertaa.
- kyllä tämä toimii erinomaisesti pienienkin matriisien kanssa. Silloikin kun arvot ovat tekstijonoja ja assosiatiivisissa osoitteetkin saavat olla tekstijonoja.
**
Bash:in matriisit ovat tosiaan niin kummalliset että ainoa keino saada varmasti oikea käsitys matriisin kokoonpanosta on katsoa millaisen määrittelyn bash itse on siitä muistiin muodostanut - lisäksi se on ehdottomasti nopein keino. Esimerkiksi matriisien yhtäsuuruuden tarkistamiseksi on lukemattomia toinen toistaa etevämpiä keinoja mutta kaikki ne kompastuvat johonkin.
Mutta tämä keino ei kompastu mihinkään ja se sopii yhtähyvin yksinkertaisiin matriiseihin kuin hyper-monimutkaisiinkin, tavallisiin ja assosiatiivisiin. Ja se on bash:iksi salamannopea:
function esitysmuistissa () { echo "$(declare -p | grep -Po "(?<=declare -[aA] $1=).*")" ;}
mat1=({1..100}); mat2=({1..100}); [[ "$(esitysmuistissa mat1)" = "$(esitysmuistissa mat2)" ]] && echo matriisit ovat samanlaiset || echo matriisit ovat erilaiset