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

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #320 : 14.10.24 - klo:14.36 »
Aikoinaan ei annettu pienintäkään toivoa sille että kaksiulotteiset matriisit toimisivat joskus - että voisi tehdä funktion hakemaan vapaa-muotoisesta matriisista yksi jäsen kun nimetään matriisi ja sen rivi ja sarake. Sellaista kaivattiin jo vuosikymmeniä sitten ja monet yrittivät sellaista tehdäkin mutta asia ei edistynyt vähääkään. Kuitenkin BASH on aina osannut kaksiulotteisten matriisien käsittelemisen mutta skriptiä sitävarten ei vain ole tehty koska ei osattu valita oikeita käskyjä - löytyi ne kyllä äskettäin mutta koska kymmenen vuotta sitten BASH:iin tuli paremmat toiminnot niin tämä on niillä tehty sillä nopeutta tuli roimasti lisää.

En alkuunkaan usko, ettei kukaan ole aikoinaan tehnyt tätä toimintoa sillä se on niin yksinkertainen että eiköhän se ole kuulunut jo alkuperäisiin suunnitelmiin siitä miten matriisien tulee toimia BASH:issakin.

Mutta koska matriisin nimi passataan nimenä eikä arvoina on tässä paljon normaalista poikkeavaa. Käsiteltävät matriisit voivat olla minkä muotoisia tahansa ja muodostettu vaikka minkälaisista olioista: kokonaisluvuista, desimaalivuista, tekstistä ja vaikka nuolenpää-kirjoituksesta ...  Nopeuskin on BASH:iksi ihan siedettävä.

Nykyvaatimusten mukaan tämä on sittenkin kelvottoman hidas mutta tämä onkin vain osoitus että kyllä BASH:issa toimivat myös kaksi-ulotteiset matriisit - ja samalla periaatteella kolmi-ulotteisetkin.

On täysin mahdotonta saada minkäänlaista käsitystä BASH:in kyvyistä koska niin paljon mahdottomaksi väitettyä on osottautunut toimivaksi - ja kun aikaisemmin isot työt kestivät sekunteja niin nyt niistä monet kestävät millisekunteja. Vaikka pitää edelleen paikkansa että BASH on ohjelmointikieleksi rääpäle niin 'tunnelin päässä on valoa'.

---

esimerkki - ensin tehdään matriisi - voisi sen lukeakin jostain - ja sitten haetaan siitä arvo rivitä:2  sarakkeesta 8:
Koodia: [Valitse]
matriisi=([0]="1 2 3 4 5 6 7 8" [1]="a b c d e f g h" [2]="1 22 333 4444 55555 666666 7777777 8.8888e-88"); echo tämmöinen matriisista tuli:; echo; printf "%s\n" "${matriisi[@]}" | column -t; echo
function hae_arvo_matriisista () { # kutsu on muotoa: matriisin_nimi rivinumero sarakenumero
 apu1=$(declare -p $1); declare ${apu1:8:2} apu2=${apu1#*=}; apu=($(echo ${apu2[$(($2-1))]})); echo ${apu[@]:$(($3-1))} ;}

time hae_arvo_matriisista matriisi 3 8
- ajan kulumisen kannalta on melkein yksilysti mitä numeroiden paikalla on: pitkää tai pätkää, numeroita, kirjaimia, mitähyvänsä merkkejä.
- samoin matriisin koko vaikuttaa suoritusaikaan vain vähäsen

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #321 : 16.10.24 - klo:11.40 »
Matriisin määrittelyn voi tehdä toisinkin: voi kirjoittaa matriisin välittömästi skriptiin ihan semmoisena kuin se on -  kuten esimerkissä on tehty. Matriisi-määrittelyn voi silloin myös kopioida jostakin.
Matriisimäärittely näyttää vähän omituiselta mutta se on kopioinnin kannalta ihanteellinen:
Koodia: [Valitse]
unset matriisi; readarray matriisi<<<"! ' ¤ % & / ( )
@ £ $ ‚ { [ ] }
ä ö å Ä Ö Å ½ §
1 22 333 4444 55555 666666 7777777 -88.88e-88";  echo tämmöinen matriisista tuli:; echo; printf "%s\n" "${matriisi[@]}" | column -t; echo
- matriisi voi olla minkäkokoinen ja muotoinen tahansa ja useimmat merkit kelpaavat - mutta esimerkiksi: jos ulkona on ' niin sisälle kelpaa vain " ja jos ulkona on " niin sisälle kelpaa vain ' . Sama sääntö on ainakin sed:issä.
- toki matriisi-kuvauksen voisi kirjoittaa yhteen pötkyynkin mutta silloin kopioinnista tulisi vaikeaa ja työlästä kopioida se jostakin:
readarray matriisi<<<"1 2 3 4 5 6 7 8"$'\n'"a b c d e f g h"$'\n'"1 22 333 4444 55555 666666 7777777 88.88e-88"
- muuten tuo alussa oleva unset nollaa matriisin niinkuin sitä ei olisi ollutkaan ja aloitetaan puhtaalta pöydältä. Unset olisi hyvä lisätä aina mudostamisen alkuun sillä se ei vie aikaa ja esimerkiksi kun matriisit ovat todella suuria niin se on jopa tarpeen.
- kirjastokielto ja huonot ohjeet - man-sivut, BASH-raamattu ja virtuoosit - aiheuttavat sen että BASH:issa on pakkokin ottaa kopiointiin uusi asenne.
« Viimeksi muokattu: 16.10.24 - klo:13.59 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #322 : 17.10.24 - klo:15.59 »
Kolmiulotteisen matriisiin tulostaminen: ensiksi muodostetaan ajatuksissa kaksiulotteinen matriisi - kaksiulottteisella puolella voi käyttää 'valenimiä' sillä eiväthän ne lopputuloksessa näy - mutta valinta yksinkertaistuu. Siis tehdään senmuotoinen kaksiulotteinen matriisi kun kuuluu - esimerkiksi:
a b c
d e f
g h i

# halutaan tulostaa tämän kolmiulotteisen mariisin jäsen 3 3 3 (=rivi 3,sarake 3, alkio 3). muodostetaan ensin valenimistä oikean muotoinen matriisi:
Koodia: [Valitse]
unset matriisi; readarray matriisi<<<'a b c
d e f
g h i';  echo tämmöinen kaksiulotteisesta matriisista tuli:; echo; printf "%s\n" "${matriisi[@]}" | column -t; echo

# Sitten muodostetaan kaksiulotteisen alkioita vastaavat lukujoukot. Lukujoukoissa on yleensä kaikissa sama määrä jäseniä mutta se ei ole pakko.
 
a="1 2 4"
b="2 4 6"
c="3 6 9"
d="4 8 12"
e="5 10 15"
f="6 12 18"
g="7 14 21"
h="8 16 24"
i="9 18 27"

# haetaan oikea alkio kaksiulotteisela puolelta sen alkio 'rivi sarake':
function hae_arvo_matriisista () { # kutsu on muotoa: matriisin_nimi rivinumero sarakenumero
 apu1=$(declare -p $1); declare ${apu1:8:2} apu2=${apu1#*=}; apu=($(echo ${apu2[$(($2-1))]})); echo ${apu[@]:$(($3-1))} ;}

# mudostetaan tuossa 'oikeassa alkiossa ' olevista alkioista matriisi josta tulostetaan valittu jäsen
apu=($(hae_arvo_matriisista matriisi 3 3)); apu=($(eval echo \$"$apu")); echo ${apu[2]} # jäsen matriisi 3 3 3 = 27

---

Toiminta on samantapaista käytännön isoissa matriiseissa: esimerkiksi vuoden jokaisena päivänä mitataan lämpötilaa kerran minuutisssa ja siitä tehdään matriisi. Matriisin kaksiulotteinen puoli jakaa sen päiviksi ja niiden tunneiksi. Sitävarten muodostetaan kaksiulotteinen matriisi näin:
Koodia: [Valitse]
readarray matriisi<<<"$(for n in {1..365}; do for m in {1..24}; do echo -n $n't'$m' '; done; echo; done)";  echo tämmöinen kaksiulotteisesta matriisista tuli:; echo; printf "%s\n" "${matriisi[@]}" | column -t; echo
Sen alkiot täytetään kerran minuutissa tapahtuvilla mitauksilla - mittausarvojen väliin tulee välilyönti - ja joka tunti siirrytään täytämään sille tunnille osoitettua kaksiulotteisen matriisin jäsentä näillä lämpötilamittauksilla.

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #323 : 21.10.24 - klo:15.23 »
BASH ei ole sellainen surkimus miksi se kuvataan - hidas se on kyllä mutta sen hitautta liioitellaan aina. Mutta toisaalta BASH:issa voi tehdä mitävaan - vaikka toisin väitetään - tosin useimmat edistynemmät toiminnot ovat vieläkin hitaampia ja kömpelömpiä mutta vastikkeeksi ne voi toteuttaa lukemattomilla täysin erilaisilla tavoilla joista jokaisella on omat ominaisuutensa - hyviä ja huonoja - joten ainakin oppii kritisoimaan omia tekemisiään.

En yritä edes ymmärtää miksi ja miten meille kaikille on pätevästi osoitettu että BASH:in matriisioperaatiot ovat kelvottomia sillä todellisuudessa ne ovat sentään käyttökelpoiset. Minäkin luulin aikoinaan BASH:in matriisioperaatioita käyttökelvottomiksi mutta tehdessäni funktioita matrisioperaatioita varten huomasin BASH:in monipuolisuuden matriiseissakin - vähättelijöistä ihmettelen kuinka kaikki voivat silmät sinisinä sanoa ettei BASH parempia menetelmiä edes tunne kun väittäjistä joku on itse ne toteuttanut?

Näin ensialkuun tein skriptin matriisin muodostamiseen : matriisin nimi on koematriisi, sen riviluku on 200 ja sarakeluku 26 - tulostuksesta sen rakenne selviää parhaiten:
Koodia: [Valitse]
function koematriisin_kasaus () { unset koematriisi; for ((n=1;n<=200;n++)); do koematriisi[$n]=$(echo {a..z} | sed "s/[a-z]/&$n/g" ); done; echo tämmöinen matriisista tuli:; echo; printf "%s\n" "${koematriisi[@]}" | column -t ;}; koematriisin_kasaus
- kun matriisi on muodostettu pysyy se määriteltynä niin kauan kuin päätettä ei sammuteta.

Sitten arvon hakeminen siitä - ja koska BASH on matriisioperaatioissa todella hidas niin vastikkeeksi laitoin tarkistuksen siitä ettei matriisin rajoja ylitetä (sentakia käytetään nimiparametria):
Koodia: [Valitse]
function arvo_matriisissa () { apu=$(declare -p $1); declare ${apu:8:2} apu2=${apu#*=} # apu2 saa funktioon nimenä tulleen muuttujan tyypin ja arvot
[[ $apu =~ \[$2\] ]] || { echo matriisissa ei ole sellaista riviä; return ;}
matriisin_rivi=($(echo ${apu2[$2]})); arvo_rivilla=${matriisin_rivi[$3-1]}
[[ $arvo_rivilla ]] && echo $arvo_rivilla|| { echo matriisissa ei ole sellaista saraketta; return ;} ;}
# esimerkkikutsu:
arvo_matriisissa koematriisi 200 26 # arvo joka on koematriisin rivillä 200 sarakkeessa 26

---

Kyllä tämä BASH on nolostuttava - koematriisin tekemiseen on aivan yksinkertainenkin tapa joka ei vaadi temppuilua - ja tehdään samalla iso koematriisi, se kylläkin kestää puolisen minuuttia:
Koodia: [Valitse]
unset koematriisi; for n in {1..20000};do koematriisi[$n]=$(echo {a..z}$n) ; done;printf "%s\n" "${koematriisi[@]}"

Samoin funktion arvo_matriisissa voi kirjoittaa yksinkertaisemminkin:
Koodia: [Valitse]
function arvo_matriisissa () { apu=$(declare -p $1); declare ${apu:8:2} apu2=${apu#*=}; printf "%s\n" "${apu2[@]}" | cut -d ' ' --fields=$3 | tr '\n' ' ' | cut -d ' ' --fields=$2 ;}
- ei siitä nopeus-hyötyä tosin ole -  se on vain sama asia kerrottuna toisella tavalla.
« Viimeksi muokattu: 27.10.24 - klo:10.40 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #324 : 27.10.24 - klo:10.47 »
Sitten desimaaliluvuista muodostetun matriisin jåsenien summaus tyyliin: 'ynnää kauppalaskun summat'- mutta sen voi koodata toimimaan ihan niinkuin haluaa. Tämäntyyppinen toiminta edellyttää parametrien siirtämistä nimiparametreina. BASH ei osaa palauttaa parametreja sillä se olisi turhaa - sillä ne palautuvat toiminnan aikana auomaattisesti mikäli tarpeen on.

- nuo lauseet funktion add_decimal_matrix alussa: apu=$(declare -p $1); declare ${apu:8:2} data=${apu#*=} kloonaavat funktioon tulleen $i nimisen muuttujan annetulle nimelle
- teoriassa kloonaamiseeen on käsky: declare -p nimi=$1 mutta se tuntuu olevan liian buginen tähän.
Koodia: [Valitse]
function add_decimal_matrix () { apu=$(declare -p $1); declare ${apu:8:2} data=${apu#*=}; savedata=(${data[@]}); apu=${#data[@]}; for (( n=0; n<=$apu; n++ )); do data[$n]=${data[$n]#*.}; data[$n]=${data[$n]}'0000000000000000000'; data[$n]=${data[$n]:0:15}; done; apu=$(($(echo ${data[@]} | tr ' ' '+'))); desimaalit=$(echo "${apu: -15}"); ylivuoto=${apu:0: -15}; kokonaiset=$(($(echo ${savedata[@]%.*} | tr ' ' '+')+$ylivuoto)); echo 'summa: '$kokonaiset'.'$desimaalit ;}

# esimerkki kutsu:
unset data; for ((n=1;n<=200;n++)); do data[$n]=$RANDOM$SRANDOM.$RANDOM$SRANDOM; done; clear; echo 'seuraava summattava matriisi vaihtuu jokaisella ajokerralla ja tällä kerralla se on:   '${data[@]}; echo; time add_decimal_matrix data; echo "bc:n antama varmasti oikea tulos johon voi verrata:"; echo -n '       ';bc -l<<<"$(echo ${data[@]} | tr ' ' '+')"

- tuo käsky: unset johtuu siitä että muuten määriteltäessä uudestaan edelliset määrittelyt häiriköisivät.
- matriisin koolla ei ole väliä muuten kuin että se hidastaa - ainakin 25000 onnistuu ja aikaa kuluu silloin sekunti.

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #325 : 30.10.24 - klo:14.56 »
Koetin siirtyä uusiin käskyihin tässä desimaali-matriisin-summaimessa niin paljon kuin mahdollista. Skriptistä tuli vaikea tehdä mutta sehän saattoi olla myös tottumattomuutta kun siirtyi toisiin menetelmiin. On mahdotonta osoittaa tarkempaa syytä - mutta sanoisiko näin: kun kokeili toimisiko joku käsky niin vanhoilla käskyillä joko toimi tai ei - mutta uusilla käskyillä tuli takaisin se BASH skriptien kirous: joskus toimii ja joskus taas ei. Toimintalogiikkakin muuttui kummalliseksi - ja taitaa olla niin että siirryttäessä Ubuntusta toisiin Linuxeihin lakkaa skriptin toiminta - nimenomaan lause:
Koodia: [Valitse]
desimaalimatriisi=$(printf "%-${maxdesimaalipituus}s\n" ${desimaalimatriisi[@]} | tr ' ' 0)
vaatii tulkilta ihan mahdottomia. Nopeus on kyllä hyvä:
Koodia: [Valitse]
unset data; for ((n=1;n<=200;n++)); do data[$n]=$SRANDOM$RANDOM.$RANDOM$SRANDOM; done; maxdesimaalipituus=$( printf "%s\n" "${data[@]}" | cut -d. -f2 | wc -L); desimaalimatriisi=$( printf "%s\n" "${data[@]}" | cut -d. -f2); desimaalimatriisi=$(printf "%-${maxdesimaalipituus}s\n" ${desimaalimatriisi[@]} | tr ' ' 0); kaikkiendesimaaliensumma=$(($(echo ${desimaalimatriisi[@]} | tr ' ' '+'))); kokonaismatrix=$( printf "%s\n" "${data[@]}" | cut -d. -f1); kokonaiset=$(($(echo ${kokonaismatrix[@]} | tr ' ' '+')+${kaikkiendesimaaliensumma:0: -$maxdesimaalipituus})); echo $kokonaiset'.'${kaikkiendesimaaliensumma: -$maxdesimaalipituus}; echo "bc:n antama varmasti oikea tulos johon voi verrata:";bc -l<<<"$(echo ${data[@]} | tr ' ' '+')"

Whig

  • Käyttäjä
  • Viestejä: 346
  • puppu-generaattori
    • Profiili
    • localhost
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #326 : 06.11.24 - klo:11.11 »
Nyt lyö päässä pahasti tyhjää mutta minulla on tiedosto jossa on sanoja yksi per rivi (sanat.txt):

sana1
sana2
sana3
sana4

Sitten minun pitäisi saada scripti joka ajaa haluamani komennon käyttäen tuossa toisessa tiedostossa olevia sanoja niin kauan, kuin sanoja riittää.

Eli scriptissä olisi vaikka jotain tyyliin:

mkdir (tähän haettu sana sanat.txt:stä)
chmod +x (tähän haettu sana sanat.txt:stä)

Ja tämä scripti tosiaan toistaisi tätä samaa kunnes jokainen rivi sanat.txt:stä olisi käyty läpi.
puppu-generaattorin outputtia
"minä olen kansainvälinen supertähti"

nm

  • Käyttäjä
  • Viestejä: 16401
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #327 : 06.11.24 - klo:14.48 »
Nyt lyö päässä pahasti tyhjää mutta minulla on tiedosto jossa on sanoja yksi per rivi (sanat.txt):

sana1
sana2
sana3
sana4

Sitten minun pitäisi saada scripti joka ajaa haluamani komennon käyttäen tuossa toisessa tiedostossa olevia sanoja niin kauan, kuin sanoja riittää.

Eli scriptissä olisi vaikka jotain tyyliin:

mkdir (tähän haettu sana sanat.txt:stä)
chmod +x (tähän haettu sana sanat.txt:stä)

Ja tämä scripti tosiaan toistaisi tätä samaa kunnes jokainen rivi sanat.txt:stä olisi käyty läpi.

Onnistuu esimerkiksi tähän tapaan while-silmukalla ja read-komennolla:

Koodia: [Valitse]
#!/bin/bash

INPUT="sanat.txt"

while read -r LINE
do
    echo "Käsitellään: $LINE"
    # Lisää tähän rivikohtaisia komentoja:
    # mkdir "$LINE"
    # chmod +x "$LINE"
done < "$INPUT"