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

petteriIII

  • Käyttäjä
  • Viestejä: 680
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #300 : 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 »

nm

  • Käyttäjä
  • Viestejä: 16344
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #301 : 14.05.23 - klo:19.26 »
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.

Tällaiseen alkulukujen etsintään kannattaa käyttää ensin probabilistista algoritmia, kuten Miller-Rabinia, ja sitten varmentaa löydetyt alkuluvut fastECPP:llä, jonka pätevä toteutus on saatavilla täältä: https://www.multiprecision.org/cm/home.html

173 numeroa on näille menetelmille pienen pieni alkupala. Esimerkiksi GMP:n mpz_nextprime() löytää ehdokkaan 2 millisekunnissa:
Koodia: [Valitse]
./nextprime 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
Lainaus
12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890409

CM:n fastECPP vahvistaa alkuluvun 200 millisekunnissa:

Koodia: [Valitse]
time ecpp -n 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890409
Lainaus
real    0m0.209s
user    0m0.166s
sys     0m0.028s

nextprimen C-koodi:

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

int main (int argc, char* argv[])
{
  double elapsed;
  struct timespec start, finish;
  mpz_t n;
  clock_gettime(CLOCK_MONOTONIC, &start);
  if (argc < 2) {
    printf("usage: nextprime <start_integer>\n");
    return 0;
  }
  mpz_init_set_str(n, argv[1], 10);
  mpz_nextprime(n, n);
  clock_gettime(CLOCK_MONOTONIC, &finish);
  elapsed = finish.tv_sec - start.tv_sec;
  elapsed += (double) (finish.tv_nsec - start.tv_nsec) * 0.000000001;
  gmp_printf("%f %Zd\n", elapsed, n);
  mpz_clear(n);
  return 0;
}

Kääntö:
Koodia: [Valitse]
gcc -Wall -O3 -march=native nextprime.c -lgmp -o nextprime

petteriIII

  • Käyttäjä
  • Viestejä: 680
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #302 : 14.05.23 - klo:22.27 »
Epäilemättä - ja on kiva että kirjoitat mikä muiden taso on sillä se kiinostaa mutta en kerkiä tietoa etsimään.

ja pitää täysin paikkansa että koska BASH:ia ei edes päivitetä kuin kerran vuodessa niin siitä on tullut surkea rääpäle jos se on koskaan varsinaisesti ohjelmointiin sopinutkaan. Ja minä nousen barrikaadeille surkeiden rääpäleiden puolesta - varsinkin kun niitä haukutaan väärin perustein.

Useimmat esittämäni skriptit ovat pelkälle BASH:ille täysin mahdottomia - siis ilman apua bc:ltä, awk:ilta tai joltain muulta. Ja kun kyseessä on jo alkuunsa mahdottomuus niin kuka voi sanoa mihin homma päätyy? Eikö se ole jo aikahyvä barrikaadi? 

Esimerkiksi juuri nyt olen kirjoittamassa logaritmien laskentaa uusiksi. Nopeus nousi yli kymmenkertaiseksi - ja sitäpaitsi se on nimeltäänkin raflaava: logaritmit binäärijärjestelmässä. Vielä uusi kymmenkertaistuminen ja bc alkaa olla vaikeuksissa. Yksinkertaisissa laskuisa se onkin 4 kertaa hitaampi.

***

Logaritmien laskeminen binäärijärjestelmässä:
- tämä skripti ei laske bc:n avulla - siis bc:n voi vaikka pyyhkiä koodista pois skriptin antaman tuloksen muuttumatta.

Logaritmin laskeminen nopeutui 17:sta millisekuntiin. Vaikka se on hidas, on sikäli mielekäs että se näyttää desimaalilaskujen toimivan ja opettaa kuinka logaritmit lasketaan - onhan BASH opetuskieli - kyllä kaikkiin muihinkin laskuihin löytyy montakin erilaista ratkaisumenetelmää - monimutkaisen näköisiä sillä ne ovat matalan-tason koodia mutta itseasiassa ne ovat helppoja. Juuri tämä onkin syy tämän skriptin kehittämiseen sillä se tosiaan osoittaa että BASH:illa on hampaat: miksi virtuoosit uskottelevat että BASH on hampaaton?

Teen tämmöisiä kummajais-skriptejä osoittaakseeni että kyllä BASH desimaalit hallitsee ja on siinäkin suhteessa ihan kelpo kieli prototyyppien tekemistä varten - tämä skripti kertoo lisäksi kuinka logaritmit lasketaan sillä logaritmeilla on kyllä hieman käyttöä edelleenkin. Skripti on kylläkin hidas mutta ei kai kukaan 17:sta millisekunnista hermostu?

Se että logaritmit lasketaan vain luvuista lukualueella 1.xxx....99.xxx... johtuu siitä tosiasiasta että logaritmit määritellään teoriassa aina tieteellisestä esitysmuodosta. Siis luvut saatetaan tälle lukualueelle muuttamalla eksponenttia siten että eksponentti on aina parillinen. Eksponentti ei osallistuu laskuun van ainoastaan määrää logaritmin ensimmäisen numeron. Tuolla perusalueellahan exponentti on nolla joten ensmmäiseen numeroon ei lisätä mitään. Tämä oikealle alueelle saattaminen on päässälasku.

Tämä logaritmien laskemiseen soveltuva skripti ei onnistu ilman nopeaa desimaalilaskentaa.
 
- jos haluat kokeilla toimiiko skripti niin leikkaa-liimaa skriptin jokainen rivi päätteeseen yhdellä kerralla - sillä toimivat skriptit niinkin.

Koodia: [Valitse]
function 2to10 () { # desimaalinen binääriluku (esim. 1.00101) muutetaan kymmenjärjestelmään:
taulu[1]=1;apu=1;for n in {2..60}; do apu=$((2*$apu)); taulu[$n]=$apu; done
apu=${1//./}; apu2=${#apu}
pilkunpaikka=$2
luku=0; for n in $(seq 0 $apu2); do (( ${apu:$n:1} )) && luku=$(($luku+1000000000000000000/${taulu[$n+1]} )); done                                                     
echo ${luku:0:$pilkunpaikka}.${luku:$pilkunpaikka} ;}               

function getlog ()  { # logaritmi määritellään binäärijärjestelmään
kokonaisosa=${1%%.*}; (($kokonaisosa>10)) && pilkunpaikka=1 || pilkunpaikka=0; (($kokonaisosa>100)) && pilkunpaikka=2
[[ ${apu//[^.]/} ]] && vanhalogaritmoitava=$1 || vanhalogaritmoitava=$1".0"
logaritmoitavankokonaisosa=${vanhalogaritmoitava%%.*}
logaritmi=$((${#logaritmoitavankokonaisosa}-1))
until (( ${#logaritmi}>=60 )); do
  apu=${vanhalogaritmoitava//./}; apu=${apu:0:1}.${apu:1}
  luku1=${apu:0:18}
  luku2=${apu:0:18}
  desimaaliosa1=${luku1##*.}
  desimaaliosa2=${luku2##*.}; desimaaleja=$((${#desimaaliosa1}+${#desimaaliosa2}))
  luku1=000000000000000000${luku1//./}
  luku2=000000000000000000${luku2//./}
  a=${luku1: -18:9}; b=${luku1: -9}
  c=${luku2: -18:9}; d=${luku2: -9}
  luku1=00000000000000000000000000000000000000$((10#$b*10#$d))
  luku2=00000000000000000000000000000000000000$((10#$d*10#$a))"000000000"
  luku3=00000000000000000000000000000000000000$((10#$c*10#$b))"000000000"
  luku4=00000000000000000000000000000000000000$((10#$a*10#$c))"000000000000000000"
  luku1=${luku1: -36}
  luku2=${luku2: -36}
  luku3=${luku3: -36}
  luku4=${luku4: -36}
  luku11=${luku1:0:18}
  luku12=${luku1:18}
  luku21=${luku2:0:18}
  luku22=${luku2:18}
  luku31=${luku3:0:18}
  luku32=${luku3:18}
  luku41=${luku4:0:18}
  luku42=${luku4:18}
  summa1=$((10#$luku12+10#$luku22+10#$luku32+10#$luku42))
  summa1pituus=${#summa1}; ylivuoto=0; (( $summa1pituus>=19 )) && ylivuoto=${summa1:0: -18} && summa1=${summa1:1}
  summa1=000000000000000000$summa1; summa1=${summa1: -18}
  summa2=$((10#$luku11+10#$luku21+10#$luku31+10#$luku41+$ylivuoto))
  apu=$summa2$summa1; (( $desimaaleja )) &&  uusilogaritmoitava=$((10#${apu:0: -$desimaaleja})).${apu: -$desimaaleja} || {  uusilogaritmoitava=$(( 10#$summa2 ))$summa1 ;}
  uudenlogaritmoitavankokonaisosa=${uusilogaritmoitava%%.*}
  logaritmi=$logaritmi$((${#uudenlogaritmoitavankokonaisosa}-1))
  vanhalogaritmoitava=$uusilogaritmoitava
done # tämä päättää se ryhmän joka alkaa käskystä: until
2to10 $logaritmi $pilkunpaikka ;}

logaritmoitava=17.3517; echo -e "\n\nLasketaan logaritmi luvusta: $logaritmoitava\nseuraavalla rivillä varmasti oikea tulos bc:stä vertailua varten ja sitäseuraavalla rivillä tämän skriptin tulos: ";  bc -l<<<"scale=18; l($logaritmoitava)/l(10)";  alkuhetki=$(date +%s.%N); getlog $logaritmoitava; loppuhetki=$(date +%s.%N);echo "kulunut aika mikrosekuntia: "$((${loppuhetki//./}/1000-${alkuhetki//./}/1000))


--- skriptin tuloste on seuraavanlainen:

Lasketaan logaritmi luvusta: 17.3517
seuraavalla rivillä varmasti oikea tulos bc:stä vertailua varten ja sitäseuraavalla rivillä tämän skriptin tulos:
1.239342030392094294
1.239342030392094280
kulunut aika mikrosekuntia: 16866

- tämän skriptin toiminta-aikaa ei voi mitata time-käskyllä sillä sen ja bc:n suhde on kummallinen. Vaan suoritusaika täytyy määritellä sillä tavalla kun se on tässä tehty jotta bc:n poistaminen ei vaikuttaisi ajoitustuloksiin. Sillä tottakai on syytä varmistaa ettei bc vaikuta tämän skriptin toimintaan mitenkään poistamalla lause: bc -l<<<"scale=18; l($logaritmoitava)/l(10)".

- tosin ajoitustavan suurempi tarkkuus on mukavaa sekin - tarkemmat ajat ovat muuten täyttä asiaa ja ihan oikeat vaikka ne heiluvatkin rajusti - nimittäin heiluminen johtuu siitä että Linux suo BASH:lle toiminta-aikaa sillointällöin, vain satunnaisesti ja silloinkin  pienissä palasissa kuin muilta töiltään kerkiää ja hommat tosiaan kestävät mitä milloinkin - tämän heilumisen estämiseksi reaaliaika-käyttöjärjestelmät kehitettiinkin sillä ongelma koskee useimpia ohjelmointi ympäristöjä.

***

Seuraava on liian omituinen käytettäväksi mutta on hyvä tietää että tällainenkin toimintatapa on mahdollinen:

Funktion nimeksi voidaan antaa melkein mitähyvänsä, vaikka merkki: +
- tosin ä:tä ja ö:tä on syytä välttää. Siten voidaan määrätä:

function + () { echo $(($1+$2)) ;}
- matemaattinen merkki + ei ole funktio joten kyseesä ei ole rekursio (eli funktio ei kutsu itseään)

kun tämänjälkeen annetaan käsky: + 1 2
niin se tulostaa 3


mutta vaikka annettaisiin käsky:
funktion + () { echo painu suolle, tänään ei lasketa mitään. ;}
niin echo $((1+2)) tulostaa 3 ihan niinkuin ennenkin ja vain tuo: + 1 2 tulostaisi:  painu suolle, tänään ei lasketa mitään.

---

Samoin voidaan määritellä: function !  () { echo $(($(seq -s* $1))) ;};
mutta käsky : ! 6 ei semmoisenaan viittaa tuohon kertoman määrittelyyn vaan event-määrittelyyn ja siten: ! 20 ei toimi vaan täytyy käskeä: \! 20
- \! 21 menee jo yli lukualueesta.

- kun lopettaa sen pääte-istunon jossa funktio on määritelty unohdetaan sen määrittely. Avattaessa uusi pääte se vanha määrittely palaa voimaan.
 


« Viimeksi muokattu: 15.11.23 - klo:10.50 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 680
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #303 : 16.05.23 - klo:20.42 »
***

Nämä skriptini ovat yritystä löytää BASHin perusta - skriptit ovat mielettömiä mutta kukaan ei voi tietää millaisiin tuloksiin ne johtavat. Ja muuten: kaikki nopeat käskyt briljanteraa tekstinkäsittelyssä vaikka on niistä apua laskennassakin.

BASH:issa on kammottava määrä käskyjä joista ei koskaan puhuta. Ne ovat nopeita ja tekevät pieniä ihmeitä. Esimerkiksi käskyt seuraavan skriptin tulostukseen: poistetaaan etunollat kokonaisosasta, takanollat desimaaliosasta ja desmaalipiste silloin kun se jää viimeiseksi:
Koodia: [Valitse]
apu2=${apu2##+(0)}; [[ ${apu2//[^.]/} ]] && apu2=${apu2%%+(0)}; echo ${apu2%.}
Toiminto on salaman-nopea ja se on ollut olemassa aina mutta jokainen joka noita toimintoja nykyisin tarvitsee tekee varmasti toimintoa varten pitkän ja hitaan skriptin - ja vain koska 'advance guidet' pitävät huolta siitä että noiden kaltaiset käskyt pysyvät unholassa. Ei niistä tosin paljoa BASH-raamatuissakaan puhuta. Mutta netti ei koskaan unohda ja sinne kerkisi kauan sitten tulla monia kirjoituksia niistä - tosin noiden käskyjen nopeudesta ei ole hyötyä jos niiden ohella käyttää uusi käskyjä. Eikä missään ei ole kattavaa yhteenvetoa.

Tehdessäni skiptiajuria kohtasin toisenlaisia ihmeellisiä käskyjä sllä se on pääasiassa tekstikäsittelyä - taas aivan toisenlainen maailma. Montakohan maailmaa BASHissa on?

***

Koska nyt  haukun virtuooseja pystyyn niin halusin varmistaa että haukun toimivien asioiden perusteella - tälläkinkertaa on kyse desimaalimatematiikasta - mutta tätä ennenhän on ollut kaikenlaista muuta - paljon merkityksellisempääkin. Jokatapauksessa tein taas yhden mielettömän skriptin jossa on runsaasti moninumeroista laskentaa - mutta toisenlaista laskentaa kuin aikaisemmissa skripteissä joten uudetkin laskut tulee testattua. BASH itse laskee muuten montakertaa nopeammin kuin matematiikka-ohjelmat joten niitä ei laskennassa käytetä - varsinaisessa matematiikassa BASH jää kyllä paljon huonommaksi ja sitähän tässä koetetaan nopeuttaa - toistaiseksi huonosti menestyen. Mutta kyse ei tälläkertaa ole varsinaisesti matematiikasta vaan siitä että soiminto ylipäätään onnistuu jollaintavalla - se että se toimii näinkin hyvin on kylläkin mukavaa.

Skripti muuntaa digitaaliluvun desimaaliosan kymmenjärjestelmään. Esimerkiksi luvun .11100000001000000010000000010000000010000000100000001001 muuntaminen kymmenjärjestelmään:
skripti:                          0.875490192331227615183197488403320317865025843484497
netin muunnosmoottori: 0.87549019233122761519

- onko kyseessä pyöristysvirhe vai laskeeko muunnosmoottori liian vähillä numeroilla? Sillä 'tarkkuuskato' näissä laskuissa on niin suuri että saa pelätä ettei tuo skriptinkään numeromäärä vielä riitä vaan kaikki nuo ylimääräiset numerot ovat jo potaskaa - ne ovat tuossa vain siksi että näkisitte paljonko niitä on.
Koodia: [Valitse]

function summaa72 () { # viimeisin versio
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia ja haetaanko vertailutulos bc:stä. Vaihtoehdot:tulosta=echo ja tulosta=: . Vaatii siis yksinkertaisen koodimuutoksen.
$tulosta
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1".0"
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2".0"
desi1=${luku1##*.}
desi2=${luku2##*.}
koko1=${luku1%%.*}
koko2=${luku2%%.*}

# desimaaliosien tulee olla yhtäpitkät. Lyhyemmän perään kirjoitetaan nollia.
(( ${#desi1} >= ${#desi2} )) && desipituus=${#desi1} || desipituus=${#desi2}
desi1=$desi1'0000000000000000000000000000000000000000000000000000000000000000000000000000'
desi2=$desi2'0000000000000000000000000000000000000000000000000000000000000000000000000000'
desi1=${desi1:0:$desipituus}; $tulosta desi1:$desi1
desi2=${desi2:0:$desipituus}; $tulosta desi2:$desi2 ;$tulosta

summattava1='0000000000000000000000000000000000000000000000000000000000000000000000000000'$koko1$desi1; summattava1=${summattava1: -72}; $tulosta $summattava1
summattava2='0000000000000000000000000000000000000000000000000000000000000000000000000000'$koko2$desi2; summattava2=${summattava2: -72}; $tulosta $summattava2 ; $tulosta

luku11=${summattava1:0:18}; luku12=${summattava1:18:18}; luku13=${summattava1:36:18}; luku14=${summattava1:54:18}; $tulosta $luku1{1..4}"  "
luku21=${summattava2:0:18}; luku22=${summattava2:18:18}; luku23=${summattava2:36:18}; luku24=${summattava2:54:18}; $tulosta $luku2{1..4}"  "; $tulosta

apu14=$((10#$luku14+10#$luku24)) ;          [[ ${#apu14} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto1; apu14=${apu14:1:18} ;} || ylivuoto=0
apu13=$((10#$luku13+10#$luku23+$ylivuoto)); [[ ${#apu13} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto2; apu13=${apu13:1:18} ;} || ylivuoto=0
apu12=$((10#$luku12+10#$luku22+$ylivuoto)); [[ ${#apu12} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto3; apu12=${apu12:1:18} ;} || ylivuoto=0
apu11=$((10#$luku11+10#$luku21+ylivuoto))
apu=$apu11$apu12$apu13$apu14

$tulosta "ylemmällä rivillä varmasti oikea tulos bc:stä ja toisella rivillä mitä tämä skripti antaa:"
$tulosta $(bc<<<"$1+$2")
# poistetaaan etunollat kokonaisosasta, takanollat desimaaliosasta ja desmaalipiste jos se jää viimeiseksi
apu2=${apu:0: -$desipituus}.${apu: -$desipituus}; apu2=${apu2##+(0)}; [[ ${apu2//[^.]/} ]] && apu2=${apu2%%+(0)}; echo ${apu2%.} ;}

function jaa () { (( ! $# )) && echo funktion ajokäsky on muotoa: jaa 1 2 . Siitä pitää tulla: .5000000000000000000000000000 && sleep 2 && return
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
 
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1"."
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2"."

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

#desimaaliosat yhtäpitkiksi:
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
$tulosta $kokonaisosa1$desimaaliosa1
$tulosta $kokonaisosa2$desimaaliosa2

#kokonaisosat yhtäpitkiksi:
(( ${#kokonaisosa2} >= ${#kokonaisosa1} )) &&
{ apu="00000000000000000000"$kokonaisosa1; kokonaisosa1=${apu: -${#kokonaisosa2}} ;} || { apu="00000000000000000000"$kokonaisosa2; kokonaisosa2=${apu: -${#kokonaisosa1}} ;}
luku1=$kokonaisosa1$desimaaliosa1;luku1=$((10#$luku1))
luku2=$kokonaisosa2$desimaaliosa2;luku2=$((10#$luku2))
$tulosta jaettava:$luku1"   "jakaja:$luku2

(($luku1 >= $luku2)) && { apu=$(($luku1/$luku2)); kokonaisiatulosteessa=${#apu}; nolliatulosteessa='' ;} || { apu=$(($luku2/$luku1)); apu=${apu//[1-9]/0}; nolliatulosteessa=${apu:1}; kokonaisiatulosteessa=0;}; $tulosta nolliatulosteessa:$nolliatulosteessa"   "kokonaisiatulosteessa:$kokonaisiatulosteessa   

# tähänasti on selvitetty desimaalipisteen paikkaa. Nyt aletaan laskea numeroita.
unset tulos # vain varmistus että kaikki on tuloksessa tämänjälkeen uutta
luku1=${luku1##+(0)}; [[ ${luku1//[^.]/} ]] && luku1=${luku1%%+(0)} # poistetaan etu-ja takanollat sillä tästälähtien ne vain haittaavat.!!!
luku2=${luku2##+(0)}; [[ ${luku2//[^.]/} ]] && luku2=${luku2%%+(0)} 
q=$(($luku1%$luku2));((q>1)) && q=$((10**$((${#q}-2)) )) || q=1 # siis q on jakojäännös esitettynä siten että ensimmäinen numero on 1 ja lopuista numeroista tulee nollia - viimeinen nolla poistettuna
luku1=$luku1'0000000000000000000'; luku1=${luku1:0:18} ;$tulosta " "$luku1 #"  "${#apu2}"  "$apu

for n in {1..6}; do # muodostetaan tulos-palasia 
apu=$(($luku1/$luku2)); tulos[$n]=${apu}; (( $(($luku1%$luku2)) > $((10*$q )) )) && apu=$apu'0' # on joskus tarpeen ja joskus liikaa
apu2=$(($luku1%$luku2)); luku1=$apu2'0000000000000000000'; luku1=${luku1:0:18} ; $tulosta " "$luku1"  "${#apu2}"  p"$apu
done

vanhatmerkit=1
for n in {1..6}; do # kootaan tulosta matriisin palasista
uudetmerkit=${#tulos[$n]}; [[ $uudetmerkit -lt $vanhatmerkit ]] && tulos[$n]=$nolliatulosteessa${tulos[$n]} ; vanhatmerkit=$uudetmerkit
tulos=$tulos${tulos[$n]}
done
 
#[[ ${tulos%%+(0)} -eq 1 ]] && nolliatulosteessa=${nolliatulosteessa:1} # purkkaviritys?
[[ $nolliatulosteessa ]] && echo .$nolliatulosteessa${tulos:0} || echo ${tulos:0:$kokonaisiatulosteessa}.${tulos:$kokonaisiatulosteessa} ;}

function muunna () { local apu7; apu7=$1;luku=0; for n in $(seq $((${#apu7}+1))); do  (( ${apu7:$n-1:1} )) && luku=$(summaa72 $luku ${taulu[$n+1]});done ; echo $luku ;} 

unset taulu;taulu[1]=1;apu=1;for n in {2..57}; do apu=$((2*$apu)); apu8=$(jaa 1 $apu); taulu[$n]=${apu8%%+(0)};  echo $n"  "${taulu[$n]}; done;   # taulukon muodostaminen

muunna 111 # esimerkkikutsu
 

***

Tässä ei varsinaisesti ole kyse desimaalimatematiikasta vaan kaikenmaailman guidejen saattamisesta naurunalaiseksi - ne apinoivat kaikki samoja rajoittuneita alkeita.. Kyllä ne silti on syytä oppia mutta niissä ei tosiaankaan ole mitään edistynyttä - ja valitettavasti tilanne on tällähetkellä se ettei mitään parempaakan ole. BASH raamatutkin ovat kovin yksipuolisia ja niistä on vaikea oppia mitään. BASH-maailma on ainakin kymmeniä kertoja suurempi kuin mitä ne antavat uskoa.

Ja desimaalimatematiikka ei ainoastaan toimi vaan on lisäksi salaman-nopeaa - alle 1ms.

Voitte olla varmoja että heitä kirpaisee jos tämä joskus heidä kurkustaan alas tungetaan - sillä homma on karmea: tappoivat oman lapsensa. Sillä tuo nopeutus ei koske yksinomaan  matematiikkaa vaan myös tekstinkäsittelystä tulee samoilla käskyillä erittäin nopeaa.

Ja vaikka ilmanmuuta koettavat vaieta desimaalilaskennan kuoliaaksi niin minkäs sille voi että tämä toimii. Syy skriptin esittämiseen onkin se ettei tällaisia voi puhua esittämättä kuinka homma hoidetaan.

Aikoinaan  BASH oli parhaimmillaan koeteltaessa toimiiko joku teoria käytännössä - syy BASH-skriptin tekoon oli selvittää kannattaako tehdä kunnollinen ohjelma jollain kunnon kielellä - nimittäin BASH:illa saa nopeasti tehtyä välttävästi toimivan skriptin. Mutta siinä hommassa säällisen nopeat peruslaskutoimitukset desimaali-luvuilekin ovat melkein välttämättömiä. Desimaalilaskuihin käytettiinkin matematiikkaohjelmaa, esimerkiksi bc:tä sillä luultiin ettei BASH desimaalilaskuihin kykene. Mutta kyllä kykenee ja pienehköissä peruslaskutoimituksissa BASH on jopa paljon nopeampi.

Desimaalilaskut suoritetiin silloin aikoinaan kokonaisluvuilla isoissakin koneissa - joten kyllä senaikaiset virtuoosit tiesivät että myös BASH kykenee niihin. Mutta he välttelivät puuttumasta koko asiaan sillä se on sellainen salahautojen viidakko että paraskin virtuoosi haksahtaisi joskus ja sitä he eivät kestä - joutua naurettavuuden kaakinpuuhun. Niinpä he eivät puhuneet koko asiasta - mikä antaa sellaisen mielikuvan etteivät desimaalilaskut BASH:issa edes onnistu. Ja oli heillä laho selityskin: BASH:in matematiikkamoottori ei osaa desimaalilaskuja eikä BASH:in muuttujien tyyppimäärityksissäkään desimaaliluvuista mitään puhuta. Mutta tuo tyyppimääritys on tahallinen väärinymmärrys sillä tekstijonojahan ne desimaaliluvut vain ovat.
 
Mutta siis niitä salahautoja on: jakolaskua vaivaa esimerkiksi 'katoavien nollien syndrooma'. Ja kun nolla katoaa on senjälkeinen tulos pelkkää potaskaa. Tämän korjaaminen on henkimaailmasta: jakolasku ei esitä etunollia -> jos jakolaskun tulos on 010 (esimerkiksi 100/10) niin näytetään vain 10. Mutta vaikka tähän löytyikin korjaus niin jakolaskua täytyy vielä kauan testailla ja kenties krjailla.

Mutta kyse ei ole mitättömän piennistä jakolaskuista vaan esimerkiksi: jaa .123456 1233457890123.23  josta tulos on: .00000000000010008935123651930204696 - joka on kyllä ihan oikein - ei niitä kadonneita nollia silloin löydä kun etsii niitä.

- itse skripti vie aikaa alle millisekunnin. bc tässä sitä aikaa vie laskiessaaan varmasti oikeaa arvoa johon verrata tämän skriptin tulosta. Kommentoi bc pois niin näet.

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

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

#desimaaliosat yhtäpitkiksi:
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
#echo $kokonaisosa1$desimaaliosa1
#echo $kokonaisosa2$desimaaliosa2

#kokonaisosat yhtäpitkiksi:
(( ${#kokonaisosa2} >= ${#kokonaisosa1} )) &&
{ apu="00000000000000000000"$kokonaisosa1; kokonaisosa1=${apu: -${#kokonaisosa2}} ;} || { apu="00000000000000000000"$kokonaisosa2; kokonaisosa2=${apu: -${#kokonaisosa1}} ;}
luku1=$kokonaisosa1$desimaaliosa1;luku1=$((10#$luku1))
luku2=$kokonaisosa2$desimaaliosa2;luku2=$((10#$luku2))
echo jaettava:$luku1"   "jakaja:$luku2

(($luku1 >= $luku2)) && { apu=$(($luku1/$luku2)); kokonaisiatulosteessa=${#apu}; nolliatulosteessa='' ;} || { apu=$(($luku2/$luku1)); apu=${apu//[1-9]/0}; nolliatulosteessa=${apu:1}; kokonaisiatulosteessa=0;}

echo nolliatulosteessa:$nolliatulosteessa"   "kokonaisiatulosteessa:$kokonaisiatulosteessa   

# tähänasti on selvitetty desimaalipisteen paikkaa. Nyt aletaan laskea numeroita.
unset tulos # vain varmistus että kaikki on tuloksessa tämänjälkeen uutta
luku1=${luku1##+(0)}; luku1=${luku1%%+(0)} # poistetaan etu-ja takanollat sillä tästälähtien ne vain haittaavat.
luku2=${luku2##+(0)}; luku2=${luku2%%+(0)} 
# seraava oli: q=$(($luku1%$luku2));q=$((10**$((${#q}-1)))) # siis q on jakojäännös esitettynä siten että ensimmäinen numero on 1 ja lopuista numeroista tulee nollia - viimeinen nolla poistettuna
q=$(($luku1%$luku2));((q>1)) && q=$((10**$((${#q}-2)) )) || q=1 # siis q on jakojäännös esitettynä siten että ensimmäinen numero on 1 ja lopuista numeroista tulee nollia - viimeinen nolla poistettuna
luku1=$luku1'0000000000000000000'; luku1=${luku1:0:18} ;echo " "$luku1 #"  "${#apu2}"  "$apu
for n in {1..6}; do # muodostetaan tulos-palasia 9 merkkiä kerrallaan
apu=$(($luku1/$luku2)); (( $(($luku1%$luku2)) < $q )) && apu=$apu'0'; tulos[$n]=${apu}  # yksi keino palauttaa niitä kadonneita nollia;  (( $(($luku1%$luku2)) > $((10*$q )) )) && apu=$apu'0' on joskus tarpeen ja joskus liikaa
apu2=$(($luku1%$luku2)); luku1=$apu2'0000000000000000000'; luku1=${luku1:0:18} ;echo " "$luku1"  "${#apu2}"  "$apu
done

vanhatmerkit=1
for n in {1..6}; do # kootaan tulosta matriisin palasista
uudetmerkit=${#tulos[$n]}; [[ $uudetmerkit -lt $vanhatmerkit ]] && tulos[$n]='0'${tulos[$n]} ; vanhatmerkit=$uudetmerkit
tulos=$tulos${tulos[$n]}
done
 
[[ ${tulos%%+(0)} -eq 1 ]] && nolliatulosteessa=${nolliatulosteessa:1} # purkkaviritys?
echo "oikea tulos 54 desimaalilla esitetynä on päällä ja alla tulos tästä skriptistä"
bc<<<"scale=${#tulos}; $1/$2" | tr -d '\\\n'; echo ' tämä rivi on bc:stä'
[[ $nolliatulosteessa ]] && echo .$nolliatulosteessa${tulos:0} || echo ${tulos:0:$kokonaisiatulosteessa}.${tulos:$kokonaisiatulosteessa} ;}

# Seuraavia on kokeiltu:
         
time jaa 1233457890123.23 .123456
time jaa .123456 1233457890123.23
time jaa 1 2
time jaa 2 1
time jaa 1 3
time jaa 1 1000
time jaa 1 1001

***

BASH supports fast floating-point calculus via its low-level commands - with low-level commands BASH has more capabilities than most anything.

Litle snippet that operates at least in vanilla Ubuntu ( copy and paste all lines of the code to terminal in one go and press return)(check with bc or such: echo "scale=36; 0.000000000000000001234*1.234567809123456") | bc)(There are still quite many moot points in it - and if they are all corrected you lose easily basic ideas): 
Koodia: [Valitse]
function kerro9 () {
[[ $1 =~ \. ]] && { apu=${1%%[1-9]*}; apu=0${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%.}  ;}

time kerro9 0.000000000000000001234 1.234567809123456
« Viimeksi muokattu: 12.07.24 - klo:14.04 kirjoittanut petteriIII »

Jere Sumell

  • Käyttäjä
  • Viestejä: 737
  • Talous, Hallinto ja Markkinointi (AMK, 2017),B.B.A
    • Profiili
    • Tietokone-blogi
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #304 : 18.06.23 - klo:22.18 »
Tässä tänä iltana parisen tuntia lueskelin Bash-scriptaukseen liittyen tutoriaalia Ryan's Tutorial -lähteestä, ja sitten mitä katsoin tuon read -komennon manuaali-ohjeistusta, niin heräsi kysymys, mikä tuo read -komennon palautusarvo on, jos nyt esimerkiksi jotain tekstitiedostosta luetaan sitä dataa, jos se nyt on esimerkiksi ihan standardi 128 -merkin ASCII-merkistö.

Mitä tuo palautusarvo on positiivinen, mutta sitten mitä useimmissa ohjelmointikielissä, yleensä sitten -1, tai negatiivinen palautusarvo on siinä kohtaa, kun tiedoston loppu, tai syötteen loppu saavutetaan, EOF. read -komennossahan -1 palautuksena heitetään virhetilanteessa.

Mitä jonkun verran katsoin tuosta syventäen sitä, mitä alkoi kiinnostamaan, niin ASCII-tiedostossa ei ole sitä EOF-riviä sitten lopussa, niin onko read -komennolla mitään arvoa, mitä se palauttaa tiedoston lopun saavuttamisen jälkeen?

Vai onko se käytännössä kertokaa te, jotka olette ehkä jo ikänne ohjelmoineet ihan käytännön työkokemusta ehkä on, tai muutkin niin jos syötevirroilla ohjaa tekstitiedoston bash-scriptille, missä sitten read komennolla luetaan muuttuja, joka käydään do-while -toistorakenteessa läpi se läpikäytävä tiedosto?

Onhan se sama asia, että tiedosto tulee läpi käytyä rivi riviltä, tai merkki merkiltä, siinä on ohjelmoimista muutamia merkkijonoa välilyönnillä erotettuna ehkä jopa enemmän, mitä sitten jos tuolla read-komennolla olisi jokin EOF-palautusarvo.

Itsekin melkein päädyin, että ei siellä melkein ole mitään, mitä olisi tullut jo tossa vastaan, mitä katselin oikeilla hakusanoilla. Kai siihenkin sitten pätevät syyt on. Se voi se tausta olla ja siinä, mitä BASH on kehitettynä 1989, niin siihen maailman aikaan varmaan valtaosin tekstitiedostot oli 128 merkin ASCII -merkistöä ja se nyt selvisi, että siinähän ei tosiaan ole sitä. Tämä nyt oma arvioni, jos joku tuntee tätä skriptauskielen historiaa ja taustoja enemmän, niin voi valaista minua.

Pistän tähän nyt, jos joku etsii hyviä tutoriaaleja, mistä itselleni oli hyötyä, vaikka nyt en nollasta ihan tätä lähtenyt liikenteeseen, mutta en ole bash-scriptejä enimmäkseen linux-koneellani ohjelmoinut.

https://ryanstutorials.net/bash-scripting-tutorial/

Tuo nyt oli selkeä ja hyvin kirjoitettu paketti. Lisäksi alkuperäinen lähde, jossa kuulin ensimmäistä kertaa tuon viittauksen joskus 2000-luvun vaihteessa Musiikmitelevisiossa esitetyssä "Pimp My Ride" -formaatissa "Pimp My Bash Script" ja viittaus tuohon auto-ohjelman juontajaan siinä, että hän ei ole sanonut niin, mutta tutoriaalin laatijan mukaan olisi voinut hyvinkin olla quoten takana.

Mitä lopulta sitten tuli 4 hakutulosta tuosta, katsoin mikä tuon alkuperä on, niin tuo on ainoa lähde, tai alkuperäinen missä tuo noin on esitetty.

Kiinnostus sitten heräsi muutoin miehen tarjontaan, ihan positiivisella tavalla kaikin puolin mielekkään oloinen tutoriaaleihin keskittyvä selkeä sivusto. Pidin kovastikin.

Sitten hyvä lunttilappu (cheatsheet) seinään teipattavaksi
https://devhints.io/bash

Päällisin puolin nuo lunttilappu-tiivistelmät aiheesta kuin aiheesta on siinä määrin käteviä myös ohjelmointikielien osalta, että siinä kohtaa kun ei ole sillä tavalla selkärangasta tule aivan kaikki, niin nopeita ja käteviä tarkastaa nopeasti asia, mikä on hukassa.
« Viimeksi muokattu: 18.06.23 - klo:22.20 kirjoittanut Jere Sumell »
Free Internet and  people for humans all over the globe!

(Profiilikuvassa oma valokuvani GIMPissä editoituna Disney Classic-väripaletin väreihin ja muunnettuna bittikartta-tiedostosta vektorigrafiikaksi.)

petteriIII

  • Käyttäjä
  • Viestejä: 680
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #305 : 03.07.23 - klo:10.41 »
BASH:in palauttaminen järkevään kuntoon täytynee alkaa inhottavimmasta päästä: BASH:in matematiikasta. Tai eihän BASH:illa matematiikkaa oikeastaan ole vaan ainoastaan kyky kokonaisluku laskentaan - kyky jota ei koskaan käytetä sillä kyky kyetä käsittelemään myös desimaalilukuja on lähes ehdoton. Desimaalilukujen käsittelemiseksi täytyy siis aluksi tehdä funktiot. Selvitetään ensin laskennan merkintätapaa:

Kaikissa kielissä matemaattiset toiminnot suoritetaan samallatavoin, mutta laskujen suorittamisen merkintätavat vaihtelevat paljon - ja kunkin merkintätavan hyvät ja huonot puolet paljastuvat vasta kun niitä käyttää paljon - mutta useimmat eivät koskaan edes kokeile muita kuin normaalia merkintätapaa.  Mutta muutama laskennan merkintätapa: 

                       Normaali merkintätapa: a=1; b=2; c=a+b
Käänteinen puolalainen merkintätapa: a=1; b=2; c=ab+ 
                        BASH:in merkintätapa: a=1; b=2; c=$(($a+$b)) . Tietokoneohjelmassa merkintätavan monimutkaisuus menettää merkityksensä - vaikka eihän se sentään merkityksetöntä ole.
               
- merkki + kutsuu niissä kaikissa funktiota joka selvittää kuinka homma hoidetaan.
- muissa kielissä laskun jäsenet voivat olla desimaalilukujakin - mutta ei BASH:issa. Joten kun muissa kielissä on desimaalien käsittelyyn jo tehtynä funktioita - käytät niitä vaikka et edes tietäisi että sellaisia tarvitaan - funktiot ovat nykyään usein matematiikkaprosessorien langoituksessa - mutta BASH:iin nuo funktiot täytyy tehdä itse. Ja käytettässsä funktiota laskuihin muuttuu toiminnon merkintätapakin: a=1.1; b=2.2; c=$( ynnää $a $b ) .
- mutta ei sen funktion nimeltä: + hommaa pysty kukaan toinen tekemään joten funktiota: + täytyy kutsua funktiossa: ynnää.

***             
             
Desimaali-laskun suorittamiseksi täytyy ensin laskea missä desimaalipiste tulee tuloksessa olemaan. Tämänjälkeen poistetaan luvuista desimaalipisteet ja suoritetaan laskutoimitus saaduilla kokonaisluvuilla. Sitten vain kirjoitetaan tulos sijoittaen desimaalipiste lasketulle paikalle. Tietokoneiden alkuaikoina oli vain  kokonaislukulaskimia ja haluttessa saada desimaalinen tulos toimittiin kuten edellä kuvattiin.

Kyllä virtuoosit siis ilmanmuuta tämän tiesivät mutta heillä oli jo toinen skriptikieli kiikarissa ja he halusivat tästä uuden skriptaamisen kuninkaaan - sillä BASH oli jo niin mennyttä ja vaikeata. Voidakseen osoittaa BASH:in olevan surkimus desimaalilaskujakin oli estettävä kehittymästä. Helppo nakki - tehtiin uusi käskykanta joka oli muissa tehtävissä parempi kuin entinen mutta desimaalilaskuissa se oli kammottavan hidas. Vanhoilla käskyillä desimaalilaskenta onkin tuhansia? kertoja nopeampaa kuin uusilla käskyillä - siis jos vanhoilla käskyillä sai aikaan millisekunnissa toimivan niin uusilla se kestäisi sekunteja. Tämä johtaa siihen että uusia käskyjä käytettäessä testauksesta tulee niin hidasta että luullaan ettei homma edes toimi.

Noiden vanhojen käskyjen kirjoitusasu on kummallinen joten ne ovat vaikeita kirjoittaa ja muistaakin. Mutta ne ovat nopeita ja ennenkaikkea kääntäjä-ystävällisiä -> kääntämisessä on käytössä  sama cache-menettely kuin kaikessa muussakin. Ja cachen koolla on rajansa ja kun sinne käänetään käskyjä niin noita vanhoja ja pieniä mahtuu sinne tusinoittain mutta uusia ja suuria käskyjä vain muutama. Joten nopeuseroa käskyillä on käsittämättömän paljon sillä uusilla käskyillä tehdyssä skriptissä kutsutaan kääntäjää vähänväliä mutta yksinomaan vanhoista käskyistä tehdyssä vain kerran.

Mutta eivät vaikeudet kirjastokelpoisen funktion valmistumiseen lopu sillä kirjastoahan ei ole. Eikä yksi tyyppi niitä voi tehdä - se olisi ihan liian hidasta ja yksipuolista. Mutta kannattanee yrittää.

***

Akutilanne: haluat laskea: yhteen kaksi korkeintaan 72 numeroista positiivista lukua(merkin huomiooon ottaminen monimutkaistaisi esimerkin vaikeaksi käsittää), desimaalipiste saa kummassakin sijaita missävaan. Toiminta on silloin:
     
       Otetaan esimerkki joka kirjoittaan ( siis desimaalipisteet samalle kohtaa alekkain ja desimaaliosissa pienemmän numerot täytetään nollilla:
       12345678901234567890.2000000000000000000000000000
                        +73.4567890123456789012345678901
     
Sitten vaan laskemaan - desimaalipisteen paikka pistetään muistiin esimerkiksi sen perusteella kummassa on kokonaisoasassa enemmän numeroita - desimaalipiste poistetaan ja jaetaan loppu18 merkin palasiin lopusta alkaen ja lasketaan palaset yhteen alkaen vähiten merkityksellisestä päästä ja mikäli jonkun palasen summaan tuleekin 19 numeroa niin se viimeinen numero lisätään seuraavaan palaseen poistaen se alkuperäisestä palasesta. Sitten vain laitetaan palaset peräkkäin ja kirjoittetaan oikealla hetkellä desimaalipistekin.
Koodia: [Valitse]

function summaa72 () {
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia ja vertailutulos bc:stä. Vaihtoehdot:tulosta=echo ja tulosta=:
$tulosta

[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1".0"
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2".0"
desi1=${luku1##*.}
desi2=${luku2##*.}
koko1=${luku1%%.*}
koko2=${luku2%%.*}

# desimaaliosien tulee olla yhtäpitkät. Lyhyemmän perään kirjoitetaan nollia silä ne eivät muuta arvoa.
(( ${#desi1} >= ${#desi2} )) && desipituus=${#desi1} || desipituus=${#desi2}
desi1=$desi1'0000000000000000000000000000000000000000000000000000000000000000000000000000'
desi2=$desi2'0000000000000000000000000000000000000000000000000000000000000000000000000000'
desi1=${desi1:0:$desipituus}; $tulosta desi1:$desi1
desi2=${desi2:0:$desipituus}; $tulosta desi2:$desi2 ;$tulosta

summattava1='0000000000000000000000000000000000000000000000000000000000000000000000000000'$koko1$desi1; summattava1=${summattava1: -72}; $tulosta $summattava1
summattava2='0000000000000000000000000000000000000000000000000000000000000000000000000000'$koko2$desi2; summattava2=${summattava2: -72}; $tulosta $summattava2 ; $tulosta

luku11=${summattava1:0:18}; luku12=${summattava1:18:18}; luku13=${summattava1:36:18}; luku14=${summattava1:54:18}; $tulosta $luku1{1..4}"  "
luku21=${summattava2:0:18}; luku22=${summattava2:18:18}; luku23=${summattava2:36:18}; luku24=${summattava2:54:18}; $tulosta $luku2{1..4}"  "; $tulosta

apu14=$((10#$luku14+10#$luku24)) ;          [[ ${#apu14} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto1; apu14=${apu14:1:18} ;} || ylivuoto=0
apu13=$((10#$luku13+10#$luku23+$ylivuoto)); [[ ${#apu13} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto2; apu13=${apu13:1:18} ;} || ylivuoto=0
apu12=$((10#$luku12+10#$luku22+$ylivuoto)); [[ ${#apu12} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto3; apu12=${apu12:1:18} ;} || ylivuoto=0
apu11=$((10#$luku11+10#$luku21+ylivuoto))
apu=$apu11$apu12$apu13$apu14

$tulosta "ylemmällä rivillä varmasti oikea tulos bc:stä ja toisella rivillä mitä tämä skripti antaa:"
bc<<<"$1+$2" | tr -d ' \\\n'; echo
# poistetaaan etunollat kokonaisosasta, takanollat desimaaliosasta ja desmaalipistekin jos se jää viimeiseksi
apu2=${apu:0: -$desipituus}.${apu: -$desipituus}; apu2=${apu2##+(0)}; [[ ${apu2//[^.]/} ]] && apu2=${apu2%%+(0)}; echo ${apu2%.} ;}
   
time summaa72 55555555555555555555555555555.55555555555555555555555555555555555555555 6666666666666666666666666666666.6666666666666666666666666666666666666666

- ei tämä kyllä vielä ihan moitteeton ole mutta kelpaaa jo käyttöönkin.
- funktion nopeus on 1ms jos poistaa rivin: $tulosta $(bc<<<"$1+$2") - sillä tuota riviä tarvitaan vain antamaan varmasi oikea tulos vertaamista varten sillä onhan bc todettu varmasti oikeelliseksi ja se on varmasti myös tarpeeksi tarkka. Mutta toiminnataan tämä funktio on paljon nopeampi kuin bc.

***

Olisihan näihin BASH:in matemattisiin ongelmiin ihan yleisestikin käytettyjä menetelmiä niinkuin bc ja AWK. Mutta ihan mielenkiinnosta koetan mihin BASH itse pystyy - ja sitäpaitsi pienissä perus-laskutoimituksissa BASH on jo paras. Ja mikäli BASH:in funktioista tehtäisiin kirjastofunktiot niin ne olisivat myös yksinkertaisimmat

BASH:in kehittäjät olivat aikoinaan toteuttaneet toimivat menetelmät moitteettomiin peruslaskuihin ja virtuoosit ovat suunnanneet nuo menetelmät lattian alle. Nimittäin kaikki haluavat päästä BASH:ista eroon sillä BASH laajeni aikoinaan niinpaljon etteivät edes virtuoosit sitä enää täysin hallitse - ei semmoista voi korjata vaan ainoastaan pyrkiä siitä eroon. Ja mikäli osoitettaisiin että BASH hallitsee myös desimaalit olisi siitä paljon hankalampaa päästä eroon.
- siis tosiaan kymmenistätuhansista asioista on tehty paksuja raamattuja täynnä käskyjä ja menetelmien selityksiä. Kenelläkään ei ole pienintäkään mahdollisuutta edes lukea kaikkia niitä - ja nykyäänhän niistä on moni historian hämärissä.

Alkuunsa kielen matemaattisia kykyjä hiotaan suorittamalla "näyttäviä laskuja". Muut kielet harrastivat näitä "näyttäviä laskuja" kolmekymmentävuotta sitten - sillä ne on pakko läpikäydä joskus. Vaikka BASH on kyllä taitanut ne jo silloin kauan sitten niin kukaan ei ole esittänyt toimivaa koodia - joten niitä täytyy esittää nyt:

Tarvitsin taulukkoa jonka ensimmäinen jäsen on .5 ja seuraavat jäsenet aina puolta pienempiä kuin edellinen. Toteutus BASH:illa onnistui hyvin - lukujen tarkkuus oli 26 numeroa ja siihen tarvittiin 86 kahdella jakamistaja. Koko 86:n laskun sarja kesti noin 10 ms - BASH:ilta se on nopeaa vaikka muilla kielillä se onkin hidasta. Skripti on näennäisesti iso, mutta muissa kielissä vastaavat ohjelmat ovat paljon isompia, mutta niissä toiminta tapahtuu kirjastoissa, siis katseelta piilossa. Skriptistä tuli tällainen:
Koodia: [Valitse]

time { number=.1; for n in {1..86}; do apu=${number%%[1-9]*}; apu2=${number:${#apu}}'00000000000000000';apu2=${apu2:0:18}; number=$(($apu2/2))'00000000000000'; (( ${apu2:0:1} == 1 )) && number=0$number; number=${number%%+(0)}; number=$apu$number; echo $n; bc<<<"scale=26;1/(2^$n)" | tr -d '\\\n'; echo; echo .${number:2:26}; echo; done ;}
- bc tarjoaa taas vain vertailuarvon. Mikäli nopeutta haluaa niin se on poistettava samoin kun turhat echot
- tämä on kokoajan melkoisessa kehitysvaiheessa

***

Törkeä temppu on väittää että BASH on helppo kieli mutta ettei se mihinkään kykenekään ja sen kykenemättömyydenkin se tekee hitaasti. Tosiasiassa BASH on niin monipuolinen ja monimutkainen että ohjelmointi sillä on niin hidasta ja haastavaa ettei sitä kannata tehdä jos haluaa työstään tuloksia järkevässä ajassa - sillä hyvistä käskyistä ja menetelmistä ei missään puhuta - ja rakenteeltaan ne ovat sellaisia ettei niitä löydä googlaamallakaan muuten kuin sattumalta. Sensijaan opetuskielenä BASH on loistava koska sillä edistyneemmät menetelmät täytyyy koota itse käyttäen käskyjä joita ei helposti löydä - mutta sellainen vika BASH:illa on että jos oppilaallasi on uskomaton tuuri löytää hän paremmat käskyt ja hänen tekemänsä toiminnot jättävät sinut toiseksi. Nopea BASH ei ole milloinkaan mutta ei kovin hidaskaan jos käyttää oikeita käskyjä.   

Kesti kymmenen vuotta käsittää että ulkomaisilla verkkosivuilla lähes poikkeuksetta käy seuraavalla tavalla: joku esittää kysymyksen. Kun siihen on esitetty kymmenkunta huonohkoa vastausta niin joku ohjelmoija ei enää kestä vaan esittää kertaluokkia paremman vastauksen. Mutta ei tämä hyvä vastaus mitään muuta vaan niitä kehnoja vastauksia tulee edelleen - ja jopa entistä enemmän. Suomesta ei ole niin paljoa dataa että voisi sanoa mitään.

***

Kuinka kauan BASH:issa kestää yhteenlasku? Esimerkiksi seuraavassa miljoona yhteenlaskua kestää noin 400ms, siis .4 mikrosekuntia per yheenlasku. Ei tämä tosin kuvaa yhden yhteenlaskun noin 50 mikrosekunnin aikaa kovinkaan hyvin mutta kertoo sentään mitä on mahdollista BASH:illa ääri-tapauksessa saavuttaa. Tulos on muuten ihan oikein joten kyllä se laskee - ja tosiaan yhteenlaskuna eikä aritmeettisen sarjan kaavalla.
Koodia: [Valitse]
mat=({1..1000000}); apu=$(printf %s+  ${mat[@]}); time echo $(($(echo ${apu%+})))

***

Kyllä jotkin BASH:in normalitkin käskyt ymmärtävät desimaalien olemassaolon. Esimerkiksi annapa käsky:
Koodia: [Valitse]
seq .111111111111111111 .111111111111111111 11

 
« Viimeksi muokattu: 21.08.24 - klo:21.08 kirjoittanut petteriIII »

kamara

  • Käyttäjä
  • Viestejä: 3008
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #306 : 03.07.23 - klo:12.37 »
Desimaalilaskut suoritetiin silloin aikoinaan kokonaisluvuilla isoissakin koneissa - joten kyllä senaikaiset virtuoosit tiesivät että myös BASH kykenee niihin. Mutta he välttelivät puuttumasta koko asiaan sillä se on sellainen salahautojen viidakko että paraskin virtuoosi haksahtaisi joskus ja sitä he eivät kestä - joutua naurettavuuden kaakinpuuhun. Niinpä he eivät puhuneet koko asiasta - mikä antaa sellaisen mielikuvan etteivät desimaalilaskut BASH:issa edes onnistu. Ja oli heillä laho selityskin: BASH:in matematiikkamoottori ei osaa desimaalilaskuja eikä BASH:in muuttujien tyyppimäärityksissäkään desimaaliluvuista mitään puhuta. Mutta tuo tyyppimääritys on tahallinen väärinymmärrys sillä tekstijonojahan ne desimaaliluvut vain ovat.
 
Toimivat ne desimaalilaskut BASH:issa ihan hyvin vaikkakin merkintätapa on epäsovinnainen - ja yleensä niitä käyttämällä saa aikaan nopeamman skriptin kuin jos turvatuisi matematiikka ohjelmiin. Mutta siis niitä salahautoja on: jakolaskua vaivaa esimerkiksi 'katoavien nollien syndrooma'. Ja kun nolla katoaa on senjälkeinen tulos pelkkää potaskaa.

Kaikki, mikä ei ole mahdotonta, ei ole järkevää. Itse teen bashilla vain pieniä scripteja, koska protoilussa se on tehokas kieli, mutta se  ei ole nopein (kuin koodauksessa) eikä selväkielisin.

Bash:n matematiikasta, silmukoista, ehto-lauseista saan vähintäänkin näppylöitä, ja jopa merkkijonojen käsittelyäkään en sillä mielellään tee.
Se, missä bash on mielestäni hyvä:
  • Nopea koodata.
  • Löytyy kaikista asiallisista käyttöjärjestelmistä, ja yleensä valmiiksi asennettuna.
  • Netistä löytyy runsaasti esimerkkejä, joita on helppo käyttää.
  • Shell exec-käskyä ei tarvita, vaan ohjelman ajamiseen riittää pelkkä ohjelman nimi,parametrit ja mahdollinen polku ajettavaan ohjelmaan.

Näistä huolimatta joudun usein googlaamaan merkkijonojen, silmukoiden ja ehto-lauseiden  käsittelyä varten, koska niissä bash ei loista.
(Jos meni liian OffTopic:n puolelle, tämän voineen siirtää testauspuolelle.)

petteriIII

  • Käyttäjä
  • Viestejä: 680
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #307 : 26.08.24 - klo:13.18 »
Sain koottua desimaaliluvuista muodostettujen 2-250 rivisten matriisien summaimen joka on melkein yhtänopea kuin BASH:in matematiikka-ohjelmat. Tottakai skriptillä on lukemattomia puutteita koska se on ensimmäinen laatuaan ja lisäksi ottaa ensi-askeliaan - mutta tuskin niitä puutteita tulee korjattua sillä eiväthän tällaiset skriptit ole enää merkityksellisiä - ja tämähän osoittaa jo nytkin että BASH on matikassa todella nopea - siis se osaa kutsua C:n avukseen niinkuin muutkin.

Skriptin ainoa tarkoitus on varmistaa uskomus siitä että BASH:in matikka on aikoinaan tuhottu tahallaan - ei ole mahdollista että kymmenet käskyt vain 'sattuvat' toimimaan aina oikein kun BASH:issa mikään muu ei toimi temppuilematta.

---

Skriptissä lasketaan summa desimaaliluvuista kootusta matriisista. Testattaess muodostetaan matriisi satunnaislukujen avulla joten se on jokaisella ajokerralla hieman erilainen joukko - jalopuksi sen jäsenien järjestyskin sekoitetaan (käskyllä shuf). Testattavassa matriisissa on tässä toteutuksessa jäseniä noin 260 mutta määrä vaihtelee jonkinverran kerrasta toiseen (jäsenien lukumäärä otetaan automaattisesti huomioon, ei sitä laskea tarvitse. Desimaalien yhteenlasku on paljon vaikeampaa kuin kokonaisosien yhteenlasku ja siksi kokonaisia ei edes ole koodia sotkemassa):

Funktioon:matsum toimitetaan matriisista vain nimi koska silloin parametrien palautuksesta ei tarvitse välittää. Ainoa kieli joka osaa toimia näin on BASH - eivätkä muut kielet pysty tunnustamaan olevansa sekundaa.
Koodia: [Valitse]

function matsum () { # ensin lasketaan matriisin jäsenluku ja senjälkeemn tehdään matriisin jäsenien desimaaliosista tekstijono muokaten sitä samalla:
apu2=$(declare -p $1); declare ${apu2:8:2} apu=${apu2#*=} # koska matriisista on tullut vain nimi täytyy se ensin yhdistää niihin arvoihin jotka saman-nimisellä matriisilla on.
lkm=${#apu[@]}; apu2=$(printf "%-16s+1" ${apu[@]#*.}); apu2=${apu2[@]// /0}; apu2=1${apu2%??}; echo $apu2
# ja sitten vain työnnetään muodostettu tekstijono matematiikkamoottoriin ja tulostetaan tulokset:
apu3=$(($apu2)); summa=${apu3:0: -16}.${apu3: -16} ; summa=$((${summa%%.*}-${#apu[@]})).${summa#*.}; echo; echo 'matriisin summa='$summa'  matriisissa on tälläkertaa jäseniä:'${#apu[@]}'  matriisin jäsenten keskiarvo on:'.$((10#${summa//./}/${#apu[@]})) ;}

# esimerkkimatriisin muodostaminen ja esimerkki-kutsu:
echo; echo; echo ensin muodostetaan matriisi kokeilua varten - sen jäsenten pituudet, lukumäärä ja järjestys vaihtelevat erikerroilla. Tämänkertaisen matriisin jäsenien desimaliosat esitettynä tekstijonona:
koematriisi=($({ seq .0111111  .01$RANDOM 1; seq .1211111 .005$SRANDOM 1 ; seq .111111111111111 .055555555555555 1 ;} | shuf | tr , . )) 
echo; time matsum koematriisi
echo ; echo tarkistukseksi bc:n laskema summa - sillä bc:n oikeellisuuteen voi luottaa aina:$( echo ${koematriisi[@]} | tr ' ' + | bc -l)

---

Kommentteja ei kuulu kirjoittaa skriptin koodissa vaan dokumenttina skriptin jälkeen. Jonkunlainen dokumentti skriptin varsinaisesta sielusta:
Koodia: [Valitse]
apu2=$(printf "%-16s+1" ${apu[@]#*.})
jakaa desimaalisen matriisin apu jäsenien desimaaliosat matriisiin apu2 lisäten jokaisen jäsenen perään niin monta välilyöntiä että jäsenestä tulee 16-merkkinen - ja se lisää myös jokaisen jäsenen eteen numeron 1 jotta myös luvut joiden alussa on nollia laskettaisiin oikein (desimaalien alussa voi olla nollia) - paitsi ensimmäisen jäsenen eteen se ei lisää numeroa 1 joten se on itse lisättävä myöhemmin. Nämä lisäykset on huomioitava loppu-tulostuksessa. Käsky myöskin lisää jokaisen luvun välilyöntien perään + merkin.
Koodia: [Valitse]
apu2=${apu2[@]// /0}
muuttaa sitten printf:n modostaman tekstijonon välilyönnit nolliksi joten jokaisesta jäsenestä tulee 16 numeroinen. Kumma juttu ettå se jättää lukujen välissä olevat välilyönnit rauhaan - eikun samalta näyttäviä välilyöntejähän on useampaa sorttia ja ehkä tässä on kyse siitä.

- koska apu2:n viimeisenä merkkinä ei saa olla + poistetaan se käskyllä:
Koodia: [Valitse]
apu2=1${apu2%??}
ja sama käsky lisää tekstijonon ensimmäiseksi numeroksi 1 .
 

petteriIII

  • Käyttäjä
  • Viestejä: 680
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #308 : 08.09.24 - klo:16.04 »
Katala juttu että BASH:ia väitetään kyvyttömäksi kuten seuraavasta pienestä näytteestä ilmenee - keskeneräistä tämä tosin vielä on muttei tässä viipyä ehdi:

Virheen rivinumeron tulostaminen on ehdottomasti tärkein asia virhe-rutiinin tehtävistä ja BASH-skripteissä sitä ei tulosteta. Perus-asennuksessa koneeseen kuitenkin tulee funktio nimeltään: command_not_found_handle - ja se toiminta on seuraava: kun ohjelmassa viitataan kirjoitus -tai muistivirheen takia sellaiseen jota ei ole niin BASH siirtää silloin toiminnan command_not_found_handle:lle joka tulostaa: command not found. Voit tulostaa tuon funktion käskyllä:
Koodia: [Valitse]
declare -f | grep -A 14 command_not_found_handle
Sieltä ilmenee että toiminta siirtyy koodiin tiedostossa: /usr/lib/command-not-found silloin kun se on olemassa - ja Ubuntussa on. Tiedoston koodi on Python3-koodia eikä se kuitenkaan osaa tulostaa virheen rivinumeroa. Mutta BASH osaa tulostaa millä rivillä virhe on tapahtunut - ja mikäli tuon command_not_found_handle tiedoston sisältö korvattaisiin BASH koodilla olisi kaikki toiminut aina - mutta kovalevyn systeemi-tiedostoihin kajomatta virherivin tulostamisen voi tällä-hetkellä aikaansaada vain lisäämällä BASH-skriptin alkuun kaksi yksirivistä funktiota:
Koodia: [Valitse]
function jup () { jxx=${BASH_LINENO[@]} ;}; jup
function command_not_found_handle () { apu="${BASH_LINENO[@]}"; echo 'virheen rivi on: '$((${apu##* }-$jxx+1)); [[ ${FUNCNAME[@]:1} ]] && echo funktio-pino:${FUNCNAME[@]} ;}
Koodia: [Valitse]

- mitään muuta ei tarvitse tehdä sillä tämänjälkeen kone hoitaa itse kaiken.
- tarvitaan kaksi funktiota sillä ${BASH_LINENO[@]} on suoritettujen rivien määrä päätteen avaamisesta lähtien ja ${BASH_LINENO[@]}:lla on arvo vain funktiossa oltaessa.
- tämä ilmoittaa rivinumeron silloin kun koodi viittaa johonkin jota ei ole - muu toiminta jää entiselleen. Ja mikäli virhe tapahtuu funktiossa niin tulostetaan funktio-pino.
- mutta funktio voidaan määritellä uudestaan vaikka jokaisessa skriptissä.
- sanaa function ei tarvitse funktio-määrittelyssä olla sillä tulkki ymmärtää muutenkin että kyse on funktiosta.
- BASH:illa ei ole rivinumeroita. Kyseessä on se rivinumero jonka editori pyydettäessä antaa - siis rivin järjestysnumero.
- siihenaikaan samaan muuttujaan ahdetiin mahdollisimman monta tietoa. Niinpä virheen tapahtussa funktiossa ensiksi tulee funktio-taso "${BASH_LINENO[@]":ssa mikäli virhe on tapahtunut funktiossa.
- funktio-tasolla on joku tajuttoman suuri maksimi joten funktio-pino voi olla pitkäkin.
- jos funktiossa tehdään virhe tulostuu kutsurivin rivinumero. Tämä johtuu siitä että kun skriptissä kutsutaan funktiota ei hypätä mihinkään vaan kopioidaan kutsutun funktion koodi kutsun paikalle. Tulkki muuten käsittää jokaisen skriptin yksirivisenä ja niinpä koko funktio on yhdellä rivillä - näet tämän hyvin kun skriptin suorituksen jälkeen painat nappulaa 'nuoli ylöspäin' - sieltä tulee koko skripti yksirivisenä. Muuten monilla on tapana lähettää toisille yksinomaan näitä yksirivisiä - niissä isokin skripti mahtuu yhdelle sivulle
.
Kokeile:
Koodia: [Valitse]
function jup () { jxx=${BASH_LINENO[@]} ;}; jup   
function command_not_found_handle () { apu="${BASH_LINENO[@]}"; echo 'virheen rivi on: '$((${apu##* }-$jxx+1)); [[ ${FUNCNAME[@]:1} ]] && echo funktio-pino:${FUNCNAME[@]} ;}
function koe () { matmax 1 2 ;}
#
c=0
koe    # ja koeta mitä tekee jos tässä ei kutsukaan funktiota vaan kirjoittaa suoraan: matmax 1 2
b=2
#

---

Mutta virhe-käyttäytyminen on sellainen miinakenttä että command_not_found_handle:a täytyy täydentää ikuisesti, esimerkiksi:
Koodia: [Valitse]
function command_not_found_handle () {
echo 'virheen rivi on: '$((${BASH_LINENO[@]}-$jxx+1))
# sitten koetetaan jos sana on jonkun tunnetun käskyn nimi jota ei vain koneessa ole ja jos se löytyy niin ladataan vastaava paketti paketti-järjestelmästä:
[[ $( apt-cache show "$komennon_nimi" | grep "Package:" ) ]] && { echo 'Koneessasi ei ole toimintoon tarvittavaa pakettia nimeltään:'$komennon_nimi'. Sen hakemiseksi tarvitsen salasanasi. Toiminnon jälkeen aja skripti uudelleen'; sudo apt-get install "$komennon_nimi" || echo tuntematon komento ;}
# mikäli olet tehnyt itsellesi kirjaston niin seuraavaksi etsitään kirjastosta:
[[ -x ~/OMATSKRIPTIT ]] && {
apu=("$(ls --hide=ARKISTO ~/OMATSKRIPTIT/FUNKTIOKIRJASTO )")
for n in ${apu[@]}; do
apu=$( grep $komennon_nimi ~/OMATSKRIPTIT/FUNKTIOKIRJASTO/$n)
[[ "$apu" ]] && echo "kirjoita skriptisi alkuun yksi näistä käskyistä: . ~/OMATSKRIPTIT/FUNKTIOKIRJASTO/$n/$komennon_nimi"
done ;}
}

petteriIII

  • Käyttäjä
  • Viestejä: 680
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #309 : 10.09.24 - klo:09.49 »
 Sain lopultakin kasattua sellaisen desimaali-matriisin jäsenten yhteenlaskun joka ottaa huomioon myös lukujen konaisosan. Eikä tämä häviä muille kielille ihan mahdottomasti mutta ei tämmöisillä laskuilla merkitystä enää ole. Paitsi sen osoittamisessa että BASH tuhottiin jo 30 vuotta sitten - tietäen että tämmöiset toimivat jo siloin ja olivat ihan kelvollisia senajan vaatimusten mukaan - ja mitähän kaikkea vielä paljastuukaan.
- kopioi koko koodi-alue päätteeseen yhdellä kertaa ja paina enter
Koodia: [Valitse]

clear; koematriisi=($(for n in {1..170}; do apu1=$SRANDOM; apu2=$SRANDOM; echo $((1${apu1:9}${apu2:8}**4)).$((1${apu2:9}${apu1:8}**4)); done)); echo; echo yhteenlaskettavien joukko:; echo; echo ${koematriisi[@]} | xargs -n$(($(tput cols)/35)) | column -t; echo
# selitystä: | xargs -n$(($(tput cols)/35)) | column -t muodostavat BASH:in super-hyvän formatointi-ohjelman joka formatoi vasta tuloksen

function desisumma () { apu=($( eval echo \${$1[*]#*.})); lkm=${#apu[@]}; apu=$(printf "1%-16s+" ${apu[@]}); apu=$(echo ${apu// /0}); apu=${apu%+}; apu=$(($apu-$lkm*10000000000000000)); echo ;}

function kokosumma () { echo -n 'matriisin jäsenien kokonais-summa on: ';bpu=($( eval echo \${$1[*]%.*})); bpu=${bpu[@]}; echo $((${bpu// /+}+${apu:0: -16})).${apu: -16} ;}; time { desisumma koematriisi; kokosumma koematriisi ;}

time echo 'tarkistukseksi bc:n laskema summa   : '$( echo ${koematriisi[@]} | tr ' ' + | bc -l) 
tuloste on jotain tämän kaltaista (sillä jokaisella ajo-kerralla muodostetaan ensin uudet numerot):

yhteenlaskettavien joukko:

10427790765681.9314758936081  12280707219456.2504508814096   8195990282496.2488619831296   1249198336.1121513121          5861899530496.7871531640625
2520473760000.1421970391296   13448539200625.5185097880561   2025637716001.3743764484161   2457068790016.2229897104656    959512576.796594176
3787013300625.6439662447201   3491998578721.9660714532561    157351936.276922881           1642047467776.2449228130001    1536953616.1171350625
260144641.875213056           83521.187388721                705911761.126247696           395254161.322417936            157351936.1330863361
623201296.1275989841          9336104694016.11637711365281   252047376.875213056           1222830961.479785216           11840653050625.6044831973376
5580787292161.9926437890625   10000.38416                    3185853730816.7648371893761   12072122454016.4275978808336   163047361.1073283121
6327217036816.7146131900625   2356596484641.15122640998656   607573201.1026625681          2052941630481.9187452028561    2296318960321.1951955471376
221533456.221533456           2496554842401.8792909200656    2480703750625.5653761639696   1385858700625.5337948160000    130321.28561
2425818710016.11512204705296  7394054078401.13476652155136   373301041.418161601           1427186233201.2944999210000    1057187014416.3841600000000
112550881.303595776           15494111297536.4915625528641   1026625681.1445900625         1146228736.38416               13932481925376.2593262550321
14252505012001.3512479453921  11764241449216.2585098014976   2303789694976.2536514910736   6455847578896.4359848400625    1097199376.623201296
592240896.1171350625          14223186420496.2593262550321   2760652033441.15431729448976  362673936.1171350625           168896016.442050625
1861107122176.11738853292401  429981696.479785216            8550360810000.1396105301761   2266617569841.10312216477696   981506241.777796321
10106606869921.3700887165361  705911761.303595776            14641.28561                   835210000.104060401            1501725251601.8978179242321
9019744817521.2918114646001   7076456545921.1551160647936    260144641.875213056           2769228810000.1178883463696    547981281.244140625
759333136.688747536           3852587765601.1485512441856    14164684960000.1121144263281  655360000.108243216            11338204200625.5451216326656
815730721.181063936           3896774700625.6455847578896    130321.1026625681             104976.193877776               6504586067281.8854344140625
1387488001.373301041          50625.83521                    1365534810721.1749006250000   1121513121.373301041           1156418486161.8957450410000
6802837650625.5712654493456   2237205241441.2873716601616    1355457106081.14757890560000  1020150500625.6232012960000    937890625.1222830961
104976.136048896              11840653050625.6044831973376   577200625.562448656           916636176.454371856            13990263006736.3941340648961
136048896.1049760000          8118761230336.11117396444176   193877776.1196883216          236421376.429981696            855036081.174900625
1647857448721.3637263079921   3282314864656.6602890412881    181063936.741200625           11512204705296.2200843458576   174900625.151807041
136048896.1146228736          937890625.592240896            11239665258721.1618961043456  6652458343696.8273769005056    3797883801856.6602890412881
9314758936081.10427790765681  562448656.244140625            3341233033216.2094413889681   10243334671441.15904215784081  10000.65536
104060401.163047361           1032386052096.11968832160000   8312865305616.10875854196736  492884401.168896016            126247696.835210000
1121513121.1536953616         15936095936016.13004685652401  38416.10000                   7520406736896.6990080303376    50625.65536
2626114239841.3054399363856   1003875856.1222830961          3647809685776.2387176412401   5076013506001.1971847850625    1073283121.193877776
9208578670096.2418052990081   146410000.519885601            8752130560000.1448193221281   3637263079921.2025637716001    3963766428241.1712789917696
6184815851041.8650804500625   577200625.1445900625           1234134359056.4669488810000   688747536.1475789056           562448656.244140625
815730721.130321              1016096256256.3841600000000    959512576.466948881           130321.14641                   1165365589441.13032100000000
83521.981506241               14223186420496.2675975777281   981506241.1506138481          506250000.121550625            2394867671296.4205058789376
7611645084241.1912622616576   4275978808336.10828022035216   1171350625.592240896          28561.28561                    639128961.1536953616
3311675679601.13674678889041  104976.83521                   121550625.50625               577200625.533794816            1475789056.141158161
533794816.442050625           3214565712241.13961350047121   146410000.108243216           1003875856.1506138481          50625.130321


matriisin jäsenien kokonais-summa on: 527257947110132.9002487349303500

real   0m0,006s
user   0m0,004s
sys   0m0,002s
tarkistukseksi bc:n laskema summa   : 527257947110132.90024873493035

real   0m0,002s
user   0m0,001s
sys   0m0,003s


petteriIII

  • Käyttäjä
  • Viestejä: 680
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #310 : 12.09.24 - klo:08.48 »
Skriptaajat on peloteltu uskomaan että BASH:illa ei oikeastaan matikkaa olekaan - mutta itseasiassa BASH:in matikallakin on terve perusta - sitä ei vaan kaivata kilpailemaan valmis-ohjelmien kanssa - ja nykyään se on käytön-puutteessa surkastunut.

- käsitelläänpä välillä BASH:in matematiikan nopeutta:annapa käsky: apu=$( seq -s+ 1000000); time echo $(($apu)) - se laske yhteen luku kerrallaan yhdestä miljoonaan ja kestää minun huonolla koneellani 129ms elikäs 129 nanosekuntia yhteenlaskulta - plus, miinus, kerro, jaa ja ota jakojäännös kestävät kaikki suurinpiirtein samanverran. Suoritus-ajan yläraja on käsitykseni mukaan noin 50 mikrosekuntia jos laskee yhteen vain kaksi lukua. Joten tällä välillä liikutaan - aika riippuu siitä kuinka lasku määrätään suoritettavaksi. Luvuissa voi olla korkeintaan 18 numeroa ja nekin pelkästään kokokonaislukuja - mutta 36 numeroinen liukuvan pilkun kertolasku, todella moninumeroinen desimaalilukujen jakolasku ja 72 numeroinen desimaalilukujen yhteenlasku toimivat omissa funktioissaan nopeudella luokkaa 1ms. Yhdeksän numeroinen neliöjuuri vie kyllä jo 1-3ms.

Ja sekin erinomainen piirre BASH:in numeron-murskaamisessa on että jokainen kuviteltavissa oleva tehtävä on ratkaistavissa lukemattomilla täysin erilaisilla tavoilla. Mutta tämä aikaansaa sellaisen ongelman että kun alkaa tutkia jotakin niin päätyy usein tutkimaan jotakin ihan muuta.
Aloin tutkia kuinka BASH selvittää virhetilanteita ja päädyin tutkimaan desimaali-matriiseita. Ja osoittautui että kun esimerkiksi nyt esitettävä tehtävä muodostuu 6:sta osatehtävästä joista jokainen kestää 10 ms on kokonaisaika silti alle 20ms - ja samaa aikaa tarjoaa myös ajoitus-ohjelma nano - selitys nopeudelle löytynee time-käskyn tulosteesta: user+sys=2*real - ja ainakin sys-alueella tehdään rinnakkais-prosessointia.

Tarkoituksena on tutkia kuinka desimaali-luvuista muodostuneesta matriisista kannattaa etsiä maksimeita ja minimeitä:
Koodia: [Valitse]
### aluksi muodostetaan matriisi josta sitten etsitään:
clear; koematriisi=($(for n in {1..170}; do apu1=$SRANDOM; apu2=$SRANDOM; echo $((1${apu1:9}${apu2:8}**4)).$((1${apu2:9}${apu1:8}**4)); done)); echo; echo yhteenlaskettavien joukko:; echo; echo ${koematriisi[*]} | xargs -n$(($(tput cols)/35)) | column -t; echo  # selitystä: xargs -n$(($(tput cols)/35)) | column -t muodostavat BASH:in super-hyvän formatointi-ohjelman joka muotoilee jo tehdyn tulosteen - se siis nappaa tulosten näyttö-puskurista, muotoilee sen silmää miellyttävään kuntoon ja palauttaa takaisin näyttöpuskuriin josta se aikanaan päätyy näyttöön - tulos on kotikoneella ajettuna paljon parempi kuin miltä foorumin tulosteesta näyttää.

### sitten aloitetaan varsinainen etsintä:
function minmax (){ echo -n 'matriisin desimaali-osien maksimi on: '; eval printf "%s\\\n" \${$1[*]#*.} | sort -d | tail -1 ;
echo -n 'matriisin kokonais-osien maksimi on: '; eval printf "%s\\\n" \${$1[*]%.*} | sort -n | tail -1 ;
echo -n 'koko matriisin maksimi on: '; eval printf "%s\\\n" \${$1[*]} | sort -d | tail -1 ;
echo
echo -n 'matriisin desimaali-osien minimi on: '; eval printf "%s\\\n" \${$1[*]%.*} | sort -d | head -1 ;
echo -n 'matriisin kokonais-osien minimi on: '; eval printf "%s\\\n" \${$1[*]#*.} | sort -n | head -1 ;
echo -n 'koko matriisin minimi on: '; eval printf "%s\\\n" \${$1[*]} | sort -n | head -1 ;}; time minmax koematriisi


- 'sort -d':n -d on selväkielisenä: --dictionary-order. Kielitieteilijä-matemaatikko saisi siitä ehkä tolkkua mutta netit ja sanakirjat eivät saa. Toiminnaltaan se on desimaali-sorttaus - siis se ottaa huomioon että desimaaleilla numeroiden paikka-arvo on laskeva - mutta kokonaisosan numeroiden paikka-arvo on nouseva.

« Viimeksi muokattu: 12.09.24 - klo:14.13 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 680
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #311 : 16.09.24 - klo:12.22 »
Aikoinaan väitin että funktion koodin voi kirjoittaa kutsun paikalle joten nyt mukaelin edellistä skriptiä sillätavoin - tai tässä tapauksessa päinvastoin erotin koodipätkiä funktioihin. Vaihto onnistui heti ja hämmästyksekseni laskenta myös nopeutui hieman - kuvittelisin että nopeutus on seurausta siitä ettei yksittäisiä käskyjä voida lukea välimuistiin - tai ei kannata sillä oikeellisuutta ei voi todeta - mutta funktiot voidaan lukea välimuistiin. 
- lisäksi koodi on paremmin jäsennelty - joten koodin toiminnan ymmärtää helpommin.
- esimerkiksi kun matriisin koko on 170000 jäsentä kestää varsinainen lasku vain nelisen sekuntia - sekin kyllä aivan liian pitkä aika. Mutta aikaisemmin kukaan ei ole edes kuvitellut tekevänsä tämmöistä BASH:illa koska se kestäisi normaalisti käytettävillä keinoilla tunteja.
Koodia: [Valitse]
# ensin tehdään matriisi josta sitten etsitään:
clear; koematriisi=($(for n in {1..170000}; do apu1=$SRANDOM; apu2=$SRANDOM; echo $((1${apu1:9}${apu2:8}**4)).$((1${apu2:9}${apu1:8}**4)); done)); echo; echo yhteenlaskettavien joukko:; echo; echo ${koematriisi[*]} | xargs -n$(($(tput cols)/35)) | column -t; echo

# ja sitten etsitään:
function tulosta_kokonaiset () { eval printf "%s\\\n" \${$1[*]%.*} ;} #1
function tulosta_desimaalit () { eval printf "%s\\\n" \${$1[*]#*.} ;} #2
function tulosta_koko_matriisi () { eval printf "%s\\\n" \${$1[*]} ;} #3
function minmax (){ echo -n 'matriisin desimaali-osien maksimi on: .'; tulosta_desimaalit $1 | sort -d | tail -1 ;
echo -n 'matriisin kokonais-osien maksimi on: '; tulosta_kokonaiset $1 | sort -n | tail -1 ;
echo -n 'koko matriisin maksimi on: '; tulosta_koko_matriisi $1 | sort -n | tail -1 ;
echo
echo -n 'matriisin kokonais-osien minimi on: '; tulosta_kokonaiset $1 | sort -d | head -1 ;
echo -n 'matriisin desimaali-osien minimi on: .'; tulosta_desimaalit $1 | sort -n | head -1 ;
echo -n 'koko matriisin minimi on: '; tulosta_koko_matriisi $1 | sort -n | head -1 ;}; time minmax koematriisi

- funktiot #1 , #2 ja #3 muodostuvat yhdestä käskystä - eval ei ole käsky vaan se kehottaa kääntäjää tulkitsemaan eval:ia seuraavan käskyn 2 kertaa - keno suojaa ensimmäisellä kerralla seuravaa merkkiä tulkinnalta mutta suojaamattomat tulkitaan - jonka jälkeen suojana olevat kenot poistetaan joten toisella kerralla tulkitaan kaikki. Muuten \\\n tulkitaan seuraavasti: \n on n ja \\ on \ elikä yhdessä \n elikä rivinsiirto.
 

petteriIII

  • Käyttäjä
  • Viestejä: 680
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #312 : 18.09.24 - klo:09.37 »
Virtuoosit haukkuivat aikoinaan maanrakoon sen tavan jolla BASH:in direktiivi: eval on toteutettu. Eihän taviksilla ole mitään mahdollisuutta tarkistaa haukkumien oikellisuutta vaan on pakko uskoa ja käyttää toisia keinoja. Tässä eval on korvattu käskyllä:
Koodia: [Valitse]
$(declare -p $1); declare ${apu2:8:2} apu=${apu2#*=}
ja muuttamalla käskyssä myöhemmin olevat $1:t $apu:ksi ja poistamalla turhiksi tulleita keno-viivoja. Muuten skripti on sama kuin edellinen.
- tosin toiminta näillä pidemmillä määrityksillä on hieman hitaampaa.
- muuten vielä 20% nopeuden-muutokset hukkuvat BASH:in epämääräisyyksiin:
Koodia: [Valitse]
# ensin tehdään taulukko josta sitten etsitään - ja vaikka taulukko muodostetaan satunnaisesti tiedetään suunnilleen mitä siellä on.
#
clear; koematriisi=($(for n in {1..170}; do apu1=$SRANDOM; apu2=$SRANDOM; echo $((1${apu1:9}${apu2:8}**4)).$((1${apu2:9}${apu1:8}**4)); done)); echo; echo yhteenlaskettavien joukko:; echo; echo ${koematriisi[*]} | xargs -n$(($(tput cols)/35)) | column -t; echo
#
# selvitystä tuosta | xargs -n$(($(tput cols)/35)) | column -t:sta: tulostettaessa taulukkoa BASH:issa ei yleensä kannata yrittääkään muotoilla tulosteita vaan kirjoittaa
# jokaisen tulostetun numeron perään välilyönti ja kirjoittaa skriptin perään: | column -t . 'column -t' on BASH:in muotoilu-ohjelma joka lisää jokaisen välilyönnin
# kohdalle niin monta välilyöntiä lisää että tulostuksesta tulee muotoiltu siten kuin jos tulostuskäskyissä olisi ollut hyvä muotoilu.
#
# mutta tässä tuo xargs vie homman astetta pidemmälle ottamalla huomioon sen näytön leveyden joka on sillä monitorilla jossa se toimii:  koska xargs ei viittaa mihinkään
# ohjelmaan niin oletetaan että se viittaa echoon. Tavallaan 'xargs -n' laskee välilyöntejä ja muuntaa -n:än jälkeisen muuttujan määräämin välein välilyönnin
# rivinsiirroksi. Tässä tuo muuttuja on: $(($(tput cols)/35)) joka ensin selvittää sen monitorin rivinpituuden jossa se annetaan ja kertoo siten kuinka monta 35:n merkin
# pituista ryhmää riville mahtuu. Näin tulee millä näytöllä tahansa siisti taulukko - tai ainakin sellainen josta selviää välittömästi numeroiden erilaisuus - muuten
# tällainen muotoilu ei taulukoilla juuri koskaan epäonnistu. 

   function tulosta_kokonaiset () { apu2=$(declare -p $1); declare ${apu2:8:2} apu=${apu2#*=}; printf "%s\n" ${apu[@]%.*} ;}
   function tulosta_desimaalit () { apu2=$(declare -p $1); declare ${apu2:8:2} apu=${apu2#*=}; printf "%s\n" ${apu[@]#*.} ;}
function tulosta_koko_matriisi () { apu2=$(declare -p $1); declare ${apu2:8:2} apu=${apu2#*=}; printf "%s\n" ${apu[*]} ;}
function minmax (){
echo -n 'matriisin desimaali-osien maksimi on: .'; tulosta_desimaalit $1 | sort -d | tail -1 ;
echo -n 'matriisin kokonais-osien maksimi on: '; tulosta_kokonaiset $1 | sort -n | tail -1 ;
echo -n 'koko matriisin maksimi on: '; tulosta_koko_matriisi $1 | sort -n | tail -1 ;
echo
echo -n 'matriisin kokonais-osien minimi on: '; tulosta_kokonaiset $1 | sort -n | head -1 ;
echo -n 'matriisin desimaali-osien minimi on: .'; tulosta_desimaalit $1 | sort -d | head -1 ;
echo -n 'koko matriisin minimi on: '; tulosta_koko_matriisi $1 | sort -n | head -1 ;}; time minmax koematriisi

petteriIII

  • Käyttäjä
  • Viestejä: 680
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #313 : 20.09.24 - klo:13.14 »
Koodia: [Valitse]
En ole aikaisemmin laittanut foorumille skriptiä desimaalilukujen potenssiin korottamista varten sillä yksinkertaisen skriptin lukualue on onnettoman pieni eikä se korkeampia potensseja pysty käsittelemään
ollenkaan - skripti on kylläkin tosi-nopea. Myöhemmin lisään koodiin suurempien lukujen ratkaisemiseksi tarvittavat osat.
- jos potenssiin korotettava luku on kokonaisluku niin tässä skriptissä sen perään lisätään .0 . Elikä: [[ $1 =~ \. ]] || set -- $1'.0' $2 -> set muodostaa uuden parametrijoukon (elikä nuo $1 $2 $3 $4 ... nimiset) sille annetuista muuttujista - nollaten kaikki vanhat - siis kaikki jolle halutaan joku arvo on liitettävä mukaan - joten jos tässä ei olisi lopussa tuota $2:ta niin $2 jäisi määrittelemättä - mutta tällätavoin uusi $2 saa vanhan $2:n arvon.
- kaikilla käsyillä voi olla parametreja ja niin myös käskyllä: set. Tässä heti set:in jälkeen on -- ja se kertoo tulkille että set:in omat parametrit on tässävaiheessa jo annettu joten seuraavassa luvussa mahdollisesti oleva - on miinusmerkki.
- muuten: ehto && teko1; teko2 || teko3 täytyy yleensä merkitä: ehto && { teko1; teko2 ... ;} || teko3 tai se merkitsee ihan muuta kuin on tarkoitus.
- laskettavien edessä on usein:10# . Se johtuu siitä että luvun ensimmäinen merkki saattaa desimaaleilla toimittaessa olla nolla ja silloin siirryttäisiin laskuissa oktaali-järjestelmään ellei olisi käskyä pysyä desimaalijärjestelmässä.
function potenssi () { [[ $1 =~ \. ]] || set -- $1'.0' $2; tulos=000000000000000000$((${1//./}**$2)); des=${1#*.};[[ $(($2*${#des})) -lt 19 ]] && { apu=${tulos:0: -$(($2*${#des}))}.${tulos: -$(($2*${#des}))}; apu=${apu##+(0)}; apu=${apu%%+(0)}; echo ${apu%.} ;} || echo liikaa numeroita ;}
 
# tarkistuksia:
a=-.2; b=10; time potenssi $a $b    # .0000001024
a=.2; b=10; time potenssi $a $b     # .0000001024
a=-2; b=10; time potenssi $a $b     # 1024
a=2; b=10; time potenssi $a $b      # 1024
a=2.0; b=10; time potenssi $a $b    # 1024
a=4.9999; b=4; time potenssi $a $b  # 624.9500014999800001  # time bc -l <<< "4.9999^4"
a=0.11; b=9; time potenssi $a $b    # .000000002357947691
a=2.11111; b=4; time potenssi $a $b # liikaa numeroita
[/code]