Sain lopulta kerättyä tarpeeksi tietoa sen selvittämiseksi miksi BASH on telottu vaivaiseksi. Ei voi varmuudella sanoa että BASH on telottu tahallisesti mutta siltä se vaikutti. Tottamaar siihen voi olla ihan kunnon syytkin mutta miksei niistä puhuta selvin sanoin?
BASH:ista on tehty surkimus riistämällä siltä sen parhaat työkalut. Silloin kauan sitten varmasti tiedettiin mihin BASH kykenisi mikäli olisi tarvetta sen kykyjä täysin käyttää. Miksi noita kykyjä ei yritettykään ottaa käyttöön vaan päinvastoin BASH:ilta vietiin sen parhaat työkalut? Haluttiinko BASH poistaa markkinoilta koska sen ulkoasusta ei pidetty?
Asian tutkiminen oli vaikeaa sillä jos toimii vastoin yleisesti hyväksyttyä käsitystä ei kukaan halua uskoa sen olevan totta vaan väittää vastaan ikuisesti - siis nimenomaan 'ei halua uskoa' eikä sillä ole mitään merkitystä onko se totta vaiko ei. Kuljet siis hullun kirjoissa niin kauan kunnes vasta monen yrityksen jälkeen löydät oikeat sanat jotta edes joku uskoo - ja senjälkeen hitaasti muutkin. Mutta jo ensimmäisessä yrityksessä laitat maineesi ja itse-arvostuksesikin peliin - eikä niin kukaan järjissään oleva tee sillä jokainenhan hölmöilee joskus ja huomaa jatkossa olleensa väärässä. Mutta on yksi asia jota kukaan ei voi vääräksi osoittaa: kaikissa tilanteissa oikein toimiva skripti. Mutta vaieta voi ja olla edes tarkistamatta - elikä vaieta kuoliaaksi.
Jokatapauksessa asia BASH:in vanhoista käskyistä iso joukko on assembler-tyyppisiä. Ne tekevät jonkun pikkuhomman mutta sen ne tekevät todella nopeasti ja hyvin - ja aikaansaamatta minkäänlaista sotkua. Näitä assembler-tyyppisiä käskyjä voi laittaa peräkkäin vaikka kuinkamonta toiminnan hidastuessa sentakia vain vähän - ja vaikka sotkut ovatkin aina kasaantuvia niin eivätpä nollat miksikään kasaannu joten lopussakaan ei ole sotkua. Mutta siitä kootusta käskystä elikä funktiosta tulee lopulta nopea ja monipuolinen. Mikäli yksinomaan assembler-tyyppisiä käytettäisiin olisi toiminta 'kymmenenkertaa nopeampaa' kuin muuten eikä enää sanottaisi mistään: BASH ei osaa.... Vielä merkittävämpää on se että assembler-tyyppiset käskyt tekevät kummallistenkin toimintojen koodaamisesta mahdollista - teki mieli sanoa helppoa mutta ei, kyllä siinä pähkäillä saa - mutta se varmuus hommassa kyllä on että asiaan on montakin ratkaisua.
Tosin koodia tulee niin paljon että se paisuttaa skriptit joskus muodottomiksi - ja se tekee muun skriptin toiminnan ymmärtämisen vaikeaksi. Tämäntakia muissa kielissä funktiot laitetaan kirjastoihin ja funktiota tarvittaessa viitataan kirjastoon. Teoriassa BASH:issakin pitäisi tehdä näin - mutta koska BASH:in ei haluta toimivan hyvin ja koska kirjastot kuuluvat BASH:in perusteisiin eikä niiden toimintaa voi estää poistettiin jo toimiva kirjasto-osoitin jotta funktioilla ei olisi sijoituspaikkaa vaan ne olisi pakko kirjoittaa skriptiin - lisäksi käyttäjät käsittivät kirjasto-osoitteen poistamisen merkitsevän sitä etteivät kirjastot enää toimi vaikka tosiasiassa ne toimivat ihan hyvin. Netissäkin on kymmenkunta julkista kirjastoa jotka tosin ovat surkastuneet mutta kyllä ne toimivat.
---
Aikoinaan puhuin niin että BASH on telottu jotta valmisohjelmat voisivat esittää erinomaisuutensa. Mutta en silloin ihan itsekään uskonut siihen ihan tosissani, Mutta nyt se on selvää: valmisohjelmista ainkin osa jää assembler-tyyppisille kuin jämsän ukko taivaasta. Niin nopeudessa kuin muussakin. Tuolla myöhemmin on koodia siitä.
---
Assembler-tyyppisten käskyjen 'unohtamisen' pahin seuraus oli kuitenkin vakaan toiminnan menettäminen: esimerkiksi BASH:in 'normaaleilla' käskyillä on ongelmia jakopisteen kanssa - asembler-tyyppisillä käskyillä tätä ongelmaa ei ole. Selvityksenä mitä jakopisteellä tässä tarkoitetaan on parasta antaa siitä esimerkit:
echo 1234-5678 | cut -d- -f1 # jakopiste käskyllä: cut . Jakopiste on tuo -d:n perässä oleva - merkki.
apu=1234-5678; echo ${apu%-*} # sama jakopiste assembler-tyyppisenä
Vakauden häviäminen on seurausta siitä että 'normaaleilla' käskyillä jakopiste muodostuu aina vain yhdestä merkistä mutta assembler-tyyppiset kelpuuttavat jakopisteeksi joko yhden merkin, merkkiryhmän,'useamman merkkiryhmän', regex:än ... - kaiketi vain mielikuvitus on rajana siinä mikä kelpaa.
---
Tuosta 'useamman merkkiryhmän' toiminnasta täytyy antaa esimerkki.
apu=kattokassinen; [[ ${apu#*kat} != $apu ]] && echo ${apu#*to}
tulostuu: kassinen. Siis tulostetaan tekstistä merkkiryhmän to jälkeinen tekstinosa mutta vain mikäli tekstissä on jossain merkkiryhmä kat
Funktio-muotoinen versio tapauksesta jossa sen löydettävän täytyy olla etsittävän edessä - skriptin tarkoitus on myös kertoa että asiasta voi tehdä funktionkin.
function kummajainen () { apu2=${3%$1*}$1; [[ ${apu2#*$2} != $apu2 ]] && echo ${3%$2*} ;}; kummajainen to ka kattokassinen
kutsun selitys: kumajainen minkä-jälkeinen-tulostetaaan mitä-täytyy-olla-edessä tekstijono-johon-toiminta-kohdistuu
---
Myös tuo regex kaipaa selvennystä -> regex on kuvaus siitä kuinka jonkun joukon jäsenet muodostetaan, esimerkiksi
[0-9][0-9]?[0-9][0-9]?[0-9][0-9] on yksinkertainen päivämäärää kuvaava regex - siinä välimerkin paikalla oleva ? tarkoittaa että silläkohtaa voi olla mikätahansa yksi merkki, myös välilyönti kelpaa. Eikä merkitse mitään mikä on päivä- kuukausi- ja vuosinumeroiden järjestys - joten käskypari:
apu="syntymäpäiväni on 29-02-00 jottas tiedätte"; echo ${apu#*[0-9][0-9]?[0-9][0-9]?[0-9][0-9]}
tulostaa sen mitä on päivämäärän perässä elikä: jottas tiedätte. Riippumatta siitä mitkä päivämäärän numerot ovat, mikä on päiväyksessä välimerkkinä ja mitkä ovat kansalliset merkintätavat joilla päivämäärä on esitetty.
Jos halutaan tulostaa ainoastaan tuo pävämäärä tulee skriptistä seuraavanlainen:
time { apu="syntymäpäiväni on 29-02-00 jottas tiedätte"; apu2=${apu//[0-9][0-9]?[0-9][0-9]?[0-9][0-9]*}; echo ${apu:${#apu2}:8} ;}
- time-käskyn ajoittama ryhmä täytyy ympäröidä aalto-suluilla jotta time-käsky mittaisi koko ryhmän kuluttaman ajan.
funktiomuotoon kirjoitettuna päiväyksen tulostamien:
function tulosta_päiväys () { apu=$1; apu1=${apu#*[0-9][0-9]?[0-9][0-9]?[0-9][0-9]}; [[ $2 ]] && { echo $((${#apu1}+1)); return ;}; echo ${apu:$((${#apu1}+1)):9} ;}
# jompikumpi kutsuista:
tulosta_päiväys "syntymäpäiväni on 29-02-00 jottas tiedätte" -> tulostetaan annetussa tekstijonossa oleva päiväys; tai
tulosta_päiväys "syntymäpäiväni on 29-02-00 jottas tiedätte" 0 -> tulostaa montako merkkiä on päiväyksen alkuun annetussa tekstijonossa
- tämä on tekstijonon grep sillä tiedostoille tämä ei sovellu tämmöisenään.
---
Assembler-tyyppisillä kaikenlaisten ihmeellisyyksien tekeminen on helppoa: esimerkiksi BASH:in normaalit haku-funktiot toimivat greedy-tyyppisesti elikä tulostavat mahdollisimman suuren haettavan ja tämä ei yleensä ole tarkoitus vaan lazy-tyyppisesti elikä etsittävästä pitäisikin tulostaa joku sen pienistä palasista - tähän tyyliin:
apu=kkkyjjjjhyffyfffyz; echo $apu | grep -o 'y.*y'
joka tulostaa: kyjjjjhyffyfffy kestäen noin 3ms - mutta todennaköisesti haluttaisiinkin tulostaa joku y-merkkien välisistä pikkupalasista. Grepin extended-versiolla tähän kelpaava lazy:kin toimii kun regex:ää muuttaa - mutta se toimii vielä hitaammin ja pikkuisen epävarmakin se on. Sensijaan ketjutettu assembler-tyyppinen haku kestää alle 0.5ms ja on toimintavarma.
- assembler-tyyppisillä käskyillä nuo palaset ovat:
apu=kkkyjjjjhyffyfffyz; apu=${apu%y*}; echo ${apu##*y} # tulostaa fff
apu=kkkyjjjjhyffyfffyz; apu=${apu%y*y*}; echo ${apu##*y} # tulostaa ff
apu=kkkyjjjjhyffyfffyz; apu=${apu%y*y*y*}; echo ${apu##*y} # tulostaa jjjh
- toimii loopillakin elikä siinä ilmoitetan vain monesko palanen lopusta laskettuna - tai pikkumuutosten jälkeen alusta laskettuna.
- toinen mielenkiintoinen 'lazy search' haku-menetelmä:
apu=kkkzixjjjjhzixffzixfffzixzapu; apu=($(echo a${apu//zix/ })); echo ${apu[1]}}
---
Siis assembler-tyyppinen käsky muokkaa joko yksittäistä tekstijonoa tai matriisin yhtä- tai jokaista jäsentä erittäin nopeasti ja valikoivasti. Matriisin käsittelyyn tarvittavat loopit ovat noissa käskyissä sisäänrakennettuja - ja ne etsivät rajansa itse sillä matriisin nimihän tunnetaan ja rajathan löytyvät silloin kirjapidosta - joten looppi on nopea koska se on käännettyä C-kieltä eikä tulkattavaa BASH:ia.
Esimerkiksi tiedostosta tehdyn matriisin tulostus muuttaen matriisin kaikilla riveillä joku ilmaisu vaikka mimmoiseksi lauseeksi:
< /boot/grub/grub.cfg readarray array; echo "${array[@]//### BEGIN/hopsan: loopin alku}"
- pelkkä tulostaminen ei ole vaarallista. Tuota tiedostoa on mukava käyttää esimerkeissä koska se on lähes samanlaisena jokaisessa Ubuntu-koneessa.
- kestää noin 6 ms elikä 'satoja kertoja' nopempaa kuin normaalisti ja melkein yhtä nopeaa kuin sed:illä.
- käsky:
exec < /boot/grub/grub.cfg; apu=$(cat); echo "${apu[@]//### BEGIN/hopsan: loopin alku}" > koe
siitä kotikansioon tiedoston nimeltä koe.
Mutta täytyy lisätä ettei tulkkaava kieli voi kovin nopea olla. Vaikka esitetyllä tavalla tiedoston-käsittely nopeutuukin 'satoja kertoja' niin eihän se sittenkään kovin nopeaa ole.