Näytä kirjoitukset

Tässä osiossa voit tarkastella kaikkia tämän jäsenen viestejä. Huomaa, että näet viestit vain niiltä alueilta, joihin sinulla on pääsy.


Viestit - petteriIII

Sivuja: 1 2 [3] 4 5 ... 34
41
Yritän jatkuvasti saada jotain järkeä siihen miksi virtuoosit ovat tehneet BASH:ista surkimuksen - selitän tätä siis itsellenikin: on pakko miettiä asiat paljon paremmin kun asiat kirjoittaa.

BASH:in ylipapit alkoivat jo yli kolme vuosikymmentä sitten toivoa että BASH hiljokseen häviää - syytä haluun en tiedä, mutta tosiasia on että BASH on niin monipuolinen ettei yksikään professori kykene hallitsemaan siitä isompaa osaakaan täydellisesti vaan oppilaat hyppivät silmille kokoajan - onhan se käytännössä pätevä syy yrittää päästä siitä eroon.

Mutta BASH oli siihenaikaan niin voimissaan ettei sen tuhoamisesta olisi tullut mitään - mutta mistähyvänsä pääsee eroon kun vaan antaa sen päättäjille riittävän löysän talutushihan niin he varmasti hirttävät siihen itsensä - ja tässätapauksessa siinä sivussa meinisi myös BASH. Hullulta tuntuva konsti toimii aina vaikkakin se on kamalan hidas - kaikkialla muuallakin käytetään tuota samaa keinoa  - sillä ei ole mitään sellaista joka ei hyödyttäisi jotakuta ja lähettäisi toiset manalaan.

Aluksi virtuoosit alkoivat sanoa että BASH on surkean hidas kaikssa muussa kuin tietokoneen hoitamisessa - mikä on täysin totta. Sitten pienen säätelyn jälkeen aletiin sanoa että useissa asioissa BASH on myös liian omituinen - sekin ihan totta. Virtoosit puhuvatkin aina varmasti totta joten esimerkiksi voi luottaa siihen että 'advanced guidet' puhuvat BASH:in alkeista ihan asiaa - pidemmäälle nuo oppat eivät edes yritä mennä huolimatta nimestään. Mutta ne esimerkiksi vihjailevat ettei BASH osaa käyttää nimiparamereja funktioissa - mikä on sekin yksi ohjelmointikielen menestymisen ehdoista. Ehkä ne itsekin uskovat väitteiseensä.

Tavallaan väite pitää kyllä paikkansa koska nimiparametrit eivät toimi BASH:issa samallatavoin kuin muissa kielissä - sillä kun BASH:issa passaat funktioon muuttujan käyttäen sen nimeä niin funktiossa se on tosiaan pelkkä nimi - kunnes se sidotaan saman-nimisen muuttujan arvoihin ja tyyppimääreisiin - tyyppimääreitä ovat sen arvot ja onko se tavallinen muuttuja vaiko tavallinen- tai assosiatiivinen matriisi - ja lisäksi niiden tarkenteet. Sitominen tapahtuu seuraavalla tavalla:

BASH pitää kirjaa muuttujistaan ja niiden senhetkisistä arvoista niinkuin muutkin kielet - ja BASH antaa kirjanpitonsa skriptaajan käyttöön käskyllä: declare. Kun kirjanpidosta löytyy sama muuttujanimi niin siinähän se muuttuja on argumentteineen ja kun määritelmät kopioi sieltä niin muuttuja on täysin kunnollinen - mutta jos jättää tämän sitomisen tekemättä niin nimiparametrit eivät toimi. Sitomiseen on muuttujakuvausten käyttämisen lisäksi pari käskyäkin: let ja read - jos oletetaan ettei käskyä: eval käytetä. Voiko olla sattumaa että kolme käskyä ja yksi menetelmä toimivat oikein asiassa johon niitä ei ole tarkoitettu? Mahdollisuus on pienempi kuin saada lotossa 10 oikein - kyllä ne on aikoinaan tarkoituksella tehty ja nimiparametreja osattu käyttää.

Samalla ratkeaa sekin ettei parametreja tarvitse palauttaa sillä jokakrran muuttujaa käytettäessä muuttujakuvausta muutetaan vastaamaan tilannetta - ja muuttujakuvaus on globaali elikkä sama kuvaus on käytössä kaikkialla - niin pääohjelmassa kuin jokaisessa funktiossakin - joten jos sitä muutetaan jossakin niin se muuttuu kaikkialla.

- senkin virtuoosit tekivät että he 'kielsivät' käyttämästä käskyä: eval - se nimittäin on helpoin ratkaisu myös yhteensitomiseen. Ja taas välittämättä virtuoosien puheista BASH:in kehittäjät ovat laittaneet tiedostoon: ~/.bashrc noin kolme eval-käskyä. Ketä uskoa?

Koetan selittää funktio-parametrien palautusta toisellakin tavalla: funktoista ei ole täyttä hyötyä elleivät funktiosta palattua sinne lähetetyt muuttujat ole muuttuneet. Tähän muuttamiseen on kaksi keinoa: muuttoksen suorittaa joko funktion kutsuja saatuaan tulokset funktiosta takaisin tai funktio itse toimintansa yhteydessä. Muissa kielisssä muutoksen tekee funktion kutsuja ja sitävartenhan parametrien arvot tulee palauttaa sieltä funktiosta. Mutta BASH ei toimi näin möllillä tavalla vaan muutoksen tekee funktio itse - tämä edellyttää että käytetään nimiparametreja ja että kielessä on kyky yhdistää nimi sen arvoon - BASH:illa on tuo kyky mutta muilla kielillä ei - mutta kyky ei riitä ellei sitä myös käytetä ja virtuoosit jättävät kylmän tyynesti kyvyn käyttämättä. Kun virtuoosit sanovat että BASH ei osaa parametreja palauttaa niin he puhuvat siis ihan totta - eikähän mikään laki vaadi kertomaan ettei BASH:issa parametreja tarvitsekaan palauttaa - mikäli on toimittu oikein. 

---

Ja 'nopeat komennot' nopeuttavat toimintaa paljon - nopeuttivathan ne aikanaankin mutta niiden käyttäminen romahti koska 'nopeat komennot' ovat nopeita vain kun skriptissä melkein kaikki käskyt ovat niitä nopeita eikä ilmeisesti kukaan huomannut sitä.

Toistaiseksi on liian aikaista edes arvella kuinka paljon ne nopeuttavat kokonaisuutta, mutta jo tähänasti tehdyt 'nopeita komentoja' hyödyntävät funktiot tekevät melkoisen kyky-loikan yksittäisissä tapauksissa - ja tekevät ainakin sen että BASH on surkea enää vain vähän. Ja on täysin varmaa että paljon lisää löytyy.

Jotta uusia ominaisuuksia voisi käyttää järkevällä tavalla täytyy niiden käyttämiseksi kirjoittaa funktio joka täytyy voida sijoittaa kirjastoon - mutta  virtuoosithan 'kielsivät' kirjastot? Ja taas BASH:in kehittäjät ovat asentaneet jokaiseen koneeseen itselleen merkittävistä toimista kirjastollisen funktioita - välittämättä virtuoosien 'kirjasto-kiellosta'. Ja eikö kehittäjä ole verrattomasti tietävämpi kuin virtuoosi?
- voit itsekin käyttää noita super-omituisia funktioita - esimerkiksi funktio nimeltä: quote palauttaa sille lähetetyn sanan lainausmerkeissä. Käske: quote antti -> tulostaa 'antti'

Ja BASH:ista yritetään muinkin keinoin tehdä surkimusta: esimerkiksi 'nopeat komennot' haudattiin uusien käskyjen alle ettei kukaan vaan huomaisi niiden käyttökelpoisuutta - ja muut keinot haudattiin muun roskan alle - samaa keinoa käytetään kaikkialla: hyväkin menetelmä hukkuu kun päälle sataa tarpeeksi roskaa. Toinen konsti on vaieta asia kuoliaaksi - eihän kukaan usko mihinkään sellaiseen josta virtuoosit eivät koskaan puhu.

Teoriassa BASH kykeneekin samoihin ohjelmointitöihin kuin muutkin kielet - hidashan BASH on eikä siitä taida nopeaa saadakaan - mutta pikkuinen toivonkipinä siitäkin on. Varsin omituinenkin BASH on. Käytännössä BASH on melkolailla toivoton kieli - mikä johtuu virtuoosien määräämistä rajoituksista joilla ei tunnu olevan kunnon syytä - mutta jos niistä ei välitä niin taivas tuntuu olevan rajana. Mutta 'kirjastokiellosta' ei oikein pääse eroon: kirjastot toimivat kyllä ihan hyvin, mutta 'kirjastokielto' estää kirjastojen lisäämisen perus-asennukseen - ja kirjastojen puute tuhoaa kielen ajanoloon takuvarmasti - ihme että BASH sinnittelee edelleen. Silti netistä löytyy vielä ainakin kymmenen isoa BASH-kirjastoa - unohtuneet nettiin ajalta jolloin kirjasto-osoitin vielä toimi?

Ja tuloksena tästä 'kirjasto kiellosta' on että olemattoman pieniinkin ongelmiin tarkoitetut skriptit ovat tuhatrivisiä hirviöitä jonka lauseet koostuvat sanoista jotka ovat todella inhottavia kirjoitus-asultaan joten niitä ei edes muista vaan ne täytyy jokakerran hakea itsetehdyistä tiedostoista.
 
Omaan koneeseensa voi toki laittaa itsetehdyn kirjaston - minun koneessani iso kirjastoni on toiminut moitteettomasti jo yli kahdeksan vuotta - vaikka koneeni ovat vaihtuneet usein ja myös Ubuntun versiot vaihtunet parikertaa vuodessa.

42
'nopeat käskyt' ovat paitsi nopeita niin niillä myös helppo määritellä nopeita ja monipuolisia toimintoja sillä ne eivät ole loogisia pommeja niinkuin uudet käskyt helposti ovat. Tosin vanha käskyt ovat vaikeasti muistettavaa merkki-sotkua. Esimerkiksi kun pyydetään:

kerro millä paikalla etsitty on hakulausekkeessa kun jossain haettavan jälkeen täytyy olla määrättävä sana:
Koodia: [Valitse]

merkkijono='abc def ghi jkl'; trigger=bc; arm=gh; etuosa="${merkkijono%%$arm*}";apu=${#etuosa}; [[ "${merkkijono:0:$apu}" =~ $trigger ]] && etuosa="${etuosa%%$trigger*}" && echo 'merkkino='$((${#etuosa}+1))
- tätä toimintoa varten on tehty myös regex - ja joku on puurtanut kuukausitolkkua kehittäessään sitä. Tämä tekemisessä meni parituntia - olihan tämä ensimmäinen laatuaan.
- kyse on aikaisemmin tuntemattomasta toiminnosta joten esimerkki on todennäköisesti huonosti valittu - mutta kannnattaa esittää sillä tiedähäntä mihin tämä johtaa.
- esimerkiksi tiedon etsimiseen rekistereistä tämäntapaisia tarvitaan - mutta BASH  on saanut niin huonon maineen hitaudestaan ja 'kiikkerästä' toiminnastaan etteivät luotettavasti toimivat 'nopeat käskyt' siihen minkäänlaista muutosta tuo.   


Tai sama haku hieman erilaisilla määrittelyillä: kerro millä paikalla etsitty on hakulausekkeessa kun haettavaa ennen täytyy jossakin olla määrättävä sana:
Koodia: [Valitse]

merkkijono='abc def ghi jkl'; trigger=bc; arm=gh; etuosa="${merkkijono%%$arm*}";apu=${#etuosa}; [[ "${merkkijono:$apu}" =~ $trigger ]] && etuosa="${etuosa%%$trigger*}" && echo 'merkkino='$((${#etuosa}+1))

- monipuolisemmillakaan hauilla nopeus ei juurikaan muutu.

43
Kirjastojen puutteen takia BASH:issa on runsaasti seuraavankaltaista: haluttaessa erottaa tekstistä joku sana käytetään yleensä seuraavantyyppistä toimintaa :
Koodia: [Valitse]
echo $text |  awk '{ print $3 }'       # BASH:in vastaava toiminto on:
matriisi=($text); echo ${matriisi[2]}  # ja se toimii kymmenkunta kertaa nopeammin. Onhan koodi vähän pidempi eikä ihan yleisesti
                                       # tunnettukaan. Mutta silti ihmettelen miksei sitä käytetä sillä virtuoosien on pakko
                                       # tuntea tuommoiset. Halutaanko vähätellä BASH:ia vaiko edistää awk:ia?
                                       # Mutta entäpä jos BASH:issa olisikin kirjasto ja siellä funktio:
                                       # function write () { matriisi=($1); echo ${matriisi[$2]} ;}. Silloin käsky olisikin:
write "$text" 2                        # Ja toiminto olisi edelleen melkein yhtä nopeaa.

-  käske: 
Koodia: [Valitse]
function write () { matriisi=($1); echo ${matriisi[$2]} ;}
käskystä jää sllähetkellä avattuna olevan päätteen muistin funktio - aivan niinkuin funktio olisi haettu kirjastosta. Käske sitten samassa pätteessä:
Koodia: [Valitse]
write "yksi lensi yli käenpesän" 2
-> tulostuu: yli .

- kyse on ajoista 4ms ja 0.5ms. Mutta ajetaanpa toiminto kuinka monesti tahansa samassa skriptissä niin toiminta kestää jokakerran melkein yhtäkauan. Ja kun samanlaisia pikkujuttuja on paljon niin koko BASH alkaa tahmaamaan. 
- voi käskeä nyös: set yksi lensi yli käenpesän; echo $3    # se on lyhyt ja nopea - niinkuin BASH taitaa olla aina jos ei hölmöillä.

44

Tai muunnos tieteellisestä esitysmuodosta normaaliin esitysmuotoon: 
Koodia: [Valitse]
function from_sci ()
{ nollia=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
kokonaisosa=${1%%.*} ; kokonaisia=${#kokonaisosa}
luku=${1//[.]/}
luku=${luku%%e*}
[[ ${luku:0:1} = - ]]  && { merkki=-; luku=${luku:1} ;} || merkki=''
exp=${1##*e}
 (( $exp > 0 )) && { luku=$luku$nollia; apu=$merkki${luku:0:$(($exp+$kokonaisia))}.${luku:$(($exp+$kokonaisia))};apu=${apu%%+(0)}; echo ${apu%.} ;} || { [[ -exp -gt $kokonaisia ]] && echo $merkki.${nollia:0:$((-$exp-$kokonaisia))}$luku || echo ${luku:0:$(($kokonaisia+exp))}.${luku:$(($kokonaisia+exp))} ;} ;}

# testausta ja suoritusajan määritystä  - yksi muunnos kestää luokaa 0.25 ms
time for expo in {-55..55}; do from_sci 123456789012345678901234567890123456890.12345678901234567901234567890123456789e$expo; done

45
Printf-käsky:llä on käynnissä ikuinen taistelu desimaalipisteen kanssa ja sillä lukujen koko rajoittuu 18 merkkiin. Kummankin puutteen voi poistaa 'nopeilla käskyillä' tehdyllä skrpteillä jotka osavat tulostaa  normaalissa esitysmuodossa annetun luvun tietellisessä esitysmuodossa tai päinvastoin - olipa luvuissa numeroita vaikka satoja - eikä desimaalipisteestä ole harmia:
Koodia: [Valitse]
function to_sci () { luku=$1
[[ ${luku:0:1} = - ]]  && { merkki=-; luku=${luku:1} ;} || { merkki=''; luku=${luku//+/} ;}
luku=${luku##+(0)}  # poistetaan etunollat - nimenomaan 0.xyx -> .xyz
apu=${luku%%.*}     # erotetaan kokonaiosa
exp=${#apu}         # lasketaan kokosasta positiivinen exponentti
[[ ${luku:0:1} = \[.,] ]] && { # katsotaan onko ensimmäin merkki desimaalipiste - silloin exponentti on negatiivinen
apu=${luku%%[1-9]*} # poistetaan kaikki siitälähtien kun ensimmäinen numero tulee desimaalipisteen jälkeisen nollasarjan perään. Siis tulos on desimaaipiste yksin jos nollia ei ole yhtään.
exp=$((1-${#apu}))  # lasketaan negatiivinen exponentti nollien lukumäärästä vähentäen yhden desimaalipisteestä
luku=${luku:1}      # palautetaan desimaaliosa jättäen desimaalipite pois
luku=${luku##+(0)}0 # poistetaa etunollat ja lisätään yksi peränolla
echo $merkki${luku:0:1}.${luku:1}e$(($exp-1)) # tulostetaan näin kun expnentti on negatiivinen - kokonaisosa on nyt ensimmäinen numero
} || { # halu dokumentoida aihuttaaa sen että ehtojen jälkeinen ryhmitys tulee rikkoa erotamalla nämä merkit tällätavoin
luku=${luku//[.,]/} # poistetaan desimaalipista sotkemasta
echo $merkki${luku:0:1}.${luku:1}0e$(($exp-1)) # tulostetaan näin kun exponentti on positiivinen - kokonaisosa on nyt ensimmäinen numero
} ;}

to_sci 1234567890123567890123456789012345678901234567890,1234567890111111111111111111111111111111111111111111111111111111111111111111
to_sci 1
to_sci +1
to_sci -1
to_sci .1
to_sci 0.1
to_sci -0.1
to_sci 10


- jos myöhemmin huomaa että muunnos toimii joskus väärin niin sehän olisi vain hyvä - joutuisi nimittäin korjaamaan ja ajukopan ränsistymistähän tässä koetetaan hidastaa.
- BASH:ia ei voi dokumentoidakaan kuten muita kieliä sillä ehtojen toinen tai kummatkin haarat saattavat olla koottuja lauseita ja mikäli ne kommentoi ninkuin normaalisti niin rakenne särkyy. Ei se kommentointi kuitenkaan raketti-tiedettä ole.
- BASH toteuttaa ilmaukset oikein silloinkin kun voisi vannoa ettei semmoista sotkua mikään pysty tulkitsemaan - joten skriptit lyhenevät ja nopeutuvat ikuisesti - tai onhan nollaa vaikeaa enää pienentää.
- nopeat käskyt' tosiaan ovat nopeita: näin monirivinen skripti on yhtänopea kuin yksi uusi käsky.

Muunnoksen kestoajan (noin .3 millisekuntia) määrittäminen:
alkuhetki=$(date +%s%N); to_sci 1234567890123567890123456789012345678901234567890.1234567890111111111111111111111111111111111111111111111111111111111111111111 ; loppuhetki=$(date +%s%N); aika=$((10#$loppuhetki-10#$alkuhetki-1000000)); apu=000000000$aika; apu=${apu: -14: -9}.${apu: -9}; echo ${apu##+(0)}
- printf "%e" 100000 kestää noin .2 millisekuntia

46
 
Käsitelläänpä sitten bc:

Kerro18 on huomattavasti nopeampi kuin bc - tosin kerro18 on vain 36 numeroinen liukuvanpilkun kertolasku. Samoin 72 numeron desimaalilukujen yhteenlasku on sekin nopeampi. Desmaalilukujen jakolaskukin toimii sekin nopeasti antaen noin 50 numeroa jotka yleensä kaikki oikein - valitettavasti eivät aina. Mutta sittenkin bc on ehdottomasti kunkku - nämä ovat vain vähäisiä säröjä bc:ssä.

Mutta esimerkiksi lukuja pyöristtessä bc on toistaitoinen: bc ei oikeastaan osaa pyöristää ollenkaan vaan se ainoastaan leikkaa sen desimalien lukumäärän määrämittaan. Ja sen leikkaamisenkin suorittamiseksi sen täyy tehdä jakolasku - se lasku voi kylläkin olla valelasku jossa jaetaan luvulla 1 joten mikään ei muutu. Mutta ihmiset eivät yleensä tiedä tätä eivätkä saa aina edes leikattua - ja taas saadaan lisää syytä mollata BASH:ia. Ja leikkaamiseenkin bc kuluttaa ylettömästi aikaa.

Mutta pyöristys-skripti pyöristää aina oikein ja on lisäksi salaman-nopea, 'kymmenenkertaa' nopeampi kuin bc:
Koodia: [Valitse]
function pyöristä () {
[[ ${1:0:1} = - ]]  && merkki=- || merkki=+
[[ $1 =~ \. ]] &&  desimaaliosa=${1##*.}000000000000000000 || desimaaliosa=0000000000000000000 # onko desimaalipistettä ...
kokonaisosa=${1%%.*}
apu=${desimaaliosa:0:$2}
desimaaliosa=$(($apu$merkki(( ${desimaaliosa:$2:1} >= 5 )) ))
echo $kokonaisosa.$desimaaliosa ;}

time pyöristä 1.22634 2          # tämä 'nopeilla käskyillä' toteutettu BASH-skripti kestää 0ms   
time bc<<<"scale=2; 1.22634/1"   # tämä bc:n lasku kestää 4ms - ja tulos on hieman virheelinen.
 
- tarkistelepa lausetta: $(($apu$merkki(( ${desimaaliosa:$2:1} >= 5 )) ))  -> BASH kokoaa ensin sellaisen tekstijonon kuin käsky määrää. Lähetettäessä tekstijono matematiikka-moottoriin sen osista jokainen määritellään sentyppisesti kuin hyvä tulee: siis jokainen osa voi olla tarpeen mukaan joko: tekstijono, numero, operaattori, laskutoimitus, funktiokutsu tai looginen arvo 0 tai 1 -> kaikki mitä sinne matematikka-moottoriin lähetetäänkin tuntuu toimivan.
- skandit funktion nimessä toimivat hyvin. Ei kyllä taida olla järkevää käyttää nimessä skandeja - mutta kokeillaan tämänkerran.

47
Sed selviytyy paljon paremmin: muutettaessa tekstistä annettu sana kaikilla toiseksi kuluu  kymmenen kilosanan tiedoston käsittelyyn aikaa noin millisekunti niinkuin sed:illäkin. Pienemmillä tiedostoilla BASH on jopa 4*nopeampi ja isommilla jopa 4*hitaampi.
 
Koodia: [Valitse]
apu=$(cat /dev/urandom | base64 | head -c 10000); time printf "%s" ${apu[@]//a/alku}     # apu:sta muodostetaan ensin 10000 sanan tiedosto.  Tämä on pelkkää BASH:ia
apu=$(cat /dev/urandom | base64 | head -c 10000); time echo ${apu[@]} | sed 's/a/alku/g' # tämä taas käyttää apunaan sed:iä

# toteutustapoja on muitakin, esimerkiksi:
< /boot/grub/grub.cfg readarray doku; time { echo "${doku[@]}" | sed 's/a/alku/g' ;}

- myös sed on niin suuri ettei sen toimintojen merkittävää osaakaan saa tutkittua ihmisiässä.
- myös pieniä sed-skripteja on tolkuton määrä - ja myös ne tekevät pieniä ihmeitä. 

48
Awk, sed ja bc ovat olleet tähänasti täysin ehdottomia nopeus-kuninkaita. Nyt ne ovat tosiaan menettämässä asemaansa 'BASH:in nopeille käskyille' - näin alkuunsa vasta muutamassa pienessä tehtävässä. Mutta esimerkiksi awk häviää taiston pahimmillaan 'kymmenkertaisesti'. Eivät ne tosin saa tuommoisia rökäletappiota kuin pienissä asioissa ja todella suurissa ne ovat nopeampiakin - myöntääköhän Linux niille enemmän resursseja? Eikä tosiaan kaikkia ziljoonia awk- ja sed-skriptejä kerkiä edes ajatella korvaavansa - mutta BASH varmasti osaisi. Tarkempi kuvaus siiä kuinka asiat tehdään:

Awk:ia, sed:iä ja bc:tä käytetään BASH:issa putkittamalla tutkittava niihin. BASH käyttää putkitusta vain kutsuessaan toisia skriptikieliä mutta kutsuessaan toisia BASH-skriptejä se käyttää mieluummin funktioita. Koska heti BASH-opetuksen alussa opetetaan käyttämään putkitusta mutta funktioista ei puhuta juuri sen enempää kuin että väitetään ettei BASH osaa funktioita kunnolla käyttää - joten eihän funktioiden käyttöäkään osata tekemisestä puhumattakaan. Esimerkki  awk:in käytetyimmästä toiminnosta:
Koodia: [Valitse]

# tämä on tosin ihan pieni mutta yleisin awk skripti:
echo  "10 20 30 40 50 60" | awk '{print $3}'

# näin se korvataan BASH:in 'nopealla käskyillä':
function awk2 () { echo ${@:$(($1+1)):1} ;}; awk2 3 10 20 30 40 50 60 

# tai jos heittomerkejä ehdottomasti haluaa käyttää: function awk2 () { apu=$1; set $2; echo ${@:$apu:1} ;}; awk2 3 "10 20 30 40 50 60"
# tai jos valittavissa on välilyöntejä niin: function awk2 () { echo ${@:$(($1+1)):1} ;}; awk2 3 'nyt saat kokea, koito' 'kokea kovaosainen' 'apen luista leukaluuta, anopin kivistä kieltä'
- BASH:in käyttämä tapa on tosin paljon hankalamman näköinen - mutta vain koska BASH:issa funktio-kirjastot on "kielletty" - muuten koko awk on sekin tavallaan kirjasto joten tilanne on epäreilu. Mutta jokatapauksessa BASH on kymmenkunta kertaa nopeampi - pärjääköhäm Pythonkaan taistossa hautaan jo saatetun BASH:in kanssa tässä pikkujutussa?
- awk on suurissa matemaattisissa toimissa paljon parempi kuin BASH. Eikä toistaiseksi voi edes aavistella kuinka noissa isoissa matemaattisissa laskuissa käy - ja onnistuvatko ne edes.
- awk:ssa on myös sadoittain pieniä muutaman merkin käskyjä jotka suorittavat pieniä ihmeitä - mutta eihän niistäkään misään kunnon yhteenvetoa ole.
- alkaa tuntua siltä että suhmurointia on liikaa ja käry on varmasti käynyt aikaisemminkin. Onko professorien valta niin ehdoton että asiat voidaan yksinkertaisesti vaieta kuoliaaksi?

49
Yritän tässä määritellä niitä mitä olen kutsunut vanhoiksi käskyiksi sillä eiväthän ne kirjaimellisesti ottaen käskyjä ole vaan dataan lisättäviä ohjeita siitä kuinka data tulisi esittää. Nopeilla käskyillä ei muuten ole matematiikan kanssa mitään tekemistä - silkkaa tekstikäsittelyä ne ovat.

Niiden nopeus tulee siitä että niiden toteuttamiseen tarvitaan vain merkki = tai käsky echo tai jotain senkaltaista nopeaa. Jos pelkästään tuloksia takastellaan niin datan muokkaamiseksi samallatavoin käytetään ohjelmina yleensä sed:iä, pieneltä osin myös awk:ia ja aivan mittömältä osaltaan myös bc:tä - mutta niillä tuloksen saaminen kestää niin kauan ettei niitä käyttäen saisi skripteistä nopeita. Ihmettelin aikanaan kun eräs virtuoosi sanoi että nopeutta haluttaessa pitää unohtaa sed, awk ja bc - täytyy siis olla niin että nopeiden skriptien olemassaolo on tiedetty aina?

Mutta enää en ihmettele sillä nuo vanhat käskyt suorittavat samantapaisia tehtäviä - kylläkin rajoitetummin mutta myös paljon nopeammin. Mutta jokatapauksessa ainoastaan sed, awk ja bc mielletään käyttökelpoisiksi - mikä suureltaosin taitaa johtua siitä etteivät virtuoosit ole koskaan tehneet noista BASH:in nopeista käskyistä minkäänlaista yhteenvetoa - sillä nopeiden käskyjen hyödyllisyys tulee täysin esiin vasta kun melkein kaikki käskyt skriptissä ovat noita vanhoja - sillä tulkki tulkkaa kerralla niitä isonkin joukon eikä tulkkia siis tarvita vähänväliä - joten joskus ne ovatkin parhaita.

Nopeat käskyt ovat vähän niinkuin BASH:in assembleria. Ja vaikka ne ovatkin ylivertaisia suorituskyvyltään niin luultavasti ne on hylätty samasta syystä kuin varsinainen assemblerkin : ohjelman tekeminen ja ylläpito niitä käyttäen on ihan liian kallista - rahallisesti jos skriptin ostaa ja jos skriptin tekee niin selkänahka paukkuu kauan. Virtuooseilla on siis ollut syytäkin tekoseensa - heitä on vaan on niin sopivaa mollata sillä onhan heillä leveät hartiat.

Toki nopeat käskyt toimivat vain niin hyvin kuin moitteettomat käskyt toimivat silloin kolmekymmentä vuotta siten. Esimerkiksi kun etsittävän alku on teksissä eri rivillä kun loppu niin plörinäksi menee. Samoin kirjoitusvirheitä ei suvaita ollenkaan.

***

- lukuja, tekstijonoja, matriiseja tai matriisien yksittäisiä jäseniä voidaan käsitellä 'äärettömän' monin eritavoin - on tosiaan mahdotonta muistaa käsittely-säännöistä pientä osaakaan vaan tiedostoista ne on haettava - eikä missään ole minkäänlaista yhteenvetoa vaan itse se on tehtävä - ja koska yhteenvedon tekeminen on liian suuri työ niin tuloksena on hutilointia ja sotkua - sillä yhteenvedon tekeminen kun olisi isollekin joukolle vaativa tehtävä. Ja nyt yhteenvedon tekeminen on hieman liian myöhäistä sillä ne vanhat käskythän on osin jo unohdettu - joten virtuoosit yrittävät tuhota BASH:in ennenkuin kukaan huomaa?

- nopeat käskyt toimivat vain yhden sanan alueella - mutta lauseesta tulee tavallaan yksi sana kun sen laittaa heittomerkkien väliin - tai myös muuttujalla nimeltään IFS voidaan määrätä sananväliksi merkki jota ei ole käytetty misssään jolloin monirivinenkin teksti tai tiedosto on yksi sana. Jokatapauksessa vanhojen käskyjen käyttämiseen ei tarvita jotakin grep-käskyn kaltaista järkälettä - kuten regex:ät tarvitsevat - joten ne ovat satoja kertoja nopeampia yhden sanan kanssa - tosin ne ovat myös satoja kertoja tehottomampia monirivisten tekstien kanssa sillä IFS:n kanssa leikkiminen on yleensä liian vaarallista - mutta oikeassa paikassa käytettynä ainoastaan nopeus merkitsee.

- BASH:issa jokaista käskyä käsitellään kymmenellä tavalla ennenkuin se lähetetään tulkattavaksi ja päteehän se nopeisiinkin käskyihin. Käsittelyjen suoritusjärjestys on päätetty etukäteen joten kaikki tehdään 'ennen tai jälkeen' monen muun tehtävän joten monet määreet kuten esimerkiksi IFS (= se määrää mikä merkki jakaa sanoja) muuttavat myöhemmin tulevat käsitteyt joskus ihan nurinkurisiksi. Tämmöinen  liian suuri monipuolisuus on myös painolasti kielen käytölle - BASH onkin turhan monipuolinen varsinaiseen ohjelmointiin - prottotyyppien tekemiseen BASH on tosin ihan kiva - ja alzheimerin välttelyssä BASH on erinomainen.

- voi olla varma että tästä kuvauksesta on moni merkittävä ominaisuus jäänyt pois sillä BASH:in kirous on se että kaikesta löytyy loppumattomiin uusia ominaisuuksia joten esimerkiksi kaikki käsikirjat ovat kelvottoman isoja tiiliskiven kokoisia raamattuja - esimerkiksi pääte-ohjelman käsikirja vaatisi viikkojen opiskelun ennenkuin homma olisi hanskassa - ja silti useimmat meistä käytävät päätettä käsikirjaa lukematta ja hyvin se sujuu niinkin - toiminta on silloin tosin joskus hidasta ja ärsyttävää mutta kaikki toimii kyllä jotenkin.

Jospa joskus tajuaisin päivittää tätäkin kuvausta - tai sitten ei, jottei tästäkin tulisi tiiliskiveä - sillä tiiliskiveä ei lue kukaan.

BASH on ainakin mielenkiintoista moniulotteista shakkia eikä hölmöilystäkään tule paineita - sillä onhan BASH siinä itsekin ihan omaa luokkaansa. Mutta jos saisi kirjastojen käyttämisen palautettua niin kuka tietää mitä BASH:ista tulisi.

***

Usein isoilla ja hitailla skripteillä on nopeat korvaajat - muihin kieliin verrattuna BASH on tosin hidas sittenkin. Koska ne on aikaisemmin tunnettu niin miksi ne on hylätty? Sillä ne ovat nopeampia, lyhyempiä ja ainakin tuntuvat vakaammiltakin.

Ehkäpä paras keino kuvata noita nopeita käskyjä on muutama esimerkki toimivista ohje-tyypeistä. Kaikki toimivat niin monella eritavalla ettei esimerkkejä kaikista voi antaa:

- tehtävä1: jaa annettu sana jonkun merkin kohdalta ja esitä joko etu- tai jälkiosa. Jakopiste voi olla myös sana - siis myöhemmin tehty uusissa käskyisä oleva 'parempi' cut-käsky joka jakaa vain yksittäisen merkkin ollessa jakopisteenä on varsinainen möhläys. Jakava merkki voi olla sanassa ensimmäinen jakavan merkin kaltainen tai sitten viimeinen. Jakava merkki voidaan esittää myös merkkiluokan osana, esimerkiksi: [1-9] -> siis nollan kohdalla ei tehdä mitään.
Esimerkiksi: desimaaliosa=${luku##*.} . Tai voidaan kirjoittaa pisteen sijaan: [.,] jolloin desimaalipisteenä voi olla joko piste tai pilkku - nopeudessa tuommoiset pikkujutut eivät tunnu vaikka niitä olisi montakin.

- tehtävä2: tuhoa, muuta tai tulosta sanasta jotakin määriteltävien ominaisuuksien perusteella. Esimerkiksi kun tekstijonona esitetystä desimaailuvusta pitäisi tehdä 'silmälle miellyttävä': poista kokonaisosasta etunollat ja desimaaaliosasta takanollat ja desimaalipistekin jos se jää viimeiseksi - eikä muihin nolliin saa kajota:
Koodia: [Valitse]
luku=${luku##+(0)}; [[ ${luku//[^.]/} ]] && luku=${luku%%+(0)}; echo ${luku%.}
Tällainenkin toiminta voidaan toteuttaa merkkiluokkien avulla - merkkiluokka on esimerkiksi: [0-9] elikä numroiden luokka - tai onhan noita: [:alnum:], [:alpha:], [:blank:], [:cntrl:], [:digit:],  [:graph:], [:lower:], [:print:], [:punct:], [:space:], [:upper:] ja [:xdigit:] - tai jotain muuta hakasulkujen välissä olevaa määritystä - kaksois-sulkuja kulemma kannattaa käyttää.

- tehtävä3. muuta joku sana/sanaryhmä jossain tiedostossa. Muutettavien sanojen ei tarvitse olla yhtäpitkiä - voit siis vaikka tuhota määrättävän sanan koko tiedostossa. Muutettavassa sanassa saa olla välilyöntejäkin. Muutettavia saa olla montakin samalla rivillä.
Koodia: [Valitse]
mika=BEGIN; miksi=ALOITA; < /boot/grub/grub.cfg readarray doku; printf "%s" "${doku[@]//"$mika"/"$miksi"}" | tee ~/poista_luettuasi
tulos ilmestyy näytölle ja myös kotikansion tiedostoon: ~/poista_luettuasi. Mitään ei varsinaisesti editoida - vaaraton esimerkki tämä on.

- tehtävä4: määrittele missä kohtaa annettavassa pitkässä sanassa on jokin kirjain/kirjainryhmä (= pos-funktio). Sekä etsittävässä ettäetsijässä saa olla välilyöntejä. Esimerkki:
Koodia: [Valitse]
isosana="Pitkät on puut Pisan mäellä, hongat Hornankalliolla"; pikkusana="Pisan m"; [[ "$isosana" =~ "$pikkusana" ]] && { echo -n 'pikkusana alkaa kohdalla:'; apu=${isosana%%$pikkusana*}; echo $((${#apu}+1)) ;} || echo 'pikkusanaa ei löydy'
- tästä olisi helppoa tehdä versio joka ilmoittaa kaikki ne paikat joista etsittävä löytyy.
- onko ihme että BASH:ia väheksytään kun tämöisistä super-nopeista ja vakaista on siirrytty epävakaisiin aikasyöppöihin?

tehtävä5: yhdistä kaksi edellistä grep-käskyn kaltaiseksi - siis tarkoituksena on käydä nimettävä tiedosto läpi ja jokaiselta riviltä miltä esittävä löytyy tulostetaan rivinumero ja löytökohta:
Koodia: [Valitse]
function grep2 () { < $2 readarray doku; rivino=0; for n in "${doku[@]}"; do rivino=$(($rivino+1)); apu=${n%%$1*}; [[ "$n" =~ "$1" ]] && { echo -n 'haettava löytyi riviltä: '$rivino' paikalta:'$((${#apu}+1)); echo ;} ; done ;}; grep2 "feature is" /boot/grub/grub.cfg
- siis kutsu on kokonaisuudessaan: grep2 mikä_ilmaisu mistä_tiedostosta. Sanassa mikä_ilmaisu saa olla välilyöntejä mikäli se laitetaan heittomerkkien väliin. Alkuperäinen grep ei edes salli heittomerkkejä ja ilman niitä se toimii hitaasti ja väärin.
- tämä on kolme kertaa hitaampi kuin alkuperäinen grep - mutta paljon nopeampi kuin BASH-skripti tavanomaisilla uusilla käskyillä. Silloin aikoinaan tällaisilla olisi hyvin voinut kilpailla grep-käskyn kanssa. Lisäksi tämän saa muokattua aivan sellaiseksi kuin käyttäjä haluaa.
- näistä nopeista käskyistä muodostetut skriptit lisäksi senjälkeenkun sen toimivat osat on koottu joko  toimivat tai sitten ei - mutta sitä uusien käskyjen raivostuttavaa taipumusta ei ole että alkuunsa saa virheellisiä tuloksia kunnes korjattuaan skiptiä miljoona kertaa alkaa saada oikeita tuloksia yhä useammin. Ovatko uudet käskyt siis paitsi satoja kertoja hitaampia niin myös loogisia pommeja?

50
Keskenkaiken mieleeni tuli että desimaalilaskennan toimiminen kertoo sen että myös suoritusajan mittaus toimisi samoja menetelmiä soveltaen paljon paremmin - joten käsitelläänpä suoritusajan mittausta nyt uudestaan sillä sen käsittely oli aikoinaan kovin epämääräistä.

On totuttu siihen ettei millisekuntia tarkemmin kannata mitään BASH:issa suoritusaikoja ajoittaakaan sillä suoritusajat vaihtelevat aina vähintään joitain millisekunnin kymmenyksiä ja tulkin kutsuminenkin ja sen toimintakin kestää jotain pientä ja epämääräistä - muuten näiden syiden takia BASH:in time-käsky ilmoittaakin erikseen user, sys ja system ajat - ja se aika joka kestää tuloksen selviämiseen kuluu taitaa olla näiden aikojen jonkinsortin summa.

Kaikki lähtee siitä kuinka kauan kestää 'nollasuoritus' elikä käsky: kaksoispiste - joka ei tee mitään mutta tulkkia siinä kyllä kutsutaan. Nollasuorituksen kestoajan luotettava mittaaminen helpottuu oleellisesti kun hyödyntää BASH:in kykyä käsitellä jopa samassa lauseessa käsiteltyä tekstijonoa laskettaessa kokonaislukuna. Vaikka kestoaikaa voi kuvata ainoastaan desimaaliluvulla niin sehän on  kokonaisluku kunhan vaan jättää desimaalipisteen kirjoittamatta - joten laskut onnistuvat ihan hyvin - kunhan vain ottaa huomioon montako niitä desimaaleja pitäisi olla. Mutta tulosten loppukäsittelystä ei tulisi mitään ilman noita vanhoja jo unohdettuja käskyjä joiden erinomaisuutta ei koskaan tajuttu - joten kunnollista ajoitusta ei ole ennen tehty - tai onhan parempi ajoitustapa toki jo aikaisemminkin tunnettu mutta keinoja sen kunnolliseen käyttöön ja oikeellisuuden osoittamiseen ei ole ollut. 
 
Nollasuorituksen kunnollinen ajoitus:
Koodia: [Valitse]
alkuhetki=$(date +%s%N); : ; loppuhetki=$(date +%s%N); aika=$((10#$loppuhetki-10#$alkuhetki)); apu=000000000$aika; apu=${apu: -14: -9}.${apu: -9}; echo ${apu##+(0)} 
- luultavasti käsky: $(date +%s%N) ei ole tarkka. Mutta se tekee suurinpiirtein saman virheen kaksi kertaa: ensimmäisellä kerralla se vaikuttaa plussana ja toisella miinuksena joten ne kumoaa toisensa melkohyvin.
- nollasuoritus on tuo yksinäinen kaksoispiste seuraavan kohdan lopussa: alkuhetki=$(date +%s%N); : 
- nollasuorituksen suoritusaika on oikeastaan aika suuri - paljon suurempi kuin mitä saa kun antaa käskyn: time :  . Miksi time-käsky antaa liian pienen ajan? Vika on nimittäin time-käskyssä sillä tuo tarkka ajoitus on varmasti tarkka - ei varmaankaan nanosekunnilleen mutta yli mikrosekunnilleen. Selitys time käskyn liian pieneen aikaan on se että time-käskyn  real-, user- ja system-ajat ovat pienempiä kuin .0005 joten ne tulkitaan nolliksi - siksi time-käskyn ajoitustulos on lähes millisekunnin liian pieni sillä oikea aika on niiden summa; kyseessä on siis pyöristysvirhe.
- äskeisen selityksen oikeellisuuden varmitus: kirjoita kaksoispisten paikalle: time sleep .001 . Saat kaksi erilaista aikaa. Mutta huomaapa että time-ajoituksen real+user on lähes sama kuin tarkempi ajoitustulos. Joten ei niitä aikaisemmin mainittuja millisekunnin kymmenyksiä oikeastaan ole ollutkaan - lisäksi on unohdettu jo kauan sitten kuinka time-käskyn tulokset tulkitaan.
- kaksoispisteen paikalle voi kirjoittaa mitähyvänsä: yksittäisiä käskyjä, skriptejä tai funktiokutsuja - ja mitä sinne laitetaankin niin sen toiminta-aika määritellään.
- koska tuloksista voi muodostaa keskiarvoa niin esimerkiksi voi vihdoinkin lopullisesti selvittää kumpi käsky on keskimäärin nopeampi yksinkertaisissa tulostuksissa: printf vai echo.

Siitä on ihan liian aikaista puhua mihin kaikkeen muuhun tästä on apua; ehkä vain BASH:in omituisuuksien tutkimiseen.
- saat muuten mielikuvan siitä paljonko nollasuoritus kestää eri suorituskerroilla käyttäen skriptiä:
Koodia: [Valitse]
for n in {1..20}; do alkuhetki=$(date +%s%N); : ; loppuhetki=$(date +%s%N); aika=$((10#$loppuhetki-10#$alkuhetki)); apu=000000000$aika; apu=${apu: -14: -9}.${apu: -9}; echo ${apu##+(0)}; sleep 1; done
-'sleep 1' lopusssa on tarkoitettu rauhoittamaan riehumista - joskus myös BASH kompastelee omiin lahkeisiinsa jos kovin kiirehtii.

51
Potenssiin korotus nopeutui yli kaksinkertaisesti skriptin rakennetta yksinkertaistamalla - nyt potenssiin korotus on yksi funktio. Pienet potenssit lasketaan aina alle 10 millisekunnin.
Eihän tämä vieläkään ole kovin käyttökelpoinen mutta toimii edes jotenkin ja toisaalta sekä nopeus että tarkkuus paranevat kokoajan. Mutta sriptien ominaisuudet paranevat aina kovin hitaasti - sillä jos koettaa kiirehtiä niin homma leviää helposti käsiin - toiminta on sama kuin hiihtomäessä: kun nopeus alkaa kiihtyä kovin nopeaksi niin takapuoli maahan tai jotain muuta hidastetta. Mutta jos jaksaa niin toiminta tuntuu paranevan ikuisesti.

Ja yleensä jossain vaiheessa tulee loikka suorituskyvyssä; tämmöinen nopeuden kaksinkertaistuminen on pientä. Sillä tuntuu olevan niin että jokaisessa asiassa on rajaton määrä ratkaisutapoja - ja meille on opetettu niitä tehottomia ratkaisutapoja. Pitää täysin paikkansa että kokeillessa paljon tulee helposti siirryttyä epäilyttäville alueille - mutta toisaalta jos skripti toimii aina luotettavasti ja vahingoittamatta ympäristöään niin mitä pahaa siinä kokeilemisessa on?

Määränpäänä on koota funktioista kirjasto josta kukahyvänsä voi hakea koneeseensa funktioita ilman että tulisi myrkytetyksi kekseillä, kirjautumatta ja asentamatta mitään ettei kone muuttuisi mitenkään - sillä kaikilla meillä on taipumus temppuhin - eihän siinä mitään mutta kun tuputamme niitä temppuja toisillekin. Hommaa kuvaa mietelause: miksi tehdä helpolla kun voi tehdä vaikeasti?

Hyvän kirjaston yksi ominaisuus on se että se toimii kaikissa tehtävissä samankaltaisin menetelmin eikä kovin usein muuta toimintaansa. Kirjaston kokoaminen onkin todella hidasta - jos keskittyisi yhteen pieneen tehtävään niin vuosi kuluisi kumminkin. Siis kirjaston kokoaminen olisi pitänyt alkaa jo silloin kolmekymmentä vuotta sitten - niin se taisi alkaakin mutta koko kirjasto-idea romutettiin sittemmin.


function potenssi () {
tulosta=: # yhdessä paikassa päätetään tulostetaanko osa-tehtävien jälkeen välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
for n in $(seq $(($2-1))); do
luku1=${1:0:18}; [[ $n = 1 ]] && luku2=$luku1 || luku2=$merkki${tulos##+(0)}; luku2=${luku2:0:18}
$tulosta "annetut numerot: $1 $2"
[[ ${1:0:1} = - ]]  && merkki=- || merkki=''
[[ ${1:0:1} = - && ${2:0:1} = - ]]  && merkki=''
apu1=${luku1//\-/}; apu2=${luku2//\-/}
desimaaliosa1=${luku1##*.};
desimaaliosa2=${luku2##*.};
[[ ! ${apu1//[^.]/} ]] && desimaaliosa1=''
[[ ${apu1//[^.]/} ]] && { luku1=${apu1:0:18}; kokonaisluku=0 ;} || { luku1=${apu1:0:18}"."; kokonaisluku=1 ;}
[[ ! ${apu2//[^.]/} ]] && desimaaliosa2=''
[[ ${apu2//[^.]/} ]] && { luku2=${apu2:0:18}; kokonaisluku=0 ;} || { luku2=${apu2:0:18}"."; kokonaisluku=$(( 1 & $kokonaisluku )) ;}
 desimaaleja=$((${#desimaaliosa1}+${#desimaaliosa2})); $tulosta desimaaliosa1:$desimaaliosa1"   desimaaliosa2:"$desimaaliosa2"   desimaaleja:"$desimaaleja
luku1=000000000000000000${luku1//./}
luku2=000000000000000000${luku2//./}
a=${luku1: -18:9}; b=${luku1: -9}
c=${luku2: -18:9}; d=${luku2: -9}; $tulosta $a' '$b; $tulosta $c' '$d
luku1=00000000000000000000000000000000000000$((10#$b*10#$d))
luku2=00000000000000000000000000000000000000$((10#$d*10#$a))"000000000"
luku3=00000000000000000000000000000000000000$((10#$c*10#$b))"000000000"
luku4=00000000000000000000000000000000000000$((10#$a*10#$c))"000000000000000000"
luku1=${luku1: -36} ; $tulosta $luku1
luku2=${luku2: -36} ; $tulosta $luku2
luku3=${luku3: -36} ; $tulosta $luku3
luku4=${luku4: -36} ; $tulosta $luku4; $tulosta
luku11=${luku1:0:18}
luku12=${luku1:18}; $tulosta a$luku11' 'b$luku12
luku21=${luku2:0:18}
luku22=${luku2:18}; $tulosta c$luku21' 'd$luku22
luku31=${luku3:0:18}
luku32=${luku3:18}; $tulosta a$luku31' 'b$luku32
luku41=${luku4:0:18}
luku42=${luku4:18}; $tulosta c$luku41' 'd$luku42;$tulosta
summa1=$((10#$luku12+10#$luku22+10#$luku32+10#$luku42)); $tulosta summa1:$summa1
summa1pituus=${#summa1}; ylivuoto=0; (( $summa1pituus >= 19 )) && ylivuoto=${summa1:0: -18} && summa1=${summa1:1}
summa1=000000000000000000$summa1; summa1=${summa1: -18} ;$tulosta ylivuoto:$ylivuoto' summa1:'$summa1
summa2=$((10#$luku11+10#$luku21+10#$luku31+10#$luku41+$ylivuoto)); $tulosta summa2:$summa2 
(( $summa2 )) && : || summa2=000000000000000000
(( $kokonaisluku )) && tulos=${summa2/*(0)/}$summa1 || { apu=$summa2$summa1; tulos=${apu:0: -$desimaaleja}.${apu: -$desimaaleja} ;}
done
echo; echo tulos laskusta: $1 \^ $2'  . Ylärivi on varmasti oikea arvo bc:stä vertaamista varten ja alarivi tästä skriptistä:'
bc<<<"scale=20; $1^$2"
(( $2 & 1 )) && echo -n $merkki; echo ${tulos##+(0)} ;}

time potenssi -1.23456789012345 10 # tämä on BASH:in merkintätapa laskulle: -1.23456789012345^10. Jos laskun haluaa tehdä jossain skriptissä niin siinä skriptissä merkintatapa on: luku=$(-1.23456789012345 10)

52
Ensiksi täytyy kuitenkin kirjoittaa uusiksi neliöjuuren laskeminen tieteellisestä esitysmuodosta sillä aikaisempi skripti ei useissa koneissa toiminut - tämän pitäisi toimia. Sitäpaitsi uuden version tuloksessa kaikki numerot ovat oikeita - matemaattisesti se merkitsee että viimeisessä numerossa on vain +-0.4999... epävarmuus. Kummallista; jo toinen tapaus jossa BASH on aina täysin oikeassa numeromääränsä puitteissa.

Neliöjuuren laskeminen BASH:in omilla peruskäskyillä on nopeampaa kuin matematiikkaohjelmilla mutta varjopuolena on että tuloksessa on vain 9 numeroa. Neliöjuuri voidaan laskea
kuinkamoni-numeroisesta luvusta tahansa mutta vain 18 merkitsevintä numeroa huomioidaan. Luku voi siis olla olla tyypiltään yhtähyvin:

.0000000000000000000000000000000000000000000000000123456789012345678900000000e+357, 1234567890123456789012345678901234567890123456789000000000000000000000000000e-52 tai yksinkertainen luku 4. Luku josta neliöjuuri otetaan saa olla joko normaalissa tai tieteellisessä esitysmuodossa mutta tulos esitetään aina tieteellisessä esitysmuodossa.

Printf kuuluu uusiin käskyihin ja se on tällaisessa käytössä hyvin hidas. Sen saisi kyllä korvattua joukolla vanhoja nopeita käskyjä mutta muunnoksen tekemisessä olisi kova homma eikä nyt ole aikaa sen tekemiseen - se olisi kyllä yli kaksikertaa nopeampi printf.

Printf on 50 kilo-sanan järkäle joka sössii usein - myös desimaalipisteen kanssa. Aikaisemmassaskriptissä korjasin printf:n käytöstä käskyllä: LANG=C mutta sekään ei tunnu aina toimivan. Muutinkin sen käskyksi: LC_NUMERIC=C joka toimii paremmin.

Kyllähän tämänkin skriptin voi sijoittaa luokkaan temput - eikä tämä olisi muissa kielissä edes hyvä. Mutta sikäli tämä on mielenkiintoinen että BASH:in omat virtuoosit koettavat epäilyttävin keinoin estää tämmöisien skriptien tekemisen BASH:iin. Mieleen tulee että virtuoosit hylkäsivät BASH:in luulojen perusteella ja nyt he yrittävät piilottaa hölmöilynsä.

Koodia: [Valitse]

function sqrt () {
apu=$(LC_NUMERIC=C printf "%.17e\n" $1); mant=${apu%%e*};apu=${apu##*e}; (( $apu & 1 )) && kerroin=10 || kerroin=1; expo=$(($apu/2));   
in=${mant//./}"000000000000000000"; in=${in:0:17}
sqrt=2147483648  # 2^31 
delta=1073741824 # 2^30 # tämä tulee voida jakaa jatkuvasti kahdella siten että jako menee aina tasan 
for ((i=0;i<32;i++)); do
   x=$(($sqrt*$sqrt/$kerroin))
   (( $x>=$in )) && sqrt=$(($sqrt-$delta)) || sqrt=$(($sqrt+$delta))
   delta=$(($delta/2)); (( $delta )) || delta=1  # echo $((1/2)) lasketaan nollaksi - oikein pyöristettynä se on 1
done
echo ${sqrt:0:1}.${sqrt:1}e$expo ;}

juurrettava=27.34567890123456789;time sqrt $juurrettava

***

Nyt kun BASH on melkein kuollut olisi entistä tärkeämpää että kaikki rakentaisivat BASH:ia yhdessä. Ei se ole rakentavaa yhteistyötä että kukaan haukkuu ketään - joten perun aikaisempia väitteitäni: eivät virtuoosit ole hölmöjä vaan halusivat vaan aikanaan muita kieliä korvaamaan BASH:ia sillä BASH on ihan liian vaikea kieli kaikille, myös virtuooseille.

Virtuoosit halusivat siis aikoinaan päästä BASH:ista eroon ja sehän olisi ollut paljon vaikeampaa mikäli BASH:in huomattaisiin toimivan desimaalilukujenkin kanssa ihan hyvin. Mutta tuhoamis halustaan huolimatta virtuoosit eivät ole koskaan sanoneet että desimaalilaskut eivät BASH:illa onnistu - he ovat sanoneet vain että BASH:in matematiikkamoottori ei desimaaleja hyväksy ja sehän on kiistaton tosiasia - ja tosiasia on sekin ettei BASH tunne desimaali-lukutyyppiäkään. Mutta desimaalilaskut lasketaan aina muutenkin kokonaisluvuilla - siis myös matematiikkaprosessoreissa numerot lasketaan kokonaislukulaskimella ja desimaalipisteen paikkaa ylläpidetään erikseen ja kokonaisuutta kutsutaan liukuvan pilkun laskimeksi. Kun BASH:in suhteen tätä ei ole koskaan käyttäjille kerrottu niin virtuoosit ovat itseasiassa johdatelleet käyttäjät tekemään virhepäätelmän siitä ettei desimaaleilla voi laskea - ja jotta varmistuisi ettei kukaan edes epäilisi desimaalilaskujen toimivan tehtiin desimaalilaskuihin soveltumattomia muutoksia käskykantaan, annettiin 'kirjastokielto', väitettiin eval-käskyä pahikseksi - ja väitettiin BASH:ia muutenkin hampaattomaksi ettei kukaan vaan kokeilisi mitään.
- itseasiassa jokaisessa BASH:issa on kirjasto vaikkei siitä koskaan puhuta. Löydät sen käskyllä: declare -f .
- jokaisessa ~/.bashrc-tiedostossa on perus-asennuksessakin todennäköisesti muutama eval-käsky.
- ei desimaali-lukutyyppiä tarvitsekaan olla sillä desimaalilukuhan on sama kuin kaksi kokonaislukua joiden välissä on desimaalipiste - ja desimaalipisteen erottamat kokonaisluvut ovat täysin erillisiä eikä toisen arvo vaikuta toisen arvoon muulloin kuin laskutoimituksissa - joissa vaikutuksen huomioiminen on ysinkertaista. Tekstijonoa voidaan käsitellä myös matemaattisesti mikäli käsiteltävän tekstijonon palanen on ulkoasultaan matematiikka-moottorin hyväksymä kokonaisluku - desimaalilukua voi siis käsitellä kun se jaetaan kahteen kokonaislukuun desimaalipisteen kohdalta ja käsitellään kumpaakin puoliskoa erikseen. Laskutoimituksissa saattaaa syntyä luku joissa desimalipuolelta joutuu hylkäämään ensimmäisen numeron ja lisäämään sen kokonailuvun puolelle - tai muodostamaan luvusta yhden komplementin ja hylkäämällä etumerkin - ja vähentämällä kokonaiosasta yksi.
- mikäli kokonaisluku lyhyempi kuin 18 merkkiä ei ole mitään ongelmaa mutta jos se on pidempi kuin 18 merkkiä jaetaan se palasiin - noiden palasten käsittely on kovin monivaiheista mutta onnistuu sekin.
- päinvastoin tekstijono on paljon parempi kuin desimaaliluku sillä siinähän voi olla merkkejä kuinkamonta tahansa.
- mikäli käsitellään palasiin katkottua lukua saattaa toinen luku alkaa nollalla. Koska BASH mieltää nollilla alkavat luvut oktaaliluvuiksi täytyy palasen eteen kirjoittaa: 10# jotta toimittaisiin jokatapauksessa desimaalijärjestelmässä.

Ja entäpä sitten funktio-parametrit: BASH:in ominaisuus on se että koko skriptin alueella tunnetaan jokaikinen muuttuja joka on määritelty jossakin. Mutta kun mainitaan joku nimi ei se vielä tee siitä muuttujaa: yleensä muuttuja muodostetaan siten että kun nimi mainitaan tarkoituksella että siitä tulisi muuttuja niin kirjoitetaan pääohjelmassa oltaessa: muuttujan_nimi=arvo - mutta funktiossa niin ei voi enää tehdä (esimerkiksi käsky $$parametri_numero=arvo ei toimi). Jos funktioon annetaan muuttuja on se tullessaan vain tekstijono eikä muuttuja - vaikka se olisi alunperin ollutkin muutuja. Mutta sen saa yhdistettyä kutsujan saman-nimiseen muuttujaan jollain käskyistä:
1. numero-muuttujan tapauksessa: let $parametri_numero=arvo
2. tekstijono-muuttujan tapauksessa: read<<<$parametri_numero arvo.
3. BASH lisäksi antaa kirjanpitonsa skriptaajien käyttöön declare-käskyn avulla (ja sallii myös kirjoittaa sinne kirjanpitoon). Ja sitäkautta onkin matriisit viisainta yhdistää nimeen - toimii keino kyllä muillekin muuttujatyypeille.
- yhteenvetona: virtuoosien väitteet etteivät nimiparametrit toimi on pahantahtoinen tulkinta: ne toimivat ihan loistavasti vaikkakin varsin epäsovinnaisesti. Arvoparametreja sensijaan ei useinkaan kannata käyttää vaan toimia nimiparametreilla sillä nimiparametrit huolehtivat parametrin palautuksesta automaattisesti -> mitään ei tehdä mutta silti parametri on 'palautettu' elikä se on muuttunut pääohjelman muistissa. Siis BASH on taas paljon parempi kuin muut kielet - funktioon ei kuljeteta yhtään arvoa eikä myöskään palauteta mitään elikä ei hukata aikaa hommiin jotka on tehty jo.
- voitaisiin kirjoittaa myös: 'eval \$$parametri_numero' mutta virtuoosit torppasivat tämän keinon väittämällä BASH:in toteutusta eval-käskystä pahikseksi - muissa kielissä eval on hyvis.
- virtuoosit ovat perillä siitä että BASH toimii näin eikä sen tarvitse parametreja palauttaa - mutta silti he väittävät edelleen että BASH ei osaa parametreja palauttaa sillä onhan se kirjaimellisesti ottaen ihan totta. Ovat vain vaiti siitä ettei BASH:in tarvitsekaan parametreja palauttaa käytettäessä nimiparametreja - ja koska 'palautus' ei toimi ihan normaalikonstein niin taas kertaalleen on saatu käyttäjät huijaamaan itseään.

- mutta eivät ne eri funktioiden muuttujat aina yhteisiä ole sillä kyllä BASH:issakin tunnetaan paikalliset muuttujat. Lisää vain funktion alkuun lauseen: local muuttuja. Silloin sitä muuttujaa ei tunneta missään muualla - ja toisessa funktiosssa saman niminen muuttuja on täysin eri muuttuja.
- paikallisten muuttujien erikoistapaus on se kun funktio kutsuu itseään. Silloin jokaisen kutsukerran muuttujat ovat ilmanmuuta paikallisia. Tätä rekursiota ei muuten yleensä kannata käyttää sillä se on erittäin hidas ohjelmarakenne - huomasin juuri äsken ettei se muuten olekaan hidas mikäli sitä käyttää siten että tuo rekursio tapahtuu C:ssä - etsipä toteutustapaa näistä kertomuksista yksinkertaisten sqrt funktion toteutuksista.

Samoin skriptien hitauskin on osin tehtyä: skriptit on jo alunperin opetettu kasaamaan väärin ja käyttämään vääräntyyppisiä käskyjä: oikein tehty skripti kutsuu tulkkia vain kerran tai muutaman - mutta väärintehty skripti kutsuu tulkkia lukemattomia kertoja ja siihen se aika kuluu - tulkkaustulos toimii C:ssä ja siellä toiminta on aina nopeaa.

Muihin kieliin verrattuna BASH on sittenkin kelvottoman hidas ja muutenkin omituinen - tuskinpa siitä saa edes välttävän hyvää kieltä vaikka kuinka kaivelisi sen hyviä puolia esiin. Mutta näin ei ollut silloin kolmekymmentä vuotta sitten kun BASH:in rappio alkoi - sillä nämä kaikki olisivat toimineet jo silloin.

Kyllä BASH:ia silti yksi ukko, Chet Ramey, kehittää jatkuvasti ja hyvää työtä hän tekeekin (kai hänellä on tukijoukkojakin?). Luepas BASH 5.2:n uusista ominaisuuksista verkkosivulta: http://tiswww.case.edu/php/chet/bash/NEWS. Jo ensimmäinen käsky saa epäilemään mistä kielestä on kyse sillä käsky on: malloc. Mutta toisaalta BASH:in käskyttääkin C:tä -> BASH:in malloc on siis ohje kääntäjälle kuinka toimia sillä C:n oma malloc mählii BASH skripteissä.

BASH ei ole saanut koskaan mahdollisuuttakaan kehittymiseen vaan yrityksetkin on torpattu. Mutta edistys ei kavahda ketään - skriptin merkittävimmät kriteerit ovat: toimiiko skripti ja vahingoittaako se ympäristöään. Tuo vahingoittaminen pitää kyllä nykyään varmaankin paikkansa sillä onhan BASH:ia laiminlyöty melkein täysin yli kolmekymmentä vuotta.

Mutta keksijä ja kokeilija ei tosiaankaan ole oikea henkilö saattamaan kieltä iskukykyiseksi, ainoastaan virtuoosit siihen kykenisivät.

***
Desimaalilukujen potenssiin korottaminen onnistuu myös hyvin, tosin toistaiseksi vain positiiviset kokonaislukupotenssit toimivat - mutta jopa desimaaliset potenssit toimisivat, kylläkin erittäin hitaasti logaritmien kautta jos vain tekisi tarvittavan kutsun jo tehdyille monille funktioille - mutta se on BASH:issa niin sotkuinen homma että se kaipaa ihan omaa tarinaansa - negatiiviset potenssit alkaisivat nekin toimia silloin.

Aikalailla vanhan kertaustahan tämä on ja tässä on vain vähän uutta - mutta on pakko käydä tämmöisetkin skriptit läpi sillä vasta tämmöisten jälkeen voi aavistella kuinka toimintaa voisi ehkä parantaa. 

Aikaa potenssiin korotuksessa kuluu suurinpiirtein potensin numeroarvon verran millisekunteja elikä BASH skriptiksi toiminta on nopeaa - vaikka muihin kieliin verrattuna toiminta on todella hidasta niin BASH:in kannalta se on jo paljon että homma onnistuu edes hitaasti.

Potenssiin korotettavat luvut katkaistaan 18-numeroisiksi. Ja tuloksen tarkkuus laskee siitäkin pahimmillaan luokkaa numero jokaista potenssin numeroa kohti - joten esimerkiksi kymmenenteen potenssiiin korotettaessa voi pahimmillaan olla täysin varma vain 14:stä oikeasta numerosta - harvoin niin huonosti tosin käy. Ja joskus virhe on mitätön suuremmillakin potensseilla.

Mutta sitten itse skripti (kaikki kirjasto-ohjelmat on syytä liittää mukaan. Tai voisihan nuo funktiot kirjoittaa tiedoston : ~/.bashrc:n perään jolloin ne olisivat aina käytettävissä):

Koodia: [Valitse]
function kerro18 () {
tulosta=: # yhdessä paikassa päätetään tulostetaanko monessa paikassa välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
luku1=${1:0:18}; luku2=${2:0:18}
# [[ ${#1} -gt 18 || ${#2} -gt 18 ]] && echo laskettavissa liikaa numeroita && return
$tulosta "annetut numerot: $1 $2"
[[ ${1:0:1} = - || ${2:0:1} = - ]]  && merkki=- || merkki=''
[[ ${1:0:1} = - && ${2:0:1} = - ]]  && merkki=''
apu1=${luku1//\-/}; apu2=${luku2//\-/}
desimaaliosa1=${luku1##*.};
desimaaliosa2=${luku2##*.};
[[ ! ${apu1//[^.]/} ]] && desimaaliosa1=''
[[ ${apu1//[^.]/} ]] && { luku1=${apu1:0:18}; kokonaisluku=0 ;} || { luku1=${apu1:0:18}"."; kokonaisluku=1 ;}
[[ ! ${apu2//[^.]/} ]] && desimaaliosa2=''
[[ ${apu2//[^.]/} ]] && { luku2=${apu2:0:18}; kokonaisluku=0 ;} || { luku2=${apu2:0:18}"."; kokonaisluku=$(( 1 & $kokonaisluku )) ;}
 desimaaleja=$((${#desimaaliosa1}+${#desimaaliosa2})); $tulosta desimaaliosa1:$desimaaliosa1"   desimaaliosa2:"$desimaaliosa2"   desimaaleja:"$desimaaleja
luku1=000000000000000000${luku1//./}
luku2=000000000000000000${luku2//./}
a=${luku1: -18:9}; b=${luku1: -9}
c=${luku2: -18:9}; d=${luku2: -9}; $tulosta $a' '$b; $tulosta $c' '$d
luku1=00000000000000000000000000000000000000$((10#$b*10#$d))
luku2=00000000000000000000000000000000000000$((10#$d*10#$a))"000000000"
luku3=00000000000000000000000000000000000000$((10#$c*10#$b))"000000000"
luku4=00000000000000000000000000000000000000$((10#$a*10#$c))"000000000000000000"
luku1=${luku1: -36} ; $tulosta $luku1
luku2=${luku2: -36} ; $tulosta $luku2
luku3=${luku3: -36} ; $tulosta $luku3
luku4=${luku4: -36} ; $tulosta $luku4; $tulosta
luku11=${luku1:0:18}
luku12=${luku1:18}; $tulosta a$luku11' 'b$luku12
luku21=${luku2:0:18}
luku22=${luku2:18}; $tulosta c$luku21' 'd$luku22
luku31=${luku3:0:18}
luku32=${luku3:18}; $tulosta a$luku31' 'b$luku32
luku41=${luku4:0:18}
luku42=${luku4:18}; $tulosta c$luku41' 'd$luku42;$tulosta
summa1=$((10#$luku12+10#$luku22+10#$luku32+10#$luku42)); $tulosta summa1:$summa1
summa1pituus=${#summa1}; ylivuoto=0; (( $summa1pituus >= 19 )) && ylivuoto=${summa1:0: -18} && summa1=${summa1:1}
summa1=000000000000000000$summa1; summa1=${summa1: -18} ;$tulosta ylivuoto:$ylivuoto' summa1:'$summa1
summa2=$((10#$luku11+10#$luku21+10#$luku31+10#$luku41+$ylivuoto)); $tulosta summa2:$summa2
# echo; echo tulos laskusta: $1 \* $2'  . Ylärivi on bc:stä ja alarivi tästä skriptistä:'
# bc<<<"scale=40; $1*$2"
(( $summa2 )) && : || summa2=000000000000000000
(( $kokonaisluku )) && tulos=${summa2/*(0)/}$summa1 || { apu=$summa2$summa1; tulos=${apu:0: -$desimaaleja}.${apu: -$desimaaleja} ;}
echo $merkki${tulos##+(0)} ;}

function potenssi () { # varsinainen potenssiin korottaminen
apu=$( kerro18 $1 $1 )
for (( n=2; n<$2; n++ )); do
apu=$( kerro18 $apu $1)
done
[[ $1 = 0 ]] && { apu=1; [[ $2 = 0 ]] || apu=0 ;} # sopimus on että:  0 ^ 0 = 1 - mutta 0 mihin tahansa muuhun potenssiin on nolla
echo; echo "lasku on: $1^$2"
echo -n "bc:n laskema varmasti oikea tulos: ";bc -l<<<"$1^$2"
echo    "tämän  skriptin   laskema   tulos: $apu" ;}

time potenssi 1507.5 5 # tämä on merkintätapa laskulle: 1507.5^5. Jos laskun haluaa tehdä jossain skriptissä niin merkintatapa on: luku=$(1507.5^5).
# luku ei ole desimaaliluku vaikka se siltä  näyttääkin vaan se on tekstijono.
 
time potenssi 2.5 30
time potenssi -2 2
time potenssi -2 3
time potenssi 9.999999 18 # tarkkuus huononee suunnattomasti
time potenssi 0.987654321098 10

Siis skriptiä kokeillaan siten että maalataan sen aivan kaikki lauseet koodista kerralla täältä verkkosivulta ja liimataan ne pc:n päätteeseen ja painetaan return. Vain BASH skriptit toimivat tällätavoin.
- kokeillessa älä haaveilekaan tiedostosta, suoritus oikeudesta tai käskystä !#/bin/bash. Käyttäjiä tylytetään niillä.



53
 Aloin suunnitella kuinka lasketaan kulman sinin arvo Taylorin sarjan mukaisesti. Taylorin sini-sarja on : sin x= x - x³/3! + x⁵/5! - X⁷/7!... ja siinä kulman arvo on radiaaneissa. Sikäli homma oli helppo että kaikki sen tarvitsemat funktiot on tehty jo - funktioiden koodia on paljon mutta itse skriptiä vähän. Ei merkitse mitään että kaikkikin tämän kaltaiset skriptit ovat hitaita ja kovin rajoittuneita - sillä kun ne saa toimimaan on se verraton voitto. Ja BASH:in kanssa on varmaa että mikään ei selviä helposti mutta kaikki selviää lopuksi.

Ja tälläkertaa tein ensiksi kunnon kuvauksen kuinka hommien pitäisi tapahtua enkä vain harakanvarpailla tupakkiaskin kanteen söherrettyä arvoituksellista hieroglyyfiä - eikä siinä vaiheessa mitään ollut tehtynä itse skriptistä.

- se mistä sini pitäisi laskea on x. Siis sen arvoa ei saa koskaan muuttaa eikä käyttää mihinkään muuhun eikä edes nimetä toisin. Ja siitä lasketun sinin merkintä on: sin_x
- laskun nopeus on noin 50ms
- arvot ovat muuten radiaaneissa. Yksi radiaani on noin 57 astetta.
- tarkkuutta rajoittavaksi tekijäksi tulee jakolasku - BASH:issakin on laskuihin pakko käyttää sen 18:sta merkkisille kokonaisluvuille tehtyä laskentamoottoria josta kyllä saa väsättyä nopean desimaalilaskimen mutta se 18:sta merkin rajoitus on jakajalla sittenkin. Lasku on tyyppiä: $((a/b)). Laskuissa tuo b on kertoma ja siinä ei siis saa olla numeroita enemmän kuin 18 - takanollat voi kuitenkin poistaa ja liittää tulokseen laskun jälkeen - niitä takanollia onkin 21-kertomassa neljä.
- mutta toisaalta kertoman laskenta on yksinkertainen tapahtuma eikä vaadi omaa funktiotaan.
- mitä sopisi odottaa tarkkuudeksi tulevan kahden radiaanin kohdalla: viimeinen korjaustermi on: .0000000001865792386 -> tarkkuus on noin kymmenen desimaalia
- korjaustermin merkin määritteleminen: (( $n==19 | $n==15 | $n==11 | $n==7 | $n==3 )) && kerroin=-1 || kerroin=1


***   funktiot kerro, jaa ja yhteenlasku*********************************************************************************************
Koodia: [Valitse]

function kerro18 () {
local apu apu1 apu2
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
luku1=${1:0:17}; luku21=${2:0:17}
# [[ ${#1} -gt 18 || ${#2} -gt 18 ]] && echo laskettavissa liikaa numeroita && return
$tulosta "annetut numerot: "$luku1 $luku2
[[ ${1:0:1} = - || ${2:0:1} = - ]]  && merkki=- || merkki=''''
[[ ${1:0:1} = - && ${2:0:1} = - ]]  && merkki=''
apu1=${1//\-/}; apu2=${2//\-/}
desimaaliosa1=${1##*.};
desimaaliosa2=${2##*.};
[[ ! ${apu1//[^.]/} ]] && desimaaliosa1=''
[[ ${apu1//[^.]/} ]] && { luku1=${apu1:0:18}; kokonaisluku=0 ;} || { luku1=${apu1:0:18}"."; kokonaisluku=1 ;}
[[ ! ${apu2//[^.]/} ]] && desimaaliosa2=''
[[ ${apu2//[^.]/} ]] && { luku2=${apu2:0:18}; kokonaisluku=0 ;} || { luku2=${apu2:0:18}"."; kokonaisluku=$(( 1 & $kokonaisluku )) ;}
 desimaaleja=$((${#desimaaliosa1}+${#desimaaliosa2})); $tulosta desimaaliosa1:$desimaaliosa1"   desimaaliosa2:"$desimaaliosa2"   desimaaleja:"$desimaaleja
luku1=000000000000000000${luku1//./}
luku2=000000000000000000${luku2//./}
a=${luku1: -18:9}; b=${luku1: -9}
c=${luku2: -18:9}; d=${luku2: -9}; $tulosta $a' '$b; $tulosta $c' '$d
luku1=00000000000000000000000000000000000000$((10#$b*10#$d))
luku2=00000000000000000000000000000000000000$((10#$d*10#$a))"000000000"
luku3=00000000000000000000000000000000000000$((10#$c*10#$b))"000000000"
luku4=00000000000000000000000000000000000000$((10#$a*10#$c))"000000000000000000"
luku1=${luku1: -36} ; $tulosta $luku1
luku2=${luku2: -36} ; $tulosta $luku2
luku3=${luku3: -36} ; $tulosta $luku3
luku4=${luku4: -36} ; $tulosta $luku4; $tulosta
luku11=${luku1:0:18}
luku12=${luku1:18}; $tulosta a$luku11' 'b$luku12
luku21=${luku2:0:18}
luku22=${luku2:18}; $tulosta c$luku21' 'd$luku22
luku31=${luku3:0:18}
luku32=${luku3:18}; $tulosta a$luku31' 'b$luku32
luku41=${luku4:0:18}
luku42=${luku4:18}; $tulosta c$luku41' 'd$luku42;$tulosta
summa1=$((10#$luku12+10#$luku22+10#$luku32+10#$luku42)); $tulosta summa1:$summa1
summa1pituus=${#summa1}; ylivuoto=0; (( $summa1pituus >= 19 )) && ylivuoto=${summa1:0: -18} && summa1=${summa1:1}
summa1=000000000000000000$summa1; summa1=${summa1: -18} ;$tulosta ylivuoto:$ylivuoto' summa1:'$summa1
summa2=$((10#$luku11+10#$luku21+10#$luku31+10#$luku41+$ylivuoto)); $tulosta summa2:$summa2
# echo; echo tulos laskusta: $1 \* $2'  . Ylärivi on bc:stä ja alarivi tästä skriptistä:'
# bc<<<"scale=40; $1*$2"
(( $summa2 )) && : || summa2=000000000000000000
(( $kokonaisluku )) && tulos=${summa2/*(0)/}$summa1 || { apu=$summa2$summa1; tulos=${apu:0: -$desimaaleja}.${apu: -$desimaaleja} ;}
echo $merkki${tulos##+(0)} ;}

#---

function jaa () { (( ! $# )) && echo funktion ajokäsky on muotoa: jaa 1 2 . Siitä pitää tulla: .5000000000000000000000000000 && sleep 2 && return
local apu apu1 apu2
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
# kun jakaja kasvaa yli 18 numerosta alkaa tulo epätarkentua kymmenenteen osaansa jokaista ylimääräistä numeroa kohti - jolloin desimaalipisteen jälkeen tulee yksi nolla lisää. Siten vielä 35 numeroinen jakaja antaa jonkinlaisen tuloksen. luku=${luku##+(0); lkm=${#luku}; [[ $lkm>18 ]] && { ekstranollia=printf "%${lkm-18))s" | tr " " 0; luku1=${luku1:0:17} ;}
 
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1"."
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2"."

desimaaliosa1=${luku1##*.}
desimaaliosa2=${luku2##*.}
kokonaisosa1=${luku1%%.*}
kokonaisosa2=${luku2%%.*}

#desimaaliosat yhtäpitkiksi:
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
$tulosta $kokonaisosa1$desimaaliosa1
$tulosta $kokonaisosa2$desimaaliosa2

#kokonaisosat yhtäpitkiksi:
(( ${#kokonaisosa2} >= ${#kokonaisosa1} )) &&
{ apu="00000000000000000000"$kokonaisosa1; kokonaisosa1=${apu: -${#kokonaisosa2}} ;} || { apu="00000000000000000000"$kokonaisosa2; kokonaisosa2=${apu: -${#kokonaisosa1}} ;}
luku1=$kokonaisosa1$desimaaliosa1;luku1=$((10#$luku1))
luku2=$kokonaisosa2$desimaaliosa2;luku2=$((10#$luku2))
$tulosta jaettava:$luku1"   "jakaja:$luku2

(($luku1 >= $luku2)) && { apu=$(($luku1/$luku2)); kokonaisiatulosteessa=${#apu}; nolliatulosteessa='' ;} || { apu=$(($luku2/$luku1)); apu=${apu//[1-9]/0}; nolliatulosteessa=${apu:1}; kokonaisiatulosteessa=0;}

$tulosta nolliatulosteessa:$nolliatulosteessa"   "kokonaisiatulosteessa:$kokonaisiatulosteessa   

# tähänasti on selvitetty desimaalipisteen paikkaa. Nyt aletaan laskea numeroita.
unset tulos # vain varmistus että kaikki on tuloksessa tämänjälkeen uutta
luku1=${luku1##+(0)}; luku1=${luku1%%+(0)} # poistetaan etu-ja takanollat sillä tästälähtien ne vain haittaavat.
luku2=${luku2##+(0)}; luku2=${luku2%%+(0)} 
#q=$(($luku1%$luku2));((q>1)) && q=$((10**$((${#q}-2)) )) || q=1 # siis q on jakojäännös esitettynä siten että ensimmäinen numero on 1 ja lopuista numeroista tulee nollia - viimeinen nolla poistettuna
luku1=$luku1'0000000000000000000'; luku1=${luku1:0:18} ;$tulosta " "$luku1 #"  "${#apu2}"  "$apu

for n in {1..5}; do # muodostetaan tulos-palasia 9 merkkiä kerrallaan
apu=$(($luku1/$luku2)); tulos[$n]=${apu}  # yksi keino palauttaa niitä kadonneita nollia;  (( $(($luku1%$luku2)) > $((10*$q )) )) && apu=$apu'0' on joskus tarpeen ja joskus liikaa
apu2=$(($luku1%$luku2)); luku1=$apu2'0000000000000000000'; luku1=${luku1:0:18} ; $tulosta " "$luku1"  "${#apu2}"  p"$apu
done

vanhatmerkit=1
for n in {1..5}; do # kootaan tulosta matriisin palasista
uudetmerkit=${#tulos[$n]}; [[ $uudetmerkit -lt $vanhatmerkit ]] && tulos[$n]=$nolliatulosteessa${tulos[$n]} ; vanhatmerkit=$uudetmerkit
tulos=$tulos${tulos[$n]}
done
 
#[[ ${tulos%%+(0)} -eq 1 ]] && nolliatulosteessa=${nolliatulosteessa:1} # purkkaviritys?
#echo "oikea tulos 54 desimaalilla esitetynä on päällä ja alla tulos tästä skriptistä"
#bc<<<"scale=$((${#tulos}+$((${#nolliatulosteessa})))); $1/$2" | tr -d '\\\n'; echo ' tämä rivi on bc:stä'
[[ $nolliatulosteessa ]] && echo .$nolliatulosteessa${tulos:0} || echo ${tulos:0:$kokonaisiatulosteessa}.${tulos:$kokonaisiatulosteessa} ;}

#---

function yhteenlasku () { # voi käyttää vähennyslaskuunkin
[[ ${1//[^-]/} ]] && m1=-1 || m1=+1; [[ ${2//[^-]/} ]] && m2=-1 || m2=+1
luku1=${1:0:18}
luku2=${2:0:18}
luku1=${luku1//[-+]/}; luku2=${luku2//[-+]/}
luku1=${luku1//-./-0.}; luku2=${luku2//-./-0.}
[[ ${luku1//./} == $luku1 ]] && luku1=$luku1".0" # on tarpeen että luvussa on yksi desimaalipiste
[[ ${luku2//./} == $luku2 ]] && luku2=$luku2".0" # joten lisätään sellainen mikäli ei jo ole
desimaaliosa1=${luku1##*.}; desimaaliosa1pituus=${#desimaaliosa1}
desimaaliosa2=${luku2##*.}; desimaaliosa2pituus=${#desimaaliosa2}
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
#echo a$desimaaliosa2' 'b$desimaaliosa1 ; read # testatessa tämä on tarpeen
desimaaleja=${#desimaaliosa1} #; echo $desimaaleja
kokonaisluku1=${luku1%%.*}
kokonaisluku2=${luku2%%.*}
kokoluku1=$kokonaisluku1$desimaaliosa1 #; echo $kokoluku1
kokoluku2=$kokonaisluku2$desimaaliosa2 #; echo $kokoluku2
(( $m2 +1 )) && luku=$((10#$kokoluku1+10#$kokoluku2)) || luku=$((10#$kokoluku1-10#$kokoluku2))
echo ${luku:0: -$desimaaleja}.${luku: -$desimaaleja} ;}


# Varsinainen koodi:
x=.5; sin_x=$x;kerroin=$x;apu=$kerroin;  # skriptiin syötetty kulma radiaaneissa
kertoma=1;takanollia='';for n in {3..23..2}; do kertoma=$(($kertoma*$n*($n-1))); (( ${kertoma: -1} )) || { takanollia=$takanollia'0'; kertoma=${kertoma:0: -1} ;}; echo -n $kertoma' '$takanollia
  kerroin=$(kerro18 $x $( kerro18 $x $kerroin) ); echo -n '   '$kerroin;
  apu=$(jaa ${kerroin:0:17} ${kertoma:0:17}$takanollia ); echo -n '   '${tulos:0:25} 
  (( $n==19 | $n==15 | $n==11 | $n==7 | $n==3 )) && sin_x=$(yhteenlasku $sin_x -$apu) || sin_x=$(yhteenlasku $sin_x $apu); echo '  '$sin_x
done

- sitten siirrytäänkin siihen jakolaskun laajennokseen - se nimittäin vaikuttaa tähänkin paljon.
***

Tämä liukuvan-pilkun kertolaskuohjelma kerro9 on hitaampi kuin isoveljensä vaikka tämän tuloksessa saa olla vain 18 merkitsevää numeroa - muuten tuo hitaus johtuu uudesta käskystä nimeltä: seq sillä jo yksi uusi käsky romahduttaa nopeuden.

Tosin tämä on sittenkin BASH-skriptiksi todella nopea. Ja se hyväksyy paljon sellaista josta isoveli menee solmuun - esimerkiksi desimaalipisteeksi kelpaa sekä piste että pilkku - ja tieteelistäkin esitysmuotoa voi sisäänmenevissä luvuissa olla ja niissa voi eksponentin merkkinä olla joko iso tai pieni e. Ja ennenkaikkea skriptin toiminta on helpompi osoittaa oikeelliseksi.

Kun tekee skriptin mihin tehävään tahansa niin alkuunsa skriptin toiminta on yleensä kovin virheelistä. Mutta olipa virhe mikähyvänsä niin se on korjattavissa - ja itseasiassa nuo korjaukset parantavat koodia muutenkin. Mutta nyt on ihan liian myöhäistä yrittää saada tälläisista skripteistä matemaattisessa mielessä moitteettomia joten virtuoosit saivat tässäkin asiassa tahtonsa läpi eli saivat taas tuhottua BASH:ia vähäsen - ja pääsivät ketkut tästäkin tahallisesta teosta kuin koira veräjästä.

Vikojen korjaaminen käy jokaisen virheen jälkeen hitammaksi sillä jokaisen koodimuutoksen jälkeen täytyisi jokainen testi suorittaa uudestaan ettei vaan korjaa siten että joku aikaisemmin tehty lopettaisi oikeintoimimisen. Ja kun korjauksia tulee tarpeeksi monta niin joskus joutuu jopa aloittamaan ihan alusta - tämäkin skripti on aloitettu jo kolmasti uudestaan - kyllä jokainen yritys silti opettaa jotakin - en siis turhaan BASH:ia opetuskieleksi kutsu.

Vikojen korjaaminen nopeasti on liian myöhäistä tehdä tänäpäivänä sillä ne viat löytyisivät vasta yhteisön ponnistuksin ja nykyäänhän BASH-yhteisöt ovat pieniä eikä tämän tasoinen matematiikka kiinnostakaan enää juuri ketään. Mutta silloin aikoinaan BASH-yhteisöt olivat erittäin suuria ja tämmöiset kiinnnostivat monia joten nopeasti näistä olisi silloin oikeellisia tullut.
Koodia: [Valitse]
function kerro9 () { # Tuloksessa voi olla korkeintaan 18 numeroa - välittömästi desimaali-pisteen perässä olevia nollia ei lasketa mukaan eikä myöskään peränolia.
luku1=${1//./,}; luku1=${luku1//E/e}
luku2=${2//./,}; luku2=${luku2//E/e}
luku1=$(printf "%.18e" $luku1)
luku2=$(printf "%.18e" $luku2) #; echo $luku1' '$luku2; read
exp1=${luku1##*e}; exp1=${exp1//+0/+}; exp1=${exp1//-0/-}
exp2=${luku2##*e}; exp2=${exp2//+0/+}; exp2=${exp2//-0/-}
# exp3=$(($exp1+$exp2+1))

luku1=${luku1%%e*}; luku1=${luku1%%+(0)}
luku2=${luku2%%e*}; luku2=${luku2%%+(0)}

[[ ${luku1:0:1} = '-' ]] && { merkki1=-1; luku1=${luku1:1} ;} || merkki1=1
[[ ${luku2:0:1} = '-' ]] && { merkki2=-1; luku2=${luku2:1} ;} || merkki2=1 

kokonaisosa1=${luku1%%[,.]*}
kokonaisosa2=${luku2%%[,.]*}
kokonaistentulo=$(($kokonaisosa1*$kokonaisosa2))
kokonaisia=${#kokonaistentulo}
exp3=$(($kokonaisia+$exp1+$exp2)) #; echo $exp3; read

# echo; bc<<<"$1*$2" # bc kertoo useasti lopputuloksen joka on ihan varmasti oikea ja siihen on kiva verrata. Mutta tässä on paljon sellaistakin josta bc kirjoittaa vain härskin viestin.
tulos=$((${luku1//,/}*${luku2//,/}))'00000000000000000000000000000000000000'; [[ ${kokonaistentulo:0:1} -eq 9 ]] && [[ ${tulos:0:1} -eq 1 ]] && exp3=$(($exp3+1))
[[ $(($merkki1*$merkki2)) -eq -1 ]] && echo -n '-'
[[ $exp3 -gt -1 ]] && apu2=${tulos:0:$exp3}.${tulos:$exp3} ||  apu2=.$(printf '0%.s' $(seq ${exp3:1}))${tulos%%+(0)}
[[ ${apu2//[^.]/} ]] && apu2=${apu2%%+(0)}; echo ${apu2%.} ;}


# kohtia jotka kannattaa tarkistaa:
kerro9 1 1
kerro9 -1 1
kerro9 -1 -1
kerro9 1 -1
kerro9 0 0 
kerro9 1.1 1,1                                       # on parempi että laskutoimitus onnistuu vaikka se ei teoriassa ihan oikein olekaan.
kerro9 4 4
kerro9 1000001.2345 1E-5

kerro9 1234567891 1234567892
kerro9 123456789012345678 2.2
kerro9 123.4567891 1234567.89
kerro9 1234567891 1234567892E-118                  # kerrottavien esitysmuoto saa olla tieteellinenkin
kerro9 .0000000000001234567892 .000000001234567891 # välittömästi desimaalipisteen perässä olevia nollia ei lasketa merkkilukuun.
kerro9 12345678000000000000 123456780000000000000  # peränollatkaan eivät ole merkitseviä nomeroita
 
kerro9 3.16227766 3.16227766                       # dekadin vaihtuminen ulostulosssa on syytä tarkistaa, siis se että kun
kerro9 3.16227767 3.16227767                       # ulostulo siirtyy uuteen dekadiin muuttuuko desimaaalipisteen paikka oikealla hetkellä
kerro9 10 100
 
kerro9 1000000001 .999999999
kerro9 +1234567891 1234567892                       # + merkkiä voi käytää osoittamaan luvun positiivisuutta eikä sitä silloin käsitetä laskutoimitukseksi.
kerro9 123.4567891 1234567.89

***

Ei kukaan viitsi tehdä noilla vanhoilla käskyillä skriptiä jos tietää että kun tekee koodia päivän joutuu kommentoimaan sitä viikkoja - sitäpaitsi kommenttejakin pitäisi selventää omilla kommenteillaan - lisäksi minulla on sellainen käsitys että jos kommentointi on moitteeton, vuokaaviot on tehty ja vertaisarvioita hankittu niin kukaan ei edes lue niitä - saatika ymmärrä kommentteja oikein jos skriptiä täytyy korjata. Melko turhaa touhua koko kommentointi. Tämä johtaa tosin siihen että myöhemmin ei korjata vaan tehdään uusi - mutta homma pelaa edes jotenkin.

Myös printf on hidas uusi käsky. Näillä uusilla massivisilla käsky-järkäleillä on sekin paha piirre että niillä on ne ominaisuudet mitkä niillä on ja jos käsky ei miellytä niin asialle ei voi tehdä mitään. Mutta jos uusia käskyjä jäljitellään vanhojen nopeiden käskyjen joukolla niin ominaisuudet voi muodostaa aivan millaiseksi haluaa - esimerkiksi voi tehdä käskyn: printf2 jolla on printf:n ne ominaisuudet jotka ovat tarpeen senhetkisessä hommassa - ja kenties hieman uusiakin ominaisuuksia - ja lopputulos on silti nopea. Mutta vasta monien versioiden jälkeen toiminta on juuri haluttu.

Tässä on tarkoitus ainoastaan matkia sitä kuinka printf kykenee tekemään sisäänmenevät luvut 'saman-näköisiksi' sillä esimerkiksi äsköinen skripti ei enempää kaipaa.  Ensimmäinen versio voisi olla vaikka:
Koodia: [Valitse]
function fprint2 () { luku1=${1##+(0)}; [[ -z "${luku1##*e*}" ]] && { exp1=$((${luku1##*[eE]})); luku1=${luku1//e*/} ;} || exp1=0; [[ ${luku1:0:1} = . ]] && { apu=${luku1%%[1-9]*}; exp1=$(($exp1-${#apu})); luku1=${luku1##*[.0]} ;} || exp1=0 ; luku1=${luku1//./}; echo ${luku1:0:1}.${luku1:1}e$exp1 ;}

# kokeet (luvullahan ei enää ole etumerkkiä):
luku1=1234.56e8; fprint2 $luku1
luku1=1234.56; fprint2 $luku1
luku1=.123456e-8; fprint2 $luku1
luku1=0.123456e-8; fprint2 $luku1
luku1=.0000123456e-8; fprint2 $luku1 # koska printf hyväksyy tämmöisiä kummallisia lukuja niin printf2:n on syytä hyväksyä ihan samanlaisia kummajaisia.
luku1=1; fprint2 $luku1
luku1=11; fprint2 $luku1

***
- nyt meni tuon printf:n kanssa miettimiseksi ja välillä kannattanee tehdä jotakin muuta.

- ei näillä laskuilla ole toistaiseksi muuta tehtävää kuin osoittaa ettei pitäisi sanoa: BASH ei osaa .... -  ja onhan nämä hyvää dementian torjumista.

- jos matemaatikkoja ei päästetä esittämään derivaattoja ja limeksiä niin e:n laskeminen Taylorin sarjakehitelmällä on todella yksinkertaista: e=1+1+1/2!+1/3!+1/4! .....

- sen kykenee laskemaan aivan tavanomaisin keinoinkin, mutta silloin tuloksessa  on kovin vähän numeroita ja laskenta on hidasta:
Koodia: [Valitse]
kerroin() { (( $1 )) && echo $(( $1 * $(kerroin $(($1 - 1))) + 1 )) ;}
summa() { apu=$(echo $(( 10**($1-1) * $(kerroin $1) / $(($(seq -s* $1))) ))) ;}
time for i in {1..11}; do summa $i; done; echo 'e='2.${apu:1}

Mutta desimaalilaskentaa käyttäen e:hen saa paljon enemmän numeroita ja nopeamminkin:


54
BASH:in palauttaminen järkevään kuntoon täytynee alkaa inhottavimmasta päästä: BASH:in matematiikasta. Tai eihän BASH:illa matematiikkaa oikeastaan ole vaan ainoastaan kyky kokonaisluku laskentaan - kyky jota ei koskaan käytetä sillä kyky kyetä käsittelemään myös desimaalilukuja on lähes ehdoton. Desimaalilukujen käsittelemiseksi täytyy siis aluksi tehdä funktiot. Selvitetään ensin laskennan merkintätapaa:

Kaikissa kielissä matemaattiset toiminnot suoritetaan samallatavoin, mutta laskujen suorittamisen merkintätavat vaihtelevat paljon - ja kunkin merkintätavan hyvät ja huonot puolet paljastuvat vasta kun niitä käyttää paljon - mutta useimmat eivät koskaan edes kokeile muita kuin normaalia merkintätapaa.  Mutta muutama laskennan merkintätapa: 

                       Normaali merkintätapa: a=1; b=2; c=a+b
Käänteinen puolalainen merkintätapa: a=1; b=2; c=ab+ 
                        BASH:in merkintätapa: a=1; b=2; c=$(($a+$b)) . Tietokoneohjelmassa merkintätavan monimutkaisuus menettää merkityksensä - vaikka eihän se sentään merkityksetöntä ole.
               
- merkki + kutsuu niissä kaikissa funktiota joka selvittää kuinka homma hoidetaan.
- muissa kielissä laskun jäsenet voivat olla desimaalilukujakin - mutta ei BASH:issa. Joten kun muissa kielissä on desimaalien käsittelyyn jo tehtynä funktioita - käytät niitä vaikka et edes tietäisi että sellaisia tarvitaan - funktiot ovat nykyään usein matematiikkaprosessorien langoituksessa - mutta BASH:iin nuo funktiot täytyy tehdä itse. Ja käytettässsä funktiota laskuihin muuttuu toiminnon merkintätapakin: a=1.1; b=2.2; c=$( ynnää $a $b ) .
- mutta ei sen funktion nimeltä: + hommaa pysty kukaan toinen tekemään joten funktiota: + täytyy kutsua funktiossa: ynnää.

***             
             
Desimaali-laskun suorittamiseksi täytyy ensin laskea missä desimaalipiste tulee tuloksessa olemaan. Tämänjälkeen poistetaan luvuista desimaalipisteet ja suoritetaan laskutoimitus saaduilla kokonaisluvuilla. Sitten vain kirjoitetaan tulos sijoittaen desimaalipiste lasketulle paikalle. Tietokoneiden alkuaikoina oli vain  kokonaislukulaskimia ja haluttessa saada desimaalinen tulos toimittiin kuten edellä kuvattiin.

Kyllä virtuoosit siis ilmanmuuta tämän tiesivät mutta heillä oli jo toinen skriptikieli kiikarissa ja he halusivat tästä uuden skriptaamisen kuninkaaan - sillä BASH oli jo niin mennyttä ja vaikeata. Voidakseen osoittaa BASH:in olevan surkimus desimaalilaskujakin oli estettävä kehittymästä. Helppo nakki - tehtiin uusi käskykanta joka oli muissa tehtävissä parempi kuin entinen mutta desimaalilaskuissa se oli kammottavan hidas. Vanhoilla käskyillä desimaalilaskenta onkin tuhansia? kertoja nopeampaa kuin uusilla käskyillä - siis jos vanhoilla käskyillä sai aikaan millisekunnissa toimivan niin uusilla se kestäisi sekunteja. Tämä johtaa siihen että uusia käskyjä käytettäessä testauksesta tulee niin hidasta että luullaan ettei homma edes toimi.

Noiden vanhojen käskyjen kirjoitusasu on kummallinen joten ne ovat vaikeita kirjoittaa ja muistaakin. Mutta ne ovat nopeita ja ennenkaikkea kääntäjä-ystävällisiä -> kääntämisessä on käytössä  sama cache-menettely kuin kaikessa muussakin. Ja cachen koolla on rajansa ja kun sinne käänetään käskyjä niin noita vanhoja ja pieniä mahtuu sinne tusinoittain mutta uusia ja suuria käskyjä vain muutama. Joten nopeuseroa käskyillä on käsittämättömän paljon sillä uusilla käskyillä tehdyssä skriptissä kutsutaan kääntäjää vähänväliä mutta yksinomaan vanhoista käskyistä tehdyssä vain kerran.

Mutta eivät vaikeudet kirjastokelpoisen funktion valmistumiseen lopu sillä kirjastoahan ei ole. Eikä yksi tyyppi niitä voi tehdä - se olisi ihan liian hidasta ja yksipuolista. Mutta kannattanee yrittää.

***

Akutilanne: haluat laskea: yhteen kaksi korkeintaan 72 numeroista positiivista lukua(merkin huomiooon ottaminen monimutkaistaisi esimerkin vaikeaksi käsittää), desimaalipiste saa kummassakin sijaita missävaan. Toiminta on silloin:
     
       Otetaan esimerkki joka kirjoittaan ( siis desimaalipisteet samalle kohtaa alekkain ja desimaaliosissa pienemmän numerot täytetään nollilla:
       12345678901234567890.2000000000000000000000000000
                        +73.4567890123456789012345678901
     
Sitten vaan laskemaan - desimaalipisteen paikka pistetään muistiin esimerkiksi sen perusteella kummassa on kokonaisoasassa enemmän numeroita - desimaalipiste poistetaan ja jaetaan loppu18 merkin palasiin lopusta alkaen ja lasketaan palaset yhteen alkaen vähiten merkityksellisestä päästä ja mikäli jonkun palasen summaan tuleekin 19 numeroa niin se viimeinen numero lisätään seuraavaan palaseen poistaen se alkuperäisestä palasesta. Sitten vain laitetaan palaset peräkkäin ja kirjoittetaan oikealla hetkellä desimaalipistekin.
Koodia: [Valitse]

function summaa72 () {
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia ja vertailutulos bc:stä. Vaihtoehdot:tulosta=echo ja tulosta=:
$tulosta

[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1".0"
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2".0"
desi1=${luku1##*.}
desi2=${luku2##*.}
koko1=${luku1%%.*}
koko2=${luku2%%.*}

# desimaaliosien tulee olla yhtäpitkät. Lyhyemmän perään kirjoitetaan nollia silä ne eivät muuta arvoa.
(( ${#desi1} >= ${#desi2} )) && desipituus=${#desi1} || desipituus=${#desi2}
desi1=$desi1'0000000000000000000000000000000000000000000000000000000000000000000000000000'
desi2=$desi2'0000000000000000000000000000000000000000000000000000000000000000000000000000'
desi1=${desi1:0:$desipituus}; $tulosta desi1:$desi1
desi2=${desi2:0:$desipituus}; $tulosta desi2:$desi2 ;$tulosta

summattava1='0000000000000000000000000000000000000000000000000000000000000000000000000000'$koko1$desi1; summattava1=${summattava1: -72}; $tulosta $summattava1
summattava2='0000000000000000000000000000000000000000000000000000000000000000000000000000'$koko2$desi2; summattava2=${summattava2: -72}; $tulosta $summattava2 ; $tulosta

luku11=${summattava1:0:18}; luku12=${summattava1:18:18}; luku13=${summattava1:36:18}; luku14=${summattava1:54:18}; $tulosta $luku1{1..4}"  "
luku21=${summattava2:0:18}; luku22=${summattava2:18:18}; luku23=${summattava2:36:18}; luku24=${summattava2:54:18}; $tulosta $luku2{1..4}"  "; $tulosta

apu14=$((10#$luku14+10#$luku24)) ;          [[ ${#apu14} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto1; apu14=${apu14:1:18} ;} || ylivuoto=0
apu13=$((10#$luku13+10#$luku23+$ylivuoto)); [[ ${#apu13} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto2; apu13=${apu13:1:18} ;} || ylivuoto=0
apu12=$((10#$luku12+10#$luku22+$ylivuoto)); [[ ${#apu12} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto3; apu12=${apu12:1:18} ;} || ylivuoto=0
apu11=$((10#$luku11+10#$luku21+ylivuoto))
apu=$apu11$apu12$apu13$apu14

$tulosta "ylemmällä rivillä varmasti oikea tulos bc:stä ja toisella rivillä mitä tämä skripti antaa:"
bc<<<"$1+$2" | tr -d ' \\\n'; echo
# poistetaaan etunollat kokonaisosasta, takanollat desimaaliosasta ja desmaalipistekin jos se jää viimeiseksi
apu2=${apu:0: -$desipituus}.${apu: -$desipituus}; apu2=${apu2##+(0)}; [[ ${apu2//[^.]/} ]] && apu2=${apu2%%+(0)}; echo ${apu2%.} ;}
   
time summaa72 55555555555555555555555555555.55555555555555555555555555555555555555555 6666666666666666666666666666666.6666666666666666666666666666666666666666

- ei tämä kyllä vielä ihan moitteeton ole mutta kelpaaa jo käyttöönkin.
- funktion nopeus on 1ms jos poistaa rivin: $tulosta $(bc<<<"$1+$2") - sillä tuota riviä tarvitaan vain antamaan varmasi oikea tulos vertaamista varten sillä onhan bc todettu varmasti oikeelliseksi ja se on varmasti myös tarpeeksi tarkka. Mutta toiminnataan tämä funktio on paljon nopeampi kuin bc.

***

Olisihan näihin BASH:in matemattisiin ongelmiin ihan yleisestikin käytettyjä menetelmiä niinkuin bc ja AWK. Mutta ihan mielenkiinnosta koetan mihin BASH itse pystyy - ja sitäpaitsi pienissä perus-laskutoimituksissa BASH on jo paras. Ja mikäli BASH:in funktioista tehtäisiin kirjastofunktiot niin ne olisivat myös yksinkertaisimmat

BASH:in kehittäjät olivat aikoinaan toteuttaneet toimivat menetelmät moitteettomiin peruslaskuihin ja virtuoosit ovat suunnanneet nuo menetelmät lattian alle. Nimittäin kaikki haluavat päästä BASH:ista eroon sillä BASH laajeni aikoinaan niinpaljon etteivät edes virtuoosit sitä enää täysin hallitse - ei semmoista voi korjata vaan ainoastaan pyrkiä siitä eroon. Ja mikäli osoitettaisiin että BASH hallitsee myös desimaalit olisi siitä paljon hankalampaa päästä eroon.
- siis tosiaan kymmenistätuhansista asioista on tehty paksuja raamattuja täynnä käskyjä ja menetelmien selityksiä. Kenelläkään ei ole pienintäkään mahdollisuutta edes lukea kaikkia niitä - ja nykyäänhän niistä on moni historian hämärissä.

Alkuunsa kielen matemaattisia kykyjä hiotaan suorittamalla "näyttäviä laskuja". Muut kielet harrastivat näitä "näyttäviä laskuja" kolmekymmentävuotta sitten - sillä ne on pakko läpikäydä joskus. Vaikka BASH on kyllä taitanut ne jo silloin kauan sitten niin kukaan ei ole esittänyt toimivaa koodia - joten niitä täytyy esittää nyt:

Tarvitsin taulukkoa jonka ensimmäinen jäsen on .5 ja seuraavat jäsenet aina puolta pienempiä kuin edellinen. Toteutus BASH:illa onnistui hyvin - lukujen tarkkuus oli 26 numeroa ja siihen tarvittiin 86 kahdella jakamistaja. Koko 86:n laskun sarja kesti noin 10 ms - BASH:ilta se on nopeaa vaikka muilla kielillä se onkin hidasta. Skripti on näennäisesti iso, mutta muissa kielissä vastaavat ohjelmat ovat paljon isompia, mutta niissä toiminta tapahtuu kirjastoissa, siis katseelta piilossa. Skriptistä tuli tällainen:
Koodia: [Valitse]

time { number=.1; for n in {1..86}; do apu=${number%%[1-9]*}; apu2=${number:${#apu}}'00000000000000000';apu2=${apu2:0:18}; number=$(($apu2/2))'00000000000000'; (( ${apu2:0:1} == 1 )) && number=0$number; number=${number%%+(0)}; number=$apu$number; echo $n; bc<<<"scale=26;1/(2^$n)" | tr -d '\\\n'; echo; echo .${number:2:26}; echo; done ;}
- bc tarjoaa taas vain vertailuarvon. Mikäli nopeutta haluaa niin se on poistettava samoin kun turhat echot
- tämä on kokoajan melkoisessa kehitysvaiheessa

55
***

Nämä skriptini ovat yritystä löytää BASHin perusta - skriptit ovat mielettömiä mutta kukaan ei voi tietää millaisiin tuloksiin ne johtavat. Ja muuten: kaikki nopeat käskyt briljanteraa tekstinkäsittelyssä vaikka on niistä apua laskennassakin.

BASH:issa on kammottava määrä käskyjä joista ei koskaan puhuta. Ne ovat nopeita ja tekevät pieniä ihmeitä. Esimerkiksi käskyt seuraavan skriptin tulostukseen: poistetaaan etunollat kokonaisosasta, takanollat desimaaliosasta ja desmaalipiste silloin kun se jää viimeiseksi:
Koodia: [Valitse]
apu2=${apu2##+(0)}; [[ ${apu2//[^.]/} ]] && apu2=${apu2%%+(0)}; echo ${apu2%.}
Toiminto on salaman-nopea ja se on ollut olemassa aina mutta jokainen joka noita toimintoja nykyisin tarvitsee tekee varmasti toimintoa varten pitkän ja hitaan skriptin - ja vain koska 'advance guidet' pitävät huolta siitä että noiden kaltaiset käskyt pysyvät unholassa. Ei niistä tosin paljoa BASH-raamatuissakaan puhuta. Mutta netti ei koskaan unohda ja sinne kerkisi kauan sitten tulla monia kirjoituksia niistä - tosin noiden käskyjen nopeudesta ei ole hyötyä jos niiden ohella käyttää uusi käskyjä. Eikä missään ei ole kattavaa yhteenvetoa.

Tehdessäni skiptiajuria kohtasin toisenlaisia ihmeellisiä käskyjä sllä se on pääasiassa tekstikäsittelyä - taas aivan toisenlainen maailma. Montakohan maailmaa BASHissa on?

***

Koska nyt  haukun virtuooseja pystyyn niin halusin varmistaa että haukun toimivien asioiden perusteella - tälläkinkertaa on kyse desimaalimatematiikasta - mutta tätä ennenhän on ollut kaikenlaista muuta - paljon merkityksellisempääkin. Jokatapauksessa tein taas yhden mielettömän skriptin jossa on runsaasti moninumeroista laskentaa - mutta toisenlaista laskentaa kuin aikaisemmissa skripteissä joten uudetkin laskut tulee testattua. BASH itse laskee muuten montakertaa nopeammin kuin matematiikka-ohjelmat joten niitä ei laskennassa käytetä - varsinaisessa matematiikassa BASH jää kyllä paljon huonommaksi ja sitähän tässä koetetaan nopeuttaa - toistaiseksi huonosti menestyen. Mutta kyse ei tälläkertaa ole varsinaisesti matematiikasta vaan siitä että soiminto ylipäätään onnistuu jollaintavalla - se että se toimii näinkin hyvin on kylläkin mukavaa.

Skripti muuntaa digitaaliluvun desimaaliosan kymmenjärjestelmään. Esimerkiksi luvun .11100000001000000010000000010000000010000000100000001001 muuntaminen kymmenjärjestelmään:
skripti:                          0.875490192331227615183197488403320317865025843484497
netin muunnosmoottori: 0.87549019233122761519

- onko kyseessä pyöristysvirhe vai laskeeko muunnosmoottori liian vähillä numeroilla? Sillä 'tarkkuuskato' näissä laskuissa on niin suuri että saa pelätä ettei tuo skriptinkään numeromäärä vielä riitä vaan kaikki nuo ylimääräiset numerot ovat jo potaskaa - ne ovat tuossa vain siksi että näkisitte paljonko niitä on.
Koodia: [Valitse]

function summaa72 () { # viimeisin versio
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia ja haetaanko vertailutulos bc:stä. Vaihtoehdot:tulosta=echo ja tulosta=: . Vaatii siis yksinkertaisen koodimuutoksen.
$tulosta
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1".0"
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2".0"
desi1=${luku1##*.}
desi2=${luku2##*.}
koko1=${luku1%%.*}
koko2=${luku2%%.*}

# desimaaliosien tulee olla yhtäpitkät. Lyhyemmän perään kirjoitetaan nollia.
(( ${#desi1} >= ${#desi2} )) && desipituus=${#desi1} || desipituus=${#desi2}
desi1=$desi1'0000000000000000000000000000000000000000000000000000000000000000000000000000'
desi2=$desi2'0000000000000000000000000000000000000000000000000000000000000000000000000000'
desi1=${desi1:0:$desipituus}; $tulosta desi1:$desi1
desi2=${desi2:0:$desipituus}; $tulosta desi2:$desi2 ;$tulosta

summattava1='0000000000000000000000000000000000000000000000000000000000000000000000000000'$koko1$desi1; summattava1=${summattava1: -72}; $tulosta $summattava1
summattava2='0000000000000000000000000000000000000000000000000000000000000000000000000000'$koko2$desi2; summattava2=${summattava2: -72}; $tulosta $summattava2 ; $tulosta

luku11=${summattava1:0:18}; luku12=${summattava1:18:18}; luku13=${summattava1:36:18}; luku14=${summattava1:54:18}; $tulosta $luku1{1..4}"  "
luku21=${summattava2:0:18}; luku22=${summattava2:18:18}; luku23=${summattava2:36:18}; luku24=${summattava2:54:18}; $tulosta $luku2{1..4}"  "; $tulosta

apu14=$((10#$luku14+10#$luku24)) ;          [[ ${#apu14} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto1; apu14=${apu14:1:18} ;} || ylivuoto=0
apu13=$((10#$luku13+10#$luku23+$ylivuoto)); [[ ${#apu13} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto2; apu13=${apu13:1:18} ;} || ylivuoto=0
apu12=$((10#$luku12+10#$luku22+$ylivuoto)); [[ ${#apu12} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto3; apu12=${apu12:1:18} ;} || ylivuoto=0
apu11=$((10#$luku11+10#$luku21+ylivuoto))
apu=$apu11$apu12$apu13$apu14

$tulosta "ylemmällä rivillä varmasti oikea tulos bc:stä ja toisella rivillä mitä tämä skripti antaa:"
$tulosta $(bc<<<"$1+$2")
# poistetaaan etunollat kokonaisosasta, takanollat desimaaliosasta ja desmaalipiste jos se jää viimeiseksi
apu2=${apu:0: -$desipituus}.${apu: -$desipituus}; apu2=${apu2##+(0)}; [[ ${apu2//[^.]/} ]] && apu2=${apu2%%+(0)}; echo ${apu2%.} ;}

function jaa () { (( ! $# )) && echo funktion ajokäsky on muotoa: jaa 1 2 . Siitä pitää tulla: .5000000000000000000000000000 && sleep 2 && return
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
 
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1"."
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2"."

desimaaliosa1=${luku1##*.}
desimaaliosa2=${luku2##*.}
kokonaisosa1=${luku1%%.*}
kokonaisosa2=${luku2%%.*}

#desimaaliosat yhtäpitkiksi:
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
$tulosta $kokonaisosa1$desimaaliosa1
$tulosta $kokonaisosa2$desimaaliosa2

#kokonaisosat yhtäpitkiksi:
(( ${#kokonaisosa2} >= ${#kokonaisosa1} )) &&
{ apu="00000000000000000000"$kokonaisosa1; kokonaisosa1=${apu: -${#kokonaisosa2}} ;} || { apu="00000000000000000000"$kokonaisosa2; kokonaisosa2=${apu: -${#kokonaisosa1}} ;}
luku1=$kokonaisosa1$desimaaliosa1;luku1=$((10#$luku1))
luku2=$kokonaisosa2$desimaaliosa2;luku2=$((10#$luku2))
$tulosta jaettava:$luku1"   "jakaja:$luku2

(($luku1 >= $luku2)) && { apu=$(($luku1/$luku2)); kokonaisiatulosteessa=${#apu}; nolliatulosteessa='' ;} || { apu=$(($luku2/$luku1)); apu=${apu//[1-9]/0}; nolliatulosteessa=${apu:1}; kokonaisiatulosteessa=0;}; $tulosta nolliatulosteessa:$nolliatulosteessa"   "kokonaisiatulosteessa:$kokonaisiatulosteessa   

# tähänasti on selvitetty desimaalipisteen paikkaa. Nyt aletaan laskea numeroita.
unset tulos # vain varmistus että kaikki on tuloksessa tämänjälkeen uutta
luku1=${luku1##+(0)}; [[ ${luku1//[^.]/} ]] && luku1=${luku1%%+(0)} # poistetaan etu-ja takanollat sillä tästälähtien ne vain haittaavat.!!!
luku2=${luku2##+(0)}; [[ ${luku2//[^.]/} ]] && luku2=${luku2%%+(0)} 
q=$(($luku1%$luku2));((q>1)) && q=$((10**$((${#q}-2)) )) || q=1 # siis q on jakojäännös esitettynä siten että ensimmäinen numero on 1 ja lopuista numeroista tulee nollia - viimeinen nolla poistettuna
luku1=$luku1'0000000000000000000'; luku1=${luku1:0:18} ;$tulosta " "$luku1 #"  "${#apu2}"  "$apu

for n in {1..6}; do # muodostetaan tulos-palasia 
apu=$(($luku1/$luku2)); tulos[$n]=${apu}; (( $(($luku1%$luku2)) > $((10*$q )) )) && apu=$apu'0' # on joskus tarpeen ja joskus liikaa
apu2=$(($luku1%$luku2)); luku1=$apu2'0000000000000000000'; luku1=${luku1:0:18} ; $tulosta " "$luku1"  "${#apu2}"  p"$apu
done

vanhatmerkit=1
for n in {1..6}; do # kootaan tulosta matriisin palasista
uudetmerkit=${#tulos[$n]}; [[ $uudetmerkit -lt $vanhatmerkit ]] && tulos[$n]=$nolliatulosteessa${tulos[$n]} ; vanhatmerkit=$uudetmerkit
tulos=$tulos${tulos[$n]}
done
 
#[[ ${tulos%%+(0)} -eq 1 ]] && nolliatulosteessa=${nolliatulosteessa:1} # purkkaviritys?
[[ $nolliatulosteessa ]] && echo .$nolliatulosteessa${tulos:0} || echo ${tulos:0:$kokonaisiatulosteessa}.${tulos:$kokonaisiatulosteessa} ;}

function muunna () { local apu7; apu7=$1;luku=0; for n in $(seq $((${#apu7}+1))); do  (( ${apu7:$n-1:1} )) && luku=$(summaa72 $luku ${taulu[$n+1]});done ; echo $luku ;} 

unset taulu;taulu[1]=1;apu=1;for n in {2..57}; do apu=$((2*$apu)); apu8=$(jaa 1 $apu); taulu[$n]=${apu8%%+(0)};  echo $n"  "${taulu[$n]}; done;   # taulukon muodostaminen

muunna 111 # esimerkkikutsu
 

***

Tässä ei varsinaisesti ole kyse desimaalimatematiikasta vaan kaikenmaailman guidejen saattamisesta naurunalaiseksi - ne apinoivat kaikki samoja rajoittuneita alkeita.. Kyllä ne silti on syytä oppia mutta niissä ei tosiaankaan ole mitään edistynyttä - ja valitettavasti tilanne on tällähetkellä se ettei mitään parempaakan ole. BASH raamatutkin ovat kovin yksipuolisia ja niistä on vaikea oppia mitään. BASH-maailma on ainakin kymmeniä kertoja suurempi kuin mitä ne antavat uskoa.

Ja desimaalimatematiikka ei ainoastaan toimi vaan on lisäksi salaman-nopeaa - alle 1ms.

Voitte olla varmoja että heitä kirpaisee jos tämä joskus heidä kurkustaan alas tungetaan - sillä homma on karmea: tappoivat oman lapsensa. Sillä tuo nopeutus ei koske yksinomaan  matematiikkaa vaan myös tekstinkäsittelystä tulee samoilla käskyillä erittäin nopeaa.

Ja vaikka ilmanmuuta koettavat vaieta desimaalilaskennan kuoliaaksi niin minkäs sille voi että tämä toimii. Syy skriptin esittämiseen onkin se ettei tällaisia voi puhua esittämättä kuinka homma hoidetaan.

Aikoinaan  BASH oli parhaimmillaan koeteltaessa toimiiko joku teoria käytännössä - syy BASH-skriptin tekoon oli selvittää kannattaako tehdä kunnollinen ohjelma jollain kunnon kielellä - nimittäin BASH:illa saa nopeasti tehtyä välttävästi toimivan skriptin. Mutta siinä hommassa säällisen nopeat peruslaskutoimitukset desimaali-luvuilekin ovat melkein välttämättömiä. Desimaalilaskuihin käytettiinkin matematiikkaohjelmaa, esimerkiksi bc:tä sillä luultiin ettei BASH desimaalilaskuihin kykene. Mutta kyllä kykenee ja pienehköissä peruslaskutoimituksissa BASH on jopa paljon nopeampi.

Desimaalilaskut suoritetiin silloin aikoinaan kokonaisluvuilla isoissakin koneissa - joten kyllä senaikaiset virtuoosit tiesivät että myös BASH kykenee niihin. Mutta he välttelivät puuttumasta koko asiaan sillä se on sellainen salahautojen viidakko että paraskin virtuoosi haksahtaisi joskus ja sitä he eivät kestä - joutua naurettavuuden kaakinpuuhun. Niinpä he eivät puhuneet koko asiasta - mikä antaa sellaisen mielikuvan etteivät desimaalilaskut BASH:issa edes onnistu. Ja oli heillä laho selityskin: BASH:in matematiikkamoottori ei osaa desimaalilaskuja eikä BASH:in muuttujien tyyppimäärityksissäkään desimaaliluvuista mitään puhuta. Mutta tuo tyyppimääritys on tahallinen väärinymmärrys sillä tekstijonojahan ne desimaaliluvut vain ovat.
 
Mutta siis niitä salahautoja on: jakolaskua vaivaa esimerkiksi 'katoavien nollien syndrooma'. Ja kun nolla katoaa on senjälkeinen tulos pelkkää potaskaa. Tämän korjaaminen on henkimaailmasta: jakolasku ei esitä etunollia -> jos jakolaskun tulos on 010 (esimerkiksi 100/10) niin näytetään vain 10. Mutta vaikka tähän löytyikin korjaus niin jakolaskua täytyy vielä kauan testailla ja kenties krjailla.

Mutta kyse ei ole mitättömän piennistä jakolaskuista vaan esimerkiksi: jaa .123456 1233457890123.23  josta tulos on: .00000000000010008935123651930204696 - joka on kyllä ihan oikein - ei niitä kadonneita nollia silloin löydä kun etsii niitä.

- itse skripti vie aikaa alle millisekunnin. bc tässä sitä aikaa vie laskiessaaan varmasti oikeaa arvoa johon verrata tämän skriptin tulosta. Kommentoi bc pois niin näet.

Koodia: [Valitse]
function jaa () { (( ! $# )) && echo funktion ajokäsky on muotoa: jaa 1 2 . Siitä pitää tulla: .5000000000000000000000000000 && sleep 2 && return
 
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1"."
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2"."

desimaaliosa1=${luku1##*.}
desimaaliosa2=${luku2##*.}
kokonaisosa1=${luku1%%.*}
kokonaisosa2=${luku2%%.*}

#desimaaliosat yhtäpitkiksi:
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
#echo $kokonaisosa1$desimaaliosa1
#echo $kokonaisosa2$desimaaliosa2

#kokonaisosat yhtäpitkiksi:
(( ${#kokonaisosa2} >= ${#kokonaisosa1} )) &&
{ apu="00000000000000000000"$kokonaisosa1; kokonaisosa1=${apu: -${#kokonaisosa2}} ;} || { apu="00000000000000000000"$kokonaisosa2; kokonaisosa2=${apu: -${#kokonaisosa1}} ;}
luku1=$kokonaisosa1$desimaaliosa1;luku1=$((10#$luku1))
luku2=$kokonaisosa2$desimaaliosa2;luku2=$((10#$luku2))
echo jaettava:$luku1"   "jakaja:$luku2

(($luku1 >= $luku2)) && { apu=$(($luku1/$luku2)); kokonaisiatulosteessa=${#apu}; nolliatulosteessa='' ;} || { apu=$(($luku2/$luku1)); apu=${apu//[1-9]/0}; nolliatulosteessa=${apu:1}; kokonaisiatulosteessa=0;}

echo nolliatulosteessa:$nolliatulosteessa"   "kokonaisiatulosteessa:$kokonaisiatulosteessa   

# tähänasti on selvitetty desimaalipisteen paikkaa. Nyt aletaan laskea numeroita.
unset tulos # vain varmistus että kaikki on tuloksessa tämänjälkeen uutta
luku1=${luku1##+(0)}; luku1=${luku1%%+(0)} # poistetaan etu-ja takanollat sillä tästälähtien ne vain haittaavat.
luku2=${luku2##+(0)}; luku2=${luku2%%+(0)} 
# seraava oli: q=$(($luku1%$luku2));q=$((10**$((${#q}-1)))) # siis q on jakojäännös esitettynä siten että ensimmäinen numero on 1 ja lopuista numeroista tulee nollia - viimeinen nolla poistettuna
q=$(($luku1%$luku2));((q>1)) && q=$((10**$((${#q}-2)) )) || q=1 # siis q on jakojäännös esitettynä siten että ensimmäinen numero on 1 ja lopuista numeroista tulee nollia - viimeinen nolla poistettuna
luku1=$luku1'0000000000000000000'; luku1=${luku1:0:18} ;echo " "$luku1 #"  "${#apu2}"  "$apu
for n in {1..6}; do # muodostetaan tulos-palasia 9 merkkiä kerrallaan
apu=$(($luku1/$luku2)); (( $(($luku1%$luku2)) < $q )) && apu=$apu'0'; tulos[$n]=${apu}  # yksi keino palauttaa niitä kadonneita nollia;  (( $(($luku1%$luku2)) > $((10*$q )) )) && apu=$apu'0' on joskus tarpeen ja joskus liikaa
apu2=$(($luku1%$luku2)); luku1=$apu2'0000000000000000000'; luku1=${luku1:0:18} ;echo " "$luku1"  "${#apu2}"  "$apu
done

vanhatmerkit=1
for n in {1..6}; do # kootaan tulosta matriisin palasista
uudetmerkit=${#tulos[$n]}; [[ $uudetmerkit -lt $vanhatmerkit ]] && tulos[$n]='0'${tulos[$n]} ; vanhatmerkit=$uudetmerkit
tulos=$tulos${tulos[$n]}
done
 
[[ ${tulos%%+(0)} -eq 1 ]] && nolliatulosteessa=${nolliatulosteessa:1} # purkkaviritys?
echo "oikea tulos 54 desimaalilla esitetynä on päällä ja alla tulos tästä skriptistä"
bc<<<"scale=${#tulos}; $1/$2" | tr -d '\\\n'; echo ' tämä rivi on bc:stä'
[[ $nolliatulosteessa ]] && echo .$nolliatulosteessa${tulos:0} || echo ${tulos:0:$kokonaisiatulosteessa}.${tulos:$kokonaisiatulosteessa} ;}

# Seuraavia on kokeiltu:
         
time jaa 1233457890123.23 .123456
time jaa .123456 1233457890123.23
time jaa 1 2
time jaa 2 1
time jaa 1 3
time jaa 1 1000
time jaa 1 1001
***
 Nämä skriptini ovat satoja kertoja nopeampia kuin skriptit samoissa laskuisssa olisivat uusilla käskyillä olleet jos olisi tajuttu kuinka ne tehdään - mutta silti skriptieni nopeudessa ei vielä ole kehumista. Mutta kukaan ei voi sanoa mihin nämä päätyvät - jos tämä tahti jatkuu niin kaikki toiset skriptikielet saavat turpiinsa jo vuoden kuluttua. Eivätkä skriptini matematiikkaa tee vaan pääasiassa tekstinkäsittelyä.

Koska BASH:illa ei ole kirjastoja niin näitä nopeita skriptejä valmistuu hyvin hitaasti. Sillä kirjastot ovat täysin välttämättömät kaikille kielille sillä niissä on ylensä paljon toimintoja joista jotkut ovat todella pitkiä ja monimutkaisia - ja kirjastojen oikeellinen toiminta on jo testattu joten ei sitä skriptaajan täydy enää paljoakaan testata. Ja kun skriptin teko kestää hetken niin sen testaaminen kestää silti iäisyyksiä.

Tässä on kyseessä liukuvan pilkun desimaalilukujen kertolasku jonka tuloksessa saaa olla korkeintaan 18 numeroa - ja skriptin toteutus on aikatavalla toisenlainen kuin aikaisemmissa skripteissä - ja jonka pahin puute on sen vähäinen numeromäärä - eikä se edes ole juurikaan nopeampi kuin matematiikka-ohjelmat. Mutta siihen saisi paljon lisää nopeutta esikääntämisellä jota aikoinaan harrastettiinkin - matematiikka-ohjelmiin ei saa lisänopeutta.

Jokatapauksessa skriptistä saa käsitystä siitä kuinka sriptit pitäisi koota sillä virtuoosit eivät sitä kerro - ja eivät halua toistenkaan kertovan? - mikä on todella omituista sillä aikoinaan BASH:ia käytettiin suunnattoman paljon ja sillä oli monia todella kuuluisia ja päteviä virtuooseja. Täytynee olla niin että sana: 'kokonaislukulaskenta' saa kaikki lopettamaan ajattettelemisen.

Tottakai skripteissäni on reikiä ja ne tarvitsevat korjauksia - mutta sen käsityksen ne varmistavat että virtuoosit jostain syystä estävät BASH:ia kehittymästä.
Koodia: [Valitse]
function kerro9 () { # syötettävissä saa olla korkeintaan 9 merkitsevää desimaalia jolloin tuloksessa voi olla 18 merkitsevää desimaalia.
luku1=${1//./,}
luku2=${2//./,}
luku1=$(printf "%.19e" $luku1)
luku2=$(printf "%.19e" $luku2)
exp1=${luku1##*e}; exp1=${exp1//+0/+}; exp1=${exp1//-0/-}
exp2=${luku2##*e}; exp2=${exp2//+0/+}; exp2=${exp2//-0/-}
exp3=$(($exp1+$exp2+1))

luku1=${luku1%%e*}; luku1=${luku1%%+(0)}
luku2=${luku2%%e*}; luku2=${luku2%%+(0)}

[[ ${luku1//[^-]/} ]] && { merkki1=-1; luku1=${luku1:1} ;} || merkki1=1
[[ ${luku2//[^-]/} ]] && { merkki2=-1; luku2=${luku2:1} ;} || merkki2=1 

kokonaisosa1=${luku1%%.*}
kokonaisosa2=${luku2%%.*}
apu=$(($kokonaisosa1*$kokonaisosa2))
kokonaisia=${#apu}

tulos=$((${luku1//,/}*${luku2//,/})) # siis lasketaan mitkä tuloksen merkittävät numerot osvat ja esitetään ne sitten erikseen.
bc<<<"scale=60; $1*$2" # bc tarjoaa vain varmasti oikean tuloksen johon tämän skriptin tulosta voi verrata - laskuihin bc ei osallistu. Voit vaikka pyyhkiä tämän rivin pois.
[[ $exp3 -gt -1 ]] && { [[ $(($merkki1*$merkki2)) -eq -1 ]] && echo -n '-'; echo ${tulos:0:$exp3}.${tulos:$exp3} ;} ||  { [[ $(($merkki1*$merkki2)) -eq -1 ]] && echo -n '-'; echo .$(printf '0%.s' $(seq ${exp3:1}))${tulos%%+(0)} ;} ;}


# esimerkkikutsuja
kerro9 1234567891 1234567892
kerro9 123456789012345678 2.2
kerro9 123.4567891 1234567.89
kerro9 1234567891 1234567892E-18                                # kerrottavien esitysmuoto saa olla tieteellinenkin (bc ei selviä tästä)
kerro9 .000000000000123456789 -.00000000123456789 # desimaaliosissa saa olla etunollia kuinkamonta vaan sillä ne eivät ole merkitseviä numeroita.

56
Epäilemättä - ja on kiva että kirjoitat mikä muiden taso on sillä se kiinostaa mutta en kerkiä tietoa etsimään.

ja pitää täysin paikkansa että koska BASH:ia ei edes päivitetä kuin kerran vuodessa niin siitä on tullut surkea rääpäle jos se on koskaan varsinaisesti ohjelmointiin sopinutkaan. Ja minä nousen barrikaadeille surkeiden rääpäleiden puolesta - varsinkin kun niitä haukutaan väärin perustein.

Useimmat esittämäni skriptit ovat pelkälle BASH:ille täysin mahdottomia - siis ilman apua bc:ltä, awk:ilta tai joltain muulta. Ja kun kyseessä on jo alkuunsa mahdottomuus niin kuka voi sanoa mihin homma päätyy? Eikö se ole jo aikahyvä barrikaadi? 

Esimerkiksi juuri nyt olen kirjoittamassa logaritmien laskentaa uusiksi. Nopeus nousi yli kymmenkertaiseksi - ja sitäpaitsi se on nimeltäänkin raflaava: logaritmit binäärijärjestelmässä. Vielä uusi kymmenkertaistuminen ja bc alkaa olla vaikeuksissa. Yksinkertaisissa laskuisa se onkin 4 kertaa hitaampi.

***

Logaritmien laskeminen binäärijärjestelmässä:
- tämä skripti ei laske bc:n avulla - siis bc:n voi vaikka pyyhkiä koodista pois skriptin antaman tuloksen muuttumatta.

Logaritmin laskeminen nopeutui 17:sta millisekuntiin. Vaikka se on hidas, on sikäli mielekäs että se näyttää desimaalilaskujen toimivan ja opettaa kuinka logaritmit lasketaan - onhan BASH opetuskieli - kyllä kaikkiin muihinkin laskuihin löytyy montakin erilaista ratkaisumenetelmää - monimutkaisen näköisiä sillä ne ovat matalan-tason koodia mutta itseasiassa ne ovat helppoja. Juuri tämä onkin syy tämän skriptin kehittämiseen sillä se tosiaan osoittaa että BASH:illa on hampaat: miksi virtuoosit uskottelevat että BASH on hampaaton?

Teen tämmöisiä kummajais-skriptejä osoittaakseeni että kyllä BASH desimaalit hallitsee ja on siinäkin suhteessa ihan kelpo kieli prototyyppien tekemistä varten - tämä skripti kertoo lisäksi kuinka logaritmit lasketaan sillä logaritmeilla on kyllä hieman käyttöä edelleenkin. Skripti on kylläkin hidas mutta ei kai kukaan 17:sta millisekunnista hermostu?

Se että logaritmit lasketaan vain luvuista lukualueella 1.xxx....99.xxx... johtuu siitä tosiasiasta että logaritmit määritellään teoriassa aina tieteellisestä esitysmuodosta. Siis luvut saatetaan tälle lukualueelle muuttamalla eksponenttia siten että eksponentti on aina parillinen. Eksponentti ei osallistuu laskuun van ainoastaan määrää logaritmin ensimmäisen numeron. Tuolla perusalueellahan exponentti on nolla joten ensmmäiseen numeroon ei lisätä mitään. Tämä oikealle alueelle saattaminen on päässälasku.

Tämä logaritmien laskemiseen soveltuva skripti ei onnistu ilman nopeaa desimaalilaskentaa.
 
- jos haluat kokeilla toimiiko skripti niin leikkaa-liimaa skriptin jokainen rivi päätteeseen yhdellä kerralla - sillä toimivat skriptit niinkin.

Koodia: [Valitse]
function 2to10 () { # desimaalinen binääriluku (esim. 1.00101) muutetaan kymmenjärjestelmään:
taulu[1]=1;apu=1;for n in {2..60}; do apu=$((2*$apu)); taulu[$n]=$apu; done
apu=${1//./}; apu2=${#apu}
pilkunpaikka=$2
luku=0; for n in $(seq 0 $apu2); do (( ${apu:$n:1} )) && luku=$(($luku+1000000000000000000/${taulu[$n+1]} )); done                                                     
echo ${luku:0:$pilkunpaikka}.${luku:$pilkunpaikka} ;}               

function getlog ()  { # logaritmi määritellään binäärijärjestelmään
kokonaisosa=${1%%.*}; (($kokonaisosa>10)) && pilkunpaikka=1 || pilkunpaikka=0; (($kokonaisosa>100)) && pilkunpaikka=2
[[ ${apu//[^.]/} ]] && vanhalogaritmoitava=$1 || vanhalogaritmoitava=$1".0"
logaritmoitavankokonaisosa=${vanhalogaritmoitava%%.*}
logaritmi=$((${#logaritmoitavankokonaisosa}-1))
until (( ${#logaritmi}>=60 )); do
  apu=${vanhalogaritmoitava//./}; apu=${apu:0:1}.${apu:1}
  luku1=${apu:0:18}
  luku2=${apu:0:18}
  desimaaliosa1=${luku1##*.}
  desimaaliosa2=${luku2##*.}; desimaaleja=$((${#desimaaliosa1}+${#desimaaliosa2}))
  luku1=000000000000000000${luku1//./}
  luku2=000000000000000000${luku2//./}
  a=${luku1: -18:9}; b=${luku1: -9}
  c=${luku2: -18:9}; d=${luku2: -9}
  luku1=00000000000000000000000000000000000000$((10#$b*10#$d))
  luku2=00000000000000000000000000000000000000$((10#$d*10#$a))"000000000"
  luku3=00000000000000000000000000000000000000$((10#$c*10#$b))"000000000"
  luku4=00000000000000000000000000000000000000$((10#$a*10#$c))"000000000000000000"
  luku1=${luku1: -36}
  luku2=${luku2: -36}
  luku3=${luku3: -36}
  luku4=${luku4: -36}
  luku11=${luku1:0:18}
  luku12=${luku1:18}
  luku21=${luku2:0:18}
  luku22=${luku2:18}
  luku31=${luku3:0:18}
  luku32=${luku3:18}
  luku41=${luku4:0:18}
  luku42=${luku4:18}
  summa1=$((10#$luku12+10#$luku22+10#$luku32+10#$luku42))
  summa1pituus=${#summa1}; ylivuoto=0; (( $summa1pituus>=19 )) && ylivuoto=${summa1:0: -18} && summa1=${summa1:1}
  summa1=000000000000000000$summa1; summa1=${summa1: -18}
  summa2=$((10#$luku11+10#$luku21+10#$luku31+10#$luku41+$ylivuoto))
  apu=$summa2$summa1; (( $desimaaleja )) &&  uusilogaritmoitava=$((10#${apu:0: -$desimaaleja})).${apu: -$desimaaleja} || {  uusilogaritmoitava=$(( 10#$summa2 ))$summa1 ;}
  uudenlogaritmoitavankokonaisosa=${uusilogaritmoitava%%.*}
  logaritmi=$logaritmi$((${#uudenlogaritmoitavankokonaisosa}-1))
  vanhalogaritmoitava=$uusilogaritmoitava
done # tämä päättää se ryhmän joka alkaa käskystä: until
2to10 $logaritmi $pilkunpaikka ;}

logaritmoitava=17.3517; echo -e "\n\nLasketaan logaritmi luvusta: $logaritmoitava\nseuraavalla rivillä varmasti oikea tulos bc:stä vertailua varten ja sitäseuraavalla rivillä tämän skriptin tulos: ";  bc -l<<<"scale=18; l($logaritmoitava)/l(10)";  alkuhetki=$(date +%s.%N); getlog $logaritmoitava; loppuhetki=$(date +%s.%N);echo "kulunut aika mikrosekuntia: "$((${loppuhetki//./}/1000-${alkuhetki//./}/1000))


--- skriptin tuloste on seuraavanlainen:

Lasketaan logaritmi luvusta: 17.3517
seuraavalla rivillä varmasti oikea tulos bc:stä vertailua varten ja sitäseuraavalla rivillä tämän skriptin tulos:
1.239342030392094294
1.239342030392094280
kulunut aika mikrosekuntia: 16866

- tämän skriptin toiminta-aikaa ei voi mitata time-käskyllä sillä sen ja bc:n suhde on kummallinen. Vaan suoritusaika täytyy määritellä sillä tavalla kun se on tässä tehty jotta bc:n poistaminen ei vaikuttaisi ajoitustuloksiin. Sillä tottakai on syytä varmistaa ettei bc vaikuta tämän skriptin toimintaan mitenkään poistamalla lause: bc -l<<<"scale=18; l($logaritmoitava)/l(10)".

- tosin ajoitustavan suurempi tarkkuus on mukavaa sekin - tarkemmat ajat ovat muuten täyttä asiaa ja ihan oikeat vaikka ne heiluvatkin rajusti - nimittäin heiluminen johtuu siitä että Linux suo BASH:lle toiminta-aikaa sillointällöin, vain satunnaisesti ja silloinkin  pienissä palasissa kuin muilta töiltään kerkiää ja hommat tosiaan kestävät mitä milloinkin - tämän heilumisen estämiseksi reaaliaika-käyttöjärjestelmät kehitettiinkin sillä ongelma koskee useimpia ohjelmointi ympäristöjä.

***

Seuraava on liian omituinen käytettäväksi mutta on hyvä tietää että tällainenkin toimintatapa on mahdollinen:

Funktion nimeksi voidaan antaa melkein mitähyvänsä, vaikka merkki: +
- tosin ä:tä ja ö:tä on syytä välttää. Siten voidaan määrätä:

function + () { echo $(($1+$2)) ;}
- matemaattinen merkki + ei ole funktio joten kyseesä ei ole rekursio (eli funktio ei kutsu itseään)

kun tämänjälkeen annetaan käsky: + 1 2
niin se tulostaa 3


mutta vaikka annettaisiin käsky:
funktion + () { echo painu suolle, tänään ei lasketa mitään. ;}
niin echo $((1+2)) tulostaa 3 ihan niinkuin ennenkin ja vain tuo: + 1 2 tulostaisi:  painu suolle, tänään ei lasketa mitään.

---

Samoin voidaan määritellä: function !  () { echo $(($(seq -s* $1))) ;};
mutta käsky : ! 6 ei semmoisenaan viittaa tuohon kertoman määrittelyyn vaan event-määrittelyyn ja siten: ! 20 ei toimi vaan täytyy käskeä: \! 20
- \! 21 menee jo yli lukualueesta.

- kun lopettaa sen pääte-istunon jossa funktio on määritelty unohdetaan sen määrittely. Avattaessa uusi pääte se vanha määrittely palaa voimaan.
 



57

Kirjoitin tämän alkulukujen etsinnän uusiksi saatuani skriptin toiminnasta kokemusta - skripti on sama kuin ennenkin mutta teksti on täysin uutta. Toiminta perustuu merkityksellisimmiltä osiltaan iänikuisen vanhaan C-kieliseen ohjelmaan joten:

1. Tämänpäivän koneille ja ohjelmille tämä on aikalailla vaatimatonta mutta silloin kolmekymmentävuotta sitten ja varsinkin huonoissa laitteessa tämä olisi ollut täysin ihmeellistä - nimittäin tämän kaikki osat ovat noilta ajoilta ja skriptin olisi voinut kasata silloinkin.

2. mikä on BASH:in merkitys sillä se pistää tuon C-kielisen ohjelman toimimaan aikalailla toisin kuin ohjelman tekijät aikoinaan tarkoittivat?

Jättimäisten alkulukujen etsimisessä peruslaskutoimituksetkin vaativat rajoittamatonta tarkkuutta ja usein erittäin edistynyttä matematiikkaa - meidän kannaltamme silkkaa henkimaailman toimintaa ja siihen täytyy löytää valmisohjelma.

Tekijöihin jakavan ohjelman kaltaistakaan ei kukaan pysty tekemään yksin. Ja maailmassa on vain muutama ukko joka saa kasattua toisten tekosia yhteen tarpeeksi monta - siis yksi äänekäs päälläpäsmäri ja useita hiljaisia neroja.

BASH:illakin on merkityksellisiä tehtäviä: se poistaa haettavien joukosta kaikki pienillä alkuluvuilla jaolliset jolloin etsittävien joukko kutistuu kymmenenteen osaansa mikä nopeuttaa toimintaa lähes vastaavasti - nämäkin laskut edellytävät että käytettävissä on rajaton tarkkuus ja laskujen tekeminen kestää silloin kauemmin. BASH myös päättää kuinka kauan yhtä lukua selvitellään sillä tässä käytetty valmisohjelma ei itse tajua sitä tehdä.

Jättikokoisia alkulukuja etsitään tässä ohjelmassa aivan samalla tavalla kuin pieniäkin: luku jaetaan alkutekijöihinsä ja alkulukuja ovat ne joilla luku itse on luvun ainoa tekijä. Normaalisti tämmöinen menetelmä on täysin kahjo etsittäessä tekijöitä jättikokoisista luvuista sillä melkein aina yritys epäonnistuu surkeasti koska useimpien tosisuurten lukujen tekijöihin jakaminen kestää iäisyyksiä.

Mutta tuon ohjelman avulla kannattaaa tehdä tällätavoin sillä ohjelma muodostaa alkuluvun ainoan tekijän erittäin nopeasti.

Joten ei tarvita muuta kuin koettaa onnistuuko jako ja jos se ei onnistu nopeasti niin kyseessä ei ole alkuluku ja voidaan siirtyä tutkimaan seuraavaa lukua. Tosin muutamien muidenkin lukujen tekijät selviävät nopeasti joten kyllä aina joutuu laskemaan myös tekijöiden lukumäärän.

Huonossa läppärissäni tuo tekijöihin jakava factor-ohjelma osaa melkein aina muodostaa alkuluvun ainoan tekijän 0.2 sekunnissa mikäli luku on alle 64 numeroinen ja kolmessa sekunnissa mikäli se on alle 192 numeroinen - ja kunnon koneessa toiminta on kymmeniäkertoja  nopeampaa. 
 
Hakuskripti on sama riippumatta siitä  kuinka suurista luvuista etsitään, mutta muutamia muutoksia sen parametreihin kannattaa tehdä:
timeout:it ovat: .2, .7 ja 3 ja etsintäalueet 5000, 50000 tai 500000 kun etsittävä alkuluku on alle 64 numeroa, 65-128 numeroa ja 129-193 numeroa. Skripti toiminee suuremmillakin numeromäärillä mutta se on toistaiseksi kokeilematta sillä laskenta-aika kasvaa suunnattomasti - esimerkiksi 173 numeroa kestää huonolla koneella päivän. Muuten myös koneen nopeus kannattaa ottaa huomioon: koneen parantuessa timeout pienenee ja hakualue kasvaa.
 
Skripti ja sen esimerkki_kutsu:
Koodia: [Valitse]
function alkuluvun_haku () { echo "Etsin alkulukuja luvun:$1  perästä."; echo -e "\n\nlöytösekunti        alkuluku" > /tmp/delme; pienet_alkuluvut=$( seq 2 200 | factor | awk {' if ($3 =="") {print $1} '} | sed 's/://'); alkuaika=$(awk 'BEGIN {printf "%s\n", systime()}'); time for n in $( seq $1 $(bc<<<$1+5000 | tr -d '\\\n')); do for apu in ${pienet_alkuluvut[*]}; do (( $(bc<<<$n%$apu)==0 )) && n=0 && break ; done; (( $n )) && echo -ne '\rTutkittavana: '$n && timeout .2  factor $n | awk -v alkuaika=$alkuaika {' if ($3 =="") {printf "\r%s\n", systime()-asta luvuitalkuaika"                  "$1}'} | sed 's/://' >> /tmp/delme; done ; cat /tmp/delme | column -t ;}

read -e -p "editoipa lukua josta etsintä aloitetaan: " -i 1234567890123456789012345678901234567890123456789012345678901234567891234567811 luku; alkuluvun_haku $luku


Skriptin toimiessa näytölle kirjoitetaan kokoajan uusia rivejä.  Noilla annetuilla arvoilla toiminta kestää noin 3 minuuttia jonka jälkeen näytölle kirjoitetaan loppuyhteenveto:

löytösekunti  alkuluku
28            1234567890123456789012345678901234567890123456789012345678901234567891234568623
105           1234567890123456789012345678901234567890123456789012345678901234567891234570811
168           1234567890123456789012345678901234567890123456789012345678901234567891234572647


- skriptiä on helppo kokeilla: senkun leikkaat-liimaat skriptin koko koodin yhdelläkertaa päätteeseesi.
ja painat return. Mitään muuta ei saa tehdä paitsi editoida kun pyydetään.

- niitä suuria alkulukuja ei tässä näytetä sillä ne eivät mahdu mihinkään kovinkaan siististi.

- alkulukujen testaamiseksi on verkossa serveri-ohjelma nimeltään: https://www.dcode.fr/primality-test. Sillä kuluu samankokoisen alkuluvun testaamiseen sekunteja - mistäs minä tiedän vaikka se kuluttaisi tuon ajan odotus-loopissa jotta serveriä ei käytettäisi alkulukujen etsintään mutta silti se osoittaa että homma on vaikea isoillekin koneille ja siihen tarkoitetuille ohjelmille.

- ja nyt näppini syyhyävät päästä kunnon koneen näppäimille sillä nopeus vähentää suoritusaikaa tunneista minuutteihin. Saavutukset alkavat olla ikivanhalta leikkikalulta kunnioitettavia?

58
Kyse ei tosiaan ole nopeuskilpailusta, sillä BASH on niin hidas että lopputulos on selvä jo ennen kuin aloitetaan.

***

Tattista nm - tämä selvitti minulle paljon siitä missä mennään.

***

BASH-guideissa esitetään niin monia kummallisia väitteitä ettei väitteiden esittäjistä varmaankaan yksikään ole kokeillut useimpia väitteitään käytännössä vaan ainoastaan väittävät sitä mitä muutkin väittävät - samoilla todistuksilla - silloin ei tarvitse tehdä kokeiluja siitä pitääkö väite paikkaansa. Ja jos hölmöilee niin se on paljon helpompaa kun joukossa jossa on paljon muitakin.

Yksi väite on se ettei BASH:issa ole desimaalilukuja. Mutta nehän ovat vain tekstijonoja, kyllähän ne tunnetaan. Ja peräti niin ettei niillä ole tuota rajoitusta että ne saisivat olla korkeintaan 18 merkkisiä - BASH:in desimaaliluvuissa saavatkin olla vaikka miljoona merkkiä. Ne vaativat kyllä oman skriptinsä laskemista varten mutta sellaisiahan on jo - ja BASH-skripteiksi vieläpä super-nopeita.

Sangen omituista on sekin ettei kirjastojen käyttämistä opeteta sillä kyllä BASH:issa kirjastot toimivat ihan hyvin - vaikka kun virtuoosit kymmenkuntavuotta sitten huomasivat ettei BASH kuole yrityksistä huolimatta niin he poistivat siltä kirjasto-osoittimen jotta rappio syvenisi - tosin kirjastojen toimintaa ei voi kokonaan estää sillä kirjastot kuuluvat BASH:in perusrakenteisiin - mutta niiden käyttöä kyllä hankaloitettiin.
Kirjastot ovatkin jokaisen kielen menestyksen perusehto: jos kirjastoja ei voi käyttää niin kieli ei voi menestyä vaan on tuomittu kitumaan. Sitäpaitsi jokaisessa Ubuntussa on jo virtuoosien itsensä sinne laittama kirjasto - virtuoosit näemmä saavat tehdä kirjastoja mutta muut eivät.

Mutta opetetaan kirjastojen käyttäminen nyt: kirjasto on vain tiedosto jossa on yksi tai useampi funktio -  tiedostolla ei ole suoritusoikeutta eikä shebangia - eikä kirjastotiedostolla yleensä ole mitään muuta tehtävää. Kaikki kirjastossa olevat funktiot saa skripteissä käyttäväksi käsky-rivillä:
. kirjasto_tiedoston_nimi_polkuineen
- siis edessä on piste. Käsky on hyvä kirjoittaa ihan skriptin alkuun - niinkuin muissakin kielissä.

Skriptit täytyy ehdottomasti kirjoittaa funktiomuotoon ja funktioista täytyy tehdä kirjastoja - BASH:issa voi itsekin  tehdä kirjastoja ja sitä aikoinaan jopa suositeltiin. Eikätoistenkaan kirjastoje käyttäminen kiellettyäole. Mutta koska kirastojen tekemistä eikä edes käyttämistä nykyään  opeteta eikä oikeastaan edes sallita niin itsekunkin skriptinteko on junnaamista melkein paikallaan - koko ikäsi joudut keksimään pyörää uudestaan - joskus keksit paremman tavan mutta useammin huonomman - ja aikaa kuluu varmasti.

Aikoinaan yleisiäkin kirjastoja oli jonkunverran ja vieläkin netistä löytyy pari kummallista kirjastoa. Kunnollinen kirjasto nopeuttaisi skriptintekoa 'tuhatkertaisesti', tekisi skriptien toiminnasta yleensä paljon nopeampaa ja vakaata - ja mahdollistaisi paljon isommat skriptit. Mikä olisi virtuooseille kauhistus - eihän BASH saa toisten skripti-kieten kanssa kilpailla - ja entäpä sitten jos se alkaaa taas menestyä? 

Myös tuo väite siitä ettei BASH osaa palauttaa funktioista niiden parametreja on outo sillä arvoparametrin palauttamisessa ei ole mieltä - eikä muuten nimiparametrienkaan arvojen palauttamisessa ole mieltä vaan se on vain näennäinen tapahtuma sillä käytännösä mitään ei tapahdu - minkätakia hukata aikaa sellaiseen joka on jo tehty? Ja kyllä BASH nimiparametrienkin kanssa osaa toimia - vaikka virtuoosit muuta väittävätkin. Nimiparametrien ongelmana on saada parametrin nimestä johdettua parametrin arvo ja sen BASH osaa. Virtuoosit olivat pahassa välikädessä - he eivät uskaltaneet myöntää että eval-käskyllähän se arvo selviää nimestä ja niinpä he kiirehtivät levittämääm uskomusta että eval-käsky on BASH:issa tietoturvariski. Mutta OK - ei käytetä eval-käskyä sillä keinoja on monta muutakin, yksi niistä esitettynä esimerkin muodossa:
function koe () { let $1=$RANDOM ;}; koe apu; koe kipu; echo $apu; echo $kipu 
- nimestä saa arvon myös declare-käskyn avulla tai read-käskyn avulla tai ... 

BASH:issa oli aikoinaan ainoastaan 'matalantason käskyjä' - ne olivat erittäin nopeita ja tulkki kääntää niitä yhdessä palasessa vaikka kuinka monta - joten skripti hidastuu vain vähän kun käskyjä tulee lisää - toisaalta 'matalantason käskyt' ovat hitaita kirjoittaa ja vaikeita muistaa sillä ne ovat osittain merkki-sotkua. Joten virtuoosit kiirehtivät apuun ja tekivät uuden käskykannan jossa on helposti muistettavat ja mielekkäät nimet käskyille - käskyissa alettiin myös käyttää kytkimiä jotka ovatkin erittäin hyödyllisiä. Mutta nuo uudet käskyt ovat hitaita kuin synti - ja skripti lisäksi hidastuu nopeasti kun käskyjä tulee lisää. Mikäli skriptinsä kasaa yksinomaan 'matalantason käskyistä' saa kymmeniä kertoja nopeamman ja kyvykkäämmän skriptin - ja muuten tämä koskee kaikkia skriptejä, nuo matematiikkaskriptit vain sattuivat tulemaan ensiksi. 'Matalantason käskyt' muuten toimivat niinkuin pitää eivätkä aiheuta käyttäjän sieluun näppylöitä - uudet käskyt aiheuttavat.

BASH:ille on jo kauan sitten kehitetty menetelmä jolla voi 'esikääntää' käskyjä - siis tehdä uusista käskyistä samanlaisia kuin 'matalantason käskyt' - nopeita ja sellaisia että tulkki kääntäisi niitäkin samalla kerralla vaikka kuinkamonta - jolloin kaikista skripteistä tulisi nopeita. Se oli jo toimiva menetelmä - muutamat tekivätkin uusia käskyjä suurella vaivalla - mutta nyt se on jo melkein kuoliaaksi vaiettu ominaisuus mutta aikanaan se oli todella kovanluokan asia.

Muutenkin opetetaan tekemään tehotonta koodia. Esimerkiksi loopeilla on aivan sama tehtävä kuin muissakin kielissä - mutta BASH:issa niihin ei isoissa tehtävissä turvauduta koskaan, sillä käskyissä on usein sisäänrakennettuja nopeita C-kielisiä looppeja - eivät grep, find ja sellaisett ole ainoat vaan useimmissa käskyissä niitä on. Käytännössä kohdattavien mega-luokkaa olevien tiedostojen kanssa hommat kestäisivät itsetehdyillä loopeilla minuutti-tolkkua. Eivät itsetehdyt loopit pienissäkään hommissa autuutta tuo mutta kyllä niitä joskus käyttää kannattaaa. Ja koska looppeja ei kielletä käyttämästä isoissa tehtävissä niin taas saa BASH:in maine tahran hidastelusta. Esimerkiksi tehokkain silkkaa BASH:ia oleva etsintämenetelmä kun halutaan löytää jotain matriisista tai tekstijonosta - tai melkein mistähyvänsä: 
Koodia: [Valitse]
function etsi () { [[ "${@:2}" =~ "$1" ]] && echo löytyi || echo ei löytynyt ;}
function haemaksimi () { maksimi=$(echo $(sort -n <(echo "$@" | tr " " '\n' )) |  sed 's/.* //') ;}
function haeminimi () { minimi=$(echo $(sort -n <(echo "$@" | tr " " '\n' )) |  sed 's/ .*//') ;}

- saa esimerkiksi maksimin etsimisen paljon nopeammaksikin, lähes awk:in luokkaan - mutta jo tämmöisetkin edellyttävät ehdottomasti kirjaston käyttämistä:
function haemaksimi () { maksimi=$(echo $(sort -n <(declare -p $1 | tr ' ' '\n' | tr -dc '\n'[0-9])) |  sed 's/.* //') ;} ; apu=""; for n in {1..10000}; do apu=$apu" "$RANDOM$RANDOM; done; time haemaksimi apu; echo $maksimi

- katsohan muuten suoritusaikaa: user on aina lähes vakio kun skriptien suoritusajat muuten heittelevät kymmeninä prosentteja.Se on aika joka kuluu maksimin etsimis funktiossa - tutkittavan tekstijonon muodostaminen se tässä tuon 5 sekuntia kestää.
- nimiparametrin käyttäminen osaltaan mahdollistaa skriptin nopeuden. Muta onhan se ihan järjetöntä etsiä maksimia sorttaamalla joten eiköhän jotenkin saa vielä nopeutettua?

BASH:in tylyttäminen on kaikilla keinoillaon jatkuvaa: esimerkiksi regex:ien kätevyyttä ei juurikaan mainita - ovathan ne niin vaikeita etteivät useimmat niitä voi tehdä mutta kirjastosta niitä voisi jokainevn  käyttää  . Ja vähälle maininnalle jää sekin että BASH ymmärtää regex:iä itsekin - joten ei regex:ien käyttäminen aina edellytä niitä hyödyntävää käskyä, esimerkiksi grep-käskyä. Muuten myös ne 'matalantason käskyt' ymmärtävät regexiä.
- ei voi kuin huuli pöreänä katsoa regex:ien tuntijoiden super-nopeita regexiä.

Kun BASH:iin tuli mahdollisuus käyttää assosiatiivisia matriiseja niin kiirehdittiin vakuuttamaan että ne ovat hitaita. Mutta niinhän BASH on muutenkin hidas eikä siinä täydy assosiatiivisia matriiseja erikseen mainita. Mutta nuo vakuuttelut tekivät sen että nykyisin assosiatiivisia matriiseja ei koskaan käytetä vaikka joissain tehtävissä ne päinvastoin nopeuttavat skriptiä paljon. 

Vaikka nyt on meneillään BASH:in versio 5.2 niin silti ollaan jämähdetty BASH 3:een - tai vielä aikaisempaan. Uusia käskyjä ei opeteta - mutta opetetaanko vanhojakaan kunnolla? Esimerkkejä kehiin - Chet Ramey kehittää BASH:ia ja tekee uusia versioita joissa on paljon uutta ja ihmellistä - silti käytännössä kaikki muuttuu hyvin hitaasti sillä esimerkkejä löytyy todella vähän - en minä eivätkä useimmat muutkaan saa kuvauksista mitään irti vaan esimerkkejä siihen tarvitaan - jotain minkä voi skriptiinsö leikata-liimata - niin ne tiedot hiljokseen karttuuja oppii samanlaisia tekemään itsekin.
 
BASH:ista on tehty väärillä väitteillä epämiellyttävä käyttääkin - tässä oikeita ohjeita: ei skripteistä täydy tiedostoja tehdä - mutta jos välttämättä haluaa niin saa niitä toki tehdä. Mutta jos ei tee niin ei tule houkutustakaan myöntää skripteille suoritus-oikeutta sillä sitä ei tarvita.  Kaikki skriptit voi kirjoittaa samaan tiedostoon - tiedosto kyllä kasvaa joka päivä mutta kyllä linux siitä selviää. Pidetän vain jotenkin huoli siitä että löydtää aina sen mitä etsii - yleensä ctrl-f riittää etsimiseen mutta linuxin muutkin hakutyökalut ovat ihan poikkeuksellisen hyviä vaikka ne ovat jo vuosikymmeniä lahonneet. Tästä isosta tiedostosta vain leikataan-liimataan se skripti päätteeseen - tai vaikka leikataan-liimataan skripti verkkosivulta sinne päätteeseen - sillä päätteellä on jo suoritusoikeus eivätkä siihen liitetyt lapset sitä suoritusoikeutta enää tarvitse. Myös shebang lauseen: !#/bin/bash voi unohtaa - kaikki muut skriptikielet tarvitsevat oman shebanginsa, mutta ei BASH.

Kaikki johtuu samasta ilmiöstä: jokainen haluaa olla suuri kala pienessä lammessa eikä pieni kala isossa järvessä - joten kielen kuristuminen mittömäksi on mukavampaa kuin sen tuleminen hyvin suosituksi. Ja ne 'guidet' ovat isoja kaloja - ja lisäksi niiden kertomuksia kukaan ei uskalla kyseenalaistaa.

59
Toisetkin ovat kertoneet että BASH on joskus omituisen nopea. Esimerkiksi jonkun virtuoosin tekemä lasku jonka tarkoitus ei oikeastaan ole laskea mielipuolista arvoa vaan kertoa nopeudesta:
Koodia: [Valitse]
echo 'n=100000;m=(n+1)/2;a=0;b=1;i=0;while(m){e[i++]=m%2;m/=2};while(i--){c=a*a;a=c+2*a*b;b=c+b*b;if(e[i]){t=a;a+=b;b=t}};if(n%2)a*a+b*b;if(!n%2)a*(a+2*b)' | bc | tr -d '\\\n'
Käskyssä bc laskee Fibonacci-sarjan 100000:nennen jäsenen jossa on yli 20.000 numeroa. Ja ne se laskee 0.1 sekunnissa huonolla läppärillä - elikä 5 mikrosekuntia per uusi jäsen - tosin uuden jäsenen laskeminen edellyttää vain jonkinverran muistiliikennettä ja yhtä usein erittäin moni-numeroista yhteelaskua. Yhteenlasku suoritetaan bc:ssä sillä bc:ssä on aina mukana rajoittamattoman tarkkuuden kirjasto. Bc kuuluu BASH:in perusasennukseen.

Gawk:iin saa myös rajoittamattoman tarkkuuden kirjaston ja sen avulla gawk laskee saman tuloksen suorittaen laskut normaaleilla kaavoilla mutta kymmenenkertaa hitaammin. Gawk on awk:in yksi versio - synapticista se löytyy ja sen voi asentaa helposti.

Paljonkohan aikaa laskuun kuluisi jos se annettaisiin samassa huonossa läppärissä suoraan C:lle?

60
Olen jatkuvasti ihmetellyt miksi kun käskyjä tulee lisää näihin desimaalimatematiikan skripteihin niin tuntuu siltä ettei se vaikuta suoritusaikaan ollenkaan. Mutta kyllä se vaikuttaa: C-koodissa kuluu aikaa kymmeniä mikrosekunteja kauemmin - eikä BASH sitä edes huomaa.

En varmasti osaa käyttää oikeita määritelmiä joten sanon vain että mikäli koko skripti on pelkkää 'risuaita'-käskyä käännetään se kerralla kaikki joten BASH toimii senaikaa C:ssä olipa homma kuinka monimutkainen hyvänsä - yksikään tulkattu kieli ei voi pistää paremmaksi mutta tottakai pääsee samaan. Ja yhtävarmasti Python-fanit teilaavat ajatuksen - mutta vaikeaa se on sillä nämähän toimii ja jos skriptaaja olisi hyvä niin ne saisi virhettömiksikin.

'risuaita-käskyjen' lukumäärä on suuri ja niiden rakenteet noudattavat tiettyjä sääntöjä joten niitä 'löytää' itse lisää - oppii kuinka ne on tehty. Niitä on sekä käskyjonoille että matriiseille - tekstijonoja ne numerot ovat.

Onhan 'risuaita-käskyissä' murheellisiakin piirteitä: kyllä se ikä painaa niitäkin eikä niitä nykyään pystytä edes päivittämään - nimenomaan ei pystytä vaikka oikein käytettyinä ne ovat erittäin nopeita.

***

Esimerkiksi ollaan totuttu siihen että BASH:issa oltaessa ainoastaan sed kykenee muuttamaan inhimillisessä ajassa ison asiakirjan jonkun sanan toiseksi. Mutta siihen pystyvät risuaidatkin - ja melkein yhtänopeasti. Tosin tässäkin asiassa käskyt ovat jostainsyystä vaikeita muistaa joten niitä pitäisi ehdottomasti kyetä noutamaan kirjastosta.

Mutta kirjastojahan ei enää pidetä suotavina - kiitos virtuoosit - onkohan kyseessä virtuoosien keino kätkeä oma hölmöilynsä? Mutta jokatapauksessa tämmöinen se koko tiedostossa jonkun sanan muuttava käskyryhmä on (mutta muutokset tehdään ainoastaan näytölle eikä niiyä voi vahingossa tallettaa - se vaatisi todella monta muutosta. Tuo tiedosto on valittu siksi että se on jokaisessa koneessa ja rakenteeltaan se on tuttu):
Koodia: [Valitse]
sana1=BEGIN; sana2=ALOITA; < /boot/grub/grub.cfg readarray doku; printf "%s" "${doku[@]//$sana1/$sana2}"
Tai otetaanpa käsky joka poistaa pari merkkiä dokumentin jokaiselta riviltä - ensimmäinen rivien alusta ja toinen rivien lopusta:
Koodia: [Valitse]
< /boot/grub/grub.cfg  readarray array; printf "%s" "${array[@]/#??/}"
< /boot/grub/grub.cfg  readarray array; printf "%s\n" "${array[@]/%???/}"

Tai poistetaan jokaisen tekstirivin lopusta muuttujan määräämä määrä merkkejä:
Koodia: [Valitse]
montako=5; < /boot/grub/grub.cfg  readarray array; printf "%s\n" "${array[@]/%$(printf "%0.s?" $(seq $montako))/}".


Käskyryhmiin voi lisätä ominaisuuksia useampiakin. Kyllä uudetkin käskyt joukkoon sopivat, ja mikäli ne laittaa oikeisiin kohtiin niin nopeuskin säilyy.

Tämmöisiä koko asiakirjaa käsitteleviä käskyryhmiä on lukemattomia, mutta yksittäisiä lauseita käsitteleviä vielä useampia. Harvoinhan niitä tarvitaan, mutta jos niitä kasaisi kirjastoon riittävän monta niin kyllä niille käyttöä löytyy.

Sivuja: 1 2 [3] 4 5 ... 34