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

nm

  • Käyttäjä
  • Viestejä: 16447
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #300 : 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ä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #301 : 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ä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #302 : 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ä: 743
  • Talous, Hallinto ja Markkinointi (AMK, 2017),B.B.A
    • Profiili
    • Tietokone-blogi
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #303 : 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ä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #304 : 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ä: 3035
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #305 : 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ä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #306 : 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ä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #307 : 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ä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #308 : 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ä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #309 : 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ä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #310 : 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ä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #311 : 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ä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #312 : 20.09.24 - klo:13.14 »
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äskyillä 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 kun luvussa on etunolla siirryttään laskuissa oktaali-järjestelmään ellei ole käskyä pysyä desimaalijärjestelmässä. Sitä kutsutaan oktaali-ansaksi ja sen inhottavin piirre on se että mikäli laskettavissa ei ole numeroita 8 tai 9 lasku onnistuu ihan hyvin - mutta tulos on oktaaliluku vaikka sitä luullaankin desimaaliluvuksi. Ja koska kyse on BASH:ista niin tottakai tuolla 10#:llä on poikkeuskin: negatiiviset luvut eivät sitä tarvitse ja peräti aiheuttavat virheen. Siksi tuo pikä litania:
Koodia: [Valitse]
[[ ${1:0:1} == - ]] && tulos=000000000000000000$((${1//./}**$2)) || tulos=000000000000000000$((10#${1//./}**$2))

Ja sitten itse skripti:
Koodia: [Valitse]
function potenssi () { [[ $1 =~ \. ]] || set -- $1'.0' $2; [[ ${1:0:1} == - ]] && tulos=000000000000000000$((${1//./}**$2)) || tulos=000000000000000000$((10#${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 liian iso luku;}
 
# 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=0.9; b=9; time potenssi $a $b     # .387420489
a=2.11111; b=4; time potenssi $a $b # liikaa numeroita
« Viimeksi muokattu: 21.09.24 - klo:10.21 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #313 : 24.09.24 - klo:10.39 »

Mikäli luvun numerot ylittävät 78:n tai 7.8:n  ei äskeisellä nopealla skriptillä kykene laskemaan edes kymmenenteen potensiin - vaan joutuu turvautumaan isoihin funktioihin. Alku on kuitenkin sama - ensin koetetaan tuota nopeaa ja jos siltä pyydetään liikaa niin siirrytään vapaaehtoisesti isoihin. Niillä saa sentään tuloksen 16 ensimmäistä numeroa oikein kun potensi on alle 60 - eivätkä ne koostaan huolimatta kovin hitaitakaan ole:
- suunnattoman kehnoahan tämä edelleen on - mutta ensisijainen tarkoitus onkin tutkia kuinka BASH saa ongelmat ratkaistua - ja tosiaan tuntuu siltä ettei tämä tähän jää.
- mikä skripteistä tuloksen antaakin niin siinä on noin 16 oikeaa desimaalia
- eihän tämä nopeaa ole, mutta BASH-skriptiksi ihan kelvollisen nopeaa.

Tämä tosiaan painottaa kuinka katala temppu 'kirjastokielto' on: kun muilla kielillä tämä tehdään yhdella käskyllä niin BASH:illa joutuu tekemään 60 lausetta vaikka tulos ei ole vieläkään kovin käyttökelpoinen. Esimerkiksi:
Koodia: [Valitse]
function potenssi () { [[ $1 =~ \. ]] || set -- $1'.0' $2; [[ ${1:0:1} == - ]] && tulos=000000000000000000$((${1//./}**$2)) || tulos=000000000000000000$((10#${1//./}**$2)); des=${1#*.};[[ $(($2*${#des})) -lt 19 ]] && { apu=${tulos:0: -$(($2*${#des}))}.${tulos: -$(($2*${#des}))}; apu=${apu##+(0)}; apu=${apu%%+(0)}; echo ${apu%.} ;} || iso_potenssi $1 $2 ;}

function kerro18 () {
tulosta=: # yhdessä paikassa päätetään tulostetaanko monessa paikassa välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
luku1=${1:0:18}; luku2=${2:0:18}
# [[ ${#1} -gt 18 || ${#2} -gt 18 ]] && echo laskettavissa liikaa numeroita && return
$tulosta "annetut numerot: $1 $2"
[[ ${1:0:1} = - || ${2:0:1} = - ]]  && merkki=- || merkki='' 
[[ ${1:0:1} = - && ${2:0:1} = - ]]  && merkki='' 
apu1=${luku1//\-/}; apu2=${luku2//\-/}
desimaaliosa1=${luku1##*.};
desimaaliosa2=${luku2##*.};
[[ ! ${apu1//[^.]/} ]] && desimaaliosa1=''
[[ ${apu1//[^.]/} ]] && { luku1=${apu1:0:18}; kokonaisluku=0 ;} || { luku1=${apu1:0:18}"."; kokonaisluku=1 ;}
[[ ! ${apu2//[^.]/} ]] && desimaaliosa2=''
[[ ${apu2//[^.]/} ]] && { luku2=${apu2:0:18}; kokonaisluku=0 ;} || { luku2=${apu2:0:18}"."; kokonaisluku=$(( 1 & $kokonaisluku )) ;}
 desimaaleja=$((${#desimaaliosa1}+${#desimaaliosa2})); $tulosta desimaaliosa1:$desimaaliosa1"   desimaaliosa2:"$desimaaliosa2"   desimaaleja:"$desimaaleja
luku1=000000000000000000${luku1//./}
luku2=000000000000000000${luku2//./}
a=${luku1: -18:9}; b=${luku1: -9}
c=${luku2: -18:9}; d=${luku2: -9}; $tulosta $a' '$b; $tulosta $c' '$d
luku1=00000000000000000000000000000000000000$((10#$b*10#$d))
luku2=00000000000000000000000000000000000000$((10#$d*10#$a))"000000000"
luku3=00000000000000000000000000000000000000$((10#$c*10#$b))"000000000"
luku4=00000000000000000000000000000000000000$((10#$a*10#$c))"000000000000000000"
luku1=${luku1: -36} ; $tulosta $luku1
luku2=${luku2: -36} ; $tulosta $luku2
luku3=${luku3: -36} ; $tulosta $luku3
luku4=${luku4: -36} ; $tulosta $luku4; $tulosta
luku11=${luku1:0:18}
luku12=${luku1:18}; $tulosta a$luku11' 'b$luku12
luku21=${luku2:0:18}
luku22=${luku2:18}; $tulosta c$luku21' 'd$luku22
luku31=${luku3:0:18}
luku32=${luku3:18}; $tulosta a$luku31' 'b$luku32
luku41=${luku4:0:18}
luku42=${luku4:18}; $tulosta c$luku41' 'd$luku42;$tulosta
summa1=$((10#$luku12+10#$luku22+10#$luku32+10#$luku42)); $tulosta summa1:$summa1
summa1pituus=${#summa1}; ylivuoto=0; (( $summa1pituus >= 19 )) && ylivuoto=${summa1:0: -18} && summa1=${summa1:1}
summa1=000000000000000000$summa1; summa1=${summa1: -18} ;$tulosta ylivuoto:$ylivuoto' summa1:'$summa1
summa2=$((10#$luku11+10#$luku21+10#$luku31+10#$luku41+$ylivuoto)); $tulosta summa2:$summa2
# echo; echo tulos laskusta: $1 \* $2'  . Ylärivi on bc:stä ja alarivi tästä skriptistä:'
# bc<<<"scale=40; $1*$2"
(( $summa2 )) && : || summa2=000000000000000000
(( $kokonaisluku )) && tulos=${summa2/*(0)/}$summa1 || { apu=$summa2$summa1; tulos=${apu:0: -$desimaaleja}.${apu: -$desimaaleja} ;}
echo $merkki${tulos##+(0)} ;}

function iso_potenssi () {
apu=$( kerro18 $1 $1 )
for (( n=2; n<$2; n++ )); do
apu=$( kerro18 $apu $1)
done
[[ $1 = 0 ]] && { apu=1; [[ $2 = 0 ]] || apu=0 ;} # sopimus on että:  0 ^ 0 = 1 -> mutta 0 mihin tahansa muuhun potenssiin on nolla
echo; echo ---; echo ; echo "lasku on: $1^$2"
echo -n "bc:n laskema varmasti oikea tulos: ";bc -l<<<"$1^$2"
echo    "tämän  skriptin   laskema   tulos: "${apu:0:19} ;}

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=10; time potenssi $a $b   # .000000002357947691
a=0.9; b=9; time potenssi $a $b     # .387420489
#
# edellä esitetyt on ratkaistu aikaisemmin esitetyllä nopealla skriptillä ja tämänjälkeen laskenta siirtyy siihen isoon ja hitaaseen - mutta kyvykkäämpään. 
#
time potenssi 2 60 # jo 61 höpertää ja siitä ylöspäin vain tusina numeroita tuloksen alussa on oikein mutta suuruusluokkakin väärin
time potenssi 2.5 30
time potenssi -2 2
time potenssi -2 3
time potenssi 9.99999 18
time potenssi 0.987654321098765 10

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #314 : 30.09.24 - klo:12.44 »
Parantelin 36-numeron liukuvan pilkun kertolaskua - sen kerrottaviksi voi antaa kaksi liukuvan pilkun 18-numeroista kerrottavaa. Poistin vanhan koodin ja laitoin tähän uuden.

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

- esimerkiksi tulos laskusta: .00900007567899123 * 900.07000012345678  on: 8.1006981175007567491845709040394   - ja lasku kestää vain 1ms - bc:llä lasku  kestää 3ms.
Koodia: [Valitse]


function kerro18 () {
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
[[ ${#1} -gt 18 || ${#2} -gt 18 ]] && echo laskettavissa liikaa numeroita && return
$tulosta "annetut numerot: "$1 $2
[[ ${1:0:1} = - || ${2:0:1} = - ]]  && merkki=- || merkki=''
[[ ${1:0:1} = - && ${2:0:1} = - ]]  && merkki=''
apu1=${1//\-/}; apu2=${2//\-/}
desimaaliosa1=${1##*.};
desimaaliosa2=${2##*.};
[[ ! ${apu1//[^.]/} ]] && desimaaliosa1=''
[[ ${apu1//[^.]/} ]] && { luku1=${apu1:0:18}; kokonaisluku=0 ;} || { luku1=${apu1:0:18}"."; kokonaisluku=1 ;}
[[ ! ${apu2//[^.]/} ]] && desimaaliosa2=''
[[ ${apu2//[^.]/} ]] && { luku2=${apu2:0:18}; kokonaisluku=0 ;} || { luku2=${apu2:0:18}"."; kokonaisluku=$(( 1 & $kokonaisluku )) ;}
 desimaaleja=$((${#desimaaliosa1}+${#desimaaliosa2})); $tulosta desimaaliosa1:$desimaaliosa1"   desimaaliosa2:"$desimaaliosa2"   desimaaleja:"$desimaaleja
luku1=000000000000000000${luku1//./}
luku2=000000000000000000${luku2//./}
a=${luku1: -18:9}; b=${luku1: -9}
c=${luku2: -18:9}; d=${luku2: -9}; $tulosta $a' '$b; $tulosta $c' '$d
luku1=00000000000000000000000000000000000000$((10#$b*10#$d))
luku2=00000000000000000000000000000000000000$((10#$d*10#$a))"000000000"
luku3=00000000000000000000000000000000000000$((10#$c*10#$b))"000000000"
luku4=00000000000000000000000000000000000000$((10#$a*10#$c))"000000000000000000"
luku1=${luku1: -36} ; $tulosta $luku1
luku2=${luku2: -36} ; $tulosta $luku2
luku3=${luku3: -36} ; $tulosta $luku3
luku4=${luku4: -36} ; $tulosta $luku4; $tulosta
luku11=${luku1:0:18}
luku12=${luku1:18}; $tulosta a$luku11' 'b$luku12
luku21=${luku2:0:18}
luku22=${luku2:18}; $tulosta c$luku21' 'd$luku22
luku31=${luku3:0:18}
luku32=${luku3:18}; $tulosta a$luku31' 'b$luku32
luku41=${luku4:0:18}
luku42=${luku4:18}; $tulosta c$luku41' 'd$luku42;$tulosta
summa1=$((10#$luku12+10#$luku22+10#$luku32+10#$luku42)); $tulosta summa1:$summa1
summa1pituus=${#summa1}; ylivuoto=0; (( $summa1pituus >= 19 )) && ylivuoto=${summa1:0: -18} && summa1=${summa1:1}
summa1=000000000000000000$summa1; summa1=${summa1: -18} ;$tulosta ylivuoto:$ylivuoto' summa1:'$summa1
summa2=$((10#$luku11+10#$luku21+10#$luku31+10#$luku41+$ylivuoto)); $tulosta summa2:$summa2
 
(( $summa2 )) && : || summa2=000000000000000000
(( $kokonaisluku )) && tulos=${summa2/*(0)/}$summa1 || { apu=$summa2$summa1; tulos=${apu:0: -$desimaaleja}.${apu: -$desimaaleja} ;}
echo $merkki${tulos##+(0)}
echo tulos laskusta: $1 \* $2  . Ylärivi on bc:stä ja alarivi tästä skriptistä. ;}

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

luku1=1 ; luku2=1 ;                                 echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=-1 ; luku2=1;                                 echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=1 ; luku2=-1;                                 echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=-1 ; luku2=-1;                                echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=.00900007567899123; luku2=900.07000012345678; echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=111111.11111111111; luku2=123456789012345.67; echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2 
luku1=10; luku2=10                                ; echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=999999999999999999; luku2=999999999999999999; echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=.9; luku2=2;                                  echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=.99999999999999999; luku2=.99999999999999999; echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=.00000000000000001; luku2=.00000000000000001; echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2   
luku1=10000000000000000; luku2=.0000000000000001;   echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2
luku1=.0000000000000001; luku2=10000000000000000;   echo ------; echo; bc<<<"scale=40; $luku1*$luku2"; time kerro18 $luku1 $luku2   




 
« Viimeksi muokattu: 30.09.24 - klo:14.16 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #315 : 02.10.24 - klo:10.32 »
Tein uuden version skriptistä joka laskee neliöjuuren kaikista sille annetuista reaaliluvuista mukaan lukien luvut tyyppiä:
1.234567e-96  elikä 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001234567
1.234567e96   elikä 1234567xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Varsinkin yksi ominaisuus skriptissä on erinomainen: sqrt 4 on 2, sqrt 9 on 3, sqrt 16 on 4 , sqrt 25 on 5 .... sqrt 2**58 on 536870912 -> ne ovat aina kokonaislukuja. Ihminen kyllä käsittää että kun ohjelma sanoo 9:n neliöjuureksi 2.99999999 niin todellinen arvo on 3 mutta onhan se luotettavampaa jos jo alunperin sanotaan sen olevan 3.
- tasaluku tulee myös kun juurrettava on 0.00000000000000000000000000000000000016 sillä tulos on silloin .000000000000000004 tai:4.0e-18
- sitämukaa kun skriptien toimintaa saa parannettua selviävät myös tulevat parannukset - nytkin seuraava parannus on vain toteutusta vailla.
- ja ennenkaikkea: numeromäärä on vain noin 9 numeroa mutta nopeus on melkein yhtähyvä kuin parhaimmilla BASH:in matematiikkaohjelmilla.
- ajoitusmenetelmä nano antaa oikean ajan ainakin mikrosekunnin tarkkuudella sillä se että aika vaihtelee kerrasta toiseen johtuu Linuxista a BASH:in kannalta aikaa kuluu tosiaan senverran kun näytetään.

- muuten kun olet tulkannut funktion (=ajanut sen kertaalleen) niin saat siitä intendoidun listauksen käskyllä: type -a funktion_nimi -> esimerkiksi: type -a to_sci. Siinä ovat jopa kommentit mukana oikeissa kohdissa jos niitä alunperinkään oli. Toisaalta kun olet ajanut funktion niin tulkki tekee koneen muistiin tällaisen 'yksirivisen' esityksen funktiosta - nämä minun esitykseni ovat juuri tällaisia 'yksirivisiä' sillä ne säästävät tilaa ja aikaamyötä niitä oppii lukemaan ja myös tekemään paljon nopeammin. Tässävaiheessa kommentit ovat vielä mukana, mutta kun tulkki tosiaan ajaa funktion niin kommentit stripataan kuutamolle ihan ensimmäiseksi. 
- kokeillessa toimiiko koodia se kopioidaan kokonaisuudessaan leikepöydälle (komannolla ctrl-c), liimataan päätteeseen (komennolla ctrl-v)ja painetaan enter.
- kokeiu muilla arvolla: paina nappia: nuoli-ylöspäin, editoi ja paina enter.

Koodia: [Valitse]
function nano () { alkuhetki=$(date +%s%N); $@; loppuhetki=$(date +%s%N);echo 'laskenta kesti: '$(($loppuhetki-$alkuhetki))' nanosekuntia' ;}
function sqrt () { apu=${1##+(0)}; [[ $apu ]] || { echo 0; return ;} # lausetta tarvitsee vain kun lasketaan:  sqrt 0  . Tai: sqrt 00000....
function to_sci () { # funktion voi määritellä funktiossa. Kommentti alkaa merkillä # ja loppuu joko kaksoispisteeseen tai rivinsiirtoon.
[[ $1 =~ e ]] && { echo $1; return ;} || { apu=$1; apu=${apu//[-+]/}; apu=${apu##+(0)}; int=${apu%%.*};[[ ${apu//[^.]/} ]] && desimaaliosa=${apu##*.} || desimaaliosa=0; [[ $int ]] && { expo=${#int}; echo ${int:0:1}.${int:1}$desimaaliosa'e'$(($expo-1)) ;} || { apu=${desimaaliosa%%[1-9]*}; ekspo=$((${#apu}+1)); desimaaliosa=${desimaaliosa:$(($ekspo-1)):1}.${desimaaliosa:$(($ekspo))}; desimaaliosa=${desimaaliosa%.}; echo $desimaaliosa'e'-$ekspo ;} ;} ;}  # oli: $(($ekspo))}; echo $desimaaliosa'0e'-$ekspo

apu=$(to_sci $1); mant=${apu%%e*}; apu=${apu##*e}; apu=${apu//+0/}; apu=${apu//-0/-}; (( $apu & 1 )) && kerroin=10 || kerroin=1; expo=$(($apu/2));   
in=${mant//./}"000000000000000000"; in=${in:0:17}
sqrt=2147483648  # 2^31
delta=1073741824 # 2^30  # tämä tulee voida jakaa jatkuvasti kahdella siten että jako menee aina tasan
for ((i=0;i<32;i++)); do # binääri-haun looppi alkaa
   x=$(($sqrt*$sqrt/$kerroin))
   (( $x>=$in )) && sqrt=$(($sqrt-$delta)) || sqrt=$(($sqrt+$delta))
   delta=$(($delta/2))
   (( $delta )) || delta=1  # echo $((1/2)) lasketaan nollaksi - oikein pyöristettynä se on 1
done
apu=00000000000000000000000000000000000000000000000000$sqrt'000000000000000000000000000000000000000000000';apu=${apu:0:$((51+$expo))}.${apu:$((51+$expo))}
luku=${apu##+(0)}; [[ ${luku//[^.]/} ]] && luku=${luku%%+(0)}; luku=${luku%.}

echo; echo -n "neliöjuuri luvusta: $juurrettava on: $luku"; [[ $luku =~ \. ]] && { echo -n " tai tieteellisesssä muodossa: "; to_sci $luku ;} || echo ;}
   
juurrettava=1.1234568e-99;nano sqrt $juurrettava
« Viimeksi muokattu: 12.11.24 - klo:01.11 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #316 : 06.10.24 - klo:12.39 »
Olen jo pitkään epäillyt että BASH:in suoritusaikojen vaihteluissa on jonkinsortin säännöllisyyttä - siis että tulos hidastuu monta kertaa peräkkäin, siten taas nopeutuu - hidastuu - nopeutuu - ... nikotellenkin silointällöin - keskiarvon pysyessä melkolailla vakaana. Vaihtelu on havaittavissa time-käskylläkin mutta yleensä sen erotelu-kyky ei ihan riitä. Ei asialla enää nykyään merkitystä ole mutta se on mielenkiintoista että miksi time-käskyn erottelukyky loppuu juuri siihen mistä ongelmat alkavat.

Tutkitaan kuitenkin se vähä mikä pystytään - skripti kestää muuten varttitunnin - ensimmäiseksi tutkitaan kuinka pitkään BASH:illa kestää tehdä koodi sille annetusta skriptistä - muuten muut kielet polkevat paikallaan paljon kauemmin kuin BASH. Ja tämä tulkkaamiseen kuluva aika vaihtelee myös. 
- aluksi laitetaan laitetaan suoritukseen todella yksikertainen skripti ja mitataan aika siihen kun työ on valmis. Yksinkertaisin skripti on kaksoispiste elikä käsky olla tekemättä mitään -  mutta ei sitäkään prosessoriin kaksoispisteenä syötetä vaan tulkin täytyy tulkata kaksoispistekin ryhmäksi bittejä joten kyllä silloinkin lähdetään toimiin tulkin kautta - käskyn suorittamien ei sitten kestä juuri mitään. Funktio nano mittaa sitten kauanko tulkkaus ja sen jälkeinen käskyn suorittaminen kestävät. Grafiikka-ohjelma gnuplot esittää sitten tulokset graafisena.
- laitettuasi skriptin toimimaan voit rauhassa vaikka surffata.
- erottelukyky ajoitus-ohjelmalla nano on hyvä - mutta joku kyllä joskus höpertää - onko se nano itse selvinnee aikanaan.
- jokainen mittaustulos esitetään graafisessa kuvaajassa merkillä: + .
- tulos riippuu kaikesta mahdollisesta: kuinka hyvä tietokone on, mikä on lämpötila, sorsiiko onnetar ...
- tietokneen kaikilla sovelluksilla on pärstäkerroin jonka perusteella käyttöjärjestelmä arvostaa itseään yli kaiken, laittaa toisista jotkut pikakaistalle jossa odottelua on vähä ja jotkut joutuu odottamaan useammin ja pikkuisen kauemmin. Ja BASH kuuluu niihin pikkuisen kauemmin odottaviin. Vähäsen apua tuo käsky: sudo renice -20 -u käyttäjänimi. Myös sleep-käskyt autavat sillä ne sanovat käyttöjärjestelmälle: nyt olisi minun kannaltani sopiva aika odotella muiden työskennellessä - mutta aikaahan sleep-käskyt vievät.
- reaaliaika-käyttöjärjestelmillä ei tätä vikaa pitäisi olla - kokeiliskohan BASH:in reaaliaika-versiota?
- aika mitataan kirjoittamalla alku- ja loppuhetki kovalevy-tiedostoon. Kovalevylle kirjoittaminen ja sieltä lukeminen ovat kylläkin hitaita toimenpiteitä, mutta itseasiassa tässä ainoastaan annetaan kirjoitusmääräykset ja laitetaan kirjoitettava nopeaan bufferiin ja kaikki muu toimii ajoituksen jälkeen eikä se silloin enää tulokseen vaikuta.
- anna varmuuden vuoksi käsky: sudo apt install gnuplot   - ja tee mitä se määrää.
- jos kokeilet skriptien toimintaa niin kopioi koko koodi keralla päätteeseen sillä odotusajat ovat pitkiä.
- tuloksia voit katsoa tiedosto-selaimella. Jos et ole sortunut 'hajoita ja hallitse' tekniikkaan toisilla versioilla, toisilla työpöydillä, toisilla ... 'onhan ne paljon parempia'.
- näilä arvoilla mittaus kestää noin varttitunnin.
Koodia: [Valitse]
function nano () { date +%s%N > alkuhetki; $@ ; date +%s%N > loppuhetki; sleep 0.01; echo $(($(cat loppuhetki) - $(cat alkuhetki))) >> ~/nano/data ;}

kertoja=7500; rm -f ~/nano/data; mkdir -p ~/nano; touch ~/nano/data ; time for (( n=1; n<=$kertoja; n++ )); do nano : ; sleep 0.09; done

rm -f ~/nano/data2; rm -f ~/nano/data3; touch ~/nano/data2; touch ~/nano/data3; readarray -t apu < ~/nano/data; for (( n=0; n<${#apu[@]}; n++ )); do echo $((${apu[$n]}/2000)) >> ~/nano/data2; done; sort -n ~/nano/data2 | uniq -c >~/nano/data3
# mutta tiedostossa  ~/nano/data3 ovat y ja x väärässä järjestyksessä joten niiden järjestys täytyy vaihtaa:
while read -r line; do set $line; echo $2 $1; done < ~/nano/data3 > ~/nano/data4 # siis saman lauseen sekä sisäänmeno että ulostulo voidaan uudelleensuunnata yhtaikaa

# tiedostossa ~/nano/data4 on paljon turhia rivejä joten poistetaan ne jotta käyrästä saisi paremmin selvää
cp <(sed -n '160,460 p' ~/nano/data4) ~/nano/data5

gnuplot -p -e 'set terminal postscript eps color enhanced;  set output "~/nano/pohjienjakauma.eps";set xlabel "aika"; set ylabel "aikojen jakauma"; plot "~/nano/data5"'
Tuloksia voit selata työkalupalkin Files selaimella ( se on se näytön reunalla olevien kuvakkeiden joukko ja hiirellä osoittaen oikea kuvake kirjoittaa viereensä: Files).
« Viimeksi muokattu: 05.11.24 - klo:12.39 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #317 : 08.10.24 - klo:11.29 »
Nyt täytyy laittaa foorumille talteen keskeneräinen työ sillä olen rähmäkäpälä ja tuhoan kotona oleat toimivat skriptit

Tarkoitus on tehdä jakauma edellsestä ja omassa koneessani se toimiikin hyvin.
Koodia: [Valitse]
rm -f ~/nano/data2; rm -f ~/nano/data3; touch ~/nano/data2; touch ~/nano/data3; readarray -t apu < ~/nano/data; for (( n=0; n<${#apu[@]}; n++ )); do echo $((${apu[$n]}/2000)) >> ~/nano/data2; done; sort -n ~/nano/data2 | uniq -c >~/nano/data3
# mutta tiedostossa  ~/nano/data3 ovat y ja x väärässä järjestyksessä joten niiden järjestys täytyy vaihtaa:
while read -r line; do set $line; echo $2 $1; done < ~/nano/data3 > ~/nano/data4 # siis saman lauseen sekä sisäänmeno että ulostulo voidaan uudelleensuunnata yhtaikaa
# tiedostossa ~/nano/data4 on paljon turhia rivejä joten poistetaan ne jotta käyrästä saisi paremmin selvää
cp <(sed -n '160,460 p' ~/nano/data4) ~/nano/data5
gnuplot -p -e 'set terminal postscript eps color enhanced;  set output "~/nano/pohjienjakauma.eps";set xlabel "aika"; set ylabel "aikojen jakauma"; plot "~/nano/data5"'

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #318 : 11.10.24 - klo:12.23 »
Paniikki iski kun edellisten skriptien saaminen toimiaan useammissa koneissa oli kovin hidasta ja aloin ihmetellä minkä takia - onko jotain ihan perusteellista? Toki sitä perusteellista löytyi - mutta oikeastaan jo kauan sitten ilmennyttä: toimiminen olion kanssa jota luulee esimerkiksi matriisiksi mutta se onkin tekstijono.

Asiaa sotkee se että BASH:issa kaikki muuttujat ovat matriiseja ja niin on myös tekstijono - tekstijono on saman-nimisen matriisin jäsen nolla - ei käsittelyssä ole mitään ristiriitaa sillä matriisin missähyvänsä osoitteessa olevaa tekstijonoa käsitellään eritavalla kuin matriisia - koko homma saa alkunsa siitä että jos matriisin jäsentä ei määrätä niin oletetaan että kyseessä on jäsen nolla. Esitetään asia esimerkillä: tehtävänä on tulostaa muuttuja mutta siitä ei tiedetä onko se tekstijono vaiko matriisi - siis jos se muodostuu numeroista niin onko sen kuvaaja:
1
2
3
vaiko: 1 2 3. Tosin käytännössä molemmat tulostetaan yleensä: 1 2 3 jokatapauksessa koska käytännön isojen matriisien kunnollinen pystysuora esitys veisi niin paljon tilaa että se sotkisi tulosteen ihan käsittämättömäksi - joten tulosteestakaan ei voi päätellä onko tulostettu tekstijono vaiko matriisi. Ja koska tekstijonojen ja matriisien käsitely on erilaista täytyy kummallekin tehdä skriptistä oma versio ja päätellä aina mitä versiota kutsutaan. Mutta on asiaan toinenkin ratkaisu: muutetaan siellä matriisin jäsenessä nolla oleva tekstijono matriisiksi ja myöhemmin kutsutaan aina vain matriisi-versiota. Mutta tälle muutokselle tehtävän funktion tulee käyttää nimiparametria. Senjälkeen jokaisen tekstijonon tai matriisin paikalle kirjoitetaan: $(matrixsize muuttujan_nimi) ja sitten käsittellään aivankuin se olisi aina ollut matriisi.

- ensimmäinen vaikeus on tehdä matrixsize-funktio - mutta se on tehty jo ja toimivaksi havaittu. Mutta paljon pahempi vaikeus on saada virtuoosit uskomaan että se toimii sillä he eivät epä-uskossaan edes kokeile. Mutta tässä se on:
Koodia: [Valitse]
function matrixsize () { matriisi=($( echo ${apu#*=} | tr \" '\n' | sed '/^[[:space:]]/d' | sed 1d | sed '$ d')); read<<<${matriisi} $1 ;}

koe="kissa kuumalla katolla"   # tämä on koeteksti1 ja: koe=(kissa kuumalla katolla) # on koeteksti2. Siis matrixsize:lle voidaan antaa matriisikin.
matrixsize koe; echo ${koe[1]} # kutsussa tuo numero on joko matriisin jäsen numero tai tekstijonon sananumero

-------------------------------------

Mutta on asiaan helpomminkin hyväksyttävä osa-ratkaisu. Esimerkiksi kun joudut tulostamaan jotakin josta et tiedä onko se tekstijono tai matriisi :
Koodia: [Valitse]

function tulosta () {
apu=$(declare -p $1) 
case ${apu:9:1} in
a ) matriisi=($( echo ${apu#*=} | tr \" '\n' | sed '/^[[:space:]]/d' | sed 1d | sed '$ d')); echo ${matriisi[$2]} ;;
* ) tekstijonomatriisi=($(eval echo \$${1#*=})); echo ${tekstijonomatriisi[$2]}  ;;
esac ;}

koe="kissa kuumalla katolla" # tämä on koeteksti1 ja: koe=(kissa kuumalla katolla) # tämä on koeteksti2
tulosta koe 0 # kutsussa tuo numero on joko matriisin jäsen-numero tai tekstijonon sananumero (= monesko välilyöntien erottama sana se on)


- skripti on tavallaan vain ohje tulkille kuinka toimia - 'ratapihakaavio: kuinka asettaa vaihteet oikein' - joten toiminta hidastuu vain vähän.
- jos tämä skripti olisi kirjastossa niin tulostaminen olisi unelmaa.
- mikäli määräät:
Koodia: [Valitse]
matriisi=({1..10}); matriisi="1 2 3 4 5 6 7 8 9 10"; echo ${matriisi[@]} niin tulostuu: 1 2 3 4 5 6 7 8 9 10 2 3 4 5 6 7 8 9 10
- katsopa millainen sotku BASH:in kirjanpidossa vallitsee käskemällä: declare -p matriisi  niin tulostuu:
Koodia: [Valitse]
declare -a matriisi=([0]="1 2 3 4 5 6 7 8 9 10" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9" [9]="10") -> status on edelleen matriisi ja mariisin jäsen nolla on tekstijono ja loput jäsenet vanhan matriisin arvoja.
- echo $matriisi kylläkin tulostaa ihan oikein ja muutenkin tuntuu sitä että kyseessä on vain kosmeettinen haitta.

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #319 : 13.10.24 - klo:11.48 »
Tekstijonon ja matriisin käsittelyyn tarvitaan omat funktiot. Kuitenkin tekstijono on matriisin jäsen nolla - selitys siihen on aivan yksinkertainen: kun matriisin indeksiä ei määrätä niin oletetaan että se on nolla. Ja vain koska tätä ei kerrota missään niin koko käsite on umpisolmussa. Sillä kaikkia muitakin matriisin jäseniä käsitellään toisin kuin koko matriisia - ei tekstijono ole minkäänlainen poikkeus. Esitetäänpä asia toisellatavalla:

BASH antaa skriptaajan tutkia kirjanpitoaan ja sieltä selviää muunmuassa kuinka minkin niminen muuttuja on määritelty. Anna kaksi käskyä ja tutkiskele tulostusta:
Koodia: [Valitse]
muuttuja="1 2 3"; declare -p muuttuja #-> tulostaa: declare -- muuttuja="1 2 3"
muuttuja=(1 2 3); declare -p muuttuja #-> tulostaa: declare -a muuttuja=([0]="1" [1]="2" [2]="3")


nyt tee kummajaiskäsky ihan vaan tutkimismielessä - tämänkaltaisissa tutkimuksissa kannattaa jokakerta avata uusi pääte sillä BASH:in roskankeruu on olematonta ja vanhat tulokset voivat muuten kummitella - mutta uudessa päätteessä voi olla varma siitä että kaikki roskat ovat hävinneet:
Koodia: [Valitse]
muuttuja=(1 2 3); muuttuja="1 2 3"; declare -p muuttuja -> tulostaa: declare -a muuttuja=([0]="1 2 3" [1]="2" [2]="3")
tulkki ei osaa suhtautua mielipuolisuuteen mutta tulostus kertoo sentään tekstijonon olevan matriisin jäsen nolla. Minkä muuten voit todeta toisinkin - avaa uusi pääte ja käske:
Koodia: [Valitse]
muuttuja="1 2 3"; echo ${muuttuja[0]} -> tulostuu: 1 2 3 #-> siis teit tekstijonon, tulostit matriisin ja silti onnistui ihan hyvin.

Kun mielestään tekee matriisin niin helposti siitä onkin tullut tekstijono eikä sitä välttämättä huomaa sillä virhe se ei ole vaan ainoastaan teko jota ei tarkoitettu - sillä matriisihan siitä tuli mutta kaikki arvot ovat jäsenessä nolla. Siis jos käsky: echo ${muuttuja[0]} tulostaa kaikki arvot niin kyseessä on tekstijono. Mutta tarkistamiseen kannattaa tehdä skripti sillä tietoja tarvitaan vähän lisääkin. Xref skriptin käytäminen on helppoa, kutsut vain: xref muuttujan_nimi. Xref ilmoittaa myös sotkuista tai jos muuttujan arvoa ei ole märätty - esimerkiksi käsky: muuttuja=(1 2 3); muuttuja="1 2 3"; xref muuttuja tulostaa tämmöistä:

muuttuja on matriisi.
arvot    : 1 2 3 2 3
osoitteet: 0 1 2
- ensimmäisen rivin kaksi viimeistä merkkiä ovat melkoisia haamuja; joillain tavoilla tulostettaessa ne näkyy ja toisilla ei.
- oikeastaan muuttuja-määrittelyn aluksi pitäisi antaa käsky: unset muutuja_nimi - silloin ei varmasti synny sotkuja - vaan enpä ole moiseen koskaan törmännyt vaikka ei käsky toimintaa hidastaisi sillä se on vain ohje kuinka pitää muutuja muodostaa.

Xref skriptin koodi:
Koodia: [Valitse]
function TulostaMuuttuja () {
[[ $(eval echo \$$1) ]] && echo -n $1' on numero- tai tekstimuuttuja arvoltaan: ' && eval echo \$$1 || echo $1" on määrittelemätön"
}

function TulostaMatriisi () {
echo $1' on matriisi.'
[[ $(eval echo \${$1[@]}) ]] && {
echo -n 'arvot    : '; eval echo \${$1[@]}               # arvojen väliin tulostuu välilyönti
echo -n 'osoitteet: '; eval echo \${!$1[@]}  | column -t # arvo ja sitä vastaava osoite kirjoitetaan aina alekkain riippumatta niiden pituuksista - joskus kylläkin vain teoriassa.
}; echo ;}

function xref () {
[[ $(eval echo \${!$1[*]} | cut -sd ' ' -f 2) ]] && TulostaMatriisi $1 && return # Matriisi ei voi olla määrittelemätön vaan silloin se on tavallinen muuttuja.
[[ $1 ]] && TulostaMuuttuja $1
echo # funktiossa täytyy aina tehdä jotakin - vaikka tulostaa tyhjää. Jos siinä on pelkkiä ehtoja eikä yksikään niistä toteudu niin muuten tulisi suoritusaikainen virhe
}

Käytännössä tämä vaikuttaa esimerkiksi näin kun muuttuja on joko testijono "1 2 3" tai matriisi (1 2 3):
Koodia: [Valitse]
echo ${muuttuja[@]} tulostaa sekä tekstijonon että matriisin:1 2 3
echo ${muuttuja[0]} tulostaa 1 jos kyseessä on matriisi mutta tekstijonon se tulostaa: 1 2 3
echo ${muuttuja[1]} tulostaa 2 jos kyseessä on matriisi mutta tekstijono ei tulosta mitään.
echo ${muuttuja[2]} tulostaa 3 jos kyseessä on matriisi mutta tekstijono ei tulosta mitään.

--------
 
Kokosin muutamia sellaisia käskyjä jotka toimivat matriiseilla joiden jäsenet voivat olla pitkiäkin tekstijonoja joissa voi olla välilyöntejä tai ei - ja käytännössähän useimmat matriisit ovat tällaisia.
Koodia: [Valitse]
echo ${muuttuja[1]:3:4} tulostaa matriisin toiselta riviltä alkaen merkistä 3 neljä merkkiä eteenpäin.
echo ${muuttuja[@]:3:4} tulostaa merkit 3-7 kaikilta matriisin riveiltä.
echo ${muuttuja[1]#* * * } puolestaan tulostaa toiselta riviltä kolmannen välilyönnin jälkeiset merkit rivin loppuun asti.
- välilyönnin paikalla voi olla myös sana, lueteltu merkkiryhmä (esimerkiksi[1 5] joka on 1 tai 5), regex (esimerkiksi [1-5] on kaikki numerot 1-5) ...
echo ${muuttuja[@]#* * * } puolestaan tulostaa saman viipaleen kaikilta matriisin riveiltä.
- ja jos haluaa tulostaa vain ensimmäisen sanan siitä erotetusta vaatii se oman käskynsä: echo ${muuttuja[@]%% *} - ja sekin toimii yli koko matriisin.:
- loopeila saa tehtyä nopeahkon skriptin joka tulostaa matriisin jokaisen rivin halutun kentän

echo ${muuttuja[1]%palloveikot*} tulostaa mitä lukee toisella rivillä sanan: palloveikot edessä
echo ${muuttuja[@]%palloveikot*} tulostaa matriisin jokaiselta riviltä mitä on kaikilla riveillä sanan palloveikot edessä.

echo ${muuttuja[@]//köksä/gastronominen_taitelija} muuttaa koko matriisissa kaikki sanat köksä sanaan gastronominen_taitelija
echo ${muuttuja[@]//ä/$(tput blink)ä$(tput sgr0)}  ennen englanniksi kirjoitetun väitöskirjan luovuttamista voi tarkistaa onko sinne jäänyt ä-kirjaimia -> koko teksti tulostuu siten että ä-kirjaimet vilkkuvat jolloin näkee hyvin jos niitä jossain on. Google&kumppanit on miljoonakertaa parempi tarkistamiseen - paitsi silloin harvoin kun se ei ole.

- kaikki nämä käskyt toimivat erittäin nopeasti - usein melkein yhtä nopeasti kuin sed. Miksei niistä puhuta että edes tiedettäisiin että tuomoisiakin käskyjä on? Koska  ne ovat tulosta monien virtuoosien vuosikausien ponnisteluista niin kuinka on mahdollista että niiden on annettu vaipua unholaan - kukaan ei ole edes jupissut vastaan? Ovatko ne liian tehokkaita - sillä valmisohjelmien reviiriähän nopeus ja tehokkuus ovat. Näissä rinkuloissa BASH:in omia valmisohjelmia ovat esimerkiksi sed ja awk - toki myös regex:ät grepin avulla - nykyäänhän ne kaikki ovat lähes kuolleet mutta aikoinaan niiden tekeminen on taannut leivän sadoille ihmisille.