Kirjoittaja Aihe: Skriptiajuri  (Luettu 144737 kertaa)

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Skriptiajuri
« Vastaus #80 : 01.05.24 - klo:17.54 »
Harrastuksena ja mieleisenä ajankuluna BASH alussa oli - mutta olin siinä luulossa että BASH on hidas ja kyvytön. Mutta enpä tiedä enää - alkaa tuntua siltä että kaiken saa toimmaan kymmeniä kertoja nopeammin kuin ennen  - ja paljon sellaista saa toimimaan jonka ei pitäisi toimia ollenkaan.

Sehän tuli kerrottua jo aikaisemminkin että yksi virtuoosi aikoinaan sanoi että jos todellista nopeutta etsii niin awk:it, sed:it ja semmoiset saa unohtaa. Silloin pidin sitä melkoisena liioitteluna mutta virtuoosi taisikin humalapäissään kertoa salaisuuksia ?

---

Miksi BASH:ista esittään kaikkialla niin vähän esimerkkejä? Ja miksi kaikki lähetyvät ongelmia samalta suunnalta vaikka BASH on niin laaja että kaikkeen on lukemattomia lähestymistapoja? Johtuuko se siitä ettei ohjeita tehdä vaan ne kopioidaan eikä niitä ohjeitakaan päivitetä koskaan - tosin joillain harvoilla verkkosivuilla mainitaan puolihuolimattomasti että 'tämmöistäkin uutta surkeutta löytyy'. BASH:ia tosiaan kehittää vain yksi ukko - ansiokkaasti kehittääkin ja varmaankin apujoukon kera - mutta mieleen tulee ettei hänkään halua sohia ampiaispesää tarpeettomasti.

Esimerkiksi 1+1 voidaan laskea ainakin kahdella yleisessä käytössä olevalla tavalla: ensinnäkin se normaali 'echo $((1+1))', se toinen tapa on : + 1 1  .
- mattematiikamoottori on kummassakin sama - tuo: $(($1+$2))
- kummessakin laskutavassa apuna täytyy olla funktio: siinä normaalissa on BASH:in sisäinen funktio nimeltään echo ja toisessa itsetehtävä funktio nimeltään +   .

Funktion nimeksi voidaankin määrätä melkein mitähyvänsä, esimerkiksi:
Koodia: [Valitse]
function + () { echo $(($1+$2)) ;}
- laskussa $(($1+$2)) + on matemaattinen merkki eikä funktiokutsu joten kyseessä ei ole rekursio (eli funktio ei kutsu itseään)
- kun annetaan käsky: + 1 2  niin se tulostaa 3 - mikäli löytyy tuo funktio nimeltään: +  .

seuraavanlainen on kätevä:
Koodia: [Valitse]
function + () { apu=$(echo ${@:1}); apu=${apu// /+}; echo $(($apu)) ;}; + 1 2 3 4 5 6 7 8 9

Tai:
Koodia: [Valitse]
function / () { echo -n $(($1/$2)).;apu=$(($1%$2))000000000000000000;apu=${apu:0:$1};echo $(($apu/$1)) ;}; / 223 17

- merkit: * @ # ? - $ ! 0  kelpaavat funktionimikisi mutta kutsussa niiden eteen täytyy laittaa keno:
Koodia: [Valitse]

function ! () { [[ $1 -eq 0 ]] && kertoma=1 || kertoma=$(seq -s* $1 | bc | tr -d '\\\n'); echo $kertoma ;}
\! 6  #-> tulostaa 720 niinkuin pitääkin. Lukualue on 'ääretön'.
Tai:
Koodia: [Valitse]

function * () {
luku1=${1//./,}; luku1=${luku1//E/e}; luku1=${luku1//+/}; luku1=${luku1//--/}
luku2=${2//./,}; luku2=${luku2//E/e}; luku2=${luku2//+/}; luku2=${luku2//--/}
luku1=$(printf "%.18e" $luku1)
luku2=$(printf "%.18e" $luku2) #; echo $luku1' '$luku2; read
exp1=${luku1##*e}; exp1=${exp1//+0/+}; exp1=${exp1//-0/-}
exp2=${luku2##*e}; exp2=${exp2//+0/+}; exp2=${exp2//-0/-}
luku1=${luku1%%e*}; luku1=${luku1%%+(0)}
luku2=${luku2%%e*}; luku2=${luku2%%+(0)}
[[ ${luku1:0:1} = '-' ]] && { merkki1=-1; luku1=${luku1:1} ;} || merkki1=1
[[ ${luku2:0:1} = '-' ]] && { merkki2=-1; luku2=${luku2:1} ;} || merkki2=1
int1=${luku1%%[,.]*}
int2=${luku2%%[,.]*}
kokonaistentulo=$(($int1*$int2))
kokonaisia=${#kokonaistentulo}
exp3=$(($kokonaisia+$exp1+$exp2)) #; echo $exp3; read
tulos=$((${luku1//,/}*${luku2//,/}))'00000000000000000000000000000000000000'; [[ ${kokonaistentulo:0:1} -eq 9 ]] && [[ ${tulos:0:1} -eq 1 ]] && exp3=$(($exp3+1))
[[ $(($merkki1*$merkki2)) -eq -1 ]] && echo -n '-'
[[ $exp3 -gt -1 ]] && apu2=${tulos:0:$exp3}.${tulos:$exp3} ||  apu2=.$(printf "%0"${exp3:1}"d")${tulos%%+(0)} 
[[ ${apu2//[^.]/} ]] && apu2=${apu2%%+(0)}; echo ${apu2%.} ;}
\* 223.12345678901234567  1.72356


Nimeltään tuo juohea laskutapa '+ 1 2' on käänteinen Puolalainen ja koska se vaatii BASH:issa käyttäjää tekemään itse tarvittavat funktiot niin käänteistä Puolalaista kannattaa käyttää vain kun matematiikkaa on tosipaljon. Ei tuota käänteistä Puolalaista tosin enää juuri koskaan käytetä.
- esimerkiksi laskuohjelman bc isä nimeltään dc oli käänteistä Puolalaista - se on koneelle helpompaa mutta ihmiselle niin vaikeaa että sille tehtiin kuori-ohjelma nimeltään bc joka muuttaa ihmisen ajatukset koneelle sopivammaksi käänteiseksi Puolalaiseksi. Onkohan käänteinen Puolalainen parempikin matikassa koska noin täytyi toimia?
« Viimeksi muokattu: 16.06.24 - klo:10.42 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Skriptiajuri
« Vastaus #81 : 04.05.24 - klo:12.17 »
Yksi menetelmä etsiä tekstistä jotakin on muodostaa tuosta etsittävästä muotti ja ajaa teksti muotista läpi jolloin kaikki muotin mukainen tarttuu muottiin. Käytännössä toimitaan seuraavasti:

- etsittävän rakennetta kuvataan oliolla nimeltä regex: regex on muotti jonka määräysten mukaan etsittävän tyyppiset muuttujat on koottu - esimerkiksi päivämäärän muotti on: [0-9]+:[0-9]+:[0-9]+
- tässä paikassa merkki + merkitsee: yksi tai useampi samanlainen kuin on edessäkin.
- monille merkeille tulkki antaa asiayhteydestä riippuvan  erikoismerkityksen - useimmilla merkeillä on aina sama merkitys mutta esimerkiksi merkillä: * niitä on monia. Jos ei haluta tulkin antavan merkille erikoismerkitystä kirjoitetaan sen eteen kenoviiva - siis esimerkiksi: \*
- kun tekstin jokaista sanaa sovitellaan tuohon muottiin niin kun muotti sopii niin kyse on päivämäärästä.
- regex:ää on totuttu käyttämään jonkun apu-ohjelman avulla - esimerkiksi grep:in avulla ja kyllähän sillä omat etunsa onkin, mutta BASH kykenee useisiin regex:iin itsekin ja silloin kun BASH kykenee niin grep jää surkeasti kakkoseksi.
- myös tuo regex-maailma on valtavan suuri - katsopa verkkosivua: https://www.regexbuddy.com/. Ja samatyyppisiä sivuja on monia muitakin.

haku kokonaisuudessaan:
Koodia: [Valitse]
teksti="höpinää 17:06:23 lisää höpinää"; regex="[0-9]+:[0-9]+:[0-9]+"
[[ "$teksti" =~ $regex ]] && echo "tekstissä on päiväys:$BASH_REMATCH"
- tuosta regexään:n sopivasta muodostetaan aina BASH:issa muuttuja $BASH_REMATCH.

---

Mutta päiväys voidaan jakaa osiin: tuntiin, minuuttiin ja sekuntiin - ja mikäli regex on jakamista silmäälläpitäen muodostettu niin kukin näistä osista talletetaan omaan BASH_REMATCH[numero] nimiseen muuttujaan. BASH_REMATCH:ia selvitetään kaikkialla juurta jaksain - ja ehkäpä joku saa niistä selityksistä selvääkin. Mutta esimerkkejä ei esitetä; siis sellaista yksinkertaista skriptiä jossa tulostetaan BASH_REMATCH, BASH_REMATCH[1], BASH_REMATCH[2], BASH_REMATCH[3] ... jotta asiasta tulisi rautalankamalli jota ei voi käsittää väärin - tai oikeastaan että asian voisi nopeasti edes käsittää. Koetetaanpa nyt esitää asia ymmärrettävästi:

- BASH_REMATCH:ia käytettäessä regex:ät on jaettu suluilla osiin ja jokainen sulkupari muodostaa uuden muuttujan nimeltään:  BASH_REMATCH[1], BASH_REMATCH[2], BASH_REMATCH[3] ... ja niiden kokonaisuuden nimeltä: BASH_REMATCH. Esimerkiksi päivämäärää kuvaava regex jaetaan:
Koodia: [Valitse]
([0-9]+):([0-9]+):([0-9]+)   

Esimerkki:
Koodia: [Valitse]
teksti="höpinää 17:06:23 lisää höpinää"; regex="([0-9]+):([0-9]+):([0-9]+)"   
[[ "$teksti" =~ $regex ]] && echo "tekstissä seuraava palanen täytti koko regex:än: $BASH_REMATCH - ja sen osat: tunti:${BASH_REMATCH[1]} minuutti:${BASH_REMATCH[2]} sekunti:${BASH_REMATCH[3]}"
- mikäli koko regex ei täyty niin ei tulosteta mitään - elikä 2013:06 ei tulosta mitään.
- siis jokaisesta regex:än (....)-kentästä muodostuu uusi BASH_REMATCH[numero] - ja numerointi alkaa ykkösestä.
- muuten teksti voi olla matriisikin:
Koodia: [Valitse]
${teksti[@]}    - silloin tosin saadaan vain ensimmäinen joka sopii.
- kieliasetukset vaativat skriptiin omat muutoksensa - näillä määrityksillä tämä toimii Suomessa.
 
toinen esimerkki:
Koodia: [Valitse]
[[ "012 a34567b89cdefgh" =~ ([a-z])[0-9]*([a-z])[0-9]*([a-z]) ]] && {
      echo "- kolmeriviä selitystä: REMATCH:it tehdäån kohdassa:[[ "012 a34567b89cdefgh" =~ ([a-z])[0-9]*([a-z])[0-9]*([a-z]) ]]."
      echo "  Regex muodostuu useasta regex:stä. Tässä esimerkissä on kolme regex:ää ympäröity kaarisuluilla - huomio että"
      echo "  [0-9] on kaksikin kertaa BASH_REMATCH:in ulkopuolella vaikka kuuluvatkin regex:ään - siis valintajoukkoon ne kuuluvat mutta eivät BASH_REMATCH:iin."
      echo "BASH_REMATCH on koko regex alue= "$BASH_REMATCH
      echo "BASH_REMATCH[1] (elikä regex:än ensimmäisessä ()-osassa olevan regex:n hyväksymä arvo)= ${BASH_REMATCH[1]}"
      echo "BASH_REMATCH[2] (elikä regex:än toisessa      ()-osassa olevan regex:n hyväksymä arvo)= ${BASH_REMATCH[2]}"
      echo "BASH_REMATCH[3] (elikä regex:än kolmannessa   ()-osassa olevan regex:n hyväksymä arvo)= ${BASH_REMATCH[3]}" ;}


kolmas esimerkki, kenttien kääntäminen - vaikkapa Suomalaisen päiväyksen muuttaminen jonkun muun maan esitystapaan sopivaksi:
Koodia: [Valitse]
[[ 1.2.2013 =~ ([0-9]+).([0-9]+).([0-9]+) ]] && echo ${BASH_REMATCH[3]}.${BASH_REMATCH[2]}.${BASH_REMATCH[1]}
- kyse on nopeudesta. Työ olis helpompaa awk:lla tai semmoisilla mutta myös hitaanpaa. Ero on on millisekunneissa mitättömän pieni eikä muiden hitauteen edes vihjata missään - mutta itseasiassa nopeusero on yli kymmenkertainen. 

---

Esimerkiksi matriisien tarkistaminen regex:ällä senvaralta että siinä olisi kirjaimia:
Koodia: [Valitse]
function tarkista_matriisi () { [[ $@ =~ [[:alpha:]] ]] || (( $BASH_REMATCH )) && echo matriisissa ensimmäinen vastaantuleva kirjain:$BASH_REMATCH || echo "matriisisssa ei ole kirjaimia";}; matriisi=({1..100000}); matriisi[5000]=7A7e; time tarkista_matriisi ${matriisi[@]}
- tuo [[:alpha:]] on tässä se regex. Regex:t voivat olla tällaisia pieniäkin mutta esimerkiksi IP6-verkko-osoitteen tarkistamiseen sopiva regex on monen rivin hirviö - mutta silti erittäin nopea.

Tavanomaisempi funktio samaan tarkistukseen - ja se ilmottaa kaikki merkit eikä hermostu miinus-merkistä, plus-merkistä eikä desimaalipisteestä - mutta isommalla matriisilla aikaa alkaa kulua paljon:
Koodia: [Valitse]
function tarkista_matriisi () { apu=${@//[-+.,0-9]/}; [[ $apu > ' *' ]] && echo löytyi seuraavat merkit:$apu ;}; matriisi=({-500..+500}); matriisi[5000]=7A7e; matriisi[2]=c; time tarkista_matriisi ${matriisi[@]}

- koska tekstijono on saman-nimisen matriisin jäsen 0 niin voi näillä tarkistaa tekstijononkin.
- + on monisäikeinen juttu - yleensä sitä ei edes tarvita tuossa: [-+.,0-9]
« Viimeksi muokattu: 17.06.24 - klo:07.37 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Skriptiajuri
« Vastaus #82 : 08.05.24 - klo:14.27 »
Funktioiden kuvaukseen tulee usein uusia huomioita joten funktioiden ohjeistus taitaa olla viisainta kirjoittaa silloin-tällöin uusiksi:
- esimerkiksi nimiparametrit ovat toimneet jo vuosia - ja ne myös poistivat tarpeen parametrien palauttamiseen. Mutta uudet käskyt muuttivat nimiparamtrien käyttämisen aina samaksi - kun siinä oli aikaisemmin paljonkin taiteilua.

Rajoitetaan sana funktio käsittämään vain niitä koodinpätkiä joiden edessä on sana function:
Koodia: [Valitse]
function funktion_nimi () { tässä on ihan normaali skripti ;}
- tulkki ei vaadi sanaa function kirjoitettavaksi sillä tulkki käsittää ilmankin mistä on kyse mutta ihmisten takia on viisainta kirjoittaa sana: function.
- se normaali skripti voi olla kirjoitettu yhteen tai useampaan riviin.
- kun skriptin koodissa on missähyvänsä sana joka on sama kuin jonkun funktion nimi niin mikäli tulkki ei keksi sille sanalle mitään muuta tehtävää niin silloin se on funktiokutsu. Tulkki vaihtaa kutsun ja sen perässäolevien muuttujien paikalle funktion koodin ja toimittaa muuttujat tähän siirtämäänsä funktioon. Muuttamatta pilkkuakaan kutsun kummallakaan puolella. Muuttujia ei tarvitse olla yhtään tai niitä voi olla vaikka kuinkamonta. Muuttujan paikalla voi olla myös vakio.
- kun päätteessä määritellään funktio niin jää se päätteen muistiin niin pitkäksi aikaa kun päätettä ei sammuteta. Mutta sammuttamisen yhteydessä kaikki katoaa peruuttamattomasti.
- funktion tarkoituksena on käsitellä muuttujia jotka funktioon on siirretty - siirrettyjä muuttujia kutsutaan parametreiksi.
- muttujasta voidaan siirtää sen arvot tai nimi - puhutaan arvoparametreista ja nimiparametreista. Pahantahtoiset ihmiset ovat kehittäneet tarun ettei BASH nimiparametreja tunne mutta se on legendaa - tosin BASH:issa nimiparametrit täytyy käsitellä toisellatavalla kuin muissa kielissä.
- arvoparametreja käytettässä voidaan siirtää vain yksi matriisi ja sen täytyy olla viimeisenä. Nimipaametreja käytettäessä nimisssä saa olla vaikka kuinkamonta matriisia eikä nimien sijaintipikallakaan ole väliä.
- sen merkiksi että parametreja saattaa funktioon tulla kirjoitetaan funktiomäärittelyyn () - näiden sulkujen väliin ei koskaan kirjoiteta mitään. Nuo sulut kannattaa kirjoittaa vaikka funktioon ei siirrettäisi yhtään parametria.
- funktiossa siihen siirretyillä parametreilla ei ole nimeä vaan vain siirtonumero - siis $1, $2 ...$n.
- $0 on funktion nimi.
- parametrien numerointi alkaa aina ykkösestä ja kasvaa parametrien välillä aina yhdellä. Toisaalta vaikka oisi siirretty muuttujanimi ei siitäkään funktiossa puhuta nimenä vaan numerona - ihmiselle vähän sotkuinen homma mutta koneelle ilmiselvä.
- parametri voi olla myös laskukaava tai funktiokutsu (= jonkun funktion nimi - myös funktion oma nimi kelpaa) - harvoin jotain muuta vaikka mahdollista se on.
- jokainen funktio vo kutsua nollaa tai useampaa ali-funktiota. Matemaattisilla funktioilla on yleensä useampia muiden funktioiden kanssa yhteisiä ali-funktioita mutta tekstinkäsittely tehtävillä usein ei ole yhtään - tosiaan paremmin pärjää kun ei jaa toimintoa funktioihin.
- tulkille on ihan selvää mikä kaikki funktiokutsun perässäoleva tauhka kuuluu parametreihin mutta tottumattomalle ihmiselle se ei ole aina selvää.
- parametrien lukumärää ei kirjoiteta mihinkään sillä tulkki haluaa laskea parametrien lukumäärän itse. Parametreja ei tarvitse olla välttämättä yhtään ja toisaalta niitä voi olla miljoona.
- nimiparametreja käytettäessä matriisejakin voi olla parametrien seassa useampia ja ne voivat sijaita misähyvänsä parametrien luettelossa - arvoparametreillahan matriiseja voi sirtää vain yhden ja sen tulee sijaita viimeisenä prametriluettelosa.
- funktioiden kutsuessa toisia funktiota täytyy kutsuista ylläpitää kutsupinoa - jotta virheen sattuessa tiedettäisiin mitä reittiä virheeseen on päädytty ja mikäli virhettä ei ole tapahtunut niin tiedettäisiin mihin pitää mistäkin palata - pinot toimivat automaattisesti eikä niihin käyttäjän tarvitse puuttua.

Funktiota ktsuttaessa BASH ei siirrä toimintaa funktioon vaan hakee funktion sinne missä toimitaan - siksi parametrien palauttaminenkin on turhaa.

Funktioiden pitää olla kirjastossa eikä sotkemassa skriptiään - kirjastojen olemassaolo onkin yksi BASH-kielen peruspilareista. Mutta koska BASH:ista halutaaan eroon niin sen kirjasto-osoitin poistettiin mikä tekee kirjastojen käyttämiestä hankalaa. Samalla alettiin mustamaalata funktioiden käyttöä. Mutta ennen kirjasto-osoittimen poistamista kerkisi nettiinkin tulla muutamia kirjastoja - siellä ne nyt kituvat unohdettuina.

Matikka kärsii kirjastojen keinotekoisesta puutteesta paljon mutta tekstinkäsittely vain vähän - ja matematiikkahan haluttiin estää kokonaan sillä sehän on valmisohjelmien reviiriä.

Tekstinkäsittely kärsii sekin vähän. Ja tekstinkäsittelyä opetetaan suorittamaan väärillä komennoilla: se hidastaa toimintaa ja aiheuttaa runsaasti epämääräisyyksiä. Lisäksi sitä vähättellään mahdottomasti mutta esimerkiksi BASH:in hakufunktiot toimivat aivan toisessa ulottuvuudessa kuin google tai mikään muukaan. Samoin oman koneen hoitamisen mahdollisuus on ensiluokkainen. Verkkotoiminnotkin ovat hyvät. Ainoa ongelma on se että kaikki on tehtävä itse, valmiita ohjelma-kokonaisuuksia on runsaastikin mutta niitä vartioidaan mustasukkaisesti - vallitseva mielipide tuntuu olevan: koko Linux saa mieluummin mennä turmioon kuin toiset käyttää töitäni. Ja nyt kaikki on hapantunut pilalle eivätkä loistavien töiden tekijät enää kehtaa töitään esittääkään.

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Skriptiajuri
« Vastaus #83 : 09.05.24 - klo:17.28 »
BASh ei toimi niin että se palauttaisi funktioista jotakin. Tätä virtuoosit jaksavat jauhaa ikäänkuin se olisi jotenkin huono asia - mutta hölmöä puhetta se on sillä arvoparametreja ei edes voi palauttaa vaan se on yksinomaan nimiparametreille mahdollista. Ja virtuoosit eivät tunnusta BASH:in nimiparametreja tuntevankaan.

Mutta BASH:issa nimiparametritkaan eivät palauta mitään - valitettavasti niitä ei voi yksinkertaisin keinoin tulostaakaan vaan niillä voi ainoastaan muuttaa muuttujia (siis BASH:in funktiot ovat WriteOnlyMemory - muuttujien arvot voi tulostaa vasta pääohjelmassa). Sentakia BASH:issa tulostusta on tarkoitus tehdä yksinomaan pääohjelmassa ja muuttujien muutokset suoritetaan funktioissa.

Kerrottuna toisella tavalla: parametrien palauttaminen ei ole päämäärä. Päämäärä on se että samaa funktiota voitaisiin kutsua mistävaan, minkänimisillä_ja_tyyppisillä muuttujilla vaan ja funktio muuttaisi oikeat muuttujat. Mikäli muutos voidaan tehdä palauttamatta parametreja niin sehän on loistavaa. Mutta ei suinkaan ole sattumaa että BASH toimii näin - toiminto on aikaansaatu monien miestyövuosien tuloksena - ja sitten toiset heitivät toiminnon roskakoriin - hölmöyttään vai roistomaisuuttaan? Jokatapauksessa toiminta on seuraavanlaista:

Numeromuuttujan kanssa:
Koodia: [Valitse]
function koe () { read<<<$(($1*99)) $1;}; muuttuja=2; koe muuttuja; echo $muuttuja
- matikkamoottori siis hallitsee nimiparametrinkin tulostuksen:
Koodia: [Valitse]
function koe () { echo $(($1+0)) ;}; muuttuja=2; koe muuttuja

Tekstijono-muuttujan kanssa:
Koodia: [Valitse]
function koe () { read<<<"kova koitos" $1 ;}; muuttuja=alkutilanne; koe muuttuja; echo $muuttuja
Numeromuuttujista kootun matriisin kanssa:
Koodia: [Valitse]
function koe () { read<<<$(($1[3]*2)) $1[2];}; matrix=(1 2 3 4 5 6 7 8 9); koe matrix; echo ${matrix[@]}
Tekstijono-muuttujista kootun matriisin kanssa:
Koodia: [Valitse]
function koe () { read<<<$3 $1[$2] ;}; matrix=(yksi kaksi kolme neljä); koe matrix 2 loisto-uutinen; echo ${matrix[@]}

---

numero-muuttujan ja tekstijono-muuttujan raja täsäkin työssä yhtä epämääräinen kuin muutenkin - ja voidaan kirjoittaa:
Koodia: [Valitse]
function kerro9 () {
luku1=$1
luku2=$2
int1=${luku1%%.*};
int2=${luku2%%.*};
apu=$(($int1*$int2))
kokonaisia=${#apu}
tulos=$((${luku1//./}*${luku2//./}))
echo ${tulos:0:$kokonaisia}.${tulos:$kokonaisia} ;}

function koe () { read<<<$(kerro9 $1 11.22) $1;}; muuttuja=2; time koe muuttuja; echo $muuttuja
« Viimeksi muokattu: 11.05.24 - klo:07.02 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Skriptiajuri
« Vastaus #84 : 12.05.24 - klo:22.00 »
Kuinka nimi-parametrin saa toimitettua funktioon ja palautettua funktiosta muutettuna:

- vaikka itseasiassa BASH ei palauta mitään: kaikki kielet pitävät kirjaa muuttujistaan. Muut kielet osaavat päivittää kirjanpitonsa vain pääohjelmassa joten niiden tarvitsee palauttaa parametrinsa funktiosta - mutta BASH osaa päivittää kirjanpitonsa jo funktiossa joten sen ei täydy parametreja palauttaa.
- aluksi esitetään vain lopputulos selittämättä vielä mitään.
- yksinkertaisuuden vuoksi parametreja on tässä vain yksi matriisi.
Koodia: [Valitse]
function koe () { apu1=$(declare -p $1); declare ${apu1:8:2} apu2=${apu1#*=}; apu2=${apu2%\"}; apu2=${apu2#\"}; apu2[2]=""oh hoh""; unset $1; read<<<${apu2[@]} $1;}; matrix=(1 2 3 4 5 6 7 8 9); koe matrix; echo ${matrix[@]}
- vastaava funktio on kaikissa kielissä pitkä hirviö - muut kielet vain saavat inhottavuudet kätkettyä kirjastoihin mutta BASH:ilta se on kielletty. Nopeuteen se ei kumminkaan vaikuta - vaikka eihän BASH ole muutenkaan kovin nopea. 
- tässä funktiossa on hyvin paljon vakio-osia: vain käsittely täytyy muuttaa sellaiseksi kuin käsiteltävä vaatii - tässä sillä paikalla on: apu2[2]="ohhoh". Kaikki muu kopioidaan. Siis esimerkiksi tavallisella muuttujalla funktio on ihan samanlainen lukuunottamatta muuttujan käsittelyä:
Koodia: [Valitse]

function koe () { apu1=$(declare -p $1); declare ${apu1:8:2} apu2=${apu1#*=}; apu2=${apu2%\"}; apu2=${apu2#\"}; apu2="oikea arvo on: "$(($apu2*2)); unset $1; read<<<${apu2[@]} $1;}; muuttuja=57; koe muuttuja; echo $muuttuja


Yksi asia on varma: jos virtuoosit sanovat joukolla jotakin mahdottomaksi ei yksikään järjissään oleva ihminen tee vakavia yrityksiä sen valheeksi osoittamiseksi. 

Tässä on kymmeniä todella edistyneitä yksityiskohtia ja yhdenkin välipalikan puuttuminen romuttaisi kokonaisuuden täysin, elikä kokonaisuus on varmasti tarkoin suunniteltu. Miksi ponnistelut on valheilla tuhottu? Kunnia menisi väärälle henkilölle?

- käskysarjat ovat todella pitkiä - mutta ne ovat aina samanlaisia joten ei niitä kirjoiteta vaan kopioidaan jostakin. Kirjastot on tehty jotta koodatessa kopiointi onnistuisi yhdellä sanalla eikä tarvitsisi leikata-liimata pitkää litaniaa - mutta kirjastojen 'käyttökielto' pakottaa tuohon leikkaamiseen-liimaamiseen ja monen sivun skripteihin?
« Viimeksi muokattu: 14.05.24 - klo:18.18 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Skriptiajuri
« Vastaus #85 : 15.05.24 - klo:08.42 »
Sain BASH:in funktio-parametrien siirrosta tehtyä uuden version:
- näiden 'ei BASH osaa' menetelmien esittäminen on päättymätön suo sillä uusia huomioita tulee kokoajan joten dokumentin valmistuttua se on yleensä jo aikalailla vanhentunut.

1. Alkeellisin tapa toimittaa parametrit funktioon on lähettää sinne funktioon parametrien arvot. Yksinomaan tätä tapaa käytetään nykyään. Pahimmat ongelmat niiden kanssa ovat ne että niiden avulla voi funktioon siirtää vain yhden matriisin jonka tulee lisäksi sijaita parametrilistan lopussa, parametrinumeroiden päätteleminen on taidetta kun funktiosta siirrytään toiseen funktioon ja lopuksi se ettei arvo-parametreja voi 'palauttaa'.
 
2. Kehittyneempi tapa on lähettää parametreista funktioon ainoastaan nimet. Kun BASH:issa funktioon tulee nimi ei sillä ole saman-nimisen muuttujan kanssa mitään tekemistä vaan se on ainoastaan tekstijono - tästä eteenpäin ei ole päästy ja niinpä on luultu että nimiparametrit eivät BASH:issa toimi - tai taas kertaalleen: BASH:ista halutaan päästä eroon ja .....

Muissa kielissä tulkissa/kääntäjässä on automatiikka nimen yhdistämiseksi saman-nimisen muuttujan arvoihin - BASH:in tulkista tuo automatiikka puuttuu ja se täytyy itse lisätä. Esimerkiksi skriptissä joka etsii matriisista sen suurinta tai pienintä arvoa tuo automatiikka on: apu=$(declare $1). Tarkistetaan samalla että arvo-parametreilla tulee sama tulos kuin nimi-parametreillakin (kaikki kolme lausetta voi kopioida-liimata yhdellä kerralla sinne päätteeseen).
- tämä yksikertainen keino on kyllä hieman likainen, sillä matriisiin täytyy liittää ylimääräinen jäsen sillä funktiossa käsky: apu=$(declare $1) jättää ensimmäisen jäsenen pois ja sen kykenisi korjaamaan vain Chet Ramey, BASH:in kehittäjä. 

Esimerkiksi matriisin tai tekstijonon maksimin tai minimin löytävä funktio:
Koodia: [Valitse]
apu=(¤ {1..9}) # tämä käsky muodostaa testattavan matriisin.
function haemaksimi () { echo -n "nimiparametrilla:  "; apu=$(declare $1) ; sort -g <(printf "%s\n" "${apu[@]}") | sed '/^$/d' | head -1 ;}; time haemaksimi apu
tarkistetaan että se silti laskee oikein:
Koodia: [Valitse]
apu=(¤ 5 4 3 2 1 2 3  5 6) # tämä käsky muodostaa testattavan matriisin.
function haemaksimi () { echo -n "nimiparametrilla:  "; apu=$(declare $1) ; sort -g <(printf "%s\n" "${apu[@]}") | sed '/^$/d' | head -1 ;}; time haemaksimi apu
isommilla matriiseilla skripteistä tulee esimerkiksi seuraavanlaisia:
Koodia: [Valitse]
apu=(¤ $(awk -v n=600000 -v seed="$RANDOM" 'BEGIN { srand(seed); for (i=0; i<n; ++i) printf("%.14f\n", rand()*10000000000) }')) # tämä käsky muodostaa testattavan matriisin.
function haemaksimi () { echo -n "arvoparametreilla: "; sort -g <( printf "%s\n" "$@") | sed '/^¤$/d' | head -1 ;}; time haemaksimi ${apu[@]}
function haemaksimi () { echo -n "nimiparametrilla:  "; apu=$(declare $1) ; sort -g <(printf "%s\n" "${apu[@]}") | sed '/^$/d' | head -1 ;}; time haemaksimi apu
Toinen tarkistus täysin erityyppisellä datalla - nyt kylläkin aakkostetaan (muuten vaikka dataa muodostetaan vähemmän kestää se silti paljon kaummin):
Koodia: [Valitse]
apu[0]=¤; for n in {1..1000}; do apu[$n]=$(cat /dev/urandom | base64 | head -c 100) ; done
function haemaksimi () { echo -n "arvoparametreilla: "; sort -g <( printf "%s\n" "$@") | sed '/^¤$/d' | tail -1 ;}; time haemaksimi ${apu[@]}
function haemaksimi () { echo -n "nimi-parametrilla: "; apu1=$(declare -p $1); declare ${apu1:8:2} apu2=${apu1#*=}; sort -g <(printf "%s\n" "${apu[@]}") | tail -1 ;}; time haemaksimi apu

Pidempi ja kaksikertaa hitaampi mutta moitteettomampi menetelmä on seuraavanlainen:
Koodia: [Valitse]
function koe () { apu1=$(declare -p $1); declare ${apu1:8:2} apu2=${apu1#*=}; echo ${apu2[@]} ;}; matrix=(1 2 3 4 5 6 7 8 9);  mitrix=(7.7 joo); koe matrix; koe mitrix; koe matrix

Nimiparametrit vaativat lisää koodia jokaista siirrettyä parametria kohti - mutta nimi-parametrejä voi käyttää vain kun se on välttämätöntä ja muuten käytetään arvo-parametreja  (siis niitä voi käyttää sekaisin - eikä suoritus-ajoissakaan ole yleensä isoa eroa). 
- sama funktio löytää sekä maksimin että minimin. maksimi on: tail -1    ja minini on: head -1.
- ilmoitettu aika on funktiossa kulunut aika - se pitkä aika kuluu testi-matriisin muodostamiseen.
- kun funktio määritellään uudestaan vaikka samassa skriptissä korvaa uusi määrittely välittömästi sen vanhan. Se täytyy takistaa erikseen että uusikin funktio-määrittely onnistuu, muuten tulee hiljainen virhe - joka yleensä karjuu inhottavasti jotain ihan ihmeellistä.
- dokumentointi on tärkeämpää kuin itse koodi. Mutta näiden 'ei BASH osaa'-skripteillä ennenkuin dokumentoidaan mitään niin täytyy osoittaa että kyllä osaa ja vasta kauan senjälkeen tilanteen hieman rauhoituttua tulee dokumentoinin vuoro.
- kunnollisessa dokumentoinnissa on valtava työ - paljon-paljon suurempi kuin koodin tekemisessä. Ja miksi dokumentoida jotakin jota on vääristelty 30 vuotta ja joka sentakia kehittyy nyt niin nopeasti että koodi muuttuu päivittäin hieman erilaiseksi kuin ennen - joten kun saa dokumentoinnin kuntoon niin uusi koodi-versio valmistuu välittömästi jolloin vanha dokumentointi lentää roskikseen ja aletaan tekemään uutta dokumentointia. Joten vain harvoin pääsee koodaamaan kun melkein aina tekee dokumenttia - joka yleensä lentää roskikseen jo ennen kuin homma toimii ihan kunnolla.
- kyllä jonkinsortin dokumentti täytyy tehdä - mutta se on vain itselle omien ajatuksien selventämiseksi. Se jonkinlainen omaan käyttöön tarkoitettu dokumentti funktiosta: 
Koodia: [Valitse]
function koe () { apu1=$(declare -p $1); declare ${apu1:8:2} apu2=${apu1#*=}; echo ${apu2[@]} ;}

- apu1=$(declare -p $1) laittaa apu1:n arvoksi esimerkiksi: declare -a matrix=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9")
 Siitä saa kunnollisen matriisin toteuttamalla sen. Toteuttaminen tapahtuu declare-käskyllä. Mutta declare täytyy kirjoittaa sillä apu1:ssä oleva sana: declare ei kelpaa. Tuo -a täytyy laittaa mukaan (ja sen paikalla voi lukea 'vaikka mitä' joten sen paikalla oleva täytyy noukkia tulosteesta). Lisäksi täytyy laittaa sanan matrix tilalle sana: apu2.
« Viimeksi muokattu: 28.07.24 - klo:13.23 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Skriptiajuri
« Vastaus #86 : 17.05.24 - klo:07.07 »
Matriisit ovat BASH:issa vähän omituisia. Esimerkiksi matriisin: jokunimi=(1 2 3 4 5 6 7 8 9) kuvaus on: 
Koodia: [Valitse]
declare -a jokunimi=([0]="1" [1]="2" [2]="8" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9")
Siitä erotetaan tekstijono joka kuvaa muuttuja arvoja: ([0]="1" [1]="2" [2]="8" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9"). Tekstijono on nykykäsityksen mukaan kummallinen koska on totuttu siihen että matriisin jäsenistä ilmoitetaan vain niiden arvo - mutta tosiasiassa matriisin jokaisella jäsenellä on sekä arvo että osoite. Muitten kielien matriisit vaan ovat sellaisia että niiden osoitteet alkavat aina ykkösestä ja kasvavat yhdellä seuraavalla jäsenellä joten osoite on sama kun jäsenen järjestysluku. Mutta BASH:issa ei ole näin vaan matriisin jokaisella osoitteella voi olla ihan mikä positiivinen arvo tahansa joten sekä arvoa että osoitetta täytyy raahata perässään - kuvatulla tavalla muodostetuilla matriiseilla on erinomaisia lisä-ominaisuuksia mutta noiden lisä-ominaisuuksen soveltaminen käytäntöön on ihmisille niin vaikeaa käsittää että käytännössä niitä ei nykyään enää sovelleta - mutta jokatapauksessa tässä toiminta on otettu huomioon eikä rikottu BASH:in matriisien rakennetta.
- joten luetaan matriisin kuvaus muuttujaan apu1. 
- muuttujan apu2 muodostamiseksi annetaan käsky:
Koodia: [Valitse]

declare ${apu1:8:2} apu2=$tekstijono

- tämänjälkeen apu2 on klooni alkuperäisestä muuttujasta - muuten paitsi nimi on apu2 - siis sillä on sama tyyppi ja samat arvot olipa se numero- tai tekstimuuttuja, matriisi tai assosiatiivinen matriisi.


Jälleen selitystä:
käsky:declare täytyy kirjoittaa itse, se ei kopiosta toimi - muu osa kuvauksesta toimii kopiosta mutta muuttujanimi täytyy muuttaa. Koska tuon -a paikalla on muuttujan määreet niin sillä paikalla lukee milloin mitäkin, mutta koska se on aina samalla paikalla niin sillä paikalla oleva kopioidaan - se on tuo ${apu1:8:2}
- muuttujan määreitä ovat: onko kyseessä tavallinen muuttuja vaiko matriisi - tai assosiatiivinen matriisi - ja ovatko parametrien alkiot read-only, integer ...   
- jokaista skriptiin lähetettävää muuttujaa kohden täytyy muodostaa oma apu1 ja apu2, apu3 ja apu4, apu5 ja apu6 ... . Ensimmäisen muuttujan nimi on apu2. Seuraavista muuttujista tulisi apu4, apu6, apu8 .... Muuttujat: apu1, apu3, apu5 ... ovat vain väliaikaisia talletuspaikkoja.
- matriiseja saa nimiparametreissa olla kuinkamonta vaan ja ne voivat sijaita kutsussa missävaan.

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Skriptiajuri
« Vastaus #87 : 18.05.24 - klo:08.56 »
Muuttujien arvot on nyt siirretty funktioon - ja millä nimellä ne ovat funktioon tulleetkin niin funktion sisällä niillä on aina sama nimi joten niitä voidaan käsitellä tulonimestä riippumatta aina samoilla käskyillä - muutokset tulevat aina sille 'samalle nimelle'.

Ennenkuin skriptistä palataan nuo 'aina saman nimiset' muuttujat kopioidaan takaisin niihin parametreina tulleisiin muuttujanimiin - käskyllä:
Koodia: [Valitse]
unset $1; read<<<${apu2[@]} $1
- tässä on uutta vain tuo 'unset $1' - se tarkoitaa: nollaa $1 - koska $1:ssä on se vanha arvo ja jos sen lisäksi kirjoitettaisiin muuttunut arvo niin se alkuperäinen tulisi tulosteessa sen muutetun jälkeen.
- muuten: jokainen muuttuja on sama kuin saman-niminen matriisi jonka osoitteessa 0 on muuttujan arvo - siksi käsky:
Koodia: [Valitse]
read<<<${apu2[@]} $1 
toimii tavallisellakin muuttujalla.

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Skriptiajuri
« Vastaus #88 : 20.05.24 - klo:18.50 »
Väitetään ettei BASH:issa voi palauttaa parametreja. Virtuoosit esittivät lisäksi että lopulta tämä johtuu siitä ettei $x=jotakin onnistu silloinkaan kun $x:ssä on nimi.

Totta molemmat väitteet - ei onnistukaan. Mutta se mitä loppuksi halutaan on pystyä tekemään funktio jota voi kutsua samassa skriptissä eri muuttujilla ja funktio muuttaa aina oikeaa muuttujaa.

Toiminta jota $x=jotakin yrittää voidaan kuitenkin saavuttaa monilla muillakin tavoilla - painotus kohdassa monilla. Esimerkiksi:
Koodia: [Valitse]
function etsi () { sana_matriisina=(${@:4}); read<<<${sana_matriisina[$2]} $1 ;}
mutta se voidaan kirjoittaa myös:
Koodia: [Valitse]
function etsi () { sana_matriisina=(${@:4}); export $1=${sana_matriisina[$2]} ;}
tai myös:
Koodia: [Valitse]
function etsi () { sana_matriisina=(${@:4}); declare -gx $1=${sana_matriisina[$2]} ;}
ja kaikilla niillä on omat erikois-ominaisuutensa.

Tarkistaa nuo funktiot voi käskyllä - vaihtaen tuota ensimmäistä numeroa joka tässä on 1:
Koodia: [Valitse]
etsi sana 1 lauseesta "9a 87b 654c 3(21)0d'ille!"; echo $sana
- nuo valittavien ympärillä olevat "" heittomerkit tarvitaan siksi että sanassa 3(21)0d'ille! on merkkejä jotka eivät muuten kelpaisi: ( ja ) ja ' ja ! . Jos niitä ei ole voi heittomerkit jättää pois.
- ja toiminnaltaan funktiot ovat tosi nopeita.
- read<<< voidaan kirjoittaa myös let mutta silloin valittavat voivat olla vain hyväksyttäviä kokonaislukuja joten parasta unohtaa se.
- muuten kun huomattiin että tuon read<<< paikalla toimii myös käsky: eval niin kiirehdittiin vakuuttamaan että eval on BASH:issa pahis - sillä noita toisia ei tunnettu joten sehän oli ensimmäinen joka tavallaan palauttaa parametrin ja olisi liian noloa myöntää että toimiihan se parametrin palautus.

***

Vanhoilla menetelmillä skriptauksen tuottavuus on niin alhainen että kesti kauan huomata ettei BASH:illa ole mitään rajoitteita - tai siis tavallaan on mutta kaikille puutteille löytyy useita keinoja kiertää ne - siis kieltämättä BASH on hankala kieli koska joutuu pähkäilemään kuinka mikin pitää koodata sillä varsinaisesti kyse ei ole kiertämisestä vaan siitä ettei BASH:issa ole automatiikkaa juuri millekään vaan kaikki on tehtävä itse. Koodi on kylläkin kummallisen oloista sillä se on sellaista kuin BASH aikoinaan oli.

Seuraavilla ohjeilla skriptaamisen tuottavuus nousee moni-kymmenkertaiseksi ja sehän se on joka merkitsee:

Kaikki skriptit kirjoitetaan samaan teksti-tiedostoon laittaen joku selvä erote skriptien otsikko-koodi-ja-koodin-dokumentointi kokonaisuuksien väliin. Vaikka tekisi kokopäiväisesti skriptejä kymmenen vuotta niin yksi tiedosto riittää useimmille sittenkin. Lausetta: #!/bin/bash ei missään nimessä saa kirjoittaa koskaan eikä hakea suoritusoikeuksia.

Skriptin saat käyttöösi kun luet tuon ison tiedoston johonkin editoriin ja leikkaamalla tarkoittamasi skriptin koodin - ja toteutettua sen saat kun liimaat leikkaamasi koodin päätteeseen ja painat return. Tai surffatessasi netissä kun kohtaat jollain verkkosivulla mielenkiintoisen skriptin voit kokeilla toimiiko se leikkaamalla nettisivulta sen koodin, liimaamalla se päätteeseen ja painamalla return - se ei ole tekijä-oikeuden loukkaamista - skriptin siirtäminen sellaisenaan omaan tiedostoosi saattaa joskus ollakin varsinkin jos et kerro lähdettä. 

Mikäli et muista missä skriptisi siinä isossa tiedostossa on niin käytössäsi on linuxin erinomaiset hakutyökalut - googlea ei edes voi käyttää mutta jos voisi niin se ei toimisi oikein sillä se löytäisi vain uusia käskyjä ja vanhoja käskyjä se löytää vain vahingossa - ja vanhat käskyt soveltuvat tiedonkäsittelyyn kertaluokkia paremmin kuin uudet sillä uudet käsyt on tarkoitettu tiedostojen käsittelyyn - elikä vanhat käskyt toimivat RAM:min kanssaja uudetkäskyt toimivat kovalevyn kanssa. Tuo iso tiedosto luetaan johonkin editoriin ja jokainen editori tuntee käskyn ctrl-f ja jo se on paljon parempi kuin google sillä se löytää mitävaan mitä tiedostossa on (esimerkiksi ##*$ ) ja lisäksi nopeasti. Mutta parempiakin haku-työkaluja kuuluu jo BASH:in käskyihinkin ja ulkoisia löytyy enemmän kuin jaksat koota tai edes lukea niistä kaikista - ja ne etsivät kirjaimellisesti mistävaan mikä alueeksi määrätään - semmoinenkin hakumoottori löytyy joka sallii sen että etsittävässä on kirjoitus virheitä.

---
 
Päätteessä on yksi skripti jo avatessasi sen: se on pääte itse ja kun kirjoitat tekstiä päätteeseen ei tarvitse vakuutella että se on BASH-koodia ja tarkoitus olisi ajaa se kun return:ia painetaan - pääte nimittäin olettaa niin ellei erikseen toisin käsketä - se että pääte on samalla myös pääte-ohjelma ei kuulu tähän.

BASH:in muuttujien näkyvyys-säännöistä vähäsen: pääte on skriptien isä ja niistä skripteistä jotka teet itse tulee sen lapsia - ja tekemäsi funktiot ovat pääohjelmasi lapsia. Ja BASH:issa lapset perivät isältään kaiken - mutta isä ei peri lapsiltaan noinvaan mitään vaan lapsen täytyy myöntää isälleen muuttujakohtaisesti perimis-oikeus käskyllä: export muuttujan_nimi - tai: declare -gx muuttujan_nimi - tai: käskyn etumääreellä eval - tämänjälkeen muuttuja tunnetaan skriptissä kaikkialla eikä sen  palauttamisessa olisi mitään mieltä sillä muuttuja on muuttunut jo.
- siis funktiossa oltaessa perimisoikeus myönnetään isälle joka yksinkertaisessa tapauksessa on päähjelma joten muuttuja tunnetaan senjälkeen kaikkialla skriptissä, myös toisissa funktioissa.
- siis ohjelmallisesti ei voi määrätä:  $x=jotakin edes funktiossa oltaessa vaikka $x:ssä olisi nimi. Täytyy tosiaan kirjoittaa: muuttujan_nimi=jotakin. Tai määrätä isälle perimis-oikeus.
- tästäsyystä BASH-skriptissäei ole lopussa: END - se on isän hommaa ekä lasten

---

Päätteellä on ilmanmuuta suoritusoikeus ja shebang ( esimerkiksi #!/bin/bash ) joten niitä ei saa itse enää antaa - shebang täytyy antaa vain mikäli BASH:in sijasta ajetaan jotain muuta skriptikieltä. Mutta itseasiassa koko shebang on salahautojen viidakko.
- ei tarvitse välittää siitä että ubuntun BASH on kuulemma DASH.
- jos saat kasattua skriptin jolla on todellista käyttöäkin niin sitten siitä kannattaa tehdä siitä skripti sillä vanhalla ja vaivalloisella tavalla - sillä on sillä etujakin - jos on haittojakin. Minä olen onnistunut vain parikertaa tekemään sellaisen skriptin ja onhan näitä skriptejä jo tuhansia - tosi-isojakin. Ja niistäkin ainoa skripti jolla on päivittäin käyttöä on skriptiajuri, muut ovat näytös-luonteisia.
« Viimeksi muokattu: 28.08.24 - klo:18.50 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Skriptiajuri
« Vastaus #89 : 29.05.24 - klo:14.11 »
Silloin kun BASH luotiin sen käskyt tulkattiin muistista ja ne olivat nopeita - ihan niinkuin muissakin kielissä.
Nykyiset käskyt luetaan levyltä ennen tulkkaamista - epäilen kylläkin että ne ovat jo käännettyä koodia eikä niissä enää mitään tulkkaamista ole - mutta levyn lukeminen niissä kestää.

Ei sitä yksiomaan tuomita voi sillä on sillä paljon hyviäkin seurauksia. Mutta esitetäänpä yks esimerkki huonoista puolista: erotetaan tekstijononsta joku määräätävä sana. Totunnaiseti aikaansaadaan jotakin seuraavankaltaista:
Koodia: [Valitse]
echo '9 87 654 3210' | awk '{ print $3 }'
# tai:
awk '{ print $3 }' <(echo '9 87 654 3210')
#tai:
echo '9 87 654 3210' | cut -d' ' -f3
# tai:
cut -d' ' -f3 <(echo '9 87 654 3210')
- awk on tosi nopea ja monipuolinen - ja tämä awk:ille epä-edullinen esimerkki. Muuten cut on nopeampi.

Nopeilla käskyillä se on esimerkiksi (tämmöisiäkin tapoja on ziljoonia täysin erilaisia ja kullakin on omat erikoisominaisuutensa - mutta kaikki ne ovat nopeita):
Koodia: [Valitse]
function tavu () { apu=$1; set ${@:2} ; eval echo \$$apu ;}
tavu 3 "9a 87b 654c 3210d"
Nopeusero on niin suuri ettei sitä saa kunnolla  mitattua millään konstilla. Yksi perusteltu arvio on: 50* nopeus.

---

Tein funktiosta version jonka toiminta-nopeus on sama mutta se toimii ikäänkuin parametri palautettaisiin eikä tulosta tarvitse pää-ohjelmassa lukea rakenteella $(kutsu). Skripti ja varsinkin sen kutsu ovat niin selväpiirteisiä että on käsittämätöntä ettei sellaisia käytännössä näy:
Koodia: [Valitse]
function etsi () { sana_matriisina=(${@:4}); read<<<${sana_matriisina[$2]} $1 ;}
etsi sana 3 lauseesta "9a 87b 654c 3(21)0d'ille!"
echo $sana # tämä echo on ihan vain varmistuksena että skripti on tehnyt sen mitä pitäisikin.
- heittomerkit voivat olla kovia, pehmeitä tai ne voivat puuttua kokonaan.
- etsintä  voi alkaa edestä tai takaa - suunta riippuu etsintä-numeron merkistä. Toiminnan nopeuteen se ei kuitenkaan vaikuta.
- etsittävässä saa olla suurinpiirtein mitätahansa merkkejä - paitsi samanlaisia heittomerkkejä kuin alussa ja lopussa. Nykymuotisessa BASH:issa ei saa.

- Puhutaan vain ettei BASH osaa palauttaa funktioista parametreja ihan niinkuin se merkitsisi BASH:issa jotakin huonoa - päinvastoin se tarkoittaa hyvää. Jos esimerkiksi skriptissä on rivi:
etsi sana 3 lauseesta "9a 87b 654c 3(21)0d'ille!"
niin jos funktio:etsi on oikein tehty niin seuraavalla rivillä muuttujalla:sana on oikea arvo - eikä varmasti ole palautettu mitään - nyt voi vedota siihen ettei BASH osaa. Paikalle:sana voi kirjoittaa minkä tekstin hyvänsä ja seuraavalla rivillä se on saman-niminen muuttuja. Voi se alunperinkin olla muuttuja jonka arvo on aikaisemmin ollut mitävaan tai peräti ollut toisentyyppinen.

- suurta nopeutta ei voi mitata suoraan - eikä se oikein kannattaisikaan sillä skriptin suoritus-aika vaihtelee aina vähäsen - mutta yksi ratkaisu on muodostaa keskiarvoa sillä antaahan se ainakin jonkinlaisen ajan:
Koodia: [Valitse]
function etsi () { sana_matriisina=(${@:4}); read<<<${sana_matriisina[$2]} $1 ;}
time { for n in {1..100000}; do etsi sana 3 lauseesta "9a 87b 654c 3(21)0d'ille!"; done ;}; echo $sana
- tämmöisessä looppi-virityksessä pitäisi laskea loopin vaikutus pois - mutta sen arvoksi voi olettaa tässätapauksessa 10% - mitattu on.
- välimuisti sotkee oikeaa tulosta, mutta pakkohan keskiarvoa on muodostaa koska tuollaista nopeutta ei voi mitata mitenkään toisin - eikä se välimuisti nopeuta sen enempää kuin 50%. Eikä se edes vaikuta täysmääräisenä heti ensimmäisellä kerralla. Kokeile. Sen kokeileminen tosin edellyttää ziljoonaa skripiä ja viikkojen pähkälyjä.

- mikäli sanoja erottaa joku muu merkki kuin välilyönti voi sen määrätä - seuraavassa sana-välinä on \n  (elikä rivinsiirto). Mutta voi se tekstiäkin olla. Skripti ja sen kutsu ovat silloin:
Koodia: [Valitse]
function etsi () { apu=${@:4}; sana_matriisina=(${apu//$5/ }); read<<<${sana_matriisina[$2]} $1 ;}
etsi sana 3 lauseesta "9a\n87\n654c\n3(21)0d'ille!" \n ; echo $sana
- jos raakaa nopeutta haluaa niin tässä on 20 mikrosekuntia kestävä versio, mutta muita ominaisuuksia sillä ei montaakaan olet:
Koodia: [Valitse]

function hae_tavu () { sana_matriisina=(${@:2}); echo ${sana_matriisina[$1]} ;}
hae_tavu 3 "9a 87b 654c 3(21)0d'ille!"

---

Onhan niitä parempiakin funktioita kuin seuraava mutta kannattaa esittää sillä se kertoo että vanhat käskyt osaavat toimia regex:ien kanssa ihan suoraan: kyseessä on skriptauksen 'ikuisuus-ongelman ratkaiseminen' - erottaa jonkun laitteen tulosteesta haluttu lukema:
- tulosteen ei saa olettaa noudattavan minkäänlaista rakennetta - täytyy varautua siihen että siellä voi halutun tekstin lisäksi olla vaikka mitä:
Koodia: [Valitse]
function etsi_arvo () { apu=${1#*$2}; apu=${apu%%[!-+.0-9]*}; [[ $apu ]] || { apu=${1#*$2[[:punct:]]}; apu=${apu%%[!-+.0-9]*} ;}; echo  $apu ;}
etsi_arvo ":::jännite215.61&'virta:-7.25 teho6.25 taajuus:123456.78" virta
- siis funktiokutsun viimeiseksi sanaksi (tässä: jännite) kirjoitetaan se jota haetaan. 
Koodia: [Valitse]
- lmaisu:apu=${1#*$2[[:punct:]]} - ihmiselle on ihan selvää mitä siinä yritetään - mutta on todella ihmeellistä että tulkki osaa tulkata sen oikein.
- lmaisun:apu=${1#*$2[[:punct:]]} voisi kirjoittaa myös: apu=${1#*$2?} 
[/code]

- mutta mitään semmoista ei voi tehdä joka ei kompastuisi johonkin.

---

Vanhojen käskyjen nopeus on tosiaan BASH:iksi tajuton - millisekuntien sijaan aletaan puhua mikrosekunneista. Mutuna sanoisin että vanhojen käskyjen kyvyt ovat kymmenesosa uusien käskyjen kyvyistä mutta koska ne ovat satakertaa nopeampia niin lopputulokseen päästään kymmenenkertaa nopeammin - ja vanhoista käskyistä voi tehdä kokonaisuuksia joiden kykyjä ei rajoita mikään muu kuin se mitä ohjelmoija osaa - ja ryhmä hidastuu vain vähän kun käskyjä tulee ryhmään lisää. 

Ja vaikka vanhoilla käskyillä ei ole kytkimiä niin kyllä niitä mukaella voi -> esimerkiksi seuraava lause erottaa tekstijonosta neljännen tavun ( looppi hidastaa toimintaa suorituskertojen lisäksi noin 10% mutta noin nopeaan välimuistin vaikutus on olemattoman pieni - siis ajat muuttuvat lineaarisesti kun loopin n muuttuu). Kun 1 sekunti jaetaan 100000:lla saadaaan 10 mikrosekuntia:
Koodia: [Valitse]
apu="1 2 3 4 5"; time  for n in {1..100000}; do apu=${apu#* * * }; echo ${apu%% *}; done


- siis käskyssä: apu=${apu#* * * } on '* '-ryhmiä yhtä vähemmän kuin minkä palasen se erottaa
- sen 10% saa määriteltyä käskyllä: apu="1 2 3 4 5"; time  for n in {1..100000}; do : ; done
- tuo käsky : on assemblerissa NOP elikä älä tee mitään. :  kestää noin 3 mikrosekuntia ja echo noin 6 - joten jos echo:n sijaan tulos kirjoitetaan muuttujaan niin nopeus nousee kaksinkertaiseksi, nopeimmillaan 9:ksi mikrosekunniksi.

---
 
Tai toinen tapa jakaa:
Koodia: [Valitse]
apu=6melkein_yhdentekevää_mitä_tässä_on_kunhan_on_jotakin2022; apu=${apu//[!0-9]/' '}; echo ${apu##* }     #-> 2022
apu=6melkein_ yhdentekevää_mitä_tässä_on_kunhan_on_jotakin2022; apu=${apu//[!0-9]/' '}; echo ${apu%% *}  #-> 6
- ^ toimii melkein samoin kuin !
- toki voidaan kirjoittaa myös: [!-+.e0-9] - ei se paljoa hidasta - mutta mitä enemmän tekee lisäyksiä jotta skripti toimisi erikois-tapauksissa oikein niin sitä useammmin kuitenkin karahtaa kiville. Jokatapauksessa tuolla [!-+.e0-9]:llä luvut voivat olla negatiivisia, desimaalilukuja tai tieteellistä esitysmuotoa.
- kyllä tuolla: melkein_yhdentekevää_mitä_tässä_on_kunhan_on_jotakin on rajoituksensa: sen ensimmäinen tai viimeinen merkki ei saa olla numero eivätkä ihan kaikki merkit kelpaa muutenkaan - ja ainakin jotakin siinä täytyy olla, vaikka vain välilyönti. 
- myös edessä tai perässä voi ollamitätahansa tekstiä kunhan siinä ei ole numeroita. Muodostuvat etu- ja perä-välilyönnit vain poistetaan - esimerkiksi
Koodia: [Valitse]
apu=6melkein_yhdentekevää_mitä_tässä_on_kunhan_on_jotakin2022jotain_muuta_tarinaa; apu=${apu//[!0-9]/' '}; apu=${apu%%+( )}; echo ${apu##* }    #-> 2022

***

- kaikki seuraavista ajoista ovat kone kohtaisia - nämä ajat ovat surkeasta läppäristä. Skriptit sensijaan toimivat missä koneessa tahansa.

Time-käskyn 1ms erottelukyky on uusille käskyille ihan riittävä - no melkein ainakin. Mutta vanhat käskyt vaatisivat vähintään 0.1 ms erottelukykyä jotta vanhoista käskyistä muodostettujen funktioiden nopeudesta saisi mielikuvan - ja joskus jopa 0.01 erottelukyky olisi tarpeen. Time-käskyä erottelukykyisempää käskyä ei kuitenkan ole mutta sellaisen voi tehdä itse - mutta se on kone riippuvainen joten time-käskyä kannattaa silti käyttää mikäli se näyttää enemmän kuin 1ms.

Tarkemman erottelukyvyn saa käskyn hetki=$(date +%s%N) avulla - se alkoi laskentansa kauan sitten ja on laskenut alkuhetken jälkeiset kellojaksot joten teoriassa se tietää tapahtumahetken nanosekunnin tarkkuudella - tämä on teoriaa mutta kuitenkin lyhyehkön ajan kuluessa se ei montaa nanosekuntia erehdy. Käyttöjärjestelmä se on kun aiheuttaa BASH:iille niin suuria horjahteluja ettei BASH tiedä kovin pitkään edes oikeaa millisekuntia - mutta 'nanosekunti'-kello pyörii kuitenkin kokoajan ja se on tarkinta mitä PC:sta löytyy.
- siis käskyllä: hetki=$(date +%s%N) BASH kysyy käyttöjärjestelmältä: mikäs nanosekunti on meneillään? Vastauksessa on 20 numeroa mikä riittää kertomaan mikä nanosekunti on meneillään vuodesta 1987 lähtien. Ja paljonko aikaa kysymykseen meneekään niin kahden kysymyksen väli on tarkka 'nanosekunnilleen'.

Ajoitetaanpa tällä menetelmällä nopea skripti:
Koodia: [Valitse]
function kerro9 () {
[[ $1 =~ \. ]] && { apu=${1%%[1-9]*}; apu=${apu##*.}; exp1=${#apu}; luku1=${1//0.$apu/0.} ;} || { apu=${1##*[1-9]}; exp1=-${#apu}; [[ $apu ]] && luku1=${1:0: -$exp1} || luku1=$1 ;}
[[ $2 =~ \. ]] && { apu=${2%%[1-9]*}; apu=${apu##*.}; exp2=${#apu}; luku2=${2//0.$apu/0.} ;} || { apu=${2##*[1-9]}; exp2=-${#apu}; [[ $apu ]] && luku2=${2:0: -$exp2} || luku2=$2 ;}
int1=${luku1%%.*}
int2=${luku2%%.*}
apu2=$(($int1*$int2))
kokonaisia=${#apu2}
tulos=$((10#${luku1//./}*10#${luku2//./}))
tulo=000000000000000000$tulos'00000000000000000000'
tulo=${tulo:0:$((19-$exp1-$exp2))}.${tulo:$((19-$exp1-$exp2))}
apu=${tulo##+(0)}; [[ ${tulo//[^.]/} ]] && apu=${apu%%+(0)}; echo ${apu%.}  ;}

function ajoita () { alku=0; alkuhetki=$(date +%s%N); sleep .1 ; loppuhetki=$(date +%%sN); alkuhetki=$(date +%s%N); $@ ; loppuhetki=$(date +%s%N); aika=$(($loppuhetki-$alkuhetki));  echo "toiminta \"$@\" kesti tälläkerralla: "$aika" ns" ;}

ajoita kerro9 0.000000000000000001234 1.234567809123456
saat ajaksi jotain: 2307981 ns

anna nyt käsky: time kerro9 0.000000000000000001234 1.234567809123456
Saat ajoiksi esimerkiksi:
real   0m0,001s
user 0m0,001s
sys   0m0,000s

Miksi ajat erovat sillä 'nanosekunti' menetelmä on taatusti tarkka - mutta niin on varmasti myös time-käsky. Niiden täytyy siis mitata eri asioiden aika.

Tarkka ajoitus laskee aikaan myös sen ajan joka kestää että BASH alkaa yleensä toimimaan - siis jotta time-käskystä saisi saman ajan täytyisi noita time-käskyn kolmea arvon laskea yhteen jollain painotuksella. Nyt ei kannata luottaa siihen 0.5s arvoon mikä arvoksi on tähänasti arvioitu vaan määritellä se itse. Koska kyse on onnettoman lyhyestä ajasta täytynee se mitata loopissa - se kestää mutta onpahan sitten tarkempi - ja toivonmukaan luotettavampikin:
- ajoitetaan siis käsky : joka kutsuu kyllä tulkkia mutta tulkki palauttaa koodin NOP - älä tee mitään.
Koodia: [Valitse]
alku=0; for n in {1..1000}; do alkuhetki=$(date +%s%N); : ; loppuhetki=$(date +%s%N); alku=$(($alku+$((10#$loppuhetki-10#$alkuhetki)))); echo "keskiarvo $n mittauksesta: $(($alku/$n)) ns"; sleep 1; done
Tulokseksi tulee jotain seuraavan kaltaista: keskiarvo tuhannesta mittauksesta: 1953962 ns. Sriptien ajoituksessa täytyy tämä huomiida korjaksena - mutta korjaustermi ei ole tämä vaan tämän puolikas:
Kun vähennetään: 2307981 - 1953962/2 saadaan: 1331000 - siis noin 1.350ms. Tuntuu oikealta.

---

Viimeajat olen yrittänyt saada kasaan kuvausta nano-sekunti menetelmän eduista mutta se on niin liukas käsiteltävä että tässävaihessa täytyy tyytyä esittämään siitä yksi taulukko:

  käsky         tulos-nano    tulos-time-real
sleep 1              1.002081         1.003
sleep .1             0.101887          .103
sleep .01           0.011309          .012
sleep .001         0.002221          .004
sleep .0001       0.001349          .003
sleep .00001     0.001349          .003

- tottakai totuus ei ole ihan tälläinen vaikka numerot ovatkin ihan oikein. Lisäksi nano:a on helpompi parantaa ja koneiden nopeutuessa se paranee itsekseenkin.

« Viimeksi muokattu: 05.08.24 - klo:17.56 kirjoittanut petteriIII »

nm

  • Käyttäjä
  • Viestejä: 16424
    • Profiili
Vs: Skriptiajuri
« Vastaus #90 : 29.05.24 - klo:14.20 »
Silloin kun BASH luotiin sen käskyt tulkattiin muistista ja ne olivat nopeita - ihan niinkuin muissakin kielissä.
Nykyiset käskyt luetaan levyltä ennen tulkkaamista - epäilen kylläkin että ne ovat jo käännettyä koodia eikä niissä enää mitään tulkkaamista ole - mutta levyn lukeminen niissä kestää.

awk ja cut eivät liity Bashiin, vaan ne ovat erillisiä ohjelmia. Awk:n historia ulottuu alkuperäistä Bourne shelliäkin kauemmas, Bashista puhumattakaan.

Ulkoista komentoa kutsuttaessa aikaa menee aina uuden prosessin käynnistämiseen. Binäärin ja mahdollisten oheiskirjastojen lukeminen kestää toki myös hetken, mutta vain ensimmäisellä kerralla. Sen jälkeen ohjelma on käytännössä valmiina kernelin levyvälimuistissa. Prosessi sen sijaan luodaan aina uudelleen, kun ulkoista komentoa kutsutaan bash-skriptissä.

Näin on aina ollut, eli tilanne on Bashin kannalta sama kuin vuonna 1989.
« Viimeksi muokattu: 29.05.24 - klo:14.25 kirjoittanut nm »