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 ... 32
1
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.

2
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)

3
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ä.



4
 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:


5
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

6
***

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.

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



8

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?

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

10
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?

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

12
kerro18 - kertollasku-skripti (floating point) jolle voi antaa kerrottavaksi 18 desimaalisia desimaalilukuja  jolloin 36 desimaalinen luku tulee ulos.

bc tässä sitä aikaa kuluttaa. Bc ei osallistu laskemimieen  ollenkaan - se vain antaa varmasti oikean arvon johon verrata. Voit ihan hyvin vaikka poistaa sen rivin jolla bc on.

Kaikki seuraavat toimivat (tässäolevat ovat niitä vaikeita paikkoja - joten kaiken muunkin ptäisi toimia):

kerro18  .00900007567899123  900.07000012345678
kerro18  10 10
kerro18  999999999999999999  999999999999999999
kerro18 .9 2
kerro18 .99999999999999999 .99999999999999999
kerro18 .00000000000000001 .00000000000000001   
kerro18  10000000000000000 .0000000000000001
kerro18  .0000000000000001 10000000000000000   
kerro18  1 1
kerro18  01 .1
kerro18  -1 1
kerro18  1 -1
kerro18  -1 -1
Koodia: [Valitse]
function kerro18 () {
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
[[ ${#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=${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)} ;}


time kerro18  .00900007567899123  900.07000012345678
time kerro18  10 10
time kerro18  999999999999999999  999999999999999999
time kerro18 .9 2
time kerro18 .99999999999999999 .99999999999999999
time kerro18 .00000000000000001 .00000000000000001   
time kerro18  10000000000000000 .0000000000000001
time kerro18  .0000000000000001 10000000000000000   
time kerro18  1 1
time kerro18  01 .1
time kerro18  -1 1
time kerro18  1 -1
time kerro18  -1 -1



13

BASH:ia pidetään surkean hitaana kielenä. Mutta se vanha 'risuaita-BASH' toimii yhtänopeasti kuin muutkin kielet mutta kärsii siitä, ettei 'risuaitatoimintaa' ole päivitetty miesmuistiin vaan tehty täysin epäonnistunut uusi BASH joka tosiaan tökkii - eikä virtuooseissa ole peruutusvaihdetta, kykyä palata vanhaan kun uusi osoittautuu epäonnistuneeksi.

Mutta kumpikin BASH toimii aivan toisin kuin muut kielet. Esimerkiksi BASH:issa ei lasketa tyyliin: x=1+2 vaan x=$(ynnää 1 2). Kieltämättä hankalaa ensialkuun mutta hämmästyttävän nopeasti siihen tottuu - eikä se myöhemmin edes hidasta.

On muissakin kielissä omituisuuksia, esimerkiksi matematiikan reverse-polish: abc-de+*+ :stä on kiva arvella mitä se tekee. Ja BASH:issa bc:n edeltäjä nimeltään dc oli BASH:inkin käyttäjille niin omituinen että sitä dc:tä nykyään kutsuu kuori-ohjelma bc. Siis  myös funktioiden käyttämiseen voisi tehdä kuori-ohjelman - mutta luotettavan kuori-ohjelman tekeminen vaatii parempaa ukkoa kuin minä.

***

Joissain tapauksissa myös sed, awk ja bc romahdutavat nopeuden. Ihmettelin aikoinaan kun eräs virtuoosi sanoi että niitä sopii välttää kun todellista nopeutta etsitään. Ihmettelin koska nuo ohjelmat loistavat nopeudellaan, monipuolisuudellaan ja sillä että ne helpottavat suunnattomasti myös BASH-skriptin tekemistä. Mutta 'risuaidoilla' toteutettuja skriptejä ne tosiaan hidastavat paljon - tuli siis virtuoosi paljastaneeksi että hänkin käyttää 'risuaita-käskyjä'.

Mutta on kovin vaikeaa muistaa mikä 'risuaita-käskyjen' kirjainryteikkö toimii mitenkin - ja paljon tietoa on kadonnutkin - ja erilaisissa linuxeissa hommia on toteutettu hieman erilailla joten käskyn toimiminen eri koneissa on pikkuisen onnenkauppaa.

'risuaita-käskyjen' paremmuutta ei huomaa ennenkuin skriptin jokainen käsky on 'risuaita-käsky' sillä yksikin 'normaali-käsky' romahdutta nopeuden - esimerkiksi noissa laskenta-skripteissä on 'risuaita-käskyjä' kymmeniä ja ne vievät aika vähemmän kuin yksi 'normaali-käsky'. Muuten 'risuaita-käsky' ei välttämättä ole ulkonäöltään risuaita - esimerkiksi echo on 'risuaita-käsky'.

Jos BASH:ia käytettäisiin oikein niin sen toiminta olisi ihan hyvää. Mutta se edellyttäisi jo tehtyjen ominaisuuksien kasaamista järkeväksi kokonaisuudeksi ja siinä olisi suunnaton työ - niin suunnaton että virtuoosien joukko kalpeni kauhusta, siirtyi Pythoniin ja senjälkeen alkoi tylyttämään BASH:ia - varsinkin kun eivät uskaltaneet tunnustaa tyrineensä BASH:in tärviölle.

Mutta itseasiassa ei merkitse mitään onko kieli hyvä tai huono kunhan se osaa kutsua mitähyvänsä avukseen - ja BASH osaa.

14
Käyttökelpoisen desimaalilaskennan myötä tuli tarve desimaalilaskennan apu-funktioille - kokonaisluku laskennassa apu-funktioiden merkitys on pieni - ja jos ei käytä 'risuaita-käskyjä' niin funktioista tulee todella hitaita. Mutta tässä niitä nopeita apu-funktioita on:
Koodia: [Valitse]

function floor () {
[[ ${1//[^.]/} ]] && luku=$1'.0' || { echo $1; return ;}
kokonaisosa=${luku%%.*}
[[ ${1//[^-]/} ]] && echo $(( $kokonaisosa-1 )) || echo $kokonaisosa 
# mikäli desimaalipistettä ei ole niin palautetaan luku sellaisenaan ja lopetetaan heti
# mikäli desimaalipiste on niin palautetaan positiiviselle luvulle sen kokonaisosa ja jos se on negatiivinen niin palautetaan kokonaisosa -1
}

floor 1.5

###

function ceil () {
[[ ${1//[^.]/} ]] && luku=$1'.0' || { echo $1; return ;}
kokonaisosa=${luku%%.*}
[[ ${1//[^-]/} ]] && echo $kokonaisosa || echo $(( $kokonaisosa +1 ))
# mikäli desimaalipistettä ei ole niin palautetaan luku ja lopetetaan heti
# mikäli desimaalipiste on niin palautetaan positiiviselle luvulle kokonaisosa+1 ja jos se on negatiivinen niin palautetaan kokonaisosa.
}

ceil 1.5

###

function abs () { echo ${1//-/} ;}  # echo ${1#[-+]} toimii myös ja + merkkikin on joskus pahis.

abs 1.5

###

function int () {  echo ${1%%.*} ;}

int 1.5

###

function fract () { [[ ${1:0:1} = - ]]  && merkki=-; echo $merkki.${1##*.} ;}

fract -1.5

###

function getexp { echo ${1##*[eE]};}

getexp 123.456e789

###

function sci_to_normal () { luku='00000000000000'${1%%e*}; luku=${luku//./}; apu=${1%%.*}; despoint=${#apu}; exp=${1##*e}; decimals=$(($despoint+$exp+14)); tulos=${luku:0:$decimals}.${luku:$decimals}; echo ${tulos/*(0)/} ;} # poistetaan turhat etunollat

sci_to_normal 1.234567890123456e-5

###

function normal_to_sci () { mant=${1%%.*}; exp=${#mant}; luku=${1//./}; echo ${mant:0:1}.${luku:1}e$exp ;}

normal_to_sci  123456789012356789012345678901234567890.123456789

###

function poistaetunollat () { echo ${1/*(0)/} ;}

poistaetunollat 0000010203.40600

***
 
function poistatakanollat () { apu2=${1##*[1-9]}; echo ${1:0: -${#apu2}} ;}

poistatakanollat 000102.0708000

###

function poistatakapiste () { [[ ${1: -1} = . ]] && echo ${1:0: -1} ;} # turha desimaalipiste

poistatakapiste 1234.

###

function siisti () { local apu ;local apu2; local apu4 ; apu=$( echo ${1/*(0)/}); apu2=${apu##*[1-9]}; apu4=${apu:0: -${#apu2}}; [[ ${apu4: -1} = . ]] && echo ${apu4:0: -1} || echo $apu4 ;}

siisti 000102304.0200

- siis tuossa viimeisessä poistetaan turhat etu- ja takanollat ja desimaalipistekin jos se jää viimeiseksi.
- sen koodissa on  muutakin selitystä kaipaavaa: koska funktioiden halutaan toimivan nopeasti toimivat ne samassa prosessissa kuin kutsujakin. Kun funktiossa muutetaan jonkun muuttujan arvoa niin se muuttaa muuttujan avoa kutsujassakin. Joskus tämä kielletään kun halutaan varmistaa ettei funktiolla ole tämänkaltaisia sivuvaikutuksia - edellisessä funktiossa kielto on: local apu ;local apu2; local apu4 - siis että nuo muuttujat tunnetaan yksinomaan funktiossa ja funktiosta palattaessa ne tuhotaan. Local-määrittelyä saa käyttää vain funktiossa. Mikäli funktiossa kutsutaan jotain toista funktiota niin sinne se arvo kyllä siirtyy - sen taas voi estää kirjoittamalla local sinnekin.
- nopeuteen määrittely ei paljoakaan vaikuta.
- toimii local niinkin päin ettei pääohjelman samannimiset muuttujat pääse häiriköimään funktiossa.
- 'kaikenkielto' tapahtuu kun prosessi siirretään omaan prosessiinsa. Tämä tapahtuu muuttamalla funktiomäärittelyssä uloimmat aaltosulut kaarisuluksi. Mutta omaan prosessiin siirtyminen on hidasta ja funktion suhteen johtaa vaikeuksiin.

15
Ei jakolaskun koodi vielä moitteeton ole, mutta kelvannee alkuunsa:

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

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

desi1=${luku1##*.}; desi2=${luku2##*.}
(( ${#desi2} >= ${#desi1} )) &&
{ apu=$desi1"000000000000000000"; desi1=${apu:0:${#desi2}} ;} || { apu=$desi2"000000000000000000"; desi2=${apu:0:${#desi1}} ;}; apu=''

apu=$(($kokonaisosa1$desi1/$kokonaisosa2$desi2)); tulosteessakokonaisia=$((${#apu})); [[ $apu -eq 0 ]] && tulosteessakokonaisia=0
apu=$(($kokonaisosa2$desi2/$kokonaisosa1$desi1)); apu=${#apu} && tulosteessaetunollia=$(printf "%0.s0" $(seq $apu)) || tulosteessaetunollia=''

luku1=$kokonaisosa1$desimaaliosa1
luku2=$kokonaisosa2$desimaaliosa2

unset tulos # vain varmistus että kaikki on tuloksessa tämänjälkeen uutta

for n in {1..5}; do # muodostetaan tulos-palasia oli:18 esimerkiksi 9 merkkiä kerrallaan
luku1=$luku1'0000000000000000000' && luku1=${luku1:0:17}; apu=$(($luku1/$luku2)); tulos[$n]=${apu}; echo -n $luku1' '$luku2' '$apu' ';
luku1=$(($luku1%$luku2)); echo $luku1
done

for n in {1..5}; do tulos=$tulos${tulos[$n]}; done

echo "tulos jakolaskusta: "$1"/"$2" . Vertailua varten ylempi tulos laskentaohjelmasta ja alempi tästä skriptistä"
bc<<<"scale=60; $1/$2" | tr -d '\\\n'; echo
[[ ${tulosteessaetunollia:1}  ]] && echo .${tulosteessaetunollia:1}$tulos || echo ${tulos:0:$tulosteessakokonaisia}.${tulos:$tulosteessakokonaisia} ;}
 
jaa 123456789.1234567 1234.23

   

***

Esimerkkituloste:

tulos jakolaskusta: 123456789.1234567/1234.23 . Vertailua varten ylempi tulos laskentaohjelmasta ja alempi tästä skriptistä
100027.376682998063569999108756066535410741920063521385803294361666
100027.3766829980635699991087560665354107419200635213858032

***

tai otetaan toinen laskettava, muutetaan koodia niin että se laskee lisää desimaaleja joten se kuluttaa  laskussa hieman lisää aikaa - näin lasku kestää peräti 7ms:

tulos jakolaskusta: .1234567/1234.23 . Vertailua varten ylempi tulos laskentaohjelmasta ja alempi tästä skriptistä
.000100027304473234324234542994417572089480890919844761511225622452865349246088654464726995778744642408627
2412759372240182137851129854241105790654902246744934088460011505148959270
.000100027304473234324234542994417572089480890919844761511225622452865349246088654464726995778744642408627
2412759372240182137851129854241105790654902246744934088460011505148959270

***

Desimaaliluku on tekstijono - BASH:issa numeron erottaa tekstijonosta vain se että numeroissa ei voi olla kirjaimia. Jos desimaalilukua kuvaavasta tekstijonosta poistaa desimaalipisteen niin sen voi ihan hyvin kertoa-jakaa-ynnätä ihan normaalisti sillä numeroiden merkitys on sama demaalipisteen kummallakin puolella - ongelmana on vain palauttaa desimaalipiste oikealle paikalle.

Desimaalimatematiikka alkoi seuraavasta yksinkertaisesta skriptistä:

Koodia: [Valitse]
function kerro9 () {
luku1=$1
luku2=$2
kokonaisosa1=${luku1%%.*};
kokonaisosa2=${luku2%%.*};

apu=$(($kokonaisosa1*$kokonaisosa2))  # näimä desimaalilaskut suoritetaan aina normaalilla matematiikkalla.

kokonaisia=${#apu}
tulos=$((${luku1//./}*${luku2//./}))
bc<<<"scale=18; $1*$2"
echo ${tulos:0:$kokonaisia}.${tulos:$kokonaisia} ;}

kerro9 15.12345 -1.22222222222



Tulos:
-18.4842166666330590
-18.4842166666330590

Huomautuksia:
- skriptissä mukana oleva bc antaa vain varmasti oikean tuloksen mihin verrata - eikä se laskuihin mitenkään sekaannu - mutta onhan se mukava nähdä heti toimiiko skripti oikein - ja bc:n kutsun voi vaikka kommentoida poiskin. Ilman bc:tä skripti nopeutuukin 2ms:ta lähes nollille.
- BASH:in mollaaminen oli alussa sitä että väitettiin ettei se desimaaleja hanskaa ja lisäksi se on sikahidas. Mutta kyllä lähes 0ms on BASH-skriptiltä hyvä suoritusaika ja onhan skripti kieltämättä myös desimaalilaskentaa. 

***

Yhteenvetona desimaalilaskennasta:

- BASH:ssa on toteutettuna vain perus-laskutoimitusten kokonaisluku laskenta.
- desimaalilaskuja varten täytyy tehdä funktioita. BASH:in mukana toimitetaan runsaasti funktioita ja miksei joitain desimaalilaskennam funktiota ole toimitettu myös voi olla ainoastaan pahantahtoisuutta. Ohjelmallinen ratkaisu on kylläkin kymmenenkertaa hitaampaa kuin kielen oma mutta tällä tasolla toiminta on niin nopeaa ettei sitä aina edes huomaa.

Ohjelmallisestikin toteutetuissa on eritasoisia - kaikille laskutyypeille useita omiaan - ei edes hyväksyttävää mutta kuka tietää mihin tämä alku johtaisi? Tässä on esimerkkinä kertolasku:
- kerro9 on nopein - ei kovin tarkka mutta jo ihan käyttökelpoinen.
- kerro18 on neljäkertaa hitaampi mutta tarkkuutta on jo enemmän kuin useimmissa kielissä.
- taustalla luuraa kerro36 - tai itseasiassa rajoittamaton tarkkuus.
- myös tieteellisen esitystavan osaavan voisi tehdä - ihan ajankulunaan, ei sillä merkitystä olisi. Mutta se olisi vielä hitaampi - toisaalta se hakkaisi tarkkuudessa useimmat.


- Neliöjuuri, logaritmi, anti-logaritmi ja semmoiset ovat toistaiseksi niin hitaita ettei niitä kannata BASH:illa laskea. Mutta ne onnistuvat kyllä. Vaikka seitsemännen asteen elliptiset integraali-funktiot onnistuvat.

***

Nämä desimaalilaskut toimivat aivan niin kuin teoriat ovat aina sanoneet. Sitä ei alussa voinut tietää onko BASH:issa kaikki tarvittavat toiminnot mutta osoittautui että kyllä on - mutta ne ovat niissä iänikuisen vanhoissa ja salamannopeissa 'risuaita-käskyisssä'. Mitään uutta ei tarvinnut kehittää - nämä ovat toimineet aina. Monikin on ilmanmuuta tehnyt samat funktiot - sitä en käsitä minkätakia tieto ei ole liikkunut - onko syynä naurunalaiseksi joutumisen pelko? - sillä onhan tämä näurettavaa väittää professorien iskeneen kätensä leppään.

Lisäksi desimaali-laskuista saa jatkuvasti kehitettyä parempia. Juuri valmistui 32 numeroisten desimaali- tai kokonaislukjen numeroiden kertolasku joka kestää 2ms. Ja se avaa helpon tien 72 numeroisten nopeaan kertolaskuun. Tietenkään näillä ei ole enää muuta merkitystä kuin sen painottamisessa että BASH on pilattu uusilla käskyillä ja niiden toimintatavoilla.
- muuten desimaalimatematiikan useimmat käskyt ovat tekstinkäsittelyn käskyjä ja matematiikkaa on vain nimeksi. Joten varmaankin tekstinkäsittely sujuu vielä paremmin.


16
BASH:ille on jo kauan sitten kehitetty menetelmä jolla voi 'esikääntää' funktioita - siis tavallaan tehdä uusia komentoja. Niiden avulla olisi siis saatu BASH:illekin  esimerkiksi salaman-mopea desimaalimatematiikka ja vaikka mitä muitakin kykyjä. Se oli jo toimiva menetelmä - muutamat tekivätkin uusia käskyjä - mutta nyt se on jo melkein kuoliaaksi vaiettu ominaisuus mutta todella kovanluokan asia. Kumpikohan BASH:in suur-guruille on miellyttävämpi mainesana: typerys vai liero?
 
- modulien kääntämisestä: https://mywiki.wooledge.org/BashLoadableBuiltins
- tottakai modulin kääntäminen on melkoisen vaikeaa 'ei-ammattilaiselle'. Mutta niinhän alkuunsa kaikki - ja mihinkähän kehitys olisi johtanut mikäli modulien tekemistä olisi jatkuvasti harrastettu? Sitäpaitsi toistenkin käännökset toimivat kunhan koneissa ei ole liikaa eroja.
- eväthän puolivillaiset kyvyt ja jonkinlainen nopeus vielä riitä mihinkään. Mutta mitähän virtuoosit saisivat aikaan jos vain jatkuvasti kääntäisivät moduleita?

***

Täytyyhän ihmisten oppinsa jostain saada mutta BASH:in suhteen en tosiaan käsitä mistä. Onhan noita 'Advanced guide':ja niinkuin BASH-raamattujakin - kyllä ne perusteet hyvin kertovat eikä niitä virheistä uskalla syyttää mutta niissä on aikapaljon osatotuuksia - ne estävät kehityksen.   

Ja kaikkia oppikirjoja koskee se että esitetään vain sellaista mitä kaikki muutkin esittävät - aivankuin kellään ei olisi omia mielipiteitä - eivätkä ne edes taida välitää. Sillä BASH on niin laaja ettei kenenkään oikeastaan tarvitsisi kulkea samoja polkuja kuin toiset. Alkeet toki ovat samat kaikilla - miksi siis nimi: Advanced guide?

Tosin erilaisia Linux:eja on lukemattomia, niissä erilaisia pääte-ohjelmia ja vaikka mitä muutakin ihan erilaista ja vallankumouksellista. Miksei ihmiset usko että distron tulee olla super-suosittu, sen perusversio eikä siinä saa olla mitään mikä ei ole valtavirtaa? Jopa lisäpakettien lataaminen on vähän arveluttavaa - voi sitä vähän harrastaa mutta pelkästää virallisista varastoista.

Mutta loppujenlopuksi se mikä merkitsee on oppi siitä kuinka saat apua ongelmissasi - se on nopein tie oppimiseen. Ja taito luntata ja kyky oppia omista ja toisten kömmähdyksistä ja saavutuksista on korvaamaton. Mutta harva meistä kuuntelee edes itseään - puhuu yhtä ja tekee toista - ihmiset huijaavat ennekaikkea itseään.

Eikä tuo opetuksen puute yksinomaan BASH:ia koske vaan myös kieliä bc, awk ja sed jotka ovat todella kovanluokan funktioita - niille sopivissa tehtävissä ne ovat paljon-paljon parempia kuin BASH - ja ne ovat aina käytettävissä tekemättä mitään. Voi noita skriptikieliä käyttää itsenäisinäkin skriptikielinä - awk-skriptejä näkeekin sillointällöin - mutta melkein aina BASH on se joka liimaa kaiken yhteen.

***

Kaikki BASH:ista esitetty kuvastaa sitä että siitä halutaan eroon - erinomaisia työkaluja on vaikka mihin - mutta niitä ei opeteta käyttämään.

Ja kaikki on niin epäjärjestyksessä että se taitaa olla tahallista - monikin on yrittänyt luoda järjestystä mutta toiset ovat samaanaikaan pitäneet huolen siitä että kaikkea uutta tulee lisää niin nopeasti että pakka pysyy sekaisin - tullessaan kaikki on muka vallankumouksellista mutta häviää niin nopeasti ettei sitä kaikki edes havaitse. Kieltämättä paljonkin loistavia hommia tehdään - mutta kymmeneessä vuodessa ne rappeutuvat melkein käyttökelvottomiksi.

Silloin aikoinaan kun yhteisöt pullistelivat skriptaajia saattoi halutessaan vaikka mennä keskustelemaan virtuoosi-joukon kanssa joita oli kaikkialla joten ei tämmöinen asioita täysin turmellut. Mutta nykyään kun esimerkiksi kohtaa käskyissä ongelmia niin eipä siinä sillähetkellä muuta keinoa ole kuin lukea käskyn man-sivu - muuten man-sivujakin on erilaisiin tarkoituksiin ja voit vain toivoa että saat eteesi sellaisen josta ymmärrät jotakin. Ja vaikka man-sivut ovatkin käyttökelpoisia pitempään skriptanneelle niin noviisille ne ovat melkolailla käyttökelvottomat sillä niissä on hyvin vähän esimerkkejä. Sillä vaikka kaiken selittäminen esimerkeillä on mahdottoman hidasta niin jo muutama esimerkki kertoo erittäin paljon. Ja minkätakia kaikki pitää tehdä hyvin vaikeaksi - kuvaannollisesti viisaudenhammas poistetaan peräreiän kautta ja se esitetään esimerkkinä.

Info-sivut ovat hieman parempia - kirjoita päätteessä esimerkiksi: info ls - mutta moniko niitä info-sivuja lukee? Sitäpaitsi kaikesta on niin paljon sanottavaa että jos ei aseta haluilleen rajaa tulee jokaisesta ohjeesta niin laaja ettei sitä kukaan viitsi lukea.

BASH:issa on myös työkalu jolla löytää niitä käskyjä jotka saattavat sopia ongelman ratkaisemiseen - siis mitä man-sivuja pitäisi lukea. Moniko on tuosta apropos-käskystä edes kuullut?

Samoin: 'whatis komento' kertoo mitä komento tekee - noin teoriassa. Siis esimerkiksi: whatis xargs

Ja kaikissa noista tulee esiin kieliongelmia - pahin kieliongelma on se että jotakin on suomennettu ja valtaosaa ei.

Siis neuvoja ja työkaluja on vaikka kuinkapaljon, joten heti alkuunsa saa niin monta teoreettista, epäjärjestyksessa esitettyä, ristiriitaista ja eri aikakausilta olevaa neuvoa että niitä joutuu pureskelemaan loppuelämänsä. Joten luojankiitos että meillä on tämä foorumi - täällä saa neuvoa ongelmiinsa eikä saa maailmankaikkeudesta kahta teoreettista esitelmää.

Googlaamalla saa kyllä neuvoja vaikka mihin - sillä olipa ongelmasi mikähyvänsä niin monet ovat kohdanneet saman ongelman ja todennäköisesti monikin on ratkaissut sen ja laittanut ratkaisun nettiin. Mutta koska netissä on niinpaljon roskaa kestää jonkinaikaa oppia löytämään ne kultajyvät. Vähänhän niitä kultajyviä on ja niiden kerääminen isosta joukosta on hidasta - ja lopputuloksesta on viisainta kirjoittaa itselleen selostus. Joten itse on nykyään ohjeensakin tehtävä.

Skriptaaminen on jokaiselle erilaista - toiselle tärkeä ei toiselle merkitse mitään. Ja oppimisessa tämä tarkoittaa sitä että tärkeän opit kerrasta ja mielestäsi merkityksetöntä et opi koskaan kunnolla - ja kerrasta oppinuthan ei ratkenneista ongelmista puhu mitään sillä hänen mielestään niiden ratkaisu on itsestäänselvää. Mutta se epävarma puhuu sitäkin enemmän.

***
 
Aivot toimivat parhaiten kun niitä ei hiillosta - ei tosiaankaan kannata yrittää parannella väärin toimivaa skriptiä kovin kauaa kerrallaan sillä jos homma ei onnistu melko nopeasti niin se merkitsee sitä että yrittää väärällä tavalla - eikå yksikään ihminen osaa muuttaa uskomuksiaan kovinkaan nopeasti - vaan välillä pitää tehdä jotain muuta - ja aivot toimivat samalla taustalla siinä alkuperäisessäkin tehtävässä   

***

Jos puhuu paljon ja varsinkin BASH:ista niin ilmanmuuta puhuu myös aikapaljon potaskaa - ja omien virheiden myöntäminen on jokaiselle vaikeaa. Samoin meidän jokaisen työnjälki jättää toivomisen varaa. Mutta ei sillä ole merkitystä - vain yrittämisellä on.

Eikä kukaan pysty tekemään yhtään skriptiä jota ei joku toinen pystyisi parantamaan - usein jopa kymmeniä kertoja nopeammaksi samalla poistaen virheitä.

Ne jotka eivät koodaa eivät koskaan tule ymmärtämään sitä että koodarin on pakko olla koodatessaaan toisessa maailmassa ja että täydellinen työrauha on suotava. Mutta ehkä häiriöt ovat hyväksikin koska ei ihmisen ole hyvä hukuttautua unelmiinsa kovin pitkäksi aikaa - se vain on syytä muistaa että toiset eivät tule kykenemäänkään ymmärtämään joten ei häiriöistä kannata perhesopua rikkoa.

***

Muutin neliöjuuren laskemisen toimimaan kaikilla reaaliluvuilla - kyllä se edelleen laskee vain yhdeksän desimaalia mutta uudessa skriptissä sille syötetty luku saa olla kuinka suuri tai pieni tahansa ja kuinka moninumeroinen tahansa ja vaikka tieteellisessä esitysmuodossa. Tuloste on aina tieteellisessä esitysmuodossa.

Mutta taas kertaalleen ihmettelin sitä että emme voi tunnustaa että keskimäärin olemme keskinkertaisia - eivätkä edes huippuälykkäät ole kovin älykkäitä.

Harkitsinkin kauan ennenkuin aloitin tekemään tätä skriptiä luullessani että se on tosi-iso tehtävä. Sillä en edelleenkään oikein tajua kuinka yksinkertaisia kaikki ratkaisut BASH:ille ovat - vaikeaa on vain löytää se yksinkertainen ja moitteeton tapa. Sillä meillä kaikilla on sama synnynnäinen vika: kun alamme tehdä jotakin niin emme koskaan usko että edes voimme yrittää väärällä tavalla joten kaikki jo tehty täytyy heittää roskikseen ja aloittaa ihan alusta.

Hakiessani ratkaisuja muutamiin ongelmiin tehdessäni tätä skriptiä etsin ratkaisuja lukemattomilta verkkosivuilta. Oletetaanpa että johonkin ongelmaan annetaan jonkun keskustelukerhon verkkosivulla sata erilaista ratkaisua - ensimmäiset niistä ovat usein melko vaatimattomia mutta usein noin kymmenes ratkaisunesittäjä esittää täysin erilaisia ja loistavia ratkaisuja. Mutta ei se mitään vaikuta sillä niitä tuhatkertaa huonompia ratkaisuja esitetään edelleen - ja jopa runsaammin kuin ennen. Onneksi olen oppinut kuinka ne timantit erotellaan siitä isosta joukosta.

- nyt myöhemmin havahduin siihen miltä tuo LANG=C vaikuttaa - mutta se siis tekee vain sen että käsky: printf käyttää desimaalipistettä eikä pilkkua. Nuo aakkostuksen asetukset muuten vaikuttaa vaikka missä - jopa sorttaaminen nopeutuu usein.

- samassa lauseessa käsky: (( $apu & 1 )) testaa parillisuutta.

Koodia: [Valitse]
function sqrt () {
apu=$(LANG=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=2110000000; delta=1005000000
for ((i=0;i<31;i++)); do
   x=$(($sqrt*$sqrt/$kerroin))
   (( $x>=$in )) && sqrt=$(($sqrt-$delta)) || sqrt=$(($sqrt+$delta))
   delta=$(($delta/2))
done 
echo ${sqrt:0:1}.${sqrt:1}e$expo ;}

echo -e 'ylempänä tulos kalkulaattorista ja alla tästä skriptistä\n'; juurrettava=27.345; LANG=C printf "%.17e\n" $(bc -l <<< "sqrt($juurrettava)");time sqrt $juurrettava

***


Oikealla tavalla tehtyjen skriptien kokeileminen on tosihelppoa - leikkaa liimaa skripti verkkosivulta päätteeseen ja siinä kaikki - paina vain return. Ja skriptien ajaminen uudestaan uusilla paramerteilla on vielä helppompaa sillä kaikki tarvittava on jo koneessasi - painat vain näppäintä nuoli-ylös niin skriptin ajava käsky palaa näytölle editoitavaksi - ja funktiot ovat jo muistissa.
- nuoli-ylös nappulassa on yleensä ylöspäin osoittava kolmio.

***

Sain aikaiseksi luotettavan version desimaalilukujen kertolaskusta. Kerrottavat voivat liikkua alueella: .000000001 - 999999999.99999999 - joko positiivisina tai negatiivisina.  Kyllä kokonaisluvutkin hyväksytään.

Skripti toimii alle millisekunnissa jos laskinohjelman nimeltä bc:n jättää pois - sillä tässävaiheessa skriptin oikeellisen toiminnan osoittamiseksi on tulosteessa hyvä olla mukana varmasti oikea tulos - ja sen saa esimerkiksi matematiikka-ohjelmasta bc joten nykymuodossa skripti kestää yli 10 ms - bc:tä on pakko käyttää  sillä hyväkään normaali kalkulaattori ei kaikkiin näihin laskuihin kelpaa. Ylitäpä sallittu lukualue ja kyllä tekemäni skripti alkaa höpertää mutta bc ei.

Esitänpä skriptin toimintaa esimerkillä - Käske: kerro18 -999999999.99999999 999999999.99999999 . Tulostuu neljä riviä:
 
Lasku:-999999999.99999999*999999999.99999999  . Ylempi tulosterivi on matematiikkaohjelmasta ja alarivi tästä skriptistä:
-999999999999999980.0000000000000001
-999999999999999980.0000000000000001
Koodia: [Valitse]
function kerro18 () {
tulosta=: # päätös tulostetaanko välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
[[ ${1:0:1} = - || ${2:0:1} = - ]]  && merkki=- || merkki=''
[[ ${1:0:1} = - && ${2:0:1} = - ]]  && merkki=''
apu1=${1//\-/}; apu2=${2//\-/}
[[ ${apu1//[^.]/} ]] && luku1=${apu1:0:18} || luku1=${apu1:0:18}".0"
[[ ${apu2//[^.]/} ]] && luku2=${apu2:0:18} || luku2=${apu2:0:18}".0"
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}; $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} # tämänjälkeen 18->17
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=20; $1*$2"
apu=$summa2$summa1; [[ ${apu: -2} = '00' ]] && echo $merkki$((10#${apu:0: -$desimaaleja})) || echo $merkki$((10#${apu:0: -$desimaaleja})).${apu: -$desimaaleja} ;} 

kerro18 -999999999.99999999 999999999.99999999

***

Olin jo pitkällä pätevän desimaalilukujen jakolaskun kehittämisessä kun alkoi edellinen kertolasku kiinnostaa uudestaan sillä sehän on 18+18 numeroinen desimaalikertolasku ja tekemällä teoriassa yksinkertaisen muutoksen saa sen desimaalipisteen-paikan liukuvaksi jolloin se itseasiassa olisi kunnollinen 36-numeroisten desimaalilukujen kertolasku. Ja muutoksen seuraukset olisivat arvaamattoman suuret - kyse ei ole nopeudesta sillä BASH on niin hidas ettei suurikaan nopeutus tunnu missään vaan kyse on siitä onnistuuko asia ollenkaan - ja 32 desimaalinen kertolasku tekisi monista asioista mahdollisia.

Puhumattakaan uudesta 9+9 kertolaskusta josta saisi tehtyä nopean 18-desimaalisen kertolaskun. Mutta koetetaan palata maanpinnalle ja jatkaa jakolaskua.

****

Matematemaattisen toiminnon virheet paljastuvat varmuudella vasta kun sitä käytetään paljon. Se että laskut virheilevät alkuunsa on normaalia mutta jos niitä virheittä ei saa nopeasti korjattua niin se on paha - ja jos ei saa korjattua ollenkaan niin roskikseenhan menetelmä joutuu.

Ja nopeasti korjaamisessa tulee esiin se että eihän se vikojen löytäminen onnistu ellei kokeilijoita ole paljon. Seuraavasta desimaalilukujen jakolaskusta tämä näkyykin erittäin selvästi - sillä sen toiminta täytyy tällähetkellä luokitella kuuluvaksi henkimaailman kummallisuuksiin. Ja koska virtuoosit ovat saattaneet BASH:in tilaan jossa sitä ei kukaan halua käyttää niin tilanne taitaa olla lukossa.

Kumpa virtuoosit olisivat alkaneet kehittää tämmöisiä desimaali-lasketaan sopivia skriptejä jo 80-luvulla - sillä nämä olisivat toimineet jo silloin. Eiköhän kymmenessä vuodessa olisi saatu desimaali-funktiotkin joko virheettömiksi tai roskikseen. Nyt ei sitä runsasta käyttöä voi enää tulla sillä yhteisöhän on siirtynyt jo Pythoniin.

Nähtävästi virtuoosit ovat silloin kauan siten halunneetkin siirtyä Pythoniin - ja sehän tarkoittaa sitä että BASH laitetaan eläkkeelle. Ihan oikeinkin se olisikin jos tosiaan se uusi kuningas on jo sillähetkellä parempi.

Ei muuten ollut kuten aikaisemmista jutuistani saat luettua - ja desimaali-laskennan kieltäminen on muuten vasta kolmanneksi merkittävin syy - puhun kieltämisestä sillä virtuoosit ovat ilmanmuuta tienneet desimaalilaskennan toimivan. Tällähetkellä Python on mielettömästi parempi sillä sitähän pataljoona ukkoja korjailee monesti joka viikko ja BASH:ia vain yksi ukko kerran vuodessa.

Elikä taisi olla niin etteivät virtuoosit halunneet BASH:ia käyttää sillä se on kovin liukas käsiteltävä heillekin. Sillä mitä BASH:ista olen kaivannutkin niin sen olen löytänyt - tai itseasiassa muut ovat löytäneet sillä olipahan ongelmani mikähyvänsä niin joku on ratkaissut sen - ja ongelmaksi jääkin enää vain tuon ratkaisun löytäminen. Olenkin nykyään lakannut yrittämästä kehittää ongelmiini ratkaisua itse, sillä maailmalla joku on varmasti kehitänyt jo  paremmankin ratkaisun mitä minä itse pystyisin keksimään.

Mielenkiintoinen homma muuten tuo kultajyvien hyljeksiminen - varmaankin koska virtuoosit eivät oikestaan koskaan keksi niitä itse ja joutuisivat myöntämään surkimusten olevan parempia. 

Ulkonäöltään nämä desimaalimatematiikan funktiot ovat kamalia. Mutta niin ovat vastaavat toiminnot kaikissa muissakin kielissä - esimerkiksi monet kielet hakevat matematiikassa apua prosessorin matematiikka-osiolta ja noiden matematiikkaosioiden rautaan langoitetut ohjeet ovat vielä kamalampia.

Ja mikäli jokin kieli suorittaa matematiikkansa ohjelmallisesti ovat laskujen tarvitsemien funktioiden  inhoittavuudet kätketty kirjastoihin joista niitä ei koskaan näe. Mutta BASH on opetuskieli ja alkuperäinen tarkoitus oli että käyttäjät tekisivät itse kirjastonsa.




17
BASH tulkkaa koodinsa C:lle joten muodostettu koodi on nopeaa. Lisäksi toimintanopeuden kannalta merkittävin aika on se aika joka kuluu tulkin lataamisen aloittamisesta siihen että tulkki alkaa toimia ja BASH:illa se on ehdottomasti paras. Kääntäjän kyvyillä ja kääntäjän tekemän koodin laadulla on vain vähäinen merkitys. Näiden perusteella voi sanoa ettei yhdelläkään skriptikielellä ole mahdollisuuksia kipailla BASH:in kanssa nopeudessa.

Silti BASH on sangen hidas. Väite ettei BASH osaa sitä ja tätä ei oikeastaan pidä paikkaansa vaan kyllä se osaa mutta paljon pidemmillä ja kummallisemmilla koodeilla. Esimerkiksi Pythonia päivitetään monta kertaa viikossa mutta BASH:ia vain vuosittain joten tottakai Python kykenee nykyään kuvaamaan toiminnan lyhyemmin ja selvemmin - sitäpaiti se oli jo alkuunsakin käyttäjäystävällisempi. Mutta se ei vielä riitä selvittämään BASH:in hitautta.

Tietokoneessa koodin nopeus riippuu paljon siitäkin kuinka sille myönnetaan toiminta-aikaa ja millaisin 'etuajo' oikeuksin - siis suureltaosin käyttöjärjestelmä päättää kuka on nopein.

***

Kohtasinpa muutaman skriptin joissa sanan function paikalla lukee alias ja niitä alun sulkuja  ei ollut - mutta käyttämisessä ei ole eroa. Skriptit olivat pitkiä ja monimutkaisia. Kiinnostuin asiasta ja aloin esiä selityksiä. Maailmalta löytyikin paljon teoreettista pohdiskelua suuntaa ja toiseen  mutta pääasiaa ne eivät kertoneet: jos funktiolla ei ole parametreja niin silloin selviää vähemmällä kirjoittamisella kun käyttää alias:ta.

Siis tämä on ihan sama alias kuin se mitä opetetaan helpottamaan tekstin kirjoittamista ja joka laitetaan tiedostoon ~/.bashrc . Ja se toimiikin tismalleen samalla tavalla - se kirjoitetaan tiedestoon vain siksi että halutaan että se tulee aina voimaan kun menee päätteeseen. Toisaata funktion voi kirjoittaa ~/.bashrc:hen jolloin sitä voi kutsus aina.

***

Kokeilin juuri kuinka alle 18 numeroiset desimaalilaskut onnistuvat - ja kyllähän ne onnistuvat mutta rutiinien tekemisessä olisi kauheasti töitä ja tuloksena kamalan rajoittunut laskenta - sillä esimerkiksi jakolaskuissa saisi luvuissa olla vain 4 numeroa kokonaisosaa ja 4 numeroa desimaaliosaa - siinä tulee 18 numeroa tulokseen - ja tulos olisi varmasti oikea ja toiminta lähes salaman-nopeaa. Mutta en jaksa edes aloittaa koska eihän näillä enää merkitystä ole.

***

Kannattaa muuten käydä verkkosivulla: https://www.commandlinefu.com - siellä on suuri joukko funktioita. Sieltä saa mielikuvan siitä mitä kaikkea kummallista löytyy - ja sielläkin on vain pieni näyte - tosin ne pitäisi koota ja laittaa järjestykseen jotta löytäisi sen mitä etsii. Tai tarjota käyttöön todella hyvät etsintätyökalut - BASH:issa itsessään muuten on, ihan eriluokan etsintätyökalut kuin muilla.

Sieltä ja muualtakin netistä löydät paljon lyhyitä funktioita jotka ratkaisevat ongelmia joita normaalisti ratkotaan paljon isommilla ohjelmilla - ja ne ovat lisäksi melkein aina nopeita. Tosiaan jokaiseen ongelmaan joku tietää ratkaisun ja usein laittaa ratkaisunsa nettiin - ainoastaan sen ratkaisun löytäminen on ongelma - saa muuten nähdä kuinka tuo ChatGPT-tekoäly alkaa löytää niitä ratkaisuja.

Ja onhan niitä paikkoja muitakin - esimerkiksi: https://www.regexbuddy.com/

BASH kehittyy vieläkin kammottavan nopeasti vaikka hyljeksitty onkin - eikä sitä kukaan edes uskalla tosimielellä opettaa BASH:ia sillä kovin usein noviisit ja lapsetkin huomaavat jotain sellaista jota edes virtuoosit eivät ole huomanneet - ja eihän sitä yksikään virtuoosi kestä vaan kyllä semmoinen kieli täytyy hävittää.

Ja kyse ei ole pikkujutuista sillä oikealla tavalla kasattu BASH-skripti toimii kymmeniä kertoja nopeammin ja varmemmin kuin ne kouluesimerkit joita on totuttu käyttämään.

Luepas BASH 5.2:n uusista ominaisuuksista verkkosivulta: http://tiswww.case.edu/php/chet/bash/NEWS

Ihan alkuun tulee ihmettelemistä: BASH ja malloc? Kyse taitaa olla siitä että BASH:in tietorakenteet ovat aina dynaamisia - koko määräytyy vasta käytön aikana - joten kun tulkki tekee BASH-skriptistä koodia C:lle niin C tarvitsee malloc:ia - joten BASH-tulkin käyttämään malloc:iin on tehty muutoksia sillä segmentointi aiheutti vanhalla mallocilla skriptien kaatumisia? Mutta eihän virtuoosit kerro tämmöisiä heidänmielestään itsestäänselvyyksistä.

Jo ajatuskin tökkii siinä kuvattujen ominaisuuksien soveltamisesta - eihän niillä ole mitään tekemistä noissa guideissa esitettyjen asioiden kanssa? Ja ilman oppaita ei pääse skriptaamisessa alkuunkaan ja mitäpä teet kun oppaat kertovat aivan toisesta aikakaudesta kuin todellisuus?

***

BASH on niin nutistettu ettei suuria BASH-ohjelmia ole tehty vuosikymmeniin - aikoinaan jokapaikka pullisteli niitä - henkistä pääomaa on hukattu surutta. Sillä kyllä BASH:illa on ihan samat kyvyt kuin muillakin kielillä - usein tosin hankalakäyttöisinä koskei BASH:ia paljoakaan kehitetä. Ja mikäli tekee asiat oikein on BASH siedettävän nopeakin - ja joissain hommissa sen kyvyt ovat ylivertaiset, esimerkiksi järjestelmän hoitamisessa.

Kaikki skriptin muuttujatkin voidaan tulostaa skriptin jokaisessa kohdassa esimerkiksi seuraavilla funktioilla:

Koodia: [Valitse]
function xref () {
function tulostaMuuttuja () { echo -n muuttuja $1':n arvo: '; eval echo \$$1 ;}

function tulostaMatriisi () { echo -n matriisin $1' arvot    : ';  eval echo \${$1[*]}
echo -n matriisin $1' osoitteet: ';  eval echo \${!$1[*]} ;}

function testaaMuuttuja () { 
echo; [[ $( eval echo "\${!$1[*]}") = 0 ]] && tulostaMuuttuja $1 || tulostaMatriisi $1 ;}

muuttuja=$(echo ${!a*}' '${!b*}' '${!c*}' '${!d*}' '${!e*}' '${!f*}' '${!g*}' '${!h*}' '${!i*}' '${!j*}' '${!k*}' '${!l*}' '${!m*}' '${!n*}' '${!o*}' '${!p*}' '${!q*}' '${!r*}' '${!s*}' '${!t*}' '${!u*}' '${!v*}' '${!w*}' '${!x*}' '${!z*}' '${!y*}' '${!A*}' '${!B*}' '${!C*}' '${!D*}' '${!E*}' '${!F*}' '${!G*}' '${!H*}' '${!I*}' '${!J*}' '${!K*}' '${!L*}' '${!M*}' '${!N*}' '${!O*}' '${!P*}' '${!Q*}' '${!R*}' '${!S*}' '${!T*}' '${!U*}' '${!V*}' '${!W*}' '${!X*}' '${!Z*}' '${!Y*})

for muuttuja in  ${muuttuja%%BASH*}; do [[ $muuttuja = muuttuja ]] && : || testaaMuuttuja $muuttuja; done ;}

# xref voidaan laittaa skriptiin mihinkä kohtaa hyvänsä. Esimerkkikutsu:
kaveri=kamu
a=55
A7=2
mat=(7 8 9); mat[15]=555
xref

***

Muuttujien talletukseen ja lukemiseen ei kannatakaan tehdä funktiota vaan kirjoittaa sen lyhyt koodi suoraan skriptiin.
Koodia: [Valitse]
Talletus:  declare -p muuttujan_nimi > ~/delme 
Lukeminen: . ~/delme

Samaan tiedostoon voidaan tallettaa niin monta muuttujaa kuin halutaan. Käsky muuttuu silloin vähän:
declare -p muuttujan_nimi >> ~/delme

- kaikki ne latautuvat muistiin yhdellä kertaa kun käskee: . ~/delme

- mikäli luettu muuttuja on matriisi tulostetaan sen arvot:
Koodia: [Valitse]
echo arvot"${nimi[*]}"osoitteet:"${!nimi[*]}"

- luettaessa ei ole mielekästä yrittää määrätä minkäniminen muuttuja halutaan lukea sillä tiedostossa on senniminen muuttuja kuin siellä on eikä sitä nimeä voi muuttaa.

- yleensä declare:lla määritellään muuttujaa. Mutta 'declare -p' pytää BASH:ia tulostamaan kuinka muutujia kuvataan BASH:in taulukoissa. Parasta kuvata tämä esimerkillä: luo matriisi: n=(1 2 3) ja katso miten BASH pitää siitä kirjaa: käske:  declare -p n . Tulos:
declare -a n=([0]="1" [1]="2" [2]="3") . Siis tulos on tekstijono - ja se kelpaa käskyksi yhtähyvin kuin tavallinenkin muuttuja - siis kun tekstijonon nimi mainitaan yksinään niin tekstijonossa mainittu muuttuja määritellään -  esimerkiksi tässä tuo -a merkitsee että ennen arvojen antamista tehdään tavallinen matriisi ilman lisämääreitä.

Ja tekstijonoilla ei ole pituusrajaa joten siihen mahtuu miljoonia parametreja: esimerkiksi matriisin kaikki jäsenet ja niiden osoitteet - ja matriisissahan voi olla miljoonia jäseniä. Muuttujan kaikki ominaisuudet säilyvät. Myös matriisin sparse-ominaisuus säilyy.

Myös assosiatiiviset matriisit siirtyvät eikä edes erillistä määräystä matriisin assosiatiivisuudesta tarvita.

Ja semmoisenkin nippelitiedon saa että BASH:in tavalliset numerot voi kirjoittaa lainausmerkkien väliin - esimerkiksi: echo $((2*"55")) näyttää tulokseksi 110.

Siis ainoastaa funktiolle: talletamuuttuja  kerrotaan mikä muuttuja halutaan tallettaa - se annetaan nimiparametrina: <talletamuuttuja muuttujan_nimi> - ja sillä ei siis ole väliä millainen se muuttuja on.

Ja luemuuttuja lukee tiedoston delme ja palauttaa siellä olevan muuttujan arvon - määritellen ensin  millainen muuttuja se on ja määräten sitten sen arvon.

***

Käskyt: declare -p > ~/delme   ja: . ~/delme siirtävät kyllä kaikki muuttujat mutta käsky: . ~/delme urputtaa inhottavasti - ei se virheitä tee vaan aiheuttaa  huomautuksia - mutta muutenkin alkaa epäillä että mitä kaikkea se tekeekään.

Moitteettomampi menetelmä järjestelmän kaikkien muuttujien tallettamiseksi on:
Koodia: [Valitse]
function snapshotLevylle () {
function talletamuuttuja () { echo $(declare -p $1) >> ~/delme ;}

echo '' > ~/delme
muuttuja=$(echo ${!a*}' '${!b*}' '${!c*}' '${!d*}' '${!e*}' '${!f*}' '${!g*}' '${!h*}' '${!i*}' '${!j*}' '${!k*}' '${!l*}' '${!m*}' '${!n*}' '${!o*}' '${!p*}' '${!q*}' '${!r*}' '${!s*}' '${!t*}' '${!u*}' '${!v*}' '${!w*}' '${!x*}' '${!z*}' '${!y*}' '${!A*}' '${!B*}' '${!C*}' '${!D*}' '${!E*}' '${!F*}' '${!G*}' '${!H*}' '${!I*}' '${!J*}' '${!K*}' '${!L*}' '${!M*}' '${!N*}' '${!O*}' '${!P*}' '${!Q*}' '${!R*}' '${!S*}' '${!T*}' '${!U*}' '${!V*}' '${!W*}' '${!X*}' '${!Z*}' '${!Y*})

for muuttuja in  ${muuttuja%%BASH*}; do talletamuuttuja $muuttuja; done ;}

# Koekeilua - kuvitellaan että seuraava on skripti ja sen muuttujat halutaan tallettaa:
kaveri=kamu
a=55
A7=2
mat=(7 8 9 'heipparallaa helsinki'); mat[15]=555
snapshotLevylle

# koe toimiko:

unset kaveri # tämä nollaa muuttujan
unset a
unset A7
unset mat
# katsotaaan ovatko ne varmasti tyhjiä - tulostuu viisi tyhjää riviä
echo $kaveri
echo $a
echo $A7
echo; echo ${mat[*]}; echo ${!mat[*]}; echo

. ~/delme 

echo $kaveri
echo $a
echo $A7
echo; echo ${mat[*]}; echo ${!mat[*]}; echo

***

Jos tulee tarve tallettaa funktiot niin sen voi tehdä näin ( jos vaikka haluaa siirtää kaikki muistissa olevat funktionsa kaverin koneeseen niin siirretäänkin tiedosto ~/delme2):
kirjoitus: declare -f > ~/delme2     ja luku: . ~/delme2
- tällätavoin siirretyt funktiot katoavat bootattaessa.

***

Myös aliakset siirretään toiseen koneeseen samantapaisesti:
Lähdekone: alias > ~/delme3 ja kopioidaan se sitten muisttikulle nimellle: delme3
Kohdekone: tikku kiinni ja kopioidaan delme3 nimelle ~/delme3. sitten käsketään: . ~/delme3
- tällätavoin siirretyt aliakset katoavat bootattaessa.

***

Elin viikon remonttimiesten pitämässä mekkalassa ja se palautti mieleeni kuinka moitteeton desimaalilukujen vertaaminen tulee suorittaa - tai vertaileehan tämä kaikkea muutakin.

Sillä lukujen matemaattisten arvojen vertaamista ei kannata tehdä matemaattisesti vaan suorittaaa vertailu tekstijonoilla - sillä matemaattinen vertaaminen rajoittaa merkkilukua erittäin pahasti, lukujärjestelmästä toiseen siirtyminen on aina ongelma - mutta jos molemmat luvut on esitetty samassa lukujärjestemässä ei tekstijonovertailussa tarvitse edes tietää mikä lukujärjestelmä on käytössä. Ja lopullinen niitti tulee jos vertailtavissa luvuissa on tekstiäkin - pelkän tekstin kunnollinen vertaaminen on kylläkin mahdotonta mutta sanottaisiinko että kyllä se usein onnistuukin ja ainakin tekstiä siedetään.

Kaikissa nykyisissä lukujärjestelmissä tunnetaan desimaalipiste ja sen merkityskin on sama - esimerkiksi luku: 10101010.101010 on 170.656250 - onhan tuommoisen muuntaminen vaikeaa ja siksi muiden
 lukujärjestelmien lukuja ei yleensä esitetä desimaalisina.

Vertailu ei kavahda tekstiäkään vaan voit kirjoittaa: kumpionsuurempi 12km/t 13km/t

Ja ennenkaikkea tälle skriptille olisi helppo opettaa uusia temppuja, esimerkiksi tietellisesti esitettujen lukujen vertailu, merkkimuutoksia tai matematiikkan suorittaminen ennen vertailuja ... Mutta tämänhetken toiminta:

1. luvut jaetaan kokonaisosaan ja desimaaliosaan. Jos luku on kokonaisluku niin desimaaleiksi määrätään 0.
2. desimaalien vertaamisessa on yksi lisäsääntö: kummankin desimaaliosan tulee olla yhtäpitkä. Desimaaliosat tehdään yhtäpitkiksi lisäämällä kummankin desimaaliosan perään nollia niin monta kuin toisen desimaaliosassa on merkkejä.
3. verrattavien etumerkit huomioidaan.
4. desimaalipiste jätetään pois kun kokonaisosa ja desimaaliosa yhdistetään. Näitä lukuja sitten verrataan
5. ensiksi täytyy testata yhtäsuuruus ja vasta senjäkeen suuremmuus - kaikki testit tehdään tekstijonovertailuna joten vertailtavien lukujen merkkimäärä on täysin rajoittamaton - yli tuhat numeroa tosin hidastaa jo vähän siitä normaalista 0.1 millisekunnista - siis tämä on melkein yhtänopea kuin erittäin rajoittunut matemaatinen vertailu BASH:in matematiikalla - ja paljon nopeampi kuin vertailu bc:llä.
 
Koodia: [Valitse]
function kumpionsuurempi () {
[[ $1 =~ .*\..* ]] && luku1=$1 || luku1=$1".0" # on tarpeen että luvussa on yksi desimaalipiste -
[[ $2 =~ .*\..* ]] && luku2=$2 || luku2=$2".0" # joten piste lisätään sellaista jos ei vielä ole
[[ ${1//[^-]/} ]] && m1=- || m1=+; [[ ${2//[^-]/} ]] && m2=- || m2=+
 
koko1=${luku1%%.*};koko2=${luku2%%.*};koko1=${koko1:=0} ;koko2=${koko2:=0}

desi1=${luku1##*.}; desi2=${luku2##*.} #; echo desi1:$desi1'   ';echo desi2:$desi2

apu=$desi2;desi2=$desi2$(echo $desi1 | tr [0-9] 0); desi1=$desi1$(echo $apu | tr [0-9] 0) #; echo desi1korjattuna:$desi1; echo desi2korjattuna:$desi2

luku1=$koko1$desi1; luku2=$koko2$desi2 # echo luku1:$luku1; echo luku2:$luku2

[[ $luku1 = $luku2 ]] && echo 'luvut ovat yhtäsuuria' || {
case $m1$m2 in
-+) echo 'toka on suurempi' ;;
+-) echo 'eka on suurempi' ;;
++) [[ $luku1>$luku2 ]] && echo 'eka on suurempi' || echo 'toka on suurempi' ;;
--) [[ $luku1>$luku2 ]] && echo 'toka on suurempi' || echo 'eka on suurempi' ;;
esac ;};}

# kumpionsuurempi 7C12.5B 7C12.5A1234567890 # siis hexa-desimaali lukujen vertailu

# tai:
kumpionsuurempi 12345678901234567890123456789012345678901234567890.12345678901234567890123456789012345678901234567892 12345678901234567890123456789012345678901234567890.12345678901234567890123456789012345678901234567891

18
Koodia: [Valitse]
Elämässäni ensimmäinen kerta kun en uskalla jotain testata kovin nopeasti. Mutta kaiken kieron mitä olen uudelle yhteen- ja vähennyslaskulle keksinytkin on se ratkaissut sen oikein. Toisaalta myös lasku 1+1 onnistui. Mutta jossain muussa helpossa se tietty mokaa.

***

Desimaali-jakolaskuun löytyi uusi menetelmä - se on aina nopea mutta vaikka joissakin laskuissa saa 48 oikeaa desimaalia niin joissakin ei saa kuin muutaman. Ja muutenkin se on toistaitoinen menetelmä. Pitääpä katsoa saako primadonnan tanssimaan.

Esitänpä laskemisen periaatteen:

pitäisi laskea mitä on: 1233457890123.23/.123456
kokonaisosa: $((1233457890123230000/123456)) -> 9991072852864

aletaan laskea desimaaleja: $((1233457890123230000%123456)) -> 52016
desimaalit ovat: $((5201600000000000/123456)) -> 42133229652
 siis vastaus on näin alkuunsa:
9991072852864.42133229652           
haetaanpa oikea vastaus: bc -l<<<"1233457890123230000/123456" -> 
9991072852864.42133229652669777086

***

Edellisestä periaatteesta tehty skripti - joka samantien laskee pari lasku-kierrosta lisää:
Koodia: [Valitse]
# tämä on uusi ja korjattu versio joka on vielä aika raakile ja muuttuu vielä paljon. Keskeneräisyyden
# osoitus on runsas välitulosten tulostaminen
 

function siisti () { apu=$1; merkki='';[[ ${apu//[^-]/} ]] && merkki=- && apu=${apu:1}; [[ ${apu//[^.]/} ]]  || apu=$apu"." ; apu=${apu%00000000000000};apu=${apu%0000};apu=${apu%00};apu=${apu%0};apu=${apu#00000000000000};apu=${apu#0000};apu=${apu#00};apu=${apu#0};apu=${apu%.}; echo $merkki$apu ;}

function jaa () { # muutos
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1"."
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2"."

desimaaliosa1=${luku1##*.}
desimaaliosa2=${luku2##*.}
kokonaisosa1=${luku1%%.*}; echo x$kokonaisosa1
kokonaisosa2=${luku2%%.*}; echo y$kokonaisosa2
# apu=$((${kokonaisosa1:0:18}/${kokonaisosa2:0:18})).
kokonaisiatulosteessa=0
nolliatulosteessa=''

apu=$((${#kokonaisosa2}-${#kokonaisosa1}-1));echo $apu
case $apu in
-1) kokonaisiatulosteessa=1 ;;
0) kokonaisiatulosteessa=0 ;; # apu=85; printf "%${apu}s" | tr " " 0
1) nolliatulosteessa='' ;;
2) nolliatulosteessa=0;;
3) nolliatulosteessa=00 ;;
4) nolliatulosteessa=000 ;;
5) nolliatulosteessa=00000 ;;
6) nolliatulosteessa=000000 ;;
7) nolliatulosteessa=0000000 ;;
8) nolliatulosteessa=00000000 ;;
9) nolliatulosteessa=000000000 ;;
10)nolliatulosteessa=0000000000 ;;
*) kokonaisiatulosteessa=$((-1*$apu-1 )) ;;
esac
echo nolliatulosteessa:$nolliatulosteessa'  kokonaisiatulosteessa:'$kokonaisiatulosteessa

luku1=$kokonaisosa1$desimaaliosa1
luku2=$kokonaisosa2$desimaaliosa2 

echo xxx$luku1' '$luku2

unset tulos # vain varmistus että kaikki on tuloksessa tämänjälkeen uutta
for n in {1..6}; do # muodostetaan tulos-palasia 9 merkkiä kerrallaan
apu=$((10#$luku1/10#$luku2)); (( ${#apu} ==8 )) && apu=$apu'0'; tulos[$n]=${apu} ;echo a$luku1' '$luku2' '$apu
luku1=$(($luku1%$luku2))'0000000000000000000'; luku1=${luku1:0:18} ;echo z$luku1
done

for n in {1..6}; do # kootaan tulosta matriisin palasista
tulos=$tulos${tulos[$n]}
done

# tulos=$( siisti $tulos)

echo "oikea tulos 54 desimaalilla esitetynä on päällä ja alla tulos tästä laskusta:"
bc<<<"scale=54; $1/$2" | tr -d '\\\n'; echo ' tämä rivi on bc:stä'
[[ $nolliatulosteessa ]] && echo .$nolliatulosteessa${tulos:0} || echo ${tulos:0:$kokonaisiatulosteessa}.${tulos:$kokonaisiatulosteessa} ;}
           
jaa 1233457890123.23 .1234567


----skripti tulostaa:
oikea tulos 54 desimaalilla esitetynä on päällä ja alla tulos tästä laskusta:
9991016203439.991511193803171476315177710079728358201701487242085686 tämä rivi on bc:stä
9991016203439.991511193803171476315177710079728358201701487242085686

- tämä osoittaa ainakin että periaate on oikea - ongelmana näyttää olevan muunmuassa desimaalipisteen paikan laskeminen ja laskujen etunollat. Kai ne aikanaan saa korjatua.
- miinus-merkkiset ei vielä toimi
- tämäkin skripti on niin kasattu että sen  voi helposti ajaa uudestaan - paina vai nappin nuoli-ylös jolloin funktiokutsu palaa näytölle edtoitavaksi ja kun painaa enter niin se ajetaan uudestaan editoiduilla parametreilla.
- kaikki tämän tyyppiset skriptit ovat nopeita ja sillä on vain pieni merkitys kuinka kookkaita nämä tällaiset skriptit ovat - ja esimrkiksi voi laskea niin monta desimaalia kuin sielu sietää - nopeus on aina siellä millisekunnin nurkilla.
- muuten nuo kummalliset käskyt ovat tekstinkäsittely-käskyjä eivätkä matematiikka-käskyjä - BASH on yksittäisten sanojen käsittelyssä ziljoonakertaa parempi kuin sed - ja sed on ehdottomasti paras isojen tekstien käsittelemisessä. Tai enpä ole tuosta ihan varma - sed on pajon parempi kuin "korkeantason" käskyillä invalidisoitu BASH mutta en tiedä kuinka nuo matalan tason käskyt isoissatekstinkäsittelyssä toimivat, en ole kokeillut. Paitsi silloin kerran kun puhuttiin Pythonin ylivertaisuudesta ja todettiin että kyllä sed-kin jää toiseksi ja naureskeltiin BASH:in kustannuksella - mutta itseasiassa eräs BASH:in matalan tason käsky oli melkein yhtähyvä - mutta luulin silloin että ihan yksittäinen tapaus se oli, vaan eipä tainnut ollakaan.

***

Oli pahantahtoinen teko viedä BASH:ilta kirjasto-osoitin - kirjastojen toimintaan se ei vaikuta mutta tekee kirjastojen käyttämisestä hankalaa.

Tästä hommasta on väännetty ikuisesti - esimerkiksi väittämällä että kirjastot ovat tietoturvariski. Mutta jokaisessa Ubuntussa on 85 funktion kirjasto joten jos ne ovat tietoturvariski niin se tietoturva on mennyt jo ja se osoittimen vieminen oli pelkkää kiusantekoa.

***

Aivan kaikesta voi ja myös pitää tehdä funktio kirjastoon sillä kukaan ei voi muistaa kaikkia pieniä kikkoja - tai ainakaan viitsi kirjoittaa. Esimerkiksi käsky joka etsii jostakin jotakin - tekstinpalasia lauseista tai tiedostoista, numeroita ja vaikka mitä. Pienissä hommissa se on paljon parempi kuin grep:
Koodia: [Valitse]
function onkoosa () { [ -z "$1" ] || { [ -z "${2##*$1*}" ] && [ -n "$2" ] && echo joo || echo ei ;};}
- kutsu etsittäessä tiedostosta: onkoosa BEGIN "$(cat /boot/grub/grub.cfg)"
- kutsu etsittäessa tekstijonosta:onkoosa öp <muutuujan nimi>

matriisista haku sitten toisella tavalla:
Koodia: [Valitse]
function onkomatriisissa () { [[ " ${@:2} " =~ $1 ]] && echo joo || echo ei ;};
esimerkiksi: matriisi=({1..100000}); onkomatriisissa 55555 ${matriisi[*]}

Nyt sain ajatuspähkinän että toimiiko tämä aina:
Koodia: [Valitse]
function onkomatriisissa () { [[ $(declare -p $2 ) =~ \"$1\" ]] && echo joo || echo ei ;}; matriisi=({1..100000}); onkomatriisissa 55555 matriisi
- siis matriisi passataan nimiparametrina - siis semmoisena jota BASH ei muka tunne.

***

Kun tuommoisia alkaa kerätä kirjastoonsa niin samalla voi nimetä ne uudestaan että funktiolla on sellainen nimi jonka muistat - ja voi niitä muutenkin hieman räätälöidä.

Kyllä niitä funktioita googlaamalla löytää. Ongelmana on ettei yksittäisestä funktiosta ole mitään iloa vaan niitä täytyy löytyä tuhansia - lajiteltuina ryhmiin, kaikkien toiminta pitäisi olla santapaista kuin muillakin samassa ryhmässä - ja ennenkaikkea omituiset omassa ryhmässään silla kaikessa on kyllä jotakin eikä niitä omituisia roskikseen pidä laittaa.

Olisikin tarve siihen että olisi paikka jossa olisi funktioita todella paljon. Ja kyllä niitä onkin - mutta nuo varastot on kaikki pilattu. Joko käyttäminen on ihan liian monimutkaista, vaikeaa ja tarkoitettu virtuooseille  - tai funktioissa on muutamia hyviä mutta käsittämättömän huonoja niin runsaasti ettei niitä kultajyviä tahdo millään  löytää.

******

Tietokoneen omienkin matematiikka-kirjastojen kehittäminen on vaatinut lukemattomia mies-työvuosia kymmeniltä loistavilta matemaatikoilta - nuo kehitystiedot on sijoitettu prosessorien matematiikka-yksikköiden langoitukseen ja mikrokoodiin, käyttöjärjestelmien kirjastoihin ja kielijärjestelmien kirjastoihin. BASH on ihan tietoisesti hankkinut vain rajoitetun pääsyn integer-kirjastoihin.   

Sillä kaikki laskenta on jossain vaiheessa kokonaisluku laskentaa ja kirjaston tekeminen kokonaisluku-laskentana ihan mahdollista - ja koska BASH on opetuskieli niin oli opetuksen takia tarkoitus pistää käyttäjät tekemään itse desimaalikirjastonsa. Ja senaikaisilla käskyillä noista kirjastoista olisi tullut kohtuullisen nopeitakin - siellä millisekunnin nopeus-luokassa isotkin matalantason käskyistä tehdyt skriptit useimmiten ovat. Lisäksi noiden skriptien nopeus laskee vain vähän koodimäärän kasvaessa. 

Mutta jostainsyystä kukaan ei silloin kauan sitten noita kirjastoja tehnyt ja kun BASH:in käskykantaa "parannettiin" olisi kirjastoista tullut uuslla käskyillä niin hitaita ettei niiden tekemisessä ollut mieltä - toimivathan ne vanhat käskyt edelleen mutta tietoisuus niiden olemassaolosta hiipui nopeasti.

Nyt on tilanne BASH:in kannalta niin toivoton ettei noiden kirjastojen kehittäminen enää paljoa kannata - BASH:in kyvyt kasvavat paljon mutta käyttäjäthän ovat jo kaikonneet eivätkä takaisin tule. Mutta saihan näistä yritelmistä ainakin sen tiedon että tässäkin asiassa BASH on telottu hengiltä levittämällä epätotuuksia - kovin on kirjastojen poissaolo omituista - niitä muuten oli aikoinaan mutta ne tuhottiin jollain ilveellä - jäljellä on tosin muutama omituinen. Ja on syytä muistaa se jokaisessa Ubuntussa oleva 85 funktion kirjasto jota vain käyttöjärjestelmä tietää käyttää. Varmaankin tuo kirjasto muistakin Linukseista löytyy käskyllä: declare -f. Sieltä muuten näkee senkin etteivät kehittäjät vierasta käskyä: eval.

***

Teoriassa laskut desimaaliluvuilla ovat yksinkertaisia silloinkin kun voi laskea ainoastaan  kokonaisluvuilla: ennen laskua poistetaan se demaalipiste, sitten lasketaan ihan normaalisti jäljelle jääneellä kokonaisluvulla ja lopuksi se desimaalipiste palautetaan. Mutta vaikka tämä toimii käytännössäkin niin eteen tulee monia vaikeuksia:

Esimerkiksi desimaalipisteen palauttaminen oikeaan paikkaan on todellinen ongelma - lisäksi se on vielä pientä jos laskusi heittää kymmenertaisesti. Seuraavankaltaisissa pikkuhommassakin desimaalipisteen paauttaminen oikealle paikalle aiheuttaa päänsärkyä:
Koodia: [Valitse]
function tuplaa () {
luku1=$1"." # jos luvussa on tässävaiheessa kaksi desimaalipistettä ei se haitaa
kokonaisosa1=${luku1%%.*}
kokonaisosanpituus=${#kokonaisosa1}
luku1=${luku1//./};luku1=${luku1:0:18} # poistetaan ne desimaalipisteet olipa niitä 1 tai 2
tulo=$(($luku1*2))
[[ ${#tulo} -gt ${#luku1} ]] && kokonaisosanpituus=$(($kokonaisosanpituus+1))
tuloste=${tulo:0:$kokonaisosanpituus}.${tulo:$kokonaisosanpituus}
echo ${tuloste%*.} # ei tulosteta desimaalipistettä jos se on tuloksen viimeinen merkki
}

tuplaa 9.65438761111111112
Desimaalipisteen paikan kanssa joutuu kamppailemaan vaikka tekisi tuplaamisem tai puolittanisen bittisiirtoina  (tuo: let "luku <<=1"):
Koodia: [Valitse]
function tuplaa () { [[ ${1//[^.]/} ]] && luku=$1"0"|| luku=$1".0"; desimaaliosa=${luku##*.}; desimaaliosanpituus=${#desimaaliosa}; luku=${luku//./}; let "luku <<=1";  apu=${luku: -$desimaaliosanpituus:$desimaaliosanpituus-1}; (( $apu )) && echo ${luku:0: -$desimaaliosanpituus}.$apu || echo ${luku:0: -$desimaaliosanpituus} ;}

function puolita () { [[ ${1//[^.]/} ]] && luku=$1"0"|| luku=$1".0"; desimaaliosa=${luku##*.}; desimaaliosanpituus=${#desimaaliosa}; luku=${luku//./}; let "luku >>=1"; echo ${luku:0: -$desimaaliosanpituus}.${luku: -$desimaaliosanpituus:desimaaliosanpituus-$((${luku: -1} == 0)) } ;} 

# Tässä testaus:
puolita $(tuplaa 5555555555555.5555)

***

Mutta mikäli laskettavien joukkoon tulee toinenkin luku niin eteen tulee lukemattomia ongelmia lisää - esimerkiksi yhteenlasku onnistuu näin vain mikäli lukujen desimaaliosat ovat yhtäpitkät - jos ne eivät ole niin lyhyempään täytyy lisätä perään nollia niin monta että ne ovat yhtäpitkiä - sillä desimaalien perässä nollat eivät muuta mitään. Samallatavoin täytyyy tehdä jos desimaalilukuja vertailee. On yli- ja alivuodot - ja ongelmat niiden kanssa laajenevat paljon pyrittäessä suurempaan numeromäärään jolloin lukuja aletaan jakaa osiin ja niiden laskentatuloksia aletaan koota. Silloin on myös pahemmat merkkiongelmat ... 

BASH tosiaan opettaa olemaan riemuitsematta kovin nopeasti - usein myöhemmin selviää ettei skripti ihan niin hyvä ollutkaan kuin oli luulo. Aina on parantamisen varaa - toisaalta se on katkeraa ja toisaalta elämän suola.

- kaikkein katkerinta on tieto siitä että jotkut ovat tienneet ikuisesti desimaalilaskujen onnnistuvan BASH:issakin oikein hyvin sillä kokonaisluvuilla desimaalilaskut aina lasketaan - siellä näyttämön takana jonne käytännössä harva kurkkii - mutta matematiikan teoreetikot ovat tienneet tämän aina - se on asiaan kuulumatonta että se on todellinen miinakenttä. BASH:issa kyky laskea desimaaleilla on nykyään  merkityksetöntä mutta aikoinaan se olisi ollut erittäin merkittävää ja ihan yhtä mahdollista kuin tänäänkin.

***

Desimaalilukujen kanssa toimiessa olisi usein tarpeen poistaa merkityksettömät etunollat kokonaisosasta ja takanollat desimaaliosasta - ja poistaa myös desimaalipiste mikäli se jäisi luvun viimeiseksi merkiksi. Yksi kammottava funktio tekee sen nopeasti ja varmasti:
Koodia: [Valitse]
function siisti () { apu=$1; merkki='';[[ ${apu//[^-]/} ]] && merkki=- && apu=${apu:1}; apu=${apu%00000000000000};apu=${apu%0000};apu=${apu%00};apu=${apu%0};apu=${apu#00000000000000};apu=${apu#0000};apu=${apu#00};apu=${apu#0};apu=${apu%.}; echo $merkki$apu ;}

# käsky kokeilemiseksi:
apu="-000120340.0400";echo -n "Luku alunperin: $apu  . Ja käsittelyn jälkeen:   "; siisti $apu

***

Aloin tutkia Taylorin sarjoja ja kestää tovi ennenkuin tuloksia tulee - teorioiden toimivuus on kyllä tarkistettu jo muttaa kerkiänkö remontin takia tekemään toimivaa skriptiä jää nähtäväksi. Silläaikaa täytyy puhua pehmoisia:


BASH:issa on kyllä samantapaiset muuttujien määrittelyt kuin muissakin kielissä mutta eipä niitä näissä pikkuhommissa tarvita - miksi suotta sotkea koodia? Mutta joidenkin mieletä ne päinvastoin selkeyttävät koodia ja ovathan ne joskus ehdottoman tarpeellisiakin. Joten on syytä  tietää että niitäkin on:

1. Funktiossa olevat muuttujat näkyvät pääohjelmassakin. Joskus funktio silloin muuttaa tarkoittamattaan pääohjelman muuttujia. Tällöin funktiossa niille härikkö-muuttujille määrätään: local muuttujan_nimi
- joskus täytyy peräti siirtyä funktiossa omaan prosessiin jolloin mikään ei varmasti vaikuta pääohjelmaan ellei nimenomaan käsketä. Tämä tapahtuu muuttamalla funktiokutsun aaltosulut kaarisuluiksi.
2. Joskus halutaan painottaa sitä että muuttuja on kokonaisluku. Tällöin määrätään:
declare -i muuttujan_nimi   (=integer).
3. joskus on tarpeen määrätä että muuttuja onkin vakio: declare -r muuttujan_nimi=arvo  (=readonly)
- siis arvo täytyy antaa määrittelyn yhteydessä.
4. joskus halutaan painottaa sitä että muutuja on matriisi-> declare -a muuttujan_nimi
5. jos matriisi on assosiatiivinen jolloin se täytyy määritellä: declare -A muuttujan_nimi (Assosiatiivisen matriisin osoite on tekstiä - jos siinä on numeroita niin tekstiksi nekin käsitetään).

. ja on määreitä muitakin: https://linuxcommand.org/lc3_man_pages/declareh.html

Onhan BASH:in muuttujat mukavia käyttää kun ainoastaan assosiaiatiivinen matriisi täytyy määritellä. Vaan on sillä varjopuolensakin - esimerkiksi muuttuja on aina myös saman-nimisen matriisin ensimmäinen jäsen ja usein kun luulet toimivasi numeroilla matriisin kanssa toimitkin itseasiassa tekstijono-muuttujan kanssa.

Voit toki määritellä interger-matriisinkin. Silloin ongelmat ovat toisenlaisia - käskepä:
declare -ai apu; apu[5]=kattokassinen; echo ${apu[5]} -> tulostuu 0 - ellei sattumalta muualla skriptissä ole muuttujaa nimeltä kattokassinen ja sillä numero-arvo.

Samoin mukavaahan se on kun ei tarvitse välittää onko muuttuja tekstiä vai numero - lukuunottamatta tilannetta jossa nimenomaan määräätään muuttujan olevan numero. Noilla integer- muutujilla on muuten automaattinen laskenta - käskepä: declare -i apu; apu=1+2; echo $apu -> tulostuu 3 - mutta RAM-muistissa se silti on:1+2

Ja mukavaa on sekin ettei välttämättä tarvitse antaa muutujalle arvoa ennenkuin sitä käyttää - sen arvo on silloin 0. Mutta toisaalta tämä tekee sen että jos esimerkiksi toisessa skriptissä onkin annettu saman-nimiselle muuttujalle jokin arvo pysyy määrittely voimassa ja yhtäkkiä tämänhetkisen skriptin muuttujalla onkin joku kummallinen alkuarvo. Tämän takia skriptin alkuun kehoiteaan laittamaan: set -u

***

BASH olettaa että asiat on määritelty siinävaiheessa kuin niitä yritetään käyttää - elikä eteenpäin viittauksia ei sallita. Tämän kiertämiseksi on lukemattomia konsteja mutta koodi pysyy yksinkertaisempana kun ei konsteja suotta harrasta - konsteista täytyy saada jotain pätevää hyötyä - ja sitä ei voi sanoa hyödyksi että haluaa BASH:in toimivan kuin C.

BASH on tuomittu ohjelmointikielenä vaika eihän BASH:ia ole ohjelmointiin tarkoitettukaan vaan ulkoisten ohjelmien yhteen-nivomiseen - BASH:in toimiminen opetuskielenäkin on tavallaan sivujuonne sillä kyllä me kaikki oppia kaipaamme. On BASH:illa ohjelmoiminen silti mielestäni mukava harraste ja nuo desimaalilaskuni olivat vain yksi osoitus siitä että BASH:ista puhutaan suupielet ruskeina kaikenlaista.

Enkä usko etteivät virtuoosit ole tienneet että BASH osaa kaikenlaista - esimerkiksi funktiot ovat tosiaan BASH:in toimimisen perusta ja kun funktioita ei käytetä ja peräti väitetään ettei funktio-nimiprametreja tunneta niin kyllä siinä kieli hiljokseen tikahtuu. Joten ei BASH toisten puheisiin ja luuloihin tainnut kuolla vaan kyllä syy oli sisäsyntyinen - virtuoosit tappoivat oman kielensä.

Sillä on aika rakentaa ja on aika rikkoa. Se ei ole meidän oma valintamme vaan meidän jokaisen elämä tapahtuu noin halusimme tai emme. Nuorena alamme jossain vaiheessa rakentaa ja jossain vaiheessa vanhuutta alamme taas rikkoa sitä mitä olemme rakentaneet - rikkomistamme yleensä edes tajuamatta.

***

Yksi asia mitä virtuoosit eivät painota enkä minäkään ole huomannut painottaa vaikka se kuuluisi kertoa jo ennen alkeita: BASH:in tietorakenteet ovat dynaamisia elikä niiden koko määräytyy vasta käytönaikana - esimerkiksi matriisia ei tarvitse edes ilmoittaa ja silti voidaan määritellä ensin matriisin miljoonas jäsen ja seuraavaksi jäsen 222 ja jättää kaikki muut määrittelemättä ja silti se on täysin kelvollinen matriisi. Sama on tekstijonon kanssa - sen koolla ei ole ylärajaa jaa se voikin kasvaa ikuisesti vaikka sen olemassaolosta ole koskaaan ede kerrottu - se vain on olemassa ja alussa sen arvo on tyhjä.

Voit siis aivan rauhassa tulostaa jotakin josta ei ole koskaan puhuttu - sillä ei vaan ole arvoa joten tulostuu tyhjää. Samoin jos käytät määrittelemätönta niin se ei ole varsinaisesti virhe vaan tuon määrittelemän matemattiseksi arvoksi oletetaan nolla ja tekstuaaliseksi arvoksi tyhjä. Tottakai tuommoisella on myös varjopuolensa ja sen virtuoosit kyllä muistavat kertoa.







19
Muuttujat ja lyhyet tekstijonot kannattaa yleensä siirtää entiseen malliin arvoparametreina - mutta tässä esimerkit että kyllä nekin toimivat nimiparametreina:
Koodia: [Valitse]
koe () { let $1=$((2*$(declare -p $1 | cut -f2 -d '"'))) ;}; a=1; koe a; echo $a

koe () { eval $1=$((2*$1+55)) ;};a=1; koe a; echo $a

***

Pitemmät tekstijonot ja matriisit kannattaa yleensä siirtää nimiparametreina:
Koodia: [Valitse]
function jonomax (){ declare -p $1 | cut -f2 -d '"' | tr " " '\n' | sort -n | tail -1  ;}; jono="1 2 3 4 5 4 3 2 1"; jonomax jono

Tai jos välttämättä haluaa käyttää eval-käskyä:
function jonomax (){ eval echo \${$1} | tr ' ' '\n' | sort -n | tail -1 ;}; jono="1 2 3 4 5 4 3 2 1"; jonomax jono

***

matriisi esimerkkejä:
Koodia: [Valitse]
function matmax () { declare -p $1 | tr = '\n' | sed 's/\[.*//g' | tr -d \" | tr -d \) | sed 1,2d | sort -n | tail -1 ;}; unset mat; mat=({1..999999}); time matmax mat
- muuten etsiminen joukosta:mat=({9..0}{0..9}{9..0}{9..0}{0..9}{9..0}) kestää suurinpiirtein yhtäkauan vaikka siinä  totisesti on sorttaamista - mutta itse sorttaus tehdäänkin C-kielisissä rutiineissa eikä se koskaan kauaa kestä. Se mikä kestää on siirtää BASH:in muuttujat sort-käskyn matriiseihin.
- silti sorttaus saattaa kestää ensimmäisellä kerralla kauan koska Linux pistää buffereitaan kuntoon?

Seuraavaksi koodi joka säilyttää BASH:in matriisin sparse ominaisuuden:
- sparse -> matriisin jokaisella jäsenellä on arvo ja osoite. Niillä matriiseilla joita normaalisti käytetään osoitteet alkavat nollasta tai ykkösestä ja siitä eteenpäin osoite kasvaa aina yhdellä. Mutta sparse-tyyppisellä matriisilla osoite voi olla mikävaan kokonaisluku. Assosiatiivisella matriisilla osoite voi olla myös teksti - siis vaikkapa desimaalinumero.
Koodia: [Valitse]

function matmax (){ name=$1; eval name='('$(declare -p $name  | sed -s 's/declare.*=(//'); echo -e ${name[*]/#/\\n} ;};mat=({999..1}); matmax mat

tai maximin etsintä eval-käskyn kanssa:
Koodia: [Valitse]
function matmax (){ eval echo \${$1[*]} | tr ' ' '\n' | sort -n | tail -1 ;}; mat=({999..1}); matmax mat

Tai siirto kovalevyn kautta:
Koodia: [Valitse]
function matmin (){ echo $(cat $1 | sort -n | tail -1)>$1 ;}; a=({1..999}); echo -e "${a[@]/#/\\n}" > ~/delme; time matmin ~/delme; cat ~/delme
- ramdisk kyllä nopeuttaisi ... mutta edellyttäisi sudoa.  luonti:
mount -t tmpfs -o size=500m tmpfs /mountpoint
     
Tai awk-versio. Esim. kun tarvitaan nopeutta mutta esitysmuoto on joskus tieteellinen:
Koodia: [Valitse]
function matmax () { cat $1 | tr " " '\n' | awk 'BEGIN {maksimi=-9e99} { if ($1>maksimi) maksimi=$1 } END { print maksimi }' ;}; a=({999999..1}); echo -e ${a[*]/#/\\n}  > delme; time matmax delme

Tai bc versio joka saa selvää ratkaisemattomista sineistä ja mitä nyt bc osaakaan:
Koodia: [Valitse]
function keskiarvo() { count=$(echo $1 | wc -w); echo $(echo '('$1')/'$count | sed 's/[eE]/*10^/g' | tr '[] ' '()+' | bc -l);}; keskiarvo 'c[1] s[1] 1e3'

- hakasulkuja on pakko käyttää sillä BASH tulkki menee kaarisuluista solmuun.

***

Tässävaiheessa alkaa funktioiden merkitys korostua: eihän erkkikään tämmöisiä litanioita viitsi kirjoittaa - kyllä nuo sotkut täytyy lykätä kirjastoon niinkuin kaikki muutkin kielet ovat tehneet. Ja pahemmaksi muuttuu. Vai mitäpä sanoisit seuraavasta funktiosta:
Koodia: [Valitse]
function onkoekasuurempi () { ((10#0${1%.*} ^ 10#0${2%.*})) || (( ${1//./} < ${2//./} )) && echo 0 || echo 1 ;}; onkoekasuurempi 8.127 8.126
Tällä funktiolla on etunsa mutta se on kaukana moitteettomasta. Lisää kummallista risuaitaa ja alkaa toimia paremmin. Pahimmillaan neljä riviä samankaltaista merkkien sotkua olevia nopeita funktioita on tehty moniakin mutta on täysin varmaa on ettei niitä käytetä jos ne eivät ole kirjastossa funktioina - ja kirjastoa jaettu muille jottei se häviäisi kun tekijä häviää. Koska kirjastojen käyttöä ei harkitakaan niin voi edelleen kertoa vakaana mielipiteenään että BASH on suunnattoman hidas eikä osaakaan mitään.

Kunnollisempi mutta hidas reaalilukujen testaus on kahdesta funktiosta tehty - ehkäpä kummastakin saisi tehtyä nopean risuaita-verion?:
Koodia: [Valitse]
function onkoekasuurempi () { kokonaiset1=$(echo $1 | cut -d. -f1 ); kokonaiset2=$(echo $2 | cut -d. -f1 ); (( $kokonaiset1==$kokonaiset2 )) && onkoekandesimaalitsuuremmat $1 $2  || { (( $kokonaiset1<=$kokonaiset2 )) && echo 0 || echo 1 ;};};

function onkoekandesimaalitsuuremmat () { desimaalit1=$(echo $1 | cut -d. -f2 ); desimaalit2=$(echo $2 | cut -d. -f2 ); (( $desimaalit1<=$desimaalit2 )) && echo 0 || echo 1 ;}; time onkoekasuurempi 8.3 8.2
Ja mitähän kaikkea vielä joutuisi lisäämään ennenkuin toiminta olisi moitteeton?

***

- muuten ei näistä esimerkeistäni ole yhdestäkään tarvinnut tehdä skriptiä - riittää kun leikkaa-liimaa ne täältä foorumilta päätteeseen.

Funktiokutsuista on tehty nopeita. Nopeuden saavuttamiseksi funktio suoritetaan samassa prosessissa kuin kutsujakin - joten kaikki on yhteistä. Näin tehdään sillä uuden prosessin luominen on hidasta. Toinen juttu on että onhan BASH muuten hidas.

Ja kaikenlaisiin asioihin voi tehdä funktion. Jopa loopin askelista voi muodostaa funktion avulla levytiedoston:
Koodia: [Valitse]
function looppimatriisi () { bc<<<"for (i=1.000000; i<=10; i+=0.02) i">delme ;}; looppimatriisi
- siis looppimatriisissa voi olla myös desimaalilukuja eikä lukujen desimaalien määrällä ole mitään rajaa. Eikä muuten ole rajoituksia kokonaisosallakaan. Tieteellisestä esitysmuodostakaan ei ole pelkoa.
- vain pieni muutos niin steppaus on exponentiaalinen: 'i+=' muutetaan: 'i*=', huomioi vaan silloin että kertoimen pitää aina olla suurempi kuin yksi.
- mikähyvänsä muukin muutotapa on mahdollinen - neliöjuurellinen, logaritminen ...
- alku- , loppu- ja askel-arvot voidaan siirtää looppimatriisiin parametreina.
  esimerkiksi:
Koodia: [Valitse]
function looppimatriisi () { bc -l<<<"for (i=$1; i<=$2; i*=$3) i" | cut -c -$4 ;}
looppimatriisi 1 1000000 1.05 8  # matriisin jäseniin tulee korkeintaan 8 numeroa

***
 
Matemaattisen kaavan ratkaiseminen muuttujan vaihdellessa onnistuu sekin - esimerkiksi voi helposti muodostaa matriisin funktion graafista kuvausta varten. Esimerkkinä päässälasku jotta toiminta selviäisi:
Koodia: [Valitse]
function ratkaisija () { echo $1 | tr '[]' '()' | sed 's/x/'$2'/g' | bc -l ;};
a=x^2+x+1; ratkaisija $a 2   # kaavaksi voi vaihtaa mitä lystäät
- siis ratkaistava kaava kirjoitetaan muuttujaan ja funktion avulla ratkaistaan sen arvo tuntemattoman annetulla arvolla. Koska mukana on bc ja sen matematiikkakirjasto onnistuvat monimutkaisetkin laskut - tosin bc:n funktiot merkitään vähän kummallisesti.

***

- koska seuraavassa piiretään niin saattaa joutua lataamaan: sudo apt install gnuplot

Laitetaanpa pari edellisistä skripteistä yhteen ja esitetään tulos graafisesti - skriptin suorituksen jälkeen näytölle pitää tulla pieni ikkuna nimeltään gnuplot ja siinä jakso sinikäyrää - jotta varmistuisi että kaikki pelaa (kopioi kaikki rivit kerralla):
Koodia: [Valitse]
function ratkaisija () { echo $1 | tr '[]' '()' | sed 's/x/'$2'/g' | bc -l ;}
a=s[x]  # bc laskee sinin näin - radiaaneissa muuten. Sini on valittu koska jokainen tietää mitä pitäisi tulla. Mutta voit muuttaa kaavan haluamaksesi sillä gnuplot muuttaa asetuksensa oikeiksi automaattisesti - jollei erikseen toisin määrätä.

function looppimatriisi () { apu=$(bc<<<"for (i=0; i<=6.29; i+=0.01) i") ;}; looppimatriisi

echo''>/tmp/delme; for n in ${apu[@]}; do echo $n' '$(ratkaisija $a $n)>>/tmp/delme; done

gnuplot -p -e 'set terminal postscript eps color enhanced; set xlabel "muuttujan arvo"; set ylabel "funktion arvo"; set terminal x11; set output "/tmp/transpose.eps"; plot "/tmp/delme"'

***

Funktion nimen voi passata parametrina - ja silläkin voi olla parametrinsa mutta parametrinumerot kyllä muutuvat määränpäässä (ihan normaali epäsuora viittaus?):
Koodia: [Valitse]
function luuppi () { apu=$(bc<<<"for (i=$1; i<=$2; i+=$3) i") ;};

function koe () { echo "täältä mennään funktioon: $1"; $1 $2 $3 $4 ;}

koe luuppi 1 10 1; echo $apu

***

Itse funktionkin voi passata parametrina? - kunhan siinä ei ole kovia lainausmerkkejä tai välilyöntejä joita ei voi pehmeillä lainausmerkeillä suojata (toimii silloinkin jos alkaa leikkiä IFS:n kanssa):
Koodia: [Valitse]
function loppi () { apu=$($1) ;}
loppi bc<<<"for (i=2; i<=20; i+=2) i"; echo $apu 

- bc<<<"for (i=2; i<=20; i+=2) i" siirtyy tosiaan semmoisenaan sillä ratkaistunahan siinä olisi välilyöntejä ja silloin siirtyisi vain ensimmäinen numero?
- ja: function loppi () { echo "$1"; apu=$($1) ;} tulostaa lisäksi: bc niinkuin: echo bc<<<"for (i=2; i<=20; i+=2) i" tulostaakin.

***

- BASH:issa on jonkinlainen 'käsky kerrallaan askellus' - eihän se kovin hyvä ole, mutta sen saa skriptissä päälle ja pois: ennen kohtaa jossa arvelee vian olevan lisää koodiin:
set -x; trap "echo paina: return;read x" DEBUG
jolloin se alkaa askeltamaan käsky kerrallaan kirjoittaen muuttuneiden muuttujien arvot.
Vikakohdan tutkimisen jälkeen täytyy lisätä koodiin:
set +x; trap "" DEBUG
jolloin toiminta palaa nomaaliksi.

***

- enpä tiedä onko se hyvä tai paha mutta BASH totisesti pitää varpaillaan - kun loppusiloittelet skriptiäsi niin ei tosiaan tiedä mihin päätyy - pieni moka ja korjaat sitä loppupäivän - koska aina muulloin tallettaa jokaisen välituloksen mutta siinävaiheessa ahneus iskee ja siistimisyritys tuhoaa koko homman.

- aikoinaan havaitessani että BASH:ista halutaan eroon aloin tuskailla siitä toimiiko BASH enää seuraavassa versiossa. Eipä taida olla pelkoa BASH:in menettämisestä - se toimii uudessa UBUNTU:ssakin - ja tulevissakin. Jopa hiljokseen kehittyenkin - tällähetkellä on meneillään versio 5.2.

- enpä tiedä kuinka epäsuosittua BASH on maailmalla - lukuunottamatta sitä ettei se henkiheitto vielä ole ja että BASH-skriptaajia palkataan vielä. Mutta jostainsyystä käyttäjät eivät BASH:ia arvosta.

- sensijaan Wintoosa lisäsi BASH:in itseensä - sillä paha vihollinen tuhotaan sisältäpäin? Myös hakkerit käyttävät BASH:ia - sitä ei ole huomattu että BASH:illa ne pahikset voisi tuhotakin.

***

Aikoinaan kun BASH tehtiin sen merkintätavat olivat aluksi toisesta maailmasta mutta se oli paljonkin nopeampi kuin nykyinen. Mutta sen merkintätavat olivat liian kummallisia joten käskykantan tehtiin uusia paljon käyttäjäystävällisempiä käskyjä. Mutta siinä menetettiin nopeutta.
 
Mutta ei siihenaikaan nopeutta arvostettukaan yli kaiken joten oltiin tilanteeseen tyytyväisiä ja hiljokseen ne vanhat risuaidat unohdettiin - mutta kyllä ne edelleen toimivat jos niitä tietää käyttää. Nyt kun on tullut tarve nopeuteen niin sitä risuaitojen nopeutta kaivattaisiin mutta niistä ei oikein tiedetä paljoakaan - tai kerrotaanhan niistä vieläkin joillain verkkosivuilla kummallisina muinaismuistoina.

***

BASH on tulkattu kieli joten sen hitaus on osin todellista - mutta osaksi tehtyä. Esimerkiksi käytetään sellaisia käskyjä että ne pakottavat tulkkin tulkkaamaan jatkuvasti ja sehän on tosihidasta.

Nimittäin BASH:illa on suuri joukko käskyjä joiden kirjoitusasu on niin kummallinen että ne kieltämättä nyrjäytävät aivot koska ei sellaisia ole tottunut kirjoittamaan. Ne ovat kuitenkin nopeita mutta ennenkaikkea ne ovat tulkki-ystävällisiä: kääntämisessä on käytössä tismalleen 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.

Tai asian voi esittää näinkin: ne alkuperäiset käskyt olivat tarkoitettu matematiikkaan ja tekstin sana kerrallaan käsittelemiseen. Sitten aloitettiin tiedostojen käsittely ja sehän vatii käskyihin sisäisiä matriiseja ja looppeja - joten käskyjen koko kasvoi paljon, tulkkaaminen hidastui eivätkä ne cache:ihinkaan oikein mahtuneet. Ja matematiikka unohdettiin melkein kokonaan.

Joten taas kerran muistelin noiden risuaita-käskyjen nopeutta ja matemaattisia kykyjä ja muutin erään surkean hitaan normaaleilla käskyillä tehdyn vertailu-funktion noilla vanhoilla risuaidoilla tehtyyn ja siitä tuli huonoimmillaankin yli viisikertaa nopeampi kuin mitä millään muulla menetelmällä aikaansaa - ja normaaleja pieniä lukuja verratessaan 50 kertaa nopeampi.

***

Sitten itse skripti:

- ei tällaisilla yksittäisillä  nopeilla skripteillä juurikaan ole käyttöä ennenkuin saa sen kavereiksi toisia nopeita - mutta kenties noista matematiikka-skripteistä saa kavereita ajankanssa. Sitäpaitsi nämä risuaitakäskyt osaavat tekstinkäsittelyäkin - tosin vai sanoilla. Toimitaanko kaikessa juuri niin kuin ei pitäisi?

- kone toteaa luvut teoriassa samoiksi vain mikäli ne kirjoitetaan samoin. Mutta ulkonäkö voi voi olla erilainen vaikka luvut ovat samoja. Skriptin tätyykin ymmärtää että esimerkiksi(2 on vain esimerkkinä):
   2.0 on sama kuin 2
  +2.0 on sama kuin 2
  .2 on sama kuin 0.2
 -.2 on sama kuin -0.2
 
- BASH-tulkki hyväksyy matemaattisessa vertailussa rajattomasti numeroita mutta leikkaa edestä pois ne numerot jotka menevät 19 merkin yli - kohtelu on sama niin kokonais-kuin desimaaliosassakin.

- desimaaliluku on sama kuin kaksi kokonaislukua joiden välissä on piste. Siis vertaillaan ensin kokonaisosia ja jos ne eivät ole samoja niin vertailu suoritetaan heti - ja desimaaleista välittämättä.

Mutta jos kokonaisosat ovat samat niin  sitten vertaillaan desimaaliosia. Mutta desimaaliosia ei voi vertailla samoin kuin kokonaislukuja elleivät ne ole yhtäpitkiä. Tässä tuo 'yhtäpituus' on saavutettu liittämällä vertailtavat yhteen: lukuarvot menevät poskelleen mutta eivät niin että se vertailua haittaisi. Samoin täytyy huomioida kokonaisosien etumerkki:

***

Koodia: [Valitse]
function looppimatriisi () { apu=$(bc<<<"for (i=1; i<=10000; i+=1) i") ;}; looppimatriisi

function onkoekasuurempi () { koko1=${1%%.*};koko1=${koko1//-/-0};koko1=${koko1:=0};koko2=${2%%.*};koko2=${koko2//-/-0};koko2=${koko2:=0}; (( $koko1==$koko2 )) && onkoekandesimaalitsuuremmat $1 $2  || { (( $koko1<=$koko2 )) && echo 0 || echo 1 ;};}
                                           
function onkoekandesimaalitsuuremmat () { 
[[ $1 =~ \. ]] && desi1=$1'00' || desi1=$1.00; [[ $2 =~ \. ]] && desi2=$2'00' || desi2=$2.00; #echo $desi1' '$desi2

desi1=${1//[^-]/}$desi1; desi2=${2//[^-]/}$desi2; #echo $desi1' '$desi2
desi1=${1//[^-]/}${desi1##*.}; des1=$desi1; desi2=${2//[^-]/}${desi2##*.}; des2=$desi2
#echo $desi1' '$desi2

# kummankin perään liitetään toinen
desi1=$desi1$des2; desi2=$desi2$des1; #echo $desi1' '$desi2

(( $des1>$des2 )) && echo 1 || echo 0 ;}    # kestoaika: ~60 mikrosekuntia/vertailu

# ja koekutsu
time { for n in ${apu[@]}; do onkoekasuurempi -2223 -2222.99999 ; done ;}


- kun nopeuden mittaa tuollatavoin saa varmasti mielikuvan liian suuresta nopeudesta, mutta saa sentään jonkinlaisen mielikuvan.

***

Tämä BASH on sikälikin mukava ettei yksikään skripti ole koskaan valmis ja virheetön - se ei tosiaankaan ole masentavaa koska se on jo etukäteen täysin varmaa ja niin käy kaikkien tekemille skripteille. Joten kun aivoni eivät tuottaneet viikkoon mitään niin vanhoissa skripteissä oli korjattavaa. Ja nopeasti kertolaskun kaksois-tarkkuuden skriptin tarkkuus nousi 33 numerosta 38:aan ja logaritmilaskuissa numeroita alkoi tulla kymmenen sijaan 19.

***

Aloin kokeilla saanko tehtyä moitteetonta skriptiä yhteen ja vähennyslaskulle. Saapa nähdä kumpi on itsepäisempi: läppäri vai minä. Skripti valmistuu kun valmistuu ja jos kiirehtii niin alkaa sotkea jo tehtyäkin. Mutta läärään muuta skriptin valmistumista odotellessa:

- BASH on tehty opetusta varten. Siksi sen data-tyyppeihin ei kuulu desimalilukuja koska tarkoituksena on että desimaali-laskujen suorittamiseen tehdään funktiot. Tautisen hidasta semmoinen laskenta on mutta hautaan kerkiää kyllä silläkin vauhdilla.

- desimaaliluvut ovat niin tärkeitä laskennassa että väite ettei BASH desimaalilukuihin kykene on törkeä teko joka osaltaan on tuhonnut BASH:in.

- opetuskielen luonteeseen kuuluu että mitähyvänsä voi tehdä lukemattomilla täysin erilaisilla tavoilla - ja yritys löytää niistä vähiten järjetön on ikuisuusprojekti jossa kyykytetään ihan jokaista.

- koska BASH on opetuskieli niin se ei myöskään ilmoita koska laskettavat luvut ovat liian suuria BASH:in matematiikalle vaan käyttäjän olisi tarkistettava se itse - sillä sehän on oiva opetettava.

- ja lista jatkuu ikuisesti ... ja kokoajan tulee uusille tutkimattomille alueille. Korjaus: aikoinaan BASH:in harrastajia riitti melkein kaikkialle.

***

Skriptit ajetaan yleensä päätteessä ja vasta kun pääte on jo avattu. Tiedostoselaimella skriptien ajaminen on vihollisen keksintö BASH:in osoittamiseksi surkeaksi - se toimii pienillä sripteillä ihan hyvin mutta sitten tulee katto vastaan.

Pääte olettaa että mitä sinne syötetäänkin on BASH:ia ellei toisin ilmoiteta joten BASH-skripteissä ei kaivata riviä: #!/bin/bash.

Ei skriptin tarvitse tiedostossa olla vaan esimerkiksi kun kohtaat jollain verkkosivulla lyhyen skriptin niin voit leikata-liimata koko skriptin kerralla päätteeseesi ja painettuasi return skripti suoritetaan.

- yksirivisen skriptin voi kutsua nuoli-näppäimellä takaisin näytölle editointia varten ja return:illa ajaa uudelleen - nimitys yksirivinen on hieman harhaanjohtava sillä kyseisessä skriptityypissä kaikki on vain kirjoitettu ilman rivinvaihtoja ja skripti voi ihan hyvin olla monen rivin pituinen. 

- tämä on yksi syy miksi skripti kannattaa kasata funktioista sillä onhan se paljon helpompaa kokeilla skriptiä muistista kuin tiedostosta. Mikäli skripti on oikein kasattu niin viimeinen rivi on se rivi jolla skriptin parametrit määrätään ja skripti käynnistetään joten vaikka skripti olisikin monirivinen niin tämän viimeisen rivin ainakin voi kutsua takaisin, editoida parametreja ja ajaa skripti uudelleen. Sillä tosiaan on ehdoton totuus ettei kukaan kykene tekemään skriptiä joka toimii aina vaan sitä täytyy testata ziljoonilla eri parametreilla ja korjailla jatkuvasti.

- mutta mikäli skripti on tiedostossa niin se ajetaan aina menemällä ensin päätteeseen ja kirjoittamalla sinne päätteeseen: . skriptin_nimi_tiedostopolkuineen    (huomio piste skriptin_nimen edessä. Se on käsky liittää osoitetun tiedoston koodi päätteessä jo toimivan skriptin rinnalle - pääte on itsekin tavallaan skripti joten ei siellä aikaisemmin mitään tarvitse olla.)

- skriptin tiedostolle ei tarvitse antaa suoritusoikeutta - paitsi jos ajaa sitä tiedostoselaimella. Kummallinen juttu muuten: kaikki BASH:in viholliset horisevat että BASH on tietoturvaton ja sitten itse opettavat todellista tietoturva ongelmaa joka on lisäksi tarpeeton.

- '. skriptin_nimi' voidaan kirjoittaa skriptiinkin ja toiminta on silloinkin sama - puhutaan kirjaston liittämisestä skriptiin. Tuollaisten rivien tulee sijaita skriptin alussa.

- samoin skriptin tiedostonimen lopussa oleva pääte .sh on vain tieto käyttäjälle että BASH:ina tämä tullaan ajamaan - eikä peräliitettä siis ole pakko kirjoittaa. Eri kielten skriptit ovat täysin ei kansioissa eikä niitä voi sotkea.
 

20
Aina tuodaan esiin se että eihän BASH tunne desimaalilukujakaan. Niinhän se tarkkaan ottaen onkin - tai tiedä tuosta tarkkuudesta sillä ihan BASH-koodillakin desimaalimatematiikka on erittäin nopeaa joskin kovin rajoittunutta. Luulossa on kymys halusta ymmärtää väärin - ei desimaalimatematiikka desimaalilukuja kaipaa mutta sitähän ei ole pakko ymmärtää sillä vain BASH laskee desimaalilaskut koodissa - ja se on todella hidasta. Toisaalta oikein valittu BASH-koodi on hirvittävän paljon nopeampaa kuin mitä annetaan ymmärtää.

Eihän näillä matikka-koodeilla enää ole mitään merkitystä paitsi että ne osoittavat että *kaikki* mitä BASH:ista puhutaan on ollut ja on edelleenkin puhetta vastoin parempaa tietoa.

Esimerkiksi lasku: 1233457890123.23 / .123456 kestää BASH:in koodissa 0.4 ms - tosin lasku kolkutteli koodin kykyjen rajoja - toisaalta olisi mahdollista tehdä lisäyksiä lisänumeroiden laskentaan sen vaikuttamatta paljoakaan suoritusaikaan - ja muista matematikka-ohjelmista jo bc:kin vaatii kymmenkertaisen ajan jo herätäkseen toimimaan - ja niin kestää muutkin.

- ei tuota jakolaskua muiksi perustoimituksiksi saa vaan ne vaativat omaa koodia.
- huomioi muuten ettei koodissa ole looppeja.
Koodia: [Valitse]

#!/bin/bash
function jaa () {
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1".0"
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2".0"
desimaaliosa1=${luku1##*.}
desimaaliosa2=${luku2##*.}
# (( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"0000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"0000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}

kokonaisosa1=${luku1%%.*}
kokonaisosa2=${luku2%%.*}

luku1ilmandesimaalipistetta=$kokonaisosa1$desimaaliosa1 #; echo $desimaaliosa1
luku2ilmandesimaalipistetta=$kokonaisosa2$desimaaliosa2 #; echo $desimaaliosa2
 
kokonaiset=$((10#$luku1ilmandesimaalipistetta/10#$luku2ilmandesimaalipistetta))
jakojaannos=$((10#$luku1ilmandesimaalipistetta%10#$luku2ilmandesimaalipistetta))
desimaalit=$(((1000000000*$jakojaannos)/$luku2ilmandesimaalipistetta))
desimaalit=000000000000000$desimaalit
kokonaiset=$kokonaiset.${desimaalit: -9}
# echo $jakojaannos

jakojaannos=$(((100000000*$jakojaannos)%$luku2ilmandesimaalipistetta))
desimaalit=$(((1000000000*$jakojaannos)/$luku2ilmandesimaalipistetta))
desimaalit=0000000000000000$desimaalit
echo $kokonaiset${desimaalit: -8} ;}
   

# tarkistus:
jaa 1233457890123.23 .123456

***

BASH:ia on hidasta kirjoittaa kirjoitus-sääntöjensä takia, se toimiikin hitaasti ja kaikessa on kummallisia rajoituksia. Skriptikielet bc ja awk ovat nopeita kirjoittaa sillä niiden kirjoitus-säännöt ovat samat kuin normaaleillakin kielillä. Lisäksi ne toimivat nopeasti verrattuna mihin hyvänsä, eikä rajoituksia juurikaan ole.
 
Esimerkiksi skripti fibonacci-sarjan 100.000:nennen jäsenen laskemiseksi:
- idioottimaisen mones jäsen hölmöstä sarjasta siksi että näkyisi selvästi kuinka helppoja, nopeita ja rajoituksettomia nämä kielet ovat - lasku kestää hitaimmalla näistä 8 sekuntia ja nopeimmalla .08 sekuntia.  Näiden yli 20.000 numeroisien lukujen laskeminen ei taida onnistua muilla kielillä yhtä nopeasti - tai C:tä nämäkin ovat joten kai C:llä onnistuisi - käyttäen kirjastoa. Tässä ainoastaan tuo gawk käyttää kirjastoa.
Koodia: [Valitse]
echo  "fibo=100000;a[1]=a[2]=1;for (i=3; i<=fibo; i+=1) a[i]=a[i-1]+a[i-2]; print a[fibo]" | bc | tr -d '\\\n'

Sen muuttaminen kymmenen kertaa nopeammaksi gawk-skriptiksi on aivan yksiviivaista:
Koodia: [Valitse]
gawk -M 'BEGIN{fibo=100000;a[1]=a[2]=1;for (i=3; i<=fibo; i+=1) a[i]=a[i-1]+a[i-2]; print a[fibo]'}

Seuraava on jonkun virtuoosin tekemä skripti joka on vielä kymmenen kertaa nopeampi (0.08sek) - mutta tätä ei edes ymmärrä - se menee lisäksi ainakin kymmenmiljoonanteen jäseneen eikä silloinkaan käytä muistia havaittavaa määrää joten ei se taida välituloksia laskeakaan. Mutta olipa kyse mistähyvänsä niin bc kykenee siihen:
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'

- silti BASH on erinomainen liimaamaan kaikkia skriptikieliä yhteen joten kyllä sitä kannattaa käyttää.

***

BASH:in muuttujiin voi laittaa mitähyvänsä: muuttujia, tekstiä, tekstijonoja tai matriiseja - kaikkia numeroina tai tekstinä, ilman minkäänsortin tyyppimäärittelyjä - ja kaikkia voi käsitellä joskus numeroina ja samassa yhteydessä välillä myös tekstinä. Ja niihin voi tallettaa myös käskyjä - ja ne toimivat muuttujastakin kuten käskyt. Esimerkiksi:

Kun tekee vaikeaa skriptiä kannattaa koodissa tulostaa runsaasti välituloksia - se on paljon parempi keino kuin mikään debug:gaus voi olla. Ongelmana on että valmiissa skriptissä välituloksien tulostaminen tekee mahdottomaksi tehdä skriptistä kirjasto-funktiota - ja sotkeehan se muutenkin kauniin lopputuloksen. Ja jos välituloksia  tehdään paljon niin niiden poistamisessa valmiista skriptistä on iso työ - ja paljon isompi työ on palauttaa ne jos myöhemmin huomaa ettei koodi aina toimikaan ja täytyy selvittää missä menee pieleen.

BASH:issa voidaan määrätä yhdellä muuttujalla tulostetaanko vai ei - koodin paljoakaan muuttumatta - tulostuksissa vaan korvataan echo sanalla $tulosta. Kun välituloksia halutaan niin koodin alkuun kirjoitetaan: tulosta=echo ja kun tulostusta ei haluta niin vaihdetaan sana echo sanaksi :  (=kaksoispiste). Ja jos myöhemmin havaitsee että kyllä niitä välituloksia taas tarvittaisiin niin se : muutetaan takaisin echo:ksi.

- esimerkki: tulosta=echo; $tulosta 'näin tämä käy' tai: tulosta=: ; $tulosta 'näin tämä käy'
- jos lopputulos täytyy tulostaa niin se tulostetaan aina echo:lla.
- eikä tällainen tulostustapa edes tee koodista yhtään epäselvempää.

***

Kaikki ohjelmointikielet tarvitsevat kirjastoja ja ne ovat BASH:inkin ydin. Mutta tätä ei skriptaajille kerrota - vaan päinvastoin vietiin BASH:ilta kirjasto-osoitin mikä tekee kirjastojen käyttämisen vaikeaksi. Mutta ei tuo kirjastojen "käyttökielto" kehittäjiä koske ja niinpä koneessasikin on kirjaston alku elikä noin 85 funktiota - ne helpottavat heidän hommiaan ja skriptaajat hypätköön sementtiin.

Jokatapauksessa saat nuo funktiot näkyviin käskyllä: declare -f. Toki voit käyttääkin niitä jos tahdot: esimerkiksi anna päätteessä käsky: quote hilivinkkeli
jolloin vastaksena tulee: 'hilivinkkeli'. Jos teet funktioita koneeseesi niin noiden joukkoon nekin ilmestyvät.

Kirjasto voi olla yksittäinen tiedosto jossa on monia funktioita mutta yleensä kirjasto on kansio jonka jokaisessa tiedostossa on samaa asiaa käsitteleviä funktioita.

Kirjastofunktioista monet ovat yksinkertaisia ja niistä onkin helppo saada käsitys mistä on kyse. Esimerkiksi:

function alku () { echo 'tästä funktionteko alkaa' ;};
- tämänjälkeen jokakerran kun kirjoitat skriptiisi sanan: alku  kirjoittuu tulostukseen: tästä funktionteko alkaa

Mutta jotkut kirjastofunktiot ovat pitkiä ja loogisestikin monimutkaisia - ja varsinkin BASH tarvitsee niitä sillä BASH:ia ei opeteta - tosiaan ilmeisesti tarkoituksella. Noista kirjastofunktioista saisi edes esimerkin siitä miten skriptit tehdään.

Kirjaston skriptien lukumäärä on viite skriptikielen laadusta. Niinpä funktioita   tarvittaisiin rajattomasti - tosin kirjaston ylläpitäminen vaikeutuu kirjaston koon kasvaessa.

Funktio on määriteltävä ennenkuin siihen viittaa - joko kirjoitettava koodin alkuun tai luettava muistiin ennen skriptin ajamista.

***

Vakaan skriptin tekeminen ilman kirjaston apua on melko mahdotonta ja niiden puutteessa BASH kituu. Funktiot helpottavat skriptitekoa suunnattomasti sillä ne tekevät pitkän toimintosarjan yhdellä käskyllä - ja usein tavalla jota et ole tullut ajatelleeksikaan ja jota yleisesti väitetään mahdottomaksi - ja joskus jopa nopeasti ja hyvin. Funktiot ovat usein työläitä kirjoittaa, mutta yleensä ne kirjoittaakin joku toinen - ja jos joudut itse kirjoittamaan funktioita niin ne kirjoitetaan vain kerran jonka jälkeen ne ovat aina käytettävissä.

Maailmalla on valmiita ja hyvälaatuisia funktioita noin ziljoona joten voisihan niitä toimittaa alkuasennuksenkin yhteydessä - mutta niin ei tehdä sillä BASH:ista halutaan eroon mutta siihen ei toistaiseksi pystytä - mutta sitä ei missään nimessä haluta että kukaan käyttäjistä käyttäisi BASH:ia - joten sen tiestä tehdään kivikkoinen.

Varsinkin kun aloittaa skriptaamisen kannattaisi käyttää virtuoosien kirjastoja - kaikki siitä hyötyisivät, nuo virtuoosit itsekin. Sillä jos virtuoosien tekemiä funktioita ei käytetä niin käy aivan niinkuin nyt on käynyt: BASH kituu hengiltä. En tiedä kuinka yleisiä yksityiset kirjastot ovat mutta se on kyllä varmaa että niitä on.

Koska aloittelijalla ei ole kirjastoja käytettävissään niin joutuu kirjoittamaan erittäin paljon ja kokeilemaan mahdottomasti - sillä BASH:in logiikkaa ei hallitse kukaan ihan täysin vaan kaikki on kokeiltava tyyliin: onnistuiskos näin? Ja kun onnistuu niin: mitä vikaa tässä on?

Onhan niitä yksittäisiä funktioita netissäkin monessa paikassa - mutta kaikkien logiikka on hieman erilainen ja niiden yhteen sovittaminen on varsin työlästä.

Joten kun skriptinsä saa toimimaan siitä kannattaa tehdä siitä kirjastoonsa funktio jottei joutuisi samaa toimintoa kehittämään monesti uudestaan - ja uudestaan keksiessä tuska on paha sillä hukkaa aikaansa sellaiseen jonka on jo kerran tehnyt eikä tuloskaan ole silti aina hyvä.

***

Funktiot tuovat melkein aina lisää nopeutta ja niitä käyttämällä oppii paljon nopeammin tekemään vakaita skriptejä. Teoriassahan funktion käyttäminen vain hidastaa, mutta
koska funktiot ovat yleensä jonkun virtuoosin tekemiä ovat ne myös nopeita ja  virheettömämpiä.

Funktiolla voi olla vain ne ominaisuudet jotka saa revittyä irti BASH:in omista käskystä - ja siitä voit olla varma että moni muu saa käskyistä irti paljon enemmän kuin sinä - tämä koskee ihan meitä kaikkia, mikään ei ole koskaan lopullista vaan jopa kaukaa historiastakin tulee yllätyksiä - useinkin muuten.

On erittäin vaikeaa tehdä BASH:iin C-kielisiä ohjelmia - mutta mahdollista se kyllä on. Sensijaan Pythonin ja Perlin käyttäminen ongelmissa on helppoa.

Ennenkuin olet koonnut itsellesi taidon tehdä omia funktioita voi käyttää virtuoosien tekemiä funktioita - tekijänoikeuksia ei kenelläkään ole joten ota irti mitä saat mutta jaa myös yhteisölle mitä itse kehität. Virtuoosien funktiot ovat yleensä nopeampia kuin mitä itse saat kasattua mutta ennenkaikkea ne eivät kompastu BASH:in omituisuuksiin.

Funktioita kuvaavat skriptit talletetaan kovalevylle - yleensä useita samaan aihepiiriin kuuluvia funktioita samaan tiedostoon. Yhdessä tiedostossa voi olla vaikka kuinkamonta funktiota. Funktion koodin voi kopioida skriptinsä alkuun tai lukea sen muistiin joko senhetkiseen pääteistuntoon tai peräti liittää päätteeseen jokakerran kun pääte avataan laittamalla funktion tai kirjaston liitoskäsky tiedostoon ~/.bashrc .

Ennen käyttämistä kaikki yhden tiedoston funktiot luetaan muistiin käskyllä joka on muotoa:
. sen_kovalevytiedoston_nimi_polkuineen_jossa_funktiot_ovat
huomioi piste alussa. Kaikki tiedostossa olevat funktiot kopioidaan muistiin jonka jälkeen funktioihin voi viitata samoin kuin niiden ollessa kirjoitettu skriptiin. Niinpä ei täydy jokakerran funktiota tarvitessaan tehdä samoille asioille uutta koodia - hölmöillen jokaisella tekokerralla hieman eritavalla.
- muuten kirjaston jokaisen skripin tulee olla käännöskelpoinen, joten kirjastoa kasattaessa on viisainta lisätä sinne funktioita yksi kerrallaan.

Koska muisti on halpaa kannattaa muistiin lukea  funktioita runsaaasti - sillä vaikka niitä olisi paljonkin niin ei se toiminnan nopeuteen juuri vaikuta.

Tosin suuresta funktioiden määrästä voi seurata inhottavuuksiakin - samalla nimellä muistiin ladatuista vain viimeinen on voimassa - joten kun joku funktio ei toimi niinkuin pitäisi niin saattaa korjata väärää funktiota ja repiä hiukset päästään kun  mikään ei auta. Tästä tulee muuten se hyöty että voit tehdä funktion esimerkiksi nimellä ls jolloin se korvaa BASH:in oman ls-käskyn.

BASH:in omatkin käskyt ovat funktioita - mutta ne on yleensä kirjoitettu C-kielellä ja niissä on omat sisäiset matriisinsa, looppinsa ja vaikka mitä joten jos niitä osaa hyödyntää niin saa käyttöönsä todella nopeita toimintoja. Mutta parhaat noista  C-kielisistä ohjelmista on tehty tietokoneen hoitamiseen joten sellaiselle jota ei tietokoneen hoitaminen kiinnosta on BASH paljon huonompi.

***

Toisten tekemillä funktioilla selviää pitkään mutta olisihan se kiva oppia tekemään niitä itsekin: funktio on muodoltaan: 

function funktion_nimi () { toiminta ;};

- sana function voi jättää poiskin, se on vain muistutus skriptaajalle.
- funktiota käytetään aivan samoin kuin skriptiäkin - elikä mainitsemalla sen nimi - ja myös parametrit toimivat kummillakin samallatavalla.
- funktiot voivat kutsua toisiaan.
- () on merkkinä siitä  että funktiossa tullaan käyttämään parametreja - parametri on tavallinen muuttuja joka siirtää tietoalkion kutsujasta funktioon - joko arvon tai nimen. Yhdessa parametrissa ei voi olle matriisia tai tekstijonoa vaan niiden jokaisesta arvosta tulee oma parametri joista ne voi koota uudelleen. BASH:issa parametrit eivät koskaan palaa.
- { } tarkoittaa sitä että funktion ja pääohjelma toimivat samalla alueella joten esimerkiksi niiden muuttujat ovat yhteiset. Muutettaessa noiden aaltosulkujen tilalle kaarisulut yhteys katkeaa.
- puolipisteet ovat mukana vain nostamassa skriptaajan verenpainetta?
- toiminta on normaalia skriptiä mutta esimerkiksi sen muuttujilla on sääntönsä:
  - funktio ja sen kutsuja tuntevat toistensa muuttujat.
  - funktio  voi määrätä omia muuttujiaan yksityiseksi määreellä: local eikä kutsuja silloin tunne niitä.
  - kovalevyn tiedostojärjestelmä on funktiossa ihan sama kuin kutsujassakin ja viestejä voi vaihtaa myös kovalevyn kautta.
  - funktiot tuntevat myös toisten funktioiden muuttujat. Esimerkiksi:
   
   function koe1 () {  a=33 ;}; function koe2 () { echo $a ;} ; koe1; koe2
   
  - kovalevyn tiedostojärjestelmä on funktiossa ihan sama kuin kutsujassakin joten viestejä voi vaihtaa myös kovalevyn kautta.
 
***   
     
- rekursio on funktio joka kutsuu itseään. Jottei rekursio kutsuisi itseään loputtomasti täytyy rekursion alussa olla käsky milloin lopetetaan. Esimerkiksi kertoma:
Koodia: [Valitse]
function kertoma () { i=$1; (( $i > 1 ))  && { kapu=$kapu$i'*'; i=$(($i-1)); kertoma $i ;} || { echo $kapu'1' | bc | tr -d '\\\n' ;} ;}; read -p 'mistä luvusta se kertoma lasketaan: ' x; kertoma $x
   - rekursio on resurssisyöppö ja hidas ohjelmarakenne eikä varsinkaan pitkiä rekursioita kannata käyttää.

***

Funktiota kutsuttaessa siis siirretään funktioon myös ne muuttujat joita funktiossa olisi määrä käsitellä. Funktio ei tiedä mitä sille siirretään vaan numeroi kaiken tulojärjestyksessä ja laittaa jokaisen numeron eteen merkin $

Muuttujista voidaan siirtää joko:
1. arvo joka ei ole sidoksissa mihinkään nimeen - eikä arvoa enää siinävaiheessa voikaan sitoa mihinkään nimeen. Matriisin jokainen alkio siirtyy erikseen - samoin tekstijono jaetaan aina välilyönnin kohdalta - siten eri matriisit ja tekstijonot vaativat aina tapauskohtaisesti erilaiset määrät parametreja. Senvuoksi tekstijonoja ja matriiseja voidaan siirtää vain yksi ja se täytyy siirtää viimeiseksi.
 
Tällaisen parametrin nimi on arvoparametri. Parametreja ei voi palauttaa ja arvoparametrien suhteen parametri täytyisi joskus palauttaa. Käytetyin keino palauttaa funktiosta jotakin on se että funktio kirjoittaa palautettavan näytölle ja pääohjelma lukee sen näytöltä ohjelmarakenteella: $( funktiokutsu parametreineen) - silloin se tulostettu ei muuten koskaan fyysiselle näytölle ilmestykään. Koko parametrijoukkoon voidaan viitata: $@. Koska matriisin tai tekstijonon edessä tulee yleensä pari tavallisen muuttujan parametria niin niiden jäseniin viitataan: ${@:2}

2. nimi joka ei ole sidoksissa mihinkään arvoon tai arvojoukkoon. Tällaisen parametrin nimi on nimiparametri. Nimi voidaan sitoa siihen arvoon joka sillä nimellä on - monellakin tavalla:
a. Eval-käskyllä. Itseasiassa tämä toimii aivan samoin kuin seuraava, eval on siitä tehty funktio?
b. BASH antaa osan kirjanpidostaan skriptaajan käyttöön käskyllä: declare -p . Kirjanpidosta etsitään kohta jossa saadusta muuttuja-nimestä puhutaan ja liitetään sieltä arvot nimeen.
c. aritmeettinen muuttuja let-käskyllä: function koe () { let $1=5 ;}; a=1; koe a; echo $a
d. tekstimuuttuja: function koe () { read $1<<<"kissa kuumalla katolla" ;}; a=d; koe a; echo "$a"
- toki tämä liittäminen on lisähomma eikä kaunista koodia mutta ei se aina ole yksinomaan haitta.
- nimiparametreja ei tarvitse palauttaa sillä on muokattu sitä alkuperäistä muuttujaa joten se on muuttunut jo.
- semmoinen pikkujuttu tässä tosin on että oletetaan ettei skriptissä ole toista  samannimistä muuttujaa - jopa toisten skriptien muuttujat voivat teoriassa sotkea.

Matriiseja ja tekstijonoja siirrettäessä on suuri merkitys sillä millainen parametrin tyyppi on:
1. suurilla matriiseilla ja tekstijonoilla arvojen siirtäminen vie aikaa.
2. nimellä voi siirtää niin monta matriisia tai tekstijonoa kun haluaa ja ihan siinä järjestyksessä kuin haluaa - ja siirron nopeus on aina suuri.
 
 

Sivuja: [1] 2 3 ... 32