Kirjoittaja Aihe: Ohjeita shell-skriptaukseen (bash)  (Luettu 376426 kertaa)

Whig

  • Käyttäjä
  • Viestejä: 353
  • puppu-generaattori
    • Profiili
    • localhost
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #280 : 14.04.20 - klo:19.57 »
Näyttäisi olevan myös ilmaiseksi ladattavissa PDF-tiedostona: Poistettu Github-linkki. (--Tomin)

Se, että netistä löytää kirjan PDF-tiedostona, vaikka sitten GitHub-repostakin ei vielä takaa, että se olisi ilmaiseksi jaossa. Tässä tapauksessa en löydä yhteyttä kirjoittajan tai kustantajan ja tuon GitHub käyttäjän tai repon väliltä, joten poistin linkin. Tuo kirja näkyy löytyvän netistä monestakin paikkaa, mutta kirjassa tai muussa löytämässäni lähteessä ei sanota, että se olisi vapaasti jaossa.

Ok. Tämä kävi mielessä mutta jostain syystä luotin, että jos on githubissa jaossa niin olisi laillisesti jaossa. Aiheellinen linkin poisto siis ylläpidolta.

retu

  • Käyttäjä
  • Viestejä: 949
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #281 : 07.05.20 - klo:20.16 »
Mikä olisi hyvä kirja opiskella bash-skriptausta? Varmaan suomeksi ei ole kirjoitettu, mutta kieli ei ole este.

Pitäisi ohjelmoida skripti, joka automatisoi käyttäjälogi-tiedostoista tiedoston poistoa, kun se on vanhenutunt tietyn aikaa, eli aika pitäisi tarkistaa, ja sitä mukaa skripti poistaa käyttäjälogi-tiedostosta rivin tai tietueen paremminkin virallista termiä käyttäen, kun ensin tarkistaa sen syntyajankohdan.

Olisi aikaa ja halukkuutta ottaa Bash-skriptaus tehokäyttöön haltuun, ja perinteinen kirja on paras tietolähteeni.
Pari hyvää (makuasia tietysti, mutta varsinkin tuota jälkimmäistä tulee välillä selattua esimerkkien takia):
Jos välttämättä haluaa hypistellä kuolleita puita, nuo saa varmaan jotain kautta tilattua tai ainakin printattua. Itelle on riittänyt että
aina tarvittaessa etin sivuilta ao. kohdan ja katon mihin järjestykseen ne välimerkit taas pitikään laittaa tässä tapauksessa.

Oikeet ohjelmat eli vähänkään monimutkaisemmat jutut teen perlillä tai pythonilla (kun meillä jokaisella on kuitenkin elinpäiviä rajallisesti).

Whig

  • Käyttäjä
  • Viestejä: 353
  • puppu-generaattori
    • Profiili
    • localhost
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #282 : 17.05.20 - klo:10.49 »
Yritän putsata turhia tiedostoja NAS:lta pois ja nyt pääsin tylsimään eli .txt tiedostojen läpikäymiseen ja näitä näyttäisi kertyneen yhteen hakemistoon yli 1500kpl joita en ihan jokaista jaksaisi käydä läpi käsin.

Olisiko heittää jotain näppärää scriptin pätkää joka kävisi tiedostot läpi ja siirtäisi vaikka tiedostot joissa on esim. yksi sana/merki/merkkijono johonkin toiseen hakemistoon? Tai jotain muita scriptejä joista voisi olla apua tuon massan läpikäymisessä?

retu

  • Käyttäjä
  • Viestejä: 949
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #283 : 26.05.20 - klo:23.25 »
Olisiko heittää jotain näppärää scriptin pätkää joka kävisi tiedostot läpi ja siirtäisi vaikka tiedostot joissa on esim. yksi sana/merki/merkkijono johonkin toiseen hakemistoon?
Yks mahollisuus:
Koodia: [Valitse]
grep -wilZ 'yksi sana\|merki\|merkkijono' *.txt |xargs -0 -I '{}' mv -v '{}' toinen\ hakemisto/Decryptattuna:
  • Ohjelma grep etsii tekstiä annetuista tiedostoista.
  • Parametrit -wilZ: -w etsii kokonaisia sanoja, -i ignore case, -l tulostaa vain tiedostojen nimet ja -Z erottimena on nul-merkki eikä rivinvaihto.
  • Hakulauseke: 'yksi sana\|merki\|merkkijono' etsii sanoja "yksi sana", "merki" tai "merkkijono". |-merkki erottaa vaihtoehtoiset sanat ja basic-regexp tarvii sen eteen escape koodiksi \-merkin. Koko hoito pitää vielä kääriä yksinkertaisiin hipsuihin ettei shell sörki erikoismerkkejä \ ja |.
  • Etsittävät tiedostot: *.txt.
  • Löydetyt tiedostot ohjataan (|-putki) xargs ohjelmalle, joka ajaa jokaiselle tiedostolle parametrina annetun komennon (mv).
  • Parametri -0 kertoo sille että erottimena on nul-merkki.
  • Parametri -I kertoo merkkijonon {}, joka korvataan tiedostonimellä. Taas pitää olla hipsut '' ettei shell sotke.
  • Parametrina annettu komento mv siirtää annetun tiedoston kohdehakemistoon.
  • Sen parametri -v tulostaa siirretyt tiedostonimet. Ilman sitä tulostuisi vain virheilmoitukset.
  • {} paikalle tulee xargs ohjelman toimesta tiedostonimi (jonka mv siirtää).
  • Lopussa kohdehakemisto "toinen hakemisto", johon löydetyt tiedostot siirretään. Shell tarvii välilyönnin eteen escape merkin \

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #284 : 18.12.22 - klo:11.32 »
 Opetetaan päätteelle vähän laskentaa
 =======================
 
Lopetin jo skriptaamisesen. Mutta sitten kinttuni halvaantui ja luonnossa vaeltelu loppui ja täytyi aloittaa skriptaaminen uudelleen sillä onhan skriptaaminen  mielekkäämpää altzheimerin välttelyssä kuin palapelin kokoaminen. Mutta onhan tällä skriptillä vähäsen käytännönkin merkitystä: tämän avulla voi kerrata matikan perustaitoja.
 
Voit kirjoittaa päätteeseen minkä matemaattisen laskun tahansa melkein normaalein säännöin. Kaarisulut täytyy tosin kirjoittaa hakasulkuina. Skripti tuntee funktiot: sin, asin, sinh ... log, ln ... e, pi, ! (=kertoma) ja niinpoispäin. Ja teoriassa uusien funktioiden lisääminen on helppoa.

Kokeillaksesi näpäytä hiirellä oheista liitettä ja näpäytä sitten: pura. Kotikansioosi ilmestyy tiedosto laskija.tar.gz. Nåpäytäsitäja valitse 'pura tänne'. Anna sitten käsky:
. ~/laskija  (huomaa piste alussa, se on käsky)
- näennäisesti mitään ei saa tapahtua - mutta pääte osaa nyt laskea. Kokeile kirjoittamalla: 1+1 ja paina röttöstä. Esimerkkilaskuja ja ohjeita saat näytölle kirjoittamalla: laske. Leikkaa niitä esimerkkilaskuja sieltä ja liimaa kehoitteen perään niin saat esimakua.
- voit nyt poistaa tiedoston laskija.tar.gz
- koneelle ei koskaan tapahdu mitään ja kun sammutat päätteen niin myös laskentataidot katoavat.
« Viimeksi muokattu: 18.12.22 - klo:11.54 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #285 : 24.12.22 - klo:09.14 »
Netissä on puolikunnollisia moottoreita useimpien matemaattisten ongelmien ratkaisemiseksi mutta alkukuluvuille ei. Päätinkin kokeilla kuinka BASH tuosta alkulukujen etsinnästä suoriutuu tekemällä todella moninumeroisten alkulukujen etsintään sopivan skriptin - ja tulos olikin uskomattoman hyvä vaikka yli kolmekymmentä vuotta vanhoihin C-kielen toteuksiin tukeutuukin - nimittäin tämä skripti hoitaa BASH:illa vain pöytäkirjaa ja itse toiminta tapahtuu C-ssä. Silti skripti on ihan pelkkää BASH:ia ja toimii useimpien Ubuntu:jen pääteohjelmissa kun sen leikkaa täältä ja liimaa päätteeseen.

Koodia: [Valitse]
alku=123456789012345678901234567890123456789; echo; echo "Etsin alkulukuja luvun:$alku  perästä."; echo "löytösekunti        alkuluku"; pienet_alkuluvut=$(seq 2 2000 | factor | awk {' if ($3 =="") {print $1} '} | sed 's/://'); alkuaika=$(awk 'BEGIN {printf "%s\n", systime()}'); time for n in $(seq $alku $(bc<<<$alku+800)); do for apu in ${pienet_alkuluvut[*]}; do (( $(bc<<<$n%$apu)==0 )) && n=0 && break ; done; (( $n )) && echo -ne '\rTutkittavana: '$n && timeout 20 factor $n | awk -v alkuaika=$alkuaika {' if ($3 =="") {printf "\r%s\n", systime()-alkuaika"                  "$1}'} | sed 's/://'; done

Skripti toimii nopeasti monilla yli 70 numeroisillakin luvuilla mutta esimerkiksi jo niin pienestä luvusta kuin 140771709250109097948956177753846587 kestää puoliminuuttia selvitää onko se alkuluku vaikka luku on vain 119 bittinen. Tutkiakseni kuinka usein toiminta tökkii tein alkulukuja metsästävään skriptiin sellaisen muutoksen että kun laskenta kestää liian kauan niin luku merkitään ja siirrytään käsittelemään seuraavaa lukua - näintehden kaikkia alkulukuja ei saa kiinni mutta toiminta ei koskaan keskeydy kovin pitkäksi aikaa - ja kyllä niitä alkulukuja on myöhemminkin. Joten vain ne tulosteen rivit joilla on pelkkiä numeroita ovat alkulukuja - ne joissa on tekstiä ovat niitä jotka tökkivät eikä niistä voi varmuudella sanoa mitään - on kuitenkin hyvä tietää milloin sellainen on kohdattu. Lukujen suuretessa tökkiviä tulee aina vaan enemmän - laskut vaatisivat todella kovanluokan tietokonetta ja minulla on huono läppäri.

- lukua: alku voi muuttaa kuinka paljon vaan. Samoin sekunneissa olevaa timeout arvoa.
- jos skripti väittää jotakin alkuluvuksi pitää se paikkansa.
- toiminnan aikana tutkittava luku vaihtelee hiljokseen sillointällöin.
- skripti ei voi toimia luvuille alle 2000 toimintaperiaatteensa vuoksi.
- hidashan tämä on, mutta alkulukujen etsintämenetelmät ovat hitaita aina.
- Mersenne prime:ille on oma sivusto mutta meille tavallisille tallaajille se on ilmoitusluonteinen. Mutta Mersenne-primejä on harvassa ja useimmat ovat niin suuria etteivät ne meikäläisten koneisiin mahdu. Mutta se hyöty niistä on että joistain niistä saa alkulukuja etsivän skriptin toiminnalle jonkinlaisen "oikellisuustodistuksen" - esimerkiksi: factor $(bc<<<2^521-1 | tr -d '\\\n') |  tr -cd ' \t' | wc -c
täytyy tulostaa: 1 .
- 2^607-1 olisi seurava Mersenne-prime.

***

Kaikki ohjelmoijat soveltavat BASH:iin niitä menetelmiä joita muissa kielissä ovat oppineet käyttämään. Ei onnistu, sillä BASH on ihan erilainen.

Se kyllä uskotaan nopeasti että BASH:in find-käsky ei looppeja tarvitse vaikka se etsisi monilta kovalevyiltä jokaisen kovalevyn kaikki kolkat. Mutta kaikki BASH:in  käskyt ovat samanlaisia - niillä on sisäiset looppinsa joten ei niitä looppeja hitaassa BASH:issa kannata tehdä - paitsi hyvin harvoin semmoisia lyhyitä.

Esimerkiksi tehokkain silkkaa BASH:ia oleva etsintämenetelmä kun halutaan löytää jotain matriisista tai tekstijonosta: [[ etsintäkohde =~ etsittävä ]] ...   Funktion muotoon kirjoitettuna:

Koodia: [Valitse]
function etsi () { [[ "${@:2}" =~ "$1" ]] && echo löytyi || echo ei löytynyt ;};

Mutta käskyä ei tulla koskaan hyväksymään sillä siinä ei ole näkyviä looppeja ja niitähän toki täytyy kunnollisessa skriptissä olla - ja kun ei noita loopittomia hyväksy niin saa syyn jatkaa BASH:in haukkumista. Sillä kyse ei niinkään ole siitä että se on nopein tapa vaan siitä että se on virheettömin.

Eikä funktiossa tarvita looppia edes silloin kun etsittävä ei ole yksikäsitteinen vaan sekin vaatii käsittelyä jossakin ja lisäksi omissa loopeissaan joita niitäkään ei kirjoiteta. Esimerkiksi:

Koodia: [Valitse]
[[ $(cat /boot/grub/grub.cfg) =~ BEGIN.*[[:digit:]]?root ]] && echo jees

- mikähän tuo kysymysmerkki on? Lisäehto että millähyvänsä rivillä pitää olla sana root ? BASH pursuaa dokumentoimattomia ominaisuuksia

***

Maximin ja minimin etsiminen onnistuu myös melkein aina loopittomilla funktioilla - ja voi etsiä tekstijonosta tai matriisista, numeroita tai tekstiä.
Koodia: [Valitse]
function haemaksimi () { maksimi=$(echo $(sort -n <(echo "$@" | tr " " '\n' )) |  sed 's/.* //') ;}

functifon haeminimi () { minimi=$(echo $(sort -n <(echo "$@" | tr " " '\n' )) |  sed 's/ .*//') ;}

Testaamista: 100.000 matriisista hakuaika: 0.3 sekuntia
Koodia: [Valitse]
function haemaksimi () { maksimi=$(echo $(sort -n <(echo $@ | tr " " '\n' ) | tail -1)) ;}; time matriisi=($(seq 100000 | awk 'BEGIN{srand();}{print 1000000*rand()}')) ; time haemaksimi ${matriisi[*]}; echo $maksimi

function haeminimi () { minimi=$(echo $(sort -n <(echo $@ | tr " " '\n' ) | head -1)) ;}; time matriisi=($(seq 100000 | awk 'BEGIN{srand();}{print 1000000*rand()}')) ; time haeminimi ${matriisi[*]}; echo $minimi


function etsi () { [[ "${@:2}" =~ "$1" ]] && echo löytyi || echo ei löytynyt ;}; time etsi 555 $(seq 1 1000000)


- sorttauksen parametreja joutuu joskus säätämään vaikka yleensä onnistuu jo näin.
- miljoonan numeron matriisilla toiminta-nopeus maximi/minimissä oli 3.8 sekuntia ja etsimisessä 2.0 sekuntia.
- miljoonan muuttujan matriisi siksi että voi olla varma että vain muistimäärä rajoittaa ja muuta omituista ei tule.

***

Teinpä vuoden viimeisenä iltana käskyn: foreach kun se BASH:ista puuttuu. Nimenomaan sen matemaattisen version joka muuttaa muistissa olevan matriisin jokaisen jäsenen arvon niiden ohjeiden mukaan jotka erikseen määrätään.
- tällähetkellä nuo käsittely ohjeet annetaan foreach:in koodissa - mutta voi ne omaan funktioonsakin laittaa.
- matriisin jäsenet tosiaan muuttuvat.
- kun funktion koodin laittaa skriptin alkuun on käsky käytettävissä skriptissä missävain, vaikka montakertaa.
- täytyy tarkentaa: tämä on edelleen BASH:ia.

Käsky on: foreach matriisin_nimi
Koodia: [Valitse]
foreach () { name=$1; name=($(declare -p | grep 'a '$name | grep -Po \".*?\" | tr -d \" | tr '\n' " " )); for n in ${name[@]}; do eval $1[$n-1]=$(($1[$n-1]**3)); done ;}; apu=({1..1000}); time foreach apu; printf "%s\n" "${apu[@]}"

- jos matriisin jäsenissä on desimaalipiste tai käsittely on monimutkaisempaa täytyy käyttää seuraavaa - mutta se on paljon hitaampi.
Koodia: [Valitse]
foreach () { name=$1; name=($(declare -p | grep 'a '$name | grep -Po \".*?\" | tr -d \" | tr '\n' " " )); for n in ${name[@]}; do eval $1[$n-1]=$(bc -l<<<"${name[$n-1]}^-4"); done ;}; apu=({1..100}); time foreach apu ; printf "%s\n" "${apu[@]}"

« Viimeksi muokattu: 01.01.23 - klo:13.15 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #286 : 02.01.23 - klo:18.03 »
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.
 
 
« Viimeksi muokattu: 03.02.23 - klo:18.11 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #287 : 16.01.23 - klo:02.54 »
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.
 
« Viimeksi muokattu: 02.02.23 - klo:02.26 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #288 : 02.02.23 - klo:02.38 »
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.






« Viimeksi muokattu: 27.02.23 - klo:06.17 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #289 : 28.02.23 - klo:10.52 »
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
« Viimeksi muokattu: 12.03.23 - klo:21.05 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #290 : 25.03.23 - klo:15.18 »

Parantelin 36-numeron liukuvan pilkun kertolaskua - sen kerrottaviksi voi antaa kaksi liukuvan pilkun 18-numeroista kerrottavaa. Poistin vanhan koodin ja laitoin tähän uuden.

Ja vaikka bc antaa oikean tuloksen johon verrata niin muuten bc ei osallistu laskentaan ollenkaan - sentakia kutsut ovatkin todella monimutkaisia. Mutta normaalisti koko bc:n voi jättää pois ja silloin kutsu on: kerro18 $luku1 $luku2

- esimerkiksi tulos laskusta: .00900007567899123 * 900.07000012345678  on: 8.1006981175007567491845709040394   - ja lasku kestää vain 1ms - bc:llä lasku  kestää 3ms.
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
 
(( $summa2 )) && : || summa2=000000000000000000
(( $kokonaisluku )) && tulos=${summa2/*(0)/}$summa1 || { apu=$summa2$summa1; tulos=${apu:0: -$desimaaleja}.${apu: -$desimaaleja} ;}
echo $merkki${tulos##+(0)}
echo tulos laskusta: $1 \* $2  . Ylärivi on bc:stä ja alarivi tästä skriptistä. ;}

clear
luku1=3.16227766; luku2=3.16227766;                  echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2 
echo kun tulos siirtyy uudelle kymmenluvulle on syytä tarkistaa että desimaaalipisteen paikka siirtyy oikealla hetkellä   
luku1=3.16227767; luku2=3.16227767;                  echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2     

luku1=1 ; luku2=1 ;                                 echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=-1 ; luku2=1;                                 echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=1 ; luku2=-1;                                 echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=-1 ; luku2=-1;                                echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=.00900007567899123; luku2=900.07000012345678; echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=111111.11111111111; luku2=123456789012345.67; echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2 
luku1=10; luku2=10                                ; echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=999999999999999999; luku2=999999999999999999; echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=.9; luku2=2;                                  echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=.99999999999999999; luku2=.99999999999999999; echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=.00000000000000001; luku2=.00000000000000001; echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2   
luku1=10000000000000000; luku2=.0000000000000001;   echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=.0000000000000001; luku2=10000000000000000;   echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2   
« Viimeksi muokattu: 12.11.24 - klo:06.48 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #291 : 08.04.23 - klo:06.48 »
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.

« Viimeksi muokattu: 12.04.23 - klo:07.56 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #292 : 13.04.23 - klo:12.31 »
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.

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #293 : 15.04.23 - klo:22.05 »

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.

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #294 : 20.04.23 - klo:12.40 »
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.

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #295 : 22.04.23 - klo:15.45 »
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?

nm

  • Käyttäjä
  • Viestejä: 16425
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #296 : 22.04.23 - klo:17.50 »
Bc kuuluu BASH:in perusasennukseen.

Bash ja GNU bc kuuluvat useimpiin GNU/Linux-jakeluihin, mutta eivät ole keskenään riippuvaisia. Lisäksi bc kuuluu POSIX-standardin vaatimiin perussovelluksiin, mutta bashin sijaan kelpaavat muutkin POSIX-yhteensopivat shellit.


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

bc:

Koodia: [Valitse]
time 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' > /dev/null
real    0m0.056s
user    0m0.053s
sys     0m0.001s


C + GMP (GNU Multiple Precision Arithmetic Library):

Koodia: [Valitse]
#include <stdio.h>
#include <stdlib.h>
#include "math.h"
#include "gmp.h"

int main() {
    int i, m, n;
    int *e;
    mpz_t a, b, c, t;
    mpz_inits(a, b, c, t, (mpz_ptr) NULL);

    n = 100000;
    m = (n + 1) / 2;
    e = (int *) malloc(sizeof(int) * (1 + (int) log2((double)m)));

    mpz_set_ui(a, 0);
    mpz_set_ui(b, 1);
    i = 0;
    while (m) {
        e[i++] = m % 2;
        m /= 2;
    }
    while (i--) {
        mpz_mul(c, a, a);
        mpz_mul(a, a, b);
        mpz_mul_ui(a, a, 2);
        mpz_add(a, a, c);
        mpz_addmul(c, b, b);
        if (e[i]) {
            mpz_set(b, a);
            mpz_add(a, a, c);
        } else {
            mpz_set(b, c);
        }
    }
    if (n % 2) {
        mpz_mul(a, a, a);
        mpz_addmul(a, b, b);
    } else {
        mpz_mul_ui(b, b, 2);
        mpz_add(b, a, b);
        mpz_mul(a, a, b);
    }
    gmp_printf("%Zd", a);
  return 0;
}

Koodia: [Valitse]
gcc -Wall -std=c99 -O3 -march=native fib.c -lgmp -o fib
Koodia: [Valitse]
time ./fib > /dev/null
real    0m0.002s
user    0m0.001s
sys     0m0.000s

Myös Python 3 on tässä tehtävässä nopeampi kuin bc, joskin suurilla n:n arvoilla ero kapenee. Isommilla n:n arvoilla C-versio on 100 - 200 kertaa nopeampi kuin bc-koodi. C-versio pystyi laskemaan vielä Fib(1000000000):n eli miljardinnen Fibonaccin luvun 66 sekunnissa. bc:llä ja Pythonilla laskenta kestäisi luultavasti useita tunteja.

AimoE

  • Käyttäjä
  • Viestejä: 2782
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #297 : 22.04.23 - klo:18.19 »
Voisko joku valaista miksi ketju "Ohjeita shell-skriptaukseen (bash)" täyttyy näistä laskunopeuskilpailuista? Eikö se olisi erillisen ketjun arvoinen aihe?

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #298 : 25.04.23 - klo:21.07 »
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.
« Viimeksi muokattu: 16.07.23 - klo:11.40 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #299 : 14.05.23 - klo:14.25 »
Tavallaan on järkevää pyrkiä eroon kielestä jossa on mielettömästi liikaa käskyjä - ja BASH:issa on. Sillä se ohjelmointi-ympäristö josta yleensä puhutaan on vain yksi sadoista. Senkin käskyt ovat tarkoitettu tiedostojen käsittelyyn ja ovat melkein täysin sopimattomia 'numeronmurskaamiseen' - eikä näin ole alkuunsa ollut joten senkin takia käskyjä on kaksinkertainen määrä.

Aikoinaan luin että BASH:issa on tuhatkunta käskyä ja nän näennäisesti olikin - ja se tuntui ihan hyvältä määrältä. Mutta nopeasti löysin toisista ympäristöistä lisää käskyjä eikä lisäyksille tullut koskaan loppua. Esimerkiksi löysin 'vanhat käskyt' merkkijonojen käsittelyyn ja niitä oli ainakin satoja - mutta niistä ei missään ole minkäänlaista yhteenvetoa, ei käsitystä niiden lukumäärästä, ei tietoa niiden suunnittelu-säännöistä (kyllä joitain perus-sääntöjä on selvinnyt joten niitä voi hyvin rajoitetusti kasata itsekin) eikä niiden olemassaolostakaan yleensä kerrota - tosin jopa BASH-raamatussa on pari mainittuna sivumennen - mutta näiden vanhojen käskyjen hyöty ei valkene ennenkuin niitä on skriptistä suurin osa. Vanhat käskyt toimivat aina samoin vaikka data vaihtuisi paljonkin - mutta uudet käskyt saattavat muuttaa käytöstään kun data muuttuu paljon. Ja vanha käskyt osaavat tehdä asiat samallatavoin muissakin kielissä - esimerkiksi toimitaan kokonaisilla sanoilla eikä merkeillä. Lisäksi ne osaavat hyödyntää yksinkertaisia regex:iä ja ne ovat yli kymmenenkertaa nopeampia kuin uudet käskyt - ja vanhojen käskyjen ryhmät ovat vielä nopeampia mikäli skripti muodostuu yksinomaan noista vanhoista käskyistä.

Esimerkiksi hyvin pieni otos tekstijonojen käsittely-käskyistä:
Koodia: [Valitse]
merkkijono=123; echo "${merkkijono::-${montakolopustapoistetaan:-1}}"  # tulostaa: 12 (tai:echo "${merkkijono%?}" tai:echo "${merkkijono%3}"
merkkijono=12343; echo "${merkkijono%?43}"                             # tulostaa: 12 (tai:echo "${merkkijono%[0-9]43}")
merkkijono=123; echo "${merkkijono: -montakolopusta tulostetaan}"
merkkijono=123; echo "${merkkijono:0:montakoalusta tulostetaan}"       # eli: "${merkkijono:monennestako aloitetaan:kuinka monta}"
merkkijono=123; echo "${merkkijono:montakoalusta poistetaan}"
merkkijono=xabyabz; echo ${merkkijono/ab/acc}    # tulostaa: xaccyabz  -> korvaa vain kerran alusta - korvaava ja korvattava voivat olla eripitkiä
merkkijono=xabyabz; echo ${merkkijono//ab/acc}   # tulostaa: xaccyaccz -> korvaa kaikki
merkkijono='9 8 7 5 4'; echo ${merkkijono//8*5/} # tulostaa: 9 4

merkkijono='9 8 7 5 4'; echo ${merkkijono%% *}   # tulostaa: 9       
merkkijono='9 8 7 5 4'; echo ${merkkijono% *}    # tulostaa: 9 8 7 5
merkkijono='9 8 7 5 4'; echo ${merkkijono##* }   # tulostaa: 4
merkkijono='9 8 7 5 4'; echo ${merkkijono#* }    # tulostaa: 8 7 5 4
- jakopiste voi olla myös sana, muuttuja, erottavien merkkien lista  tai regex  - tai olisikohan taas niin että vain mielikuvitus on rajana. 
- erottavien merkkien lista esimerkiksi - .:/  ->
merkkijono='9:8:7:5:4'; echo ${merkkijono%%[- .:/]*}
merkkijono='9 8 7 5 4'; apu=${merkkijono% * *}; echo ${apu##* } # tulostaa: 7 . Tämmöisiä kannattaa muodostaa ohjelmallisesti.
- näiden käskyjen suurin heikkous on se, että mikäli erottavaa merkkiä ei edes ole tulostetaankin koko sana josta etsitään - eikä tulosteta tyhjää niinkuin pitäisi. Mutta tuohon oikeaan käyttäytymiseen pääsee kun haun eteen kirjoittaa: [[ "$merkkijono" =~ "$jakavasana" ]] && -> esimerkiksi: 
jakavasana=next; merkkijono='9next8next7next5next4'; [[ "$merkkijono" =~ "$jakavasana" ]] && echo ${merkkijono%%"$jakavasana"*} # tulostaa: 9
Ja vaikka merkintä on pitkä on se edelleen nopea; eikä sitä edes kirjoiteta koodiin vaan kopioidaan-liimataan dokumentista.
Joten tästä ryhmästä voidaan muodostaa seuraavat funktiot (siis määritellä uudet käskyt):
otaeka () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono%%$erottaja*} ;}
# kutsuesimerkki: otaeka '9 8 7 5 4' tai: otaeka '9 8 7 5 4' erottavasana
#
# siis mikähyvänsä muuttujan:erota merkeistä voi toimi erottajana - ei koko joukko.
# muuttuja:erottavasana sensijaan voi olla tosiaan sana. Sitä ei tarvise kutsussa olla mutta jos se on niin sitä käytetään muutta jos ei ole niin sitten käytetään merkkejä.
# rakenne: if-then-else vanhentui - vastaava merkintä nykyään on tuo tässäkin oleva: [[ $2 ]] && 'joko hypi' || 'tai tärise'. Ja koko ehto on aina samalla rivillä.
## on kommentin lisä-selvennys. Joten ### on seli-seli-seli
## [[ $2 ]] on se if - elikä looginen ehto: onko-olemassa - mikähyvänsä teksti on jotakin joten nollakin on jotakin - ainoastaan '' on ei-mitään
## (( $2 )) olisi matemaattinen if: onko matemaatista arvoa vai onko se nolla. Vain numeroita voi testata näin.
## && on then ja || on else

otavika () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono##*$erottaja} ;}
poistaeka () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono#*$erottaja} ;}
poistavika () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono%$erottaja*} ;}
# kutsuesimerkki ja muut kommentit näissä kolmessa ovat samat kuin otaeka:ssa. Nämä yksittäiset käskyt ovat tosi-nopeita.

# Ja näistä saa kasattua käskyn poimijoku:
otaeka () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono%%$erottaja*} ;}
poistaeka () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono#*$erottaja} ;}
poimijoku () { merkkijono=$1; apu=$1; for (( n=1; n<=$(($2-1)); n++ )); do apu=$(poistaeka "$apu"); done; otaeka "$apu" ;}
# esimerkkikutsu:
poimijoku '987654321 87654321 7654321 54321 4321' 3 # tulee siis 7654321

Hidashan tämmöinen poimijoku on ja turhan monimutkainenkin mutta ajankuluahan sen kokoaminen oli - ja saihan sen tekemisestä paljon oppiakin. Kymmenenkertaa nopeampi ja yksinkertaisempi on:
Koodia: [Valitse]
poimijoku () { IFS="- .:/&¤"; apu=($1); unset IFS; echo ${apu[$2-1]} ;} # IFS (->sananväli joka oletuksena on välilyönti) on aina yksi merkki kerrallaan annetusta listasta - siis tavallaan kokeillaan kunnes joku kelpaa.
time poimijoku '987654321-87654321 7654321.54321:4321/321&21¤1' 8       # siis erottava merkki ovat yleensä sama mutta voivat ne vaihdella etsittävässä vaikka jokakerta.
# mutta IFS on sikäli huono ettei se voi olla sana - ja toisekseenkin leikit IFS:n kanssa menevät joskus pahasti pieleen. Seuraava hyväksyy erotusmerkiksi sanan - mutta se on
# hidas:
poimijoku () { apu=$(echo ${1//$2/' '}); apu=($apu); echo ${apu[$3-1]} ;}
# esimerkkikutsu:
poimijoku '987654321next87654321next7654321next54321next4321' next 3
# apu=($apu) ei ole väkivaltaa sillä jokainen BASH:in tavallinen muuttuja on samalla myös matriisi.
# kohdassa: echo ${apu[$3-1]} lasku: $3-1 on merkinnältään normaali eikä $(($3-1)) niinkui BASH:issa muuten -> sulut tämän aiheuttavat

***

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?
« Viimeksi muokattu: 20.08.24 - klo:17.10 kirjoittanut petteriIII »