BASH-raamattu:
https://www.gnu.org/software/bash/manual/html_node/index.html#SEC_Contents on iso - ja epäilenpä ettei sitä kukaan ole edes lukenut kunnolla sillä sen hyödyt selviävät vasta kun hallitse sen kokonaan - ja siihen useimpien muistikaan ei riitä eikä kenenkään aika ainakaan - sitäpaitsi suuri osa sitä tuntuu lasten leikeiltä ja aikuisten lasten temmellyskenttä se onkin.
Mutta onhan BASH:illa sittenkin omat virtuoosinsa sillä suurelta osalta sen tiedot ovat lukemattakin selviä mikäli omaa hyvän pohjakoulutuksen - ennenkaikkea asioista jotka ovat kaikille kielille yhteisiä jotka siis tietää jo ennen BASH:in opiskelua. Mutta BASH on kieleksi niin omituinen että tulkinta-möhläyksiä tulee.
Suomessa pahin este BASH:in oppimisessa on se että BASH-raamattu on Englanninkielinen ja tosiharvoilla taviksilla on riittävä sanavarasto puhumattakaan sen käsitteiden ymmärtämisestä. Ei toki ole useimmilla virtuooseillakaan mutta heistä sentään monilla. Eikä hyvää Suomenkielistä vastaavanlaista esitystä ole.
Ja BASH-raamatussakin asiat kuvataan siten että vain vankan perus-tietopohjan omaavat virtuoosit voivat ymmärtää ne - sillä tiivistetty selitys vaatii paljon pohjatietoja jotta ymmärtäisi kaiken oikein.
Mutta taviksia oli aikoinaan tuhansia kertoja enemmän kuin virtuooseja joten BASH:ille tehtiin karmeaa hallaa sillä tavisten kautta kaikki kehittyy, ei virtuoosien.
Joten:
- opetuksen tulee ehdottomasti tapahtua Suomenkielellä - siitä sekä tavikset että virtuoosit saavat täyden hyödyn
- virtuoosi ei ole hyvä opettaja sillä asiat täytyy esittää taviksille aivan toisin kuin virtuooseille - mutta virtuoosi kyllä ymmärtää taviksille tarkoitetun puheen.
Mutta ainoastaan sellainen tavis joka on yksinään itkien opetellut asiat pystyy niitä myös opettamaan. Joten alanpa kuvata asioita sillä olen itkenyt jo tarpeeksi:
Pikkupojat ovat aina halunneet puhua niin etteivät toiset ymmärrä - tai vielä mukavampaa on jos ymmärtävät väärin. Näin on BASH:issakin ja tätä salakirjoitus-tapaa kutsutaan laajennoksiksi jotka kertovat miten asioita on lyhenetty - siis kuinka pika-kirjoituksella kirjoitetut lauseet tulisi tulkita. Ja siitä se riemu alkaakin sillä usein sama lyhenne laajennetaan eritavoin riippuen asiayhteydestä: esimerkiksi * voi olla kertomerkki tai se voi kuvata kovalevyn jonkun kansion sisältöä tai se voi olla mikä merkki tai merkkiryhmä tahansa. Asiat ratkaistaan seuraavassa järjestyksessä - järjestys on erittäin tärkeää - sillä kun ilmaisun paikalle on kirjoitettu tulkkaustulos niin sitä ilmaisuhan ei senjälkeen enää ole - joten sitä ei enää tulkita mitenkään. Tulkkaus tapahtuu seuraavassa järjestyksessä:
1. kommentit poistetaan - siis alkaen merkistä # seuraavaan ; merkkiin tai rivinvaihtoon. Mikäli merkki # on keskellä riviä tulee sen edessä olla välilyönti.
2. {tekstijono} paikalle kirjoitetaan mitä siitä ratkaistuna tulee. Esimerkiksi käsky: echo {1..5} paikalle kirjoitetaan: 1 2 3 4 5
3. ~ -merkin paikalle kirjoitetaan skriptinktekijän kotikansion nimi - esimerrkiksi: /home/käyttäjä_nimi
4. $tekstijono:n paikalle kirjoitetaan sen arvo - jos tekstijonoa ei ole määritelty tulee arvoksi '' eikä 0 - jos ei jotain muuta määrätä.
5. $(komento) paikalle kirjoitetaan arvo joka komento tulostaa.
lause jaetaan sanoiksi - normaalisti välilyönnin kohdalta.
6. $((tekstijono)) paikalle kirjoitetaan sen arvo - tämä on siis se matemtiikkamoottori.
7. mikäli käskyssä vielä on ilmaisu: tekstijono* niin tulkki hyväksyy kaiken joka alkaa sanalla joka on sanan tekstijono paikalla ja perässä voi olla mitävaan
- siis * voi olla myös tyhjää mutta jos sen korvaa merkillä + niin sen paikalla täytyy olla ainakin yksi merkki
- jos tekstijono on tyhjä niin * merkitseekin kotikansion sisältöä - esimerkiksi: echo * on melkein sama kuin käsky: ls ~ . Ei tällä mitään käyttöä ole mutta on hyvä asia tietää se ettei ihmettelisi pitkään miksi skriptit joskus riehahtavat.
8. mikäli käskyssä vielä on ilmaisu: tekstijono? niin tulkki hyväksyy kaiken joka alkaa sanalla joka on sanan tekstijono paikalla ja perässä voi olla yksi merkki
9. mikäli käskyssä vielä on ilmaisu: [tekstijono] niin mikäl tekstijono kuvaa jonkun ryhmän niin tekstijonon paikalle kirjoitetaan sitä vastaava ryhmä - esimerkiksi [0-9] paikalle kirjoitetaan: 0 1 2 3 4 5 6 7 8 9
- nykyään suositellaan käytettäväksi rakennetta [[tekstijono]] tai [[:ryhmänimi:]] - esimerkiksi [[:num:]]
10. tiedostonimet ratkaistaan
11. lopuksi lainausmerkit poistetaan samoin kuin merkki \ elleivät ne ole lainausmerkkien välissä - älä siis hämmästy kun kohtaat ilmaisun "'"
- ja kaikissa on edellämainituissa on seuraavankaltaisia ehtoja: 'ellei tulkille ole annettu ohjetta fhöj.gåöh'
***
Tämmöistä tein:
Vanhankin .bash_history:n sai käyttöön kun poisti sen duplikaatit ulkoisella käskyllä:
cat ~/.bash_history | tac | awk '!a[$0]++' | tac > ~/koe . Ja koe-tiedoston tutkimisen jälkeen talletin sen .bash_history:n tilalle.
Samalla kirjoitin .bashrc:hen:
export HISTSIZE=100000
export HISTFILESIZE=100000
HISTCONTROL=ignoredups:erasedups
shopt -s histappend
PROMPT_COMMAND="history -n; history -w; history -c; history -r; $PROMPT_COMMAND"
Mutta myöntää täytyy että liian syvillä vesillä liikutaan.
***
Kukaan ei kykene tekemään yhtäkään skriptiä jossa ei olisi parantamisen varaa - ja usein parantamista on paljon sillä pitkät ja hitaaat skriptit supistuvat nopeiksi muutaman sanan yksirivisiksi - mutta kannattaako sen vaatima työ on kyseenalaista - eikä niiden kehittämiseen kenenkään omat rahkeet riitä. Joten puhumalla skripteistä mitähyvänsä puhuu ilmanmuuta paljon myös palturia.
***
Väite etteivät BASH:in funktiot kykene palauttamaan parametrejaan on vain puolittain tosi, se nimittäin osaa palauttaa nimiparametrin eikä sitäkään automaattisesti. Useimmilla asian merkitys on hakusessa, mutta virtuooseilta on tahallinen ja ilkeämielinen unohdus että BASH osaa tehdä tuosta parametrin palauttamisesta turhaa. Ikuisesti on tunnettu seuraava esimerkki:
a=b;let $a=55;echo b -> tulostaa 55
Funktiomuotoon sovellettuna tämä on:
function koe () { let $1=55 ;}; koe a; echo $a
joka myös tulostaa 55
- siis passataan nimiparametri.
- $1 eteen lisätään joku käsky, esimerkiksi let, read, readarray, test, eval ...
- let toimii vain numeroiden kanssa.
- readarray toimii vähän erilailla kuin muut:
function koe () { readarray $1 < /boot/grub/grub.cfg ;}; koe a; printf "%s\n" "${a[@]}"
Väite kyvyttömyydestä palauttaa parametrejä ontuu muutenkin vähäsen sillä kun joku kirjoittaa näytölle - ei ole väliä mikä - niin toinen voi käydä lukemassa sen sieltä; toiminta on erittäin nopea eikä se jätä näytölle jälkeä siitä että sitä on käytetty. Tosin se toimii ongelmitta vain kun tulostusta on yksi rivi mutta se riittääkin usein. Esimerkiksi
function koe () { echo töttöröö ;}; a=$(koe); echo $a # tulostaa: töttöröö
function koe () { cat /boot/grub/grub.cfg ;}; a=$(koe); echo $a # koko teksti menee yhdelle riville ja se on usein ongelmallista.
Siis BASH:in funktiot eivät palauta parametrejään automaattisesti vaan se pitää määrätä.
Ja aina voi funktiossakin kirjoittaa levylle ja pääohjelmassa käydä lukemassa sieltä. Keino on ihan ongelmaton ja se hidastaa toimintaa olemattoman vähän - ajatuskin on kyllä inhottava.
Nopeasti toimiva matriisin sorttausfunktio numero-arvoille
=====================================
- sort-ohjelma sorttaa mitä sille tulostetaan - on aivan sama tulostetaanko sille tiedosto vaiko matriisi.
- matriisia ei muodosteta uudestaan funktiossa, vaan pääohjelmassa käydään näytöltä lukemassa funktioon tulostus ja muodostetaan matriisi uudestaan senperusteella - sentakia kutsu on omituinen mutta toiminta on paljon nopeampaa.
- 1900 jäsenisen matriisin sorttauksessa kuluva aika on noin 60ms
#!/bin/bash
export LC_ALL=C # desimaalipiste on tällä sivulla piste eikä pilkku niinkuin normaalilla koodisivulla ->
# desimaalit sorttautuvat oikein samoinkuin tieteellinen esitysmuoto kytkimellä -g.
function sorttaamatriisi () { declare | grep ^$1= | sed "s/$1=//;s/\s\[/\n/g" | cut -d= -f2 | tr -d \(\)[]\" | sort -g ;} # sort-käskyn parametreja saattaa joutua muuttamaan.
#mat=($(seq 10000;seq 10000))
mat=($(seq -5700 1.5 5700;seq -5700 1.0 5700)) # noin joka neljäs on duplikaatti ja niiden pitää löytyä
#echo matriisi sorttaamatta: ;echo ${mat[*]}
alkuhetki=$(date +%s.%N)
mat=($(sorttaamatriisi mat))
loppuhetki=$(date +%s.%N)
echo matriisi sortattuna: ; echo "${mat[*]}"
echo -n sorttaukseen kulunut aika sekuntia: ; echo $loppuhetki-$alkuhetki | bc
(( ${#mat[*]} > 1 )) && echo kysessä on tosiaan matriisi
# duplikaattien lukumäärä
singlikaatit=$(echo "${mat[*]}" | tr ' ' '\n' | sort | uniq | wc -w)
kaikki=$(echo "${mat[*]}" | wc -w)
echo duplikaatteja $((2*($kaikki-$singlikaatit)))
export LC_ALL=
- BASH:in sorttausrutiinit syövät usein duplikaatteja ja testatessa on syytä varmistaa että duplikaatteja ei syödä - esimerkiksi laskemalla ne ja vertaamalla siihen paljonko niitä pitäisi olla.
- jos luulee työskentelevänsä matriisin kanssa niin kannattaa testata työskenteleekö matriisin vaiko samannimisen muuttujan kanssa - jossa matriisin koko sisältö on on yhtenä tekstijonona.
- nimenomaan kannattaa huomioida ettei tässä funktiossa tarvita eval-käskyä.
- tämä kykenee sorttaamaan vain numeroita - ja yksinkertaista tekstiä.
- toki parametrin voisi "palauttaakin". Mutta aikaa se vain tuhraa.
***
Verkkosivuilla esitetystä BASH-koodista ei juuri koskaan skriptin kokeilemisen takia tarvitse tehdä skripti-tiedostoa eikä edes lausetta #!/bin/bash välttämättä tarvita. Riittää kun maalaat koodin kokonaisuudessaan ja painat ctrl-c. Siten avaa pääte painamalla ctrl-alt-t ja sitten paina ctrl-shift-v . Lopuksi kannattaa painaa enter. Näyttö ei ole ihan siisti mutta asia selviää kyllä.
***
BASH:illa oli aikoinaan kirjastot ja niitä alettiin oppia hyödyntämään. Mutta BASH:ista halutaan eroon eikä se passannut ja niinpä tietoturvaan vedoten BASH:in kirjasto-osoitin poistettiin. Se ei tee kirjastojen käyttämisestä mahdotonta mutta koska entiset kirjastoja hyödyntävät skriptit lakkasivat toimimasta niin koko kirjastoidea haudattiin.
Mikäli kieleltä viedään sen kirjastot niin kielestä tulee kelvoton - niinkuin BASH:ista tuli kun siltä vietiin kirjastot. Ja BASH:issa kirjastot ovat vielä tärkeämpiä kuin muissa kielissä sillä BASH:in toiminnoista monet ovat kammottavaa merkkisotkua jota ei saa kasattua kohtuullisella työllä mitään kevollista edes asiaan vihkiytynyt.
Silloin kun BASH:illa oli kirjastot se olikin kiistaton kunkku. Mutta monikin haluaa kunkuksi kunkun paikalle ja sehän ei onnistu mikäli vanha kunkku on edes jotenkin kelvollinen. Siispä tietoturvaan vedoten BASH:in kirjasto-osoitin poistettiin. Se ei tee kirjastojen käyttämisestä mahdotonta mutta koska entiset kirjastoja hyödyntävät skriptit lakkasivat toimimasta niin koko kirjastoidea haudattiin.
Mutta mikäli ohjelmointikielen kirjastoista ei löydy jotain ominaisuutta ja joutuu koodaamaan itse tuon ominaisuuden kestää koodaaminen joskus iäisyyksiä ja koodista tulee suurinpiirtein aina hyvin hidas virhegeneraattori.
Kuitenkin maailmalla on vieläkin monia BASH-kirjastoja - joitain löytyy netistäkin. Mutta tällähetkellä tilanne on sellainen että jos haluat olla aikaansaapa skriptaaja niin sinun on tehtävä omat kirjastosi - sillä jos hyvästä koodista ei ole kirjastofunktiota rönsyilee sen käyttö ikuisesti.
Seuraava isku BASH:in tuhoamisessa oli eval-käskyn lainsuojattomaksi julistaminen. Siinäkin käytettiin tekosyytä että eval-käsky on tietoturvariski - ja onkin ilmanmuuta selvää että se on tieoturvariski sillä koko ATK on pelkkää tietoturvariskiä. Kirjastofunktioista valtaosa tarvitsee eval-käskyä.
Tosiasiahan on että BASH on ohjelmointiin sopimaton varsinkin kun toiset heittelee kapuloita rattaisiin. Muiden hämärähommien lisäksi huomasin omituisen ilmiön: skripti toimi yhtänopeasti surkealla tabletilla kuin tehokkaalla pöytäkoneella. Millätavoin BASH:in nopeutta rajoitetaan? Sillä rajoittamistahan on pakko harrastaa moniajoympäristössä tai muuten joku´omii kaikki resurssit - mutta BASH:in suhteen koko raudan parantuminen viedään ilmeisesti toisiin käyttöihin.
Pahan iskun BASH:ille ovat antaneet BASH:in kehittäjät itsekin jättämällä opettamatta sen että BASH-kieliset loopit hidastavat BASH:in toiminnan mateluksi.
***
Sensijaan että tekee skriptiin BASH-kielisiä looppeja tulisi käyttää BASH:in monessa käskyssä sisällä olevaa looppia - ja on käskyissä hyödynnettäväksi nopeita mariisejakin. Käskyjen sisäisten looppien hyödyntäminen nostaa BASH:in toiminnan nopeammaksi. Nämä esimerkit ovat työskentelyä matriisin kanssa - mutta toisaalta kaikki on matriisia. Muuten looppien hidastava vaikutus kertautuu jos niitä hölvätään jokapaikkaan.
Kokeile itse:
matriisi=({1..1000000}); time printf "${matriisi[*]}" # kestää 1.2s
matriisi=({1..1000000}); time for n in {1..1000000}; do printf $n' '; done # kestää 7.5s vaikka looppi on nopein mahdollinen - tuommoiseen looppiin ei voi laittaa muutujaa, mutta loopit jotka hyväksyvät muuttujan ovat parikin sekuntia hitaampia.
- nopeuden muutokset ovat sittenkin pieniä. Mutta koko skriptin osalta nämä pinet erot usein kertautuvat eivätkä summaudu.
- ei BASH:in kanssa pidä toimia kovin suurten matriisien kanssa mutta tämä on vain osoitus että nekin toimivat. Suurten matriisien sivuvaikutukset ovat myös suuria siinä päättessä jossa toimitaan. Ehkä raja on jossain välillä: 20.000 - 200.000. Sen näkee myös liitteistä.
***
Kun käsitellään tiedostoja se ei onnistu kovalevyllä vaan siitä täytyy ensin tehdä käskyn sisällä matriisi RAM:miin. Eihän silloin ole kovin odottamatonta ole että totunnaisesti vain tiedostojen käsittelyyn käytetyt käskyt toimivat matriisienkin kanssa? Esimerkiksi matriisin sorttaus onnistuu ihan hyvin mikäli matriisi tulostetaan sinne sort-rutiiniin:
matriisi=({1..1000000}); time echo "${matriisi[*]}" | tr ' ' '\n' | sort -nr # kestää 2.5 sek
tai saman tekee "käänteinen cat" tässä erikoistapauksessa
matriisi=({1..1000000}); time echo "${matriisi[*]}" | tr ' ' '\n' | tac
Sitkeässä elää harhaluulo että funktion parametrin palauttamisesta olisi jotain hyötyä. BASH:issa funktioon lähetetään muuttujan nimi - siis matriisinkin tapauksessa vain nimi. Funktio käsittelee muuttujan. Funktiosta ei tarvitse palauttaa mitään sillä muuttuja on muuttunut BASH:in kirjanpidossa.
Seuraava matrisin sorttausfunktio joka sopii pelkästään numeroille ja tekstille jossa ei ole välilyöntejä ei palauta parametreja sillä se olisi tarpeetonta:
function sorttaamatriisi () { readarray $1 < <(eval echo \${$1[*]} | tr ' ' '\n' | sort -nr) ;}
- 20.000-jäsenisen matrisin sorttaaminen kestää noin 150ms - täysin uskomatonta BASH:ista.
- eval-käsky mahdollistaa funktion tekemisen - readarray vain yksinkertaistaa koodia.