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

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #220 : 18.03.16 - klo:09.57 »
Kyllä BASH on verraton prototyypin teossa: piti määritellä onko luvun neliöjuuri kokonaisluku. Sen koodiksi tuli:
Koodia: [Valitse]
[[ $(echo $(echo "sqrt (kirjoita_luku_tähän)") | bc -l | grep -o '\.0*$')  ]] && echo luvun neliöjuuri on kokonaisluku || echo luvun neliöjuuri ei ole kokonaisluku
- siis tehdään raa'asti juuri niin kuin ongelma esitetään. Tuommoisissa ratkaisuissa on se hyvä puoli ettei salahautoja juurikaan ole.
- siis tuo grepin regex:  '\.0*$'    tarkoittaa: desimaalipisteen jälkeen välittömästi tulee 0 ja siitä eteenpäin kaikki merkit ovat nollia. 
- pilkunviilausta: kun kuulemma täytyy erottaa luvut: 152415789666209426002111556165263283035677489 ja: 152415789666209426002111556165263283035677490 niin koodiksi tulee
Koodia: [Valitse]
[[ $(echo $(echo "scale=66;sqrt (kirjoita_luku_tähän)") | bc -l | tr -d '\\\n' | grep -o '\.0*$')  ]] && echo luvun neliöjuuri on kokonaisluku || echo luvun neliöjuuri ei ole kokonaisluku
- tai itseasiassa luvussa voi olla neljä a4-arkillista numeroita - scale:a joutuu vain kasvattamaan.
**
Teoriassa Linux pitää kirjaa kaikesta mitä PC:ssä tapahtuu. Siis on aivan yksinkertaista tehdä ohjelma: "Kuka vieras riehuu koneessani ja kerropa vieraan jokainen teko ja tekemisen yritys"?

Vaan eipä ole. Kirjanpito löytyy ja sen on pakko olla kunnollinen jotta Linux toimisi. Mutta jollekulle ei sovi se että ensinnäkin vieraan tekemiset paljastetaan ja todella kauheaa olisi jos PC:n omistajat voisivat itsekin selvittää sen. Siten ohjelmia löytyy kyllä - versiota 0.02 vuodelta 1927 tai sentapaista.

Mutta yrittää voi - vaikka työkalut ovat alkujaankin tylsiä ja nykyään vielä ruostuneitakin. 

Kirjanpito löytyy suureltaosin kansiosta /proc. Siellä olevissa tiedostoissa on teoriassa selväkielisenä esimerkiksi tapahtumien lukumäärät. Mutta se on tehty silloin kun kovalevytilakin oli kallista ja toiminta hidasta, joten sen tiedoista on vaikeaa saada tolkkua. Mutta käytännössä tuo kansio onkin viisainta jättää rauhaan ja käyttää ohjelmia jotka poimivat sieltä tietoja. Niinkuin esimerkiksi ohjelma: hardinfo joka selvittää jo paljon. Esimerkiksi kun olet äskettäin bootannut PC:si ja et ole vielä avannut internet-selainta niin silti koneessasi on jo monia tyyppejä. Suurin osa ihan omasta määräyksestäsi sillä esimerkiksi kello hakee netistä sillointällöin tarkan ajan. Tai kun PC:si verkko on päällä mutta selain ei ole avattuna niin internet-tarjoajasi käy silti kurkkimassa ja tuopa mukanaan siipiveikkojakin.

Tai verkon tapahtumista keräät tietoa käskyllä nstat ja sillä voit todeta homman itsekin: boottaa PC:si mutta älä avaa internet-selainta vaan anna heti käsky:
Koodia: [Valitse]
while true;do nstat | grep IpInReceives | awk '{print $2}' ; done
ja seuraa kun verkosta tulee paketteja.
Siis liikenne on merkityksettömän pientä? Mutta pankkitunnus ja salasana eivät paljoa vaadikaan.
- tietenkin Järjestelmänvalvonta kertoo tämän myös. Mutta Järjestelmän valvonnalla on vaikea tehdä pitkänajan-yhteenvetoja.
------------
Ne konessasi kullakin hetkellä olevat siipiveikot saat listattua käskyllä:
Koodia: [Valitse]
apu2=''; while true; do apu1=$(sudo netstat -p | grep -v ^unix | grep -v ^Active | grep -v ^Proto | awk '{print $5}' ); [[ $apu1 != $apu2 ]] && date && echo -e $apu1 | tr " " '\n' && echo && apu2=$apu1; done
- aina kun koneessasi olevien siipiveikot muuttuvat annetaan uusi tuloste muotoa: tyhjä rivi, päiväys, luettelo siipiveikoista yksi rivillään. Tulostetta kylläkin tulee alkuunsa niin paljon ettei kuvaputki riitä.

Boottaa ja anna tuo käsky välittömästi: se ei saisi tulostaa mitään niin kauan kuin jaksatkin päätettä tuijottaa. Avaa sitten internet-selain ja lataa esimerkiksi Iltasanomat ja anna senjälkeen päätteessä tuo käsky. Tulostetta tulee paljon ja jokainen rivi edustaa yhtä iltasanomien siipiveikkoa. Internet tarjoajallasi on taas omat siipiveikkonsa. Parin minuutin kuluessa näytön pitäisi taas vähitellen tyhjentyä - mutta se johtuu siipiveikoista ja teoriassa huonosti käyttäytyvä siipiveikko voi jäädä asumaan päästyään koneellesi.

Sulje sitten internet-selain ja mene päätteeseen seuraamaan nstat:in tulostetta. Kyllä ne siipiveikot vielä siellä ovat - internet:hän on yhteydetön joten syyllinen tähän on selain. Siipiveikot häipyvät sitten parissa minuutissa. Toivottavasti. Melkein kaikki.

Kaikki nämä ovat tulostetta vain niistä jotka eivät kätkeydy; siis hyviksistä.

Linuxissa on vielä korkeammankin tason kirjanpito: lokitiedostot. Teoriassa niistä saa listan kaikesta koneen tapahtumista. Ja kyllähän virtuoosit sieltä löytävätkin melkeinpä mitävain. Mutta käytäntö tökkii; mikäli näin olisi ei kaikenmaailman netstatit kehittyisi. Mutta päinvastoin: lokit eivät kehity mutta netstat juhlii.
**
Täytyi tulostaa tiedoston viimeinen kappale - siis kappaleiden välissä on tyhjä rivi. Tehtävään on kyllä yksinkertaisia awk-skriptejä mutta ne ovat aina jotenkin omituisia. Hommaan voi kasata funktionkin:
Koodia: [Valitse]
function tulosta_vikakappale () { vika=$(cat "$1" | grep -Pn ^$  | cut -d: -f1 | tail -1); awk "NR>$vika" "$1" ;}  # kutsu: vika_kappale tiedosto
**
Minua on aina pänninyt että sleep-komento on niin virheellinen pienillä sleep-arvoilla: esimerkiksi "time sleep 1" näyttää 1.004 - ja niin kauan se muuten käytännössäkin suurinpiirtein kestää. Luultavasti koneessasi on kylläkin super-sleepin binääri, mutta herra yksin tietää missä se on ja jokatapauksessa sen käyttöönottaminen edellyttäisi kääntämistä. Mutta itseasiassa käytettävissäsi on parikymmentä-kertaa parempi ajoitus-käsky kuin sleep: se on yksinkertaisesti:
Koodia: [Valitse]
read -t aika-sekunneissa_0.001sekunnista_alkaen
- siis käsky: time read -t 1 näyttää: 1.000
- minun koneessani se kestää tarkemmissa mittauksissa 1.00022 sekuntia. Muuten käsky on aivan laillinen vaikka ei määrää muuttujaa - muuttujan nimi on silloin:  RESULT
- käytännön mittauksissa on usein pieniä odotusaikoja ja niiden virheellisyys tunkee kaikkeen. BASH on kelvoton ennenkaikkea siksi, että tämän on jätetty kelvottomaksi. Parikymmen-kertainen parannus ei vielä paljoakaan auta mutta hyvä alku se on. Millisekunnin pituisen viiveen höpertäminen miljoonassa mittauksessa:
« Viimeksi muokattu: 08.02.17 - klo:18.03 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #221 : 15.04.16 - klo:13.49 »
Tulipa mieleen, että päivämäärän tarkistamiseen tarkoitettu regex joutuu käyttämään grepin hidasta kytkintä -P, se on suurikokoinen ja vaikeasti ymmärrettävä mikäli tarkoitus on ottaa huomioon se kuinka monta päivää kussakin kuussa on. Mikäli on tarkoitus ottaa huomioon sekin että onko karkausvuosi niin meneekin vaikeaksi. Mutta hommaan on myös nopea  ja helppo ratkaisu. Siinä päivämäärä "esitarkastetaan" helpolla regex:llä ja mikäli luku saattaa olla päivämäärä niin kysytään date-komennolta onko kyseessä tosiaankin päivämäärä. Tulos lasketaan nopeammin kuin monimutkainen regex sen laskisi, tulos on käsittääkseeni ehdottomasti oikea ja tapahtumien kulku helppo ymmärtää: 
Koodia: [Valitse]
pvm=$( echo 2016-02-29 | grep  '[0-9][0-9][0-9][0-9][.-][0-1][0-9][.-][0-3][0-9]'); date -d$pvm > /dev/null 2>&1 ; [[ $( echo $?) = 0 ]] && echo $pvm
- tarkisatettava päivämäärä on 2016-02-29 mikä sattuu olemaan karkauspäivä. Muutapa vuotta ei-karkausvuodeksi ja ei tulosta mitään
- päiväyksen muoto on vvvv.kk.pp mutta eiköhän sen saisi muutettua myös "Suomalaiseen" muotoon: vvvv.pp.kk
- siis kun päivä on kelvollinen niin se tulostetaan mutta kelvotonta päivää ei tulosteta
- käsky taipuu tarkistamaan kaikki tiedostossa olevat päivämäärät ihan niinkuin normaali regex:kin.

- ei se tiedostosovellus ihan helppo ollutkaan ja hidaskin se on; pari millisekuntia päivä. Mutta vuoden 2000 karkauspäivä tuli oikein koska sekä 100:n että 400:n vuoden poikkeukset otettiin huomioon:
Koodia: [Valitse]
rm /tmp/ tiedosto; for n in {1999..2001}; do for o in {00..13}; do for m in {00..32}; do echo $n-$o-$m >> /tmp/tiedosto; done; done; done
pvm=($( grep '[0-9][0-9][0-9][0-9][.-][0-1][0-9][.-][0-3][0-9]' /tmp/tiedosto)); for (( n=1; n<=${#pvm[*]}; n++ )); do date -d${pvm[$n]} > /dev/null 2>&1 ; [[ $( echo $?) = 0 ]] && echo ${pvm[$n]}; done
read -p "paina enter lopettakseesi"
rm /tmptiedosto
**
Myös satunnaislukujen kehittäminen BASH:issa on huonoa, sillä se perustuu 16-bittiseen koodiin. Ja 16-bittiseltä ei paljoa voi odottaakaan. Mutta BASH:in koodi sallii bittimäärän kasvattamisen 15-bitin välein. Esimerkiksi haluttaessa satunnaislukuja väliltä 0-40 on käsky kun kehittämisessä halutaan käyttää apuna 31-bittistä satunnaislukua:
Koodia: [Valitse]
echo $((((RANDOM<<15)|RANDOM)*40/1073741824))   
ja 46- bittisen satunnaisluvun perusteella kehitetty olisi:
Koodia: [Valitse]
echo $(((((((RANDOM<<30)|(RANDOM<<15)))|RANDOM))*40/35184372088832))

Käskyt ovat  inhottavan näköisiä, mutta ne ovat nopeita;  ja 31-bittinen hidastaa noin 25% ja 46 bittinen hidastaa noin 28% verrattuna 16-bittiseen. 16-bittisellä voi pitkällä mittauksella havaita säännönmukaisuutta, mutta 31- ja varsinkin 46-bittisistä ei pitkälläkään mittauksella voi havaita omituisuuksia.
**
Kuka on esittänyt että BASH-koodissa voi olla looppeja?  Vain laiskuus on hyvä syy käyttää BASH:in looppeja, sillä esimerkiksi bc:llä looppaaminen on joskus niin sotkuista puuhaa että siinä aivot nyrjähtää. Mutta koodi jossa looppaaminen suoritetaan BASH-koodilla on kammottavan hidasta.

Miettimällä saa loopit usein pois kokonaankin ja onhan noita nopeitakin looppauskeinoja niinkuin esimerkiksi awk tai bc. Esimerkiksi looppi bc:llä:
Koodia: [Valitse]
echo "for (i = 4.00; i < 5.42; i += 0.02)  i" | bc   # looppausehdoiksi sopivat kyllä muuttujatkin. Nopeutus voi olla jopa luokkaa 100* .
Mutta bc-looppi on vaikea soveltaa ja koodista tulee sotkuista. Esimerkiksi:
Koodia: [Valitse]
for n in $(echo "for (i = 1; i < 1000000000; i += 1)  i" | bc) ; do  toiminta; done 
- siis itse loopinmuodostus nopeutuu "satakertaisesti" mutta koko tehtävä nopeutuu paljonkin vähemmän sillä toimintahan se on jonka suorittamiseen ajasta suurin osa kuluu ja sehän ei nopeudu ollenkaan. Nopeuden kaksinkertaistuminenkin tapahtuu vain niissä harvoissa tapauksissa kun toiminta vie ajasta vain pienen osan.

Nopein tavallinen looppi on malliltaan (mutta tämä on siis paljonkin hitaampi kuin tuo sotkuinen bc-looppi, se ei kykene muuttujiin eikä varsinkaan desimaalilukuihin ja muistisyöppökin se on):
Koodia: [Valitse]
for n in {1..1000}; do toiminta; done
Melkein yhtänopea on:
Koodia: [Valitse]
for n in $(seq alku steppi loppu); do toiminta; done   # muuten desimaaliset arvot soveltuvat myös kunhan desimaalipilkku muutetaan pisteeksi
- erikoistapauksissa seq-looppi on nopeinkin. Esimerkiksi kertoman laskeminen on nopeinta ja siisteintä muodostaa: seq -s* luku_josta_kertoma_lasketaan  | bc -l
- esimerkiksi nopein tapa laskea kertoma 20:ntä pienemmistä luvuista:
Koodia: [Valitse]
echo $(($(seq -s* luku_josta_kertoma_lasketaan)))

Käskyllä for n in {1..joku_tosisuuri_luku} on looppi jaettava osiin sillä brace-expansion on muistisyöppö:
Koodia: [Valitse]
for n in {1..10000} ; do for m in {1..10000} ; do toiminta; done; done
Ulkonäöltääm siistimpi käsky olisi:
Koodia: [Valitse]
for (( o=1; o<=1000000; o++ )); do toiminta ; done
mutta se on 20% hitaampi. 
**
Bash:in satunnaislukugeneraattorin 46-bittisen version tulosten poikkeamat kun ulostulo on jaettu 512:sta astiaan. Näillä asetuksilla skripti kestää noin 15 sekuntia
-  tarkoituksena on selvittää esiintyykö satunnaisluvuissa jotain omituista kuten esimerkiksi jaksollisuutta. Ainakaan otoksen koon ollessa neljäsataamiljoonaa ja mittausajan ollessa 72 min. ei näy vielä mitään.
- 16 bittinen (pelkkä RANDOM) osoittaa jaksollisuutta jo kymmenellä miljoonalla.
- satunnaislukujen jakauma on gaussiaaninen; mutta pientä kummallisuutta on
Koodia: [Valitse]
#!/bin/bash
# laskenta.
unset matriisi; for n in $(seq 1000000) ; do (( matriisi[$(((((((RANDOM<<30)|(RANDOM<<15)))|RANDOM))/68719476736)) ]++ )); done
# graafinen esitys:
minimi=$( echo ${matriisi[*]} | awk 'BEGIN {minimi=9e99} { if ($1<minimi) minimi=$1 } END { print minimi }' )
for x in {1..511}; do echo $x" "$( echo "100*(${matriisi[$x]}-$minimi)/$minimi" | bc -l ); done | gnuplot -p -e 'set terminal wxt size 350,262 enhanced font "Verdana,10"; set xrange [0:511]; set ylabel "poikkeama %"; set xlabel "satunnaisluku"; plot "/dev/stdin" with lines'
echo -e ${matriisi[@]/%/\\n} | awk '{sum+=$1; sumsq+=$1*$1} END {print "keskipoikkeama=" sqrt(sumsq/NR - (sum/NR)^2)}'
**
Koetin taas kertaalleen saada nopeutta satunnaisluvun muodostuksen tarkistukseen. Esiin tulikin että käyttämäni: let a=1 tyyppisen lauseen voi korvata (( a=1 )) tyyppisellä laiseella, jota käyttäen nopeus nousee yli kaksinkertaiseksi. Kyllä tulos on ihan kiva muutaman tunnin saldoksi.

Muuten näiden nopeutuksien hakemisen tarkoituksena ei ole etsiä BASH:ista hyvää, vaan saada jonkinlainen käsitys siitä miksi Perl ja Python on kehitetty elikä pystyykö käytännön ongelmat ratkaisemaan myös BASH:illa. Tämänhetkinen tilanne on se sekä Perl että Python ovat melkein jokasuhteessa kertaluokkia parempia sillä onhan niitä kehitetty runsaasti jo vuosikymmeniä ja BASH:ia varsin vähän. En silti vertailisi niitä keskenään sillä BASH:ista löytyy potkua mikäli sen kieroutunutta sielunelämää hallitsee.
- muistatteko kuinka OS2:lle kävi Windowsin kanssa: hävisi huonommalleen.
**
Nämä satunnaisluku-mittaukset ovat erinomaisia rasitustestejä. Sillointällöin koneen tuuletin alkaa kiljumaan. Kun järjestelmän valvonnasta katsoo ytimien rasitusta niin yksi ydin kerrallaan on kuormitettu 100%:iin ja toisissa se normaali pariprosenttia. Mutta tuulettimen kiljuminen tuntuu ajoittuvan niihin epämääräisesti ajoittuviin hetkiin jolloin käyttöjärjestelmä vaihtaa rasitettavaksi toisen ytimen. Siitä tulee muuten mieleen että käyttöjärjestelmä osaisi käyttää kaikkiakin ytimiä mutta jostain syystä ei käytä. Yksi melkein hyvä syy olisi että jos kaikkia ytimiä käytettäisiin niin prosessorit alkaisivat saada lämpöhalvauksia ja powerien liian pienen tehon takia prosessorit alkaisivat virheillä.
**
Aloinpa tutkia kuinka muodostetaan alkulukuja. Ensialkuun tutustuin skripteihin joista jopa minä ymmärsin mitä tehdään - mutta ne olivat surkean hitaita; miljoonan ensimmäisen alkuluvun etsimiseen kului useita kymmeniä minuutteja. Siitä sitten tutkin nopeampia, mutta nopeutuessaan niiden logiikka alkoi tulla minulle hämäräksi. Kunnes kohtasin skriptin joka haki 1.3 miljoonaa ensimmäistä alkulukua viidessä sekunnissa. Sen täytynee laskea oikein ja sen toiminta on helppo käsittää mutta sen toimintatapa ei helposti tule mieleenkään. Skripti:
Koodia: [Valitse]
seq 3 2 10000000 | factor | sed '/ .* / d ; s/:.*//'
Olenkin saanut BASH:ista käsityksen että näin on aina: jokaisesta skriptistä löytyy varmuudella super-nopea versio mutta maksaa hintansa löytää se.  Ja mahdollisesti tuon nopeamman koodia ei joko ole koskaan nettiin vietykään tai se ei vain löydy ja sen joutuu "keksimään" uudestaan.
**
BASH:illa ei ole vakiota MAXINT, mutta sen arvo on 64-bittisellä: 9223372036854775807 minkä voi tarkistaa käskyllä: cat /usr/include/limits.h | grep ' LONG_MAX'.
Rajan ylittäminen ei aiheuta virhettä vaan arvo kerrostuu: echo $((9223372036854775807+1)) on: -9223372036854775808
Eihän BASH:in matematiikkakäskyjä käytetä juuri ollenkaan, mutta jos tarvitsee niin voi varmistaa kuuluuko luku kerrokseen yksi käskyllä:
positiivisella puolella: [[ $(echo $numero '<=' 9223372036854775807  | bc) -eq 0 ]] && echo liian suuri numero
negatiivisella puolella: [[ $(echo $numero '>=' -9223372036854775808  | bc) -eq 0 ]] && echo liian pieni numero
**
BASH on täynnänsä pieniä ärsyttäviä yksityiskohtia. Niiden ärsyttävyyttä lisää tieto että niistä useimmat on ratkaistu - monien selitykset löytyvät kyllä man-sivuilta mutta kenelläkään ei ole aikaa kahlata kaikkia läpi. Mutta osa on vain katoavaa BASH-perinnetietoutta.

Esimerkiksi jo vuosia olen luullut että bc tulostaa aina kuten seuraava kun tuloksessa on numeroita paljon:
seq -s* 999 | bc
mutta kyllä siitä saa kunnollisenkin tulosteen:
seq -s* 999 | BC_LINE_LENGTH=0 bc
Kun näin tämän ensikerran niin tuntuipa hölmöltä ratkaisulta. Mutta matematiikkaohjelma jossa kutsutaan bc:tä usein on ihan toinen juttu: kyseessä on ympäristömuuttuja jolle annetaan arvo vain kerran ja sen nopeuttava vaikutus tuntuu samassa ohjelmassa kaikkialla; sen voi vaikka laittaa ohjelman aloituslauseisiin.
- voi tuon määreen kirjoittaa skriptin alkuunkin ja niin että jokainen bc tunnistaa sen; kuitenkin se tarvitsee hieman lisää:
Koodia: [Valitse]
EXPORT BC_LINE_LENGTH=0

BC:ssä ärsyttävistä ominaisuuksista toinen on se, että bc esittää tuloksissaan 0 desimaalia ellei toisin määrätä. Tämän kiertäminen:
echo "scale= kuinka_monta_desimaalia_haluat_tuloksissa_olevan" > /tmp/delme; BC_ENV_ARGS=/tmp/delme
- siis määräys annetaan vain kerran alussa sillä se on voimassa kunnes pääte sammutetaan.
**
Joskus haluaa tiedostolistauksen perään tulostuvan montako riviä siinä on. Sen saa aikaiseksi esimerkiksi seuraavankaltaisella käskyllä:
Koodia: [Valitse]
awk ' {print $0} END {print "tiedostoja yhteensä: " NR}' /boot/grub/grub.cfg
tai kyllä BASH itsekin tähän kykenee:
Koodia: [Valitse]
cat /boot/grub/grub.cfg | tee /dev/stderr | echo tiedostoja yhteensä: $(wc -l)
**
BASH:in goto-käsky ei se tarvitse goto-sanaa. Tai pikemminkin se on gosub-käsky mutta palatessa ei tarvitse kirjoittaa return. Sillä on kyllä sellainen rajoite että sillä voi kutsua ainoastaan funktioita => kauheaa, joutuu tekemään jäsennettyä koodia. Sen käyttö:
Koodia: [Valitse]
$muuttuja
- jos rivillä on jotakin muutakin, esimerkiksi ehto niin $muuttuja täytyy olla viimeisenä, esimerkiksi:
Koodia: [Valitse]
etsi_kenen_synttärit_on && $järjestä_pippalot
- toiminta on mielekästä kun muuttujassa järjestä_pippalot on synttärisankarin nimi.
- siis mainitsemalla muuttujan nimen voi hypätä miljoonaan eri paikkaan.
**
find käskyllä saa helposti tulostettua koko tiedostojärjestelmän tai sen palasia melkein niinkuin haluaa. Huonoina puolina on se että se tulostaa piilotiedostotkin ja sekoittaa tulostuksiinsa asiaankuulumattomuuksia kuten esimerkiksi: "Lupa evätty" - ja täysin kelvollista konstia niiden poistamiseksi en ole löytänyt. Tässä pikkuisen kelvoton:
Koodia: [Valitse]
find ~  -not -path '*/\.*' 2>&1 | grep -v 'Lupa evätty'

**
Bash:ista tuli uusi versio: bash 4.4 ; ei se repoissa vielä ole vaan käytössä on edelleen 4.3 . Sen muutos-listalla on kokoa niin paljon, ettei sitä "kukaan lue" . Tietysti se lisää bash:iin vain 0.001%.
**
Yksi syy BASH:in huonoon maineesen on käskyjen tulosteiden lukeminen muuttujiin sillä se johtaa usein skripteihin jotka toiminta riippuu siitä millaista dataa ne silläkertaa saa. Yleensä muuttujaan lukeminen on ihan järkevä toimenpide, mutta varsinkin find- ja ls-käskyn tulosteet tulee aina lukea matriisiin. Ja koska matriisiin lukeminenkin tehdään yleensä väärin niin satunnaisuus senkun kasvaa. Ja kun tulosteissa vielä usein on välilyöntejä niin tästä saadaan kehitettyä kunnon soppa.

Find-käskyn tuloste luetaan matriisiin seuraavalla käskyllä:
Koodia: [Valitse]
IFS=$'\n'; matriisin_nimi=($(find  kansio_mistä_alkaen_luetaan <haun rajoitukset> )); unset IFS
- siis sulkuja pitää olla kaksi; yksi sulku lukee muuttujaan jolloin kaikki näyttää päällisinpuolin tarkasteltuna olevan kunnossa mutta "ei tää nyt toimi".
- tuo IFS on varmistamassa että jokainen rivi luetaan matriisin samaan jäseneen silloinkin kun luettavassa on välilyöntejä.
- tällöin yksittäiset polku-tiedostonimet päätyvät matriisiin kukin omaan jäseneensä. Jäsenet voi hakea matriisista jäsenen järjestysnumerolla. Siis esimerkiksi tällätavoin:
Koodia: [Valitse]
echo ${matriisin_nimi[jäsenen järjestysnumero]}
tai lukemisen voi tehdä näinkin:
Koodia: [Valitse]
mapfile -t matriisin_nimi < <(find kansio_mistä_alkaen_luetaan <haun rajoitukset> )

- mikäli haluat varmistaa minkä kanssa pelaat käske: echo ${matriisin_nimi[0]} - jos käsky tulostaa monta riviä on lukeminen todennäköisesti tapahtunut väärin.

- muuten annapa käsky:
Koodia: [Valitse]
a[0]=höh; echo $a
- toimii toisinkinpäin: muuttujan asettaminen asettaa samalla samannimisen matriisin jäsenen 0 .
Onko matriisi helppo erottaa muuttujasta? Konekaan ei osaa. Mutta tämä ei ole ongelma kun asian tietää.
 
« Viimeksi muokattu: 09.02.17 - klo:16.52 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #222 : 26.09.16 - klo:18.36 »
BASH:ille on maailmalla monia päteviä kirjastoja, mutta ei ole mitään helppoa tapaa saada niitä käyttöönsä. Mutta ilman kirjastoja BASH:illa tehdyt skriptit ovat kymmeniä kertoja turhan hitaita ja toimivat miten sattuu. BASH on surkea - koska siltä kehitetään olemattoman vähän, se on tulkattu kieli ja ennenkaikkia koska sillä ei ole kirjastoja. Ja vaikuttaa siltä että jossakin pelätään BASH:ia ja kirjastojen käyttöä pyritään estämään.

Nimenomaan matriisien käsittely kaipaa kirjastoja sillä matriisien käsittelemistavat ovat käytännölle vieraita. BASH:in matriiseilla on monia erikois-ominaisuuksia, mutta niitä erikoisominaisuuksia ei nykyään osata hyödyntää joten missään niitä ei käytetä. Itseasiassa ne ovat kuitenkin käyttökelpoisia, joten ne kannattaa aina huomioida - ja "kirjasto-skriptit" ottavatkin yleensä ne huomioon mutta itse ohjelmoidut eivät.

Otetaanpa esimerkki: skripti joka hakee matriisin jäsenen arvoa vastaavan osoitteen: BASH:issa matriisin jäsenten osoitteet voivat pomppia sinnetänne jättäen osoitteita väliinkin, samaa arvoa voi löytyä montakin eri osoitteissa ja ensimmäinen osoite voi olla mikähyvänsä positiivinen luku - ja assosiatiivisessa matriisissa osoite voi olla tekstiä. Näiden ominaisuuksien vuoksi eivät "normaalit" skriptit toimi hyvin etsittäessä jäsenen arvoa vastaavaa osoitetta - tai voihan se etsitty arvohan voi löytyä monestakin osoitteesta. Tässä yksi tiivis ja nopea ratkaisu jolla ei ole pahoja varjopuolia:
- mukana on testaamiseen sopivia matriisin-määrittelyjä
Koodia: [Valitse]
#!/bin/bash
function arvoa_vastaava_osoite () { apu=$( eval echo -e \${$1[@]} | sed 's/ /\n/g' | awk -v apu2=$2 '{if ($0==apu2) print NR" "}'); for n in $apu; do eval echo -e \${!$1[@]} | awk -v apu=$n '{print $apu}'; done ;}

declare -A matriisi2

matriisi1[7]=2
matriisi1[7777]=55
matriisi1[77]=12
matriisi1[888888]=eka
matriisi1[54]=12
matriisi1[17]=55
matriisi1=({1..32001}) # kokeillessa tämä lause voi olla kommentoitu tai ei

matriisi2[ykkönen]=eka
matriisi2[kakkonen]=toka
matriisi2[kolmonen]=kolmas
matriisi2[nelonen]=neljäs
matriisi2[tuntematon]=eka


time arvoa_vastaava_osoite matriisi1 1200 # kokeiltaessa on joko matriisi1 tai matriisi2. Etsittävää arvoa muutellaan myös
- BASH-loopit hidastavat aina paljon ja siksi tässä on vain yksi BASH-looppi, ja sekin käydään yleensä läpi vain kerran-muutaman kerran.
- 32000 rivisen matriisin käsittelyyn kuluu noin 85ms;  puhdasta BASH:ia olevalta skriptiltä kestää noin 220ms mutta sehän ei toimisikaan aina.
- ratkaisu, jossa on puhdasta BASH:ia sisältää viitisen kriittistä vikaa sen lisäksi että se on todella hidas.
- jos tämmöistä ei löydy kirjastosta niin ratkaisu on siirtyä käyttämään sellaista kieltä jonka kirjastosta löytyy.
**
Valtaosa muistakin tehokkaista matriisinkäsittely-menetelmistä ovat täysin käsittämättömiä. Niin on muissakin kielissä, mutta niissä ohjelmoinnin nämä osat ovat kirjastoissa. Esimerkiksi:
Koodia: [Valitse]
function matriisien_ero () { awk 'BEGIN{RS=ORS=" "} {NR==FNR?a[$0]++:a[$0]--}END{for(k in a)if(a[k])print k}' <(echo -n "${!1}") <(echo -n "${!2}") ;}
- awk on matriisien käsittelyyn ehdottomasti sopivampi kuin BASH - tosin awk:in voi ajatella kuuluvan BASH:iin. Se huono puoli awk:illa on että sen "numeromäärä" on vain noin 19 numeroa - gawk:issa tosin on rajoittamattoman tarkkuuden kytkin -M.
- tällekin voi antaa verrattaviksi assosiatiiviset matriisit.
**
Sleep-käsky on prantunut; esimerkiksi ennen yhden sekunnin viive oli ennen luokkaa 1.004 ja nykyään 1.0012. Mutta "read -t 1" on kymmenen kertaa parempi: 1.00014. Lyhyet viiveet olivat ennen varsin käyttökelvottomia, mutta nyt sata mikrosekuntia on 108 mikrosekuntia ja 10 mikrosekunnin viive on sillä 17 mikrosekuntia. Osoituksena tästä:
Koodia: [Valitse]
for n in $(seq -s " " 100000); do read -t 0.00001; done
kestää noin 1.83 sekuntia kun pitäisi kestää 1 sekuntia. Toinen virhelähde on tuo looppi joka myös aiheuttaa virhettä; luultavasti noin 0.2  sekuntia.

Jos tuommoisia 10-100 mikrosekunnin viiveitä tekee niin mihin niitä käytetään? Herra yksin tietää, mutta kyllä niille käyttöä aikanaan tulee kunhan päähän uppoaa se että homma on mahdollista.

Koska noiden pienten viiveiden kuluttama aika lasketaan karkeasti kuvattuna suorittamalla viive miljoonakertaa ja jakamalla kulunut aika miljoonalla niin ei olekaan varmuutta ettei kääntäjä oikaise ja kasvata ensin viivettä ja tee sitten yhtä mittausta tuolla lopullisella viiveen arvolla. Todetakseni ettei noin tapahdu tein skriptin - siinä pyörii yhtaikaa kaksi toisistaan riippumatonta prosessia:
Koodia: [Valitse]
#!/bin/bash
# tässä skriptissä on kaksi prosessia pyörimässä yhtäaikaisesti. Ensin omia aikojaan tiedoston lukuarvoa kasvattavava prosessi taustalla.
for (( n=0; n<=999999; n++ )); do echo $n > file1;done &

# ja sitten perään toinen looppi ottamaan tiedostosta näytteitä. Lukujen tulisi useimmiten kasvaa, mutta koska BASH on muutenkin ajallisesti horjahteleva niin eihän niin aina ole. Yleensä tämä ei edes tulosta kaikkia 30:tä; esimerkiksi
# jos ensimmäinen prosessi kirjoittaa juuri kun toinen haluaa lukea niin BASH lopettaa
apu1=0; apu2=0; for m in {0001..0030}; do read -t 0.$m apu; apu1=$apu2;apu2=$(cat file1); [[ $apu1 && $apu2 ]] && echo -n $(($apu2-$apu1))' ' ; done
kill $! # tapa viimeiseksi määrätty taustaprosessi - siis tuo jonka perässä on merkki &

tulostusrivi esimerkiksi: 14 15 24 31 45 49 50 59 52 59 61 63 65
- skripti ei yleensä tulosta näinkään montaa 30:stä puhumattakaan - esimerkiksi jos toinen prosessi kirjoittaa samaan aikaan kuin toinen haluaa lukea niin BASH lopettaa koko homman. Mutta jo vähäinenkin tulostus osoittaa sen että viive tosiaan muuttuu sillä yleensä arvot kasvavat kokoajan.
- Jos haluaa aloittaa 10mikrosekunnista niin toinen looppi muutetaan muotoon for m in {00001..00030}
**
Taas kertaalleen piti laittaa toiminnan suorittamiselle ehdoksi se että erään muuttujan arvo on nolla. Siis ihan samaa kuin jo aikoinaan piti oppia; mutta nyt vasta tuli mieleen että se tehdään aina hieman liian työläästi. Varmaankin useimmat muutkin tekevät sen väärin, sillä nopein ja yksinkertaisin tapa on:
Koodia: [Valitse]
(( $muuttuja )) || toiminta

Testi:
Koodia: [Valitse]
[[ $muuttuja ]] || toiminta
sitten puolestaan ei testaa onko muuttujan arvo 0 , vaan onko muuttuja aritmeettiselta arvoltaan määrittelemätön tai 'teksti-jono arvoltaan' tyhjä-joukko (elikä '')
**
Useissa BASH:in käskyissä on sisäänrakennettu erittäin nopea looppi. Esimerkiksi jokaisen a:n muuttaminen b:ksi tiedostossa nimeltään ohje sen jokaisella rivillä:
Koodia: [Valitse]
cat ohje | tr a b
- tr-käsky hyväksyy kuitenkin vain yksittäisten merkkien muuttamisen. Hankalinta on, että käsky:
Koodia: [Valitse]
cat ohje | tr ab cd
onnistuu, mutta se tarkoittaa: vaihda kaikki a:t c:ksi ja b:t d:ksi. Mikäli tiedostossa oleva tekstijono täytyy korvata toisella tekstijonolla niin seuraavantyyppinen pelkkää BASH:ia oleva käsky käy:
Koodia: [Valitse]
readarray matriisi < tiedoston_nimi ; echo "${matriisi[*]//mikä/miksi}"
- sed:issä, awk:issa ... ja monessa muussakin on vielä hieman nopeampi sisäinen automaattilooppi. Varsinkin awk:iin voi kyllä lisätäkin looppeja eikä se paljoakaan hidasta.
- myös regex-moottorissa on sisäiset nopeat automaattiloopit.
- erittäin harvoin BASH:iin kannattaa tehdä loopeja BASH-koodilla, ne ovat kymmeniä-satoja kertoja hitaampia kuin nuo käskyjen sisäiset loopit.

Skriptejä joissa ei ole BASH-koodisia looppeja löytyy melkeinpä mihinhyvänsä tilanteeseen, esimerkiksi: 
Alussa kirjoitettiin data-tiedosto nimeltään: 0
Sitten piti kirjoittaa uusi 0 - kuitenkin piti ensin uudelleen-nimetä olemassa-oleva 0 1:ksi.
Sitten piti taas kirjoittaa uusi 0 - kuitenkin piti ensin uudelleen-nimetä olemassaolevat 1:ksi ja 2:ksi.
Sitten piti taas kirjoittaa uusi 0 - kuitenkin piti ensin uudelleen-nimetä olemassaolevat 1:ksi, 2:ksi ja 3:ksi.
ja niin edelleen. "Normaaliskriptissä" on vilisemälla looppeja ja väliaikaistiedostoja ja se on tautisen hidas. Sellainen käsky uudelleen-nimeämiseen jossa ei ole BASH-kielisiä looppeja:
Koodia: [Valitse]
mikäli ollaan valmiiksi oikeassa kansiossa: rename 's/(\d+)/$1+1/ge' $(ls | sort -rg)
mikäli saatetaan olla väärässä kansiossa  : ( cd kansio_jossa_uudelleen_nimettävät_ovat; rename 's/(\d+)/$1+1/ge' $(ls | sort -rg ))
- 10.000 tiedoston uudelleen nimeäminen kesti 170ms. Myös awk/BASH-toteutus jossa awk tekee loopit ja BASH toteuttaa kestää 5600ms ja lisäksi "start, stop ja step" täytyy asetella.
- tätä kannattaa käyttää jos vain mahdollista sillä nuo muut ovat kovalevylle todella rasittavia ja yksikin erehdys saa koko koneen vaikeaan tilaan.
Koodia: [Valitse]
awk -v a=$(ls | tail -1) 'BEGIN { while (a-->-1) string=string "mv " a+1" " a+2"\n"; print string }' | bash
ja vastaava silkkaa BASHia oleva käsky kestää noin 10 sekuntia, mutta toisaalta se on helpoiten räätälöitävissä: esimerkiksi seuraavassa tiedostonimien edessä on tekstiä, siis tiedostonimet ovat tässä seuraavanlaisia: data>numero<
Koodia: [Valitse]
touch data10001; for n in {10000..2}; do  mv data$n data$(($n+1)); done
Mutta kyse ei ole yksinomaan hitaudesta vaan siitä että esimerkiksi prosessorin kuormitus kasvaa liialliseksi kun kestoaika kasvaa kohtuuttomasti.

Mutta BASH on paitsi helpompi räätälöidä niin sillä voi myös laittaa tehtävän suoritukseen taustalle; siis voi jatkaa muiden hommien tekemistä melkein välittömästi. Mutta vaikka yhden tiedoston arvoja voi käyttää 10ms kuluttua niin kaikki tiedostot on käsitelty vain hieman nopeammin kuin tavallisesti.
- huomaa että tiedostojen nimet ovat: data1  data2 ... data10000 eikä pelkkiä numeroita. Kokelin toimintaa antamalla käskyt:
Koodia: [Valitse]
mkdir ~/koe; cd ~/koe
for n in $(seq -s " " 10000); do echo $n > data$n; done                                                                                                         # tämä lause muodostaa uudelleen-nimettävien tiedostojen joukon
time ( touch data10001; for n in {10000..1}; do  mv data$n data$(($n+1)) ; done & ) ; sleep 7.5; cat data10001; cat data2 # uudelleen-nimeäminen ja tuloksen tarkistus   
tulostaa: 10000 ja 1 niinkuin pitääkin ja nautiluksella katsoenkin kaikki on kunnossa. Tuo vähintään 7.5sekunnin sleep tuntuu olevan pakollinen jotta kumpikin lukema tulisi oikein - koska niitä uudelleen-nimeämisiä tehdään taustalla kokoajan.

Ensimmäinen tiedosto on kylläkin selvä jo 10ms kuluttua ja yksi lisää valmistuu aina vajaassa millisekunnissa mutta koko joukko siis vasta 7.5 sekunnin kuluttua.
- skriptiajurissa tiedostoja on noin paljon editoitaessa hankalimpia skriptejä. 
« Viimeksi muokattu: 01.01.17 - klo:11.39 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #223 : 20.10.16 - klo:15.57 »
boottauskyvynpalautus käyttäen ainoastaan live-Ubuntua

Teoriassa verkkosivuilla olevat skriptit voi leikata-liimata live-Ubuntun pääteeseen sellaisenaan - tällöin skriptille ei tarvitse myöntää suoritusoikeuttakaan. Käytännössä skriptejä ei saa toimimaan tällätavoin esimerkiksi:
1. terminaalin skriptikielenä on Dash ellei toisin määrätä. Dash eroaa Bash:ista niin vähän että useimmat Bash-skriptit toimivat Dash:issa ihan hyvin mutta jotkut skriptit eivät. Terminaali määrätään käyttämään Bash:ia käskyllä: #!/bin/bash. Määräyksen: #!/bin/bash eteen täytyy kirjoittaa välilyönti.
2. skriptit toimivat sittenkin vain useimmiten. Sillä jonkun tulkin bugin vuoksi myös Bash vaatii että joskus muidenkin lauseiden eteen täytyy lisätä välilyönti - ilmeisesti kuitenkin vain pääohjelmassa ja siellä voi lisätä kaikkien lauseiden eteen välilyönnin. Mutta funktioihin ei tarvitse koskea. Saavathan ne välilyönnit siellä rivien alussa olla valmiiksikin sillä eivät ne toimintaan vaikuta, mutta skriptin voi aina leikata-liimata mikäli lauseiden edessä on välilyönti. Ja mikäli rivien alussa ei ole välilyöntejä niin skripti ei aina toimi ennenkuin rivien eteen lisää välilyönnin käyttäen esimerkiksi gedit-editoria antamalla käsky: gedit. Liimaa teksti sinne. editoi sitä ja leikkaa koko teksti uudestaan ja liimaa päätteeseen. Mutta geditissäkin ainoastaan editointi toimii mutta talletus ei, joten kun boottaat niin menetät kaiken.
- suomenkielisen näppiksen saa näin: näpäytä yläpalkista neliötä jossa lukee EN ja avautuvasta valikosta: TextEntrySettings... ja avautuvan tekstineliön vasemmasta alakulmasta + . Avautuvaa valikkoa selataan kunnes esiin tulee: Finland jota näpäytetään ja sitten näpäytetään ADD. Valikot suljetaan ja näpäytetään taas yläpalkissa neliötä EN ja avautuvasta valikosta näpäytetään FI .
- verkkosivulla olevasta koodista ei näe onko sen lauseiden edessä välilyönnit vai ei.
3. live-Ubuntussa ei ole kaikkia niitä sovellus-ohjelmia jotka normaali-koneessa on. Sovellus-ohjelmien lisääminen live-Ubuntuun on mahdollista mutta siinä on melkoinen työ.
4. interaktiivisuus katoaa - esimerkiksi kun skripti kysyy jotakin niin se odottaa vastausta väärästä paikasta joten skripti jää ikuisesti odottamaan vastausta.


Todetaksesi että tämä toimii voit ajaa boottauskyvynpalautus-skriptin - sillä on hyvä kokeilla monipuolisuutensa takia. Siis koko koodin voi liimata-leikata yhtenä palana live-Ubuntun päätteeseen - suoritusoikeutta ei tarvitse antaa. Boottauskyvynpalautus-skriptin hakeminen UbuntuFoorumin sivuilta ja sen:
1. Boottaa live-Ubuntulla ja avaa sen verkkoyhteys (verkkoyhteys avataan ihan samallatavoin kuin normaalistikin, elikä napauttamalla yläpalkista verkkokuvaketta ja .... )
2. Avaa selain, kirjoita selaimen hakukenttään: ubuntu
3. avautuvasta valikosta valitse ubuntu suomi ja siellä näpäytä UbuntuYhteisö - keskustelualueet.
4. Kirjoita UbuntuSuomen hakukenttään: boottauskyvynpalautus ja paina enter. Hetkenkuluttua avautuvasta ikkunasta näpäytä: Ohjeita shell-skriptaukseen
-  hakukenttiä on kaksi. Se oikea kenttä on se alempi.
5. Paina Ctrl-f ja kirjoita: boott
-  maalaa alempana oleva harmaa koodikenttä.
-  maalaaminen tapahtuu näin: kohdista hiiriosoitin kopioitavan alkuun ja paina hiiren vasen nappi alas. Pitäen nappia alhaalla siirrä hiiriosoitin kopioitavan loppuun jossa voit päästää hiirinapin ylös.
6. Paina ensin Ctrl-c , sitten paina Ctrl-ALT-T ja pääteikkunan auettua paina: SHIFT-Ctrl-v . Heti alkaa tapahtua joten pistä näppisi taskuun.
-  siis esimerkiksi Ctrl-ALT-t tehdään näin: ensin nappulat Ctrl ja ALT alas ja pitäen niitä alhaalla painetaan kirjainnappulaa t
Koodia: [Valitse]
#!/bin/bash
# 1.1.2016 petteriIII.
# Tämä päivitys-skripti paivittää kovalevyltä sen kaikki Ubuntut (ja monet muutkin Linuxit). 105
# bootata voidaan mistä halutaan, eikä merkitse mitään jos jommassakummassa on EFI tai vaikka molemmissa.
# päivitys-skripti voi myös sijaita joko päivitettävässä tai päivittävässä. Päivitys-skripti kannattaa ajaa boottaus-välineen nautiluksella käyttäjätilassa.
# Jos salasanaa kysytään on se boottaus-välineen salasana; jos on bootattu live-versioon ei salasanaa kysytä ollenkaan.
# Btrfs-tiedostomuoto vaatii päivitys-skriptiin muutoksia.
# Kun on bootattu live-versiolta täytyy verkkoyhteys luoda ensin; live-versiossa verkkoyhteys luodaan samallatavoin kuin kovalevyllä olevassa.
# Kun on bootattu live-versiolta korjataan kovalevyn kaikkien liittämättömien osioiden levyvirheet ennen päivitystä.
# Ennen päivitystä yritetään poistaa kaikki päivityksen esteet ja turhista roskista pyritään eroon.
# Jos koneessa on lukemattomia turhia imageja saattaa kestää pitkäänkin poistaa ne, mutta muuten päivitys toimii lähes yhtä nopeasti kuin tavallinenkin päivitys.
function KirjoitaOhje { echo '- ennen päivittämistä kannaattaa sulkea muut ohjelmat.
- aluksi kysytään salasanaa ettei sitä kysyttäisi sitten toiminnan aikana keskenkaiken. Salasana on kovalevyllä toimittaessa oma
  salasanasi ja muistitikulla toimittaessa se on ubuntu jos sitä edes kysytään.
- toimiva internet-yhteys on välttämätön; live-versioiden verkkoyhteys asennetaan aivan samoin kuin normaalistikin - muistitikun asetukset säilyvät mutta cd:n ei.
- kovalevyosiot tarkistetaan ja yritetään korjata lukuunottamatta sitä osiota jolla on bootattu. Windows on kylläkin kantona kaskessa.
- roskikset tyhjennetään, vain viimeinen image jätetään ja päivityksen esteitä poistetaan.'; }
function tarkistaKovalevyjenKaikkiOsiot () {
read -t 5 -n 1 -p 'jos haluat tarkistaa kovalevyn perusteellisesti paina jotain kirjainta; silloin tarkistus kestää jonkinverran kauemmin mutta tarkistaa paremmin.' apu
echo
for osio in $(sudo blkid | grep -v swap | awk '{print $1}' | sed 's/\://g'); do
  [[ $(echo $osio | grep loop) ]] && continue
  sudo fsck -MVCaf $osio ||   sudo fsck -MVCac $osio
  echo ""
done
# live-tikku saattaa aiheuttaa virheviestin:There are differences between boot sector and its backup.
}
function unmountmnt { # if grep -qs '/mnt/foo' /proc/mounts; then
cd / # umount ei onnistu, jos joku umountattavan kansio on valittuna
sudo echo ""
for mountattu in $(sudo mount | grep /mnt | awk '{print $3}')
do
  if grep -qs $mountattu /proc/mounts; then sudo umount -l $mountattu; fi
done
echo '/mnt:n mountit on poistettu'
}
function Levy {  echo -n $(ls -l /dev/disk/by-id | grep -m 1 '/'${Chrootosio##*/} | awk '{print $9}' | sed "s/-part*//g") ;}
function paivitaKaikkiOsiot () { # itseensä chroottaus onnistuu ihan kivasti joten tällätavoin voi ihan hyvin päivittää itsensä
unmountmnt
for Chrootosio in $(sudo blkid | grep -v swap | grep -v vfat | awk '{print $1}' | sed 's/\://g'); do
  # [[ $( mount | grep $Chrootosio) ]] && echo && echo $Chrootosio'ohitettu' && echo && continue # hypätään seuraanaan loopissa
  osiontyyppi=$(sudo blkid -o value -s TYPE $Chrootosio)
  sudo mount -t $osiontyyppi $Chrootosio /mnt
  [[ -d /mnt/boot/grub ]] && [[ $(cat /mnt/boot/grub/grub.cfg | grep menuentry.*Ubuntu ) ]] &&  {
  sudo cp -L /etc/resolv.conf /mnt/etc/ ######################################### koe 21.10.16
  sudo mount -t sysfs none /mnt/sys &&  sudo mount -t proc proc /mnt/proc && sudo mount --bind /dev/ /mnt/dev &&  sudo mount --bind /dev/pts /mnt/dev/pts && sudo mount -o bind /etc/resolv.conf /mnt/etc/resolv.conf && sudo mount -o bind /dev/shm /mnt/dev/shm && sudo mount --bind /proc /mnt/proc
  echo; echo -n '********** päivitetään: '$Chrootosio '   levyllä: '; Levy; echo '     tiedostomuoto: '$osiontyyppi; echo
  [[ -d /mnt/home/$(env | grep USER)/.local/share/Trash ]] && sudo rm -rf /mnt/home/$(env | grep USER)/.local/share/Trash # poistaa roskikset kaikilta osioilta joiden käyttäjä on sama kuin se jonka tililtä päivitä-skriptiä ajetaan eikä eriliskodin roskisia tyhjennetä koskaanfunction  käskynlähde() ( set -o pipefail;
[[ $(type $1  2> /dev/null | grep sisäänrakennettu ) ]] && echo sisäänrakennettu ||
which "$@" | xargs -r readlink -f | xargs -r dpkg -S ;
(( $? )) && echo -n käskyä ei ole asennettu mutta se löytyy paketista: &&
apt-cache search $1 | awk '{print $1}' | grep -w ^$1 ;); käskynlähde agrep
:
  # Kohdekoneen muistin vapauttaminen ei helpota tätä ohjelmaa. Kohdekoneen siistimisen syy onkin se, että kohdekone pärjäisi tulevaisuudessa itsekseen.
  sudo chroot /mnt tune2fs -m 1 $Chrootosio # rajoittaa pääkäyttäjälle varattua kovalevytilaa yhteen prosenttiin.
   for n in $(ls /mnt/boot | grep config | grep -v $(ls /mnt/boot | grep config | sort | tail -1) | sed 's/config-//g'); do
    sudo chroot /mnt apt-get --yes  purge 'linux-image-'$n
    sudo chroot /mnt apt-get --yes  purge 'linux-image-extra-'$n # turha lause, mutta muistutuksena siitä mitä tapahtuu.
    sudo chroot /mnt apt-get --yes  purge 'linux-headers-'$n
  done # Mikäli et halua poistaa imageja kommentoi nämä 5 riviä
  # seuraavia kommentointeja poistetaan kun haetaan vikaa jos päivitys-skripti toimisi joskus virheellisesti; ei toistaiseksi ole tarvittu
  #sudo fuser -cuk /var/lib/dpkg/lock; sudo rm -f /var/lib/dpkg/lock   
  #sudo fuser -cuk /var/cache/apt/archives/lock; sudo rm -f /var/cache/apt/archives/lock
  sudo chroot /mnt rm -f /var/cache/apt/archives/lock /var/lib/aptitude/lock /var/lib/dpkg/lock /var/lib/apt/lists/lock
  #sudo chroot /mnt rm /var/lib/apt/lists/*$; sudo rm /var/lib/dpkg/lock$; sudo rm /var/cache/apt/archives/lock # pitäiskö siirtää alkuun?
  sudo chroot /mnt apt-get check # korjaa pakettivaraston riippuvuudet
  sudo chroot /mnt dpkg --force-confnew --configure -a
  sudo chroot /mnt apt-get --yes  --fix-broken install
  sudo chroot /mnt apt-get --yes  autoclean
  #sudo chroot /mnt apt-get --yes  clean # poistaa myös osittaiset_ja_vahingoittuneet paketit ja tekee toiminnan varmemmaksi, mutta hidastaa ellei verkkoyhteys ole tosinopea.
  sudo chroot /mnt apt-get --yes  autoremove
  # jos jossain vaiheessa kun vahingoittueen paketin nimi selviää saa sen poistettua: sudo dpkg --remove -force --force-remove-reinstreq paketin_nimi
  sudo chroot /mnt rm -f /var/crash/* # poistaa tehdyt raportit etteivät ne jatkuvasti kiusaisi. Jos ne kiusaa sittenkin: sudo gedit /etc/default/apport ja kirjoitta 0
  sudo chroot /mnt apt-get update
  [[ $(env | grep USER.*=ubuntu) ]] && sudo chroot /mnt apt-get -y upgrade || sudo chroot /mnt apt-get -y dist-upgrade
  # $($Chrootosio | sed -r 's/[0-9]+$//g')
   sudo chroot /mnt update-grub && echo -n 'grub.cfg sijoitettiin levylle: '; Levy
} # vastaava avaava sulkumerkki on rivillä 38
unmountmnt
done
}
# Pääohjelma
# echo ${0%} | tr "/" " " | awk '{print $(NF-2)}';read
#[[ $(lsb_release -i | grep Ubntu$ ) ]] && echo tämä on standardi Ubuntu joten jatketaan || echo tämä ei ole stardardi Ubuntu ja jatkaminen saattaa olla vaarallista. Vain vastaamalla: kyllä jatketaan. Haluatko välttämättä jatkaa? && read apu && [[ $apu != kyllä ]] && exit || echo jatketaan kun lupa saatiin
 KirjoitaOhje
 echo ''; sudo echo
 hommaalkoi="$(date +%s)"
 tarkistaKovalevyjenKaikkiOsiot # sitä osiota jolta on bootattu ei tarkisteta.
 paivitaKaikkiOsiot
 echo -e 'päivitetty: '$(date +"%d-%m-%y %k:%M")'\t ja päivittäminen kesti sekunneissa: '$(($(echo $(date +%s)-$hommaalkoi)))
:
**
Koska BASH:ilta puuttuu kirjastot tulee sen alkeellisistakin käskyistä usein todella hankalia eikä niiden laatu päätä huimaa. Esimerkiksi: Efi_osio voi sijaita millä osiolla hyvänsä, ei sen levyn ensimmäinen osio tarvitse olla. Kun halutaan tietää mikä laitenimi on sillä efi-osiolla jota osio /dev/sda6 käyttää niin se saadaan tietää käskyllä:
Koodia: [Valitse]
function laitenimeä_vastaava_levy () { echo ${1##*/} | sed -r 's/[0-9]+$//g' ;}
function levyn_efi_osio () { sudo fdisk -l | grep EFI | grep $(laitenimeä_vastaava_levy $1) | awk '{print $1}';}   # funktiosta voidaan kutsua toista funktiota
levyn_efi_osio  osion_laitenimi    # siis esimerkiksi: levyn_efi_osio /dev/sda6
- muuten se keno-viritelmä jota usein käytetään jotta saataisiin käskyn esitys selväpiirteiseksi onkin yleensä turha ja hankalan-näköinen sillä esimerkiksi tässä nuo kolme lausetta voi kopioida päätteeseen yhtenä palana - perään joutuu kylläkin painamaan return. Voit kopioida päätteeseen vaikka tuhatrivisen skriptin ja perään painaa return yhden kerran.
**
Siitäkin tämä BASH on kiva että kaikessa on ikuisesti parannettavaa ja parantaminen on yksinkertaista. Jokatapauksessa seuraavassa on tärkeää funktion muodostustapa. Muodosta: funktio {}  siirrytty muotoon: funktio () .
- kaarisulut muodostavat funktiolle oman prosessin joten funktion asetukset eivät saastuta muuta skriptiä:
Koodia: [Valitse]
function  käskynlähde() ( set -o pipefail; [[ $(type $1 | grep sisäänrakennettu ) ]] && echo sisäänrakennettu || which $1 | xargs -r readlink -f | xargs -r dpkg -S ; (( $? )) && echo käskyä ei ole ;); käskynlähde käskyn_nimi
- itseasiassa sekin on täysin selvää että testi on: (( $? )) eikä [[ $? ]] sillä tottakai vertailun pitää olla aritmeettinen. Kestipä pikään tajuta.

Nyt alkaa parantelu mennä alueelle jossa en osaa kunnolla testata, mutta toiminto olisi todella tarpeellinen jos sen vaan saisi toimimaan virheettömästi (saattaa toimiakin mutta en ole läheskään varma):
- selvitystä: käsky ei välttämättä löydy käskyn nimen mukaisesta paketista niinkuin esimerkiksi käsky: ls . Paketin nimi täytyy selvittää kulman takaa.
Koodia: [Valitse]
function  käskynlähde() ( set -o pipefail;
[[ $(type $1  2> /dev/null | grep sisäänrakennettu ) ]] && echo sisäänrakennettu ||
which "$@" | xargs -r readlink -f | xargs -r dpkg -S ;
(( $? )) && echo -n käskyä ei ole asennettu mutta se löytyy paketista: &&
apt-cache search $1 | awk '{print $1}' | grep -w ^$1 ;); käskynlähde agrep
:

- leikka-liimaa kaikki käskyn rivit kerralla - ei koodiin tarvitse laittaa kenoja vaan toimii se ilmankin
- kun käsky on kerran toiminut niin painamalla nappia nuoli ylös saat koko käskyn historiasta yhtenä rivinä ja voit kirjoittaa agrep:in tilalle ls tai echo tai xargs tai mitämielitkin.
- agrep on muuten käyttökelpoinen: se etsii samankaltaisia kuin mitä määrätään.
**
Taas kertaalleen aloin kehittää menetelmää jolla saisi tulostettua päätteessä käskyn antamishetkellä määriteltyjen muuttujien nimet ja arvot:
Koodia: [Valitse]
apu=$(echo ' '${!a*}' '${!b*}' '${!c*}' '${!d*}' '${!e*}' '${!f*}' '${!g*}' '${!h*}' '${!i*}' '${!j*}' '${!k*}' '${!l*}' '${!m*}' '${!n*}' '${!o*}' '${!u*}' '${!v*}' '${!w*}' '${!x*}' '${!z*}\
' '${!y*}' '${!A*}' '${!B*}' '${!C*}' ' ${!D*}' '${!E*}' '${!F*}' '${!G*}' '${!H*}' '${!I*}' '${!J*}' '${!K*}' '${!L*}' '${!M*}' '${!N*}' '${!O*}' '${!P*}' '${!Q*}' '${!R*}' '${!S*}' '${!T*}\
' '${!U*}' '${!V*}' '${!W*}' '${!X*}' '${!Z*}' '${!Y*}  | sed 's/ /\n$/g' | sed '/$\B/d' | sed '/$apu/d' | grep [[:lower:]] | tr [[:space:]] ¤) && (echo $apu | tr ¤ '\n';\
 eval echo $apu | tr ¤ '\n') > /tmp/delme && awk '{a[NR]=$0; i=NR}END{i=i+i%2;for(m=1;m<=i/2;m++) print a[m]"    "a[m+i/2]}' /tmp/delme | column -t
- tämän käskyn voi liittää skriptiinsä tai tehdä esimerkiksi näin: ensiksi tyhjennät päätteessä määritellyt muuttujat käskyllä: "exec bash"  ja sitten esimerkiksi: a=1 ja: b=2 jonka jälkeen annat edelläkuvatun käskyn. Se tulostaa:
$a   1
$b   2
- alle 10ms nopeus on BASH-skriptille erittäin suuri sillä tavanomaisin menetelmin ei saa 200ms nopeampaa aikaiseksi.
- nopeus ei myöskään riipu muuttujien lukumäärästä mikä on BASH-skriptille erikoista.     
- muuttujissa saa olla välilyöntejäkin eikä muuttujia silti tarvitse laittaa sulkuihin - mutta kyllä sulutkin saa olla.
- mikäli jokin muuttuja on määritelty mutta sillä ei ole arvoa näkyy tämä listauksesta.
- matriisista näytetään vain jäsen 0.
- awk on BASH:iakin monipuolisempi ja vaikeampi erittäin nopea skripti-kieli. Tuossa edellisessä se hoitaa tulosteen muotoilun mikä on se hidas ja vaikea osa. Tuo awk-osa on niin nopea ettei sen lisääminen tunnu edes vaikuttavan - Perl ja Python eivät missään nimessä pääse edes samaan. Awk kärsii aivan sama/media/petteri/tikku/OMATSKRIPTITsta kuin BASH:kin ja ne molemmat kärsivät samasta kuin assembler: ihan liian työläitä - vaikka ovatkin tarvittaessa tosi- nopeita.
Muuten: skriptien muuttujien nimissä pitää olla ainakin yksi pieni kirjan - yksinomaan suurilla kirjaimilla kirjoitetut ovat BASH:in omia muuttujia.
**
Assosiatiivisen matriisin kirjoittaminen levylle, sen lukeminen levyltä ja sen tulostaminen.
- matriisin nimi ja koko voivat olla mitä hyvänsä.
- muuten matriisit sorttaavat itsensä automaattisesti, joten lukujen järjestys muuttuu. Mutta osoitteet ovat aina samassa järjestyksessä kuin arvotkin.
- esitän tämän sillä se kertoo miksi bash:ia mollataan: esimerkiksi tämä matriisin tallettaminen: jos meitä pyydetään tekemään  skripti matriisin tallettamiseksi niin luultavasti tehtävä yritetään ratkaista  tekemällä skripti joka tallettaa matriisin? Totaalisen idioottimaista, vain opin saanut voi sortua moiseen.  Sensijaan tietämätön saattaa ajatella niinkuin tässä: matriisi on jo bash:in muistissa ja sen kuva muistissa on yksi tekstijono. Mikäli talletetaan tämä tekstijono niin homma on tehty ehdottomasti moitteetta - se normaalitapa sensijaan on buginen ja hidas kuin .... Näin on bash:issa aina: paljon puhuvan suupielet ovat ruskeat, niin minun kuin myös professorien.

Bash:issa assosiatiivisen matriisin voi siirtää funktioon globaalialueella mutta palautus ei globaalialueen kautta assosiatiivisena matriisina toistaiseksi onnistu. Palauttamisen voi kuitenkin tehdä "normaalina" matriisina. Sillä sekä "normaalin" että assosiatiivisen matriisin muistikuvat ovat samanlaisia ja kun siirretään muistikuva niin on bash:ista kiinni tulkitseeko se matriisin muistikuvan "tavallisena" vai assosiatiivisena.
Koodia: [Valitse]
#!/bin/bash
declare -A matriisi # assosiatiivinen matriisi siirtyy globaalialueella funktioon päin mutta palauttaminen ei onnistu noinvain. 181

function talleta_matriisi () { echo $(declare -p | grep "declare -A".*$1  | cut -d= -f 2-) > /tmp/delme ;} # assosiatiivisen matriisin muistikuva RAM:missa talletetaan levylle.

function tulosta_matriisi () { ( echo -n 'jäsenten arvot   : '; eval echo \${$1[*]}; echo -n 'jäsenten osoitteet : '; eval echo \${!$1[@]} ) | column -t ;}

# muodostetaan koematriisi:
matriisi[ykkönen]=yksi
matriisi[555]=2
matriisi[17]=3
matriisi[2]=4

talleta_matriisi matriisi
unset matriisi; declare -A matriisi # unset nollaa matriisin joten voi olla varma että matriisi on luettu levyltä. Mutta unset vie samalla assosiative-statuksen joten se täytyy palauttaa. Lauseen voi kommentoida
eval matriisi=$(cat /tmp/delme)     # assosiatiivinen matriisi muodostetaan uudelleen levyltä luetun muistikuvan mukaan. Erillistä funktiota levyltä lukemiseen ei edes tarvita
tulosta_matriisi matriisi
 
« Viimeksi muokattu: 09.12.16 - klo:11.49 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #224 : 11.12.16 - klo:10.50 »
 Loopit ovat sellaisia kieliä varten jotka ratkaisevat ongelmansa väkivoimin ja bash:issahan ei ole voimaa. Mutta bash on ammattilaisten tekemä ja poikkeuksetta kaikkeen löytyy keinonsa. Kunhan ei käytetä looppeja; sana: for ... kuuluu kelvottomiin skripteihin; varmaankin jonkun Perlin kehittäjät ujuttivat loopit bashiin jotta kilpailijasta tulisi entistäkin kehnompi.
- toki laiskuus on ihan kelpo syy tehdä bashissakin looppeja mikäli esimerkiksi hitaus ei merkitse.
- minua on aina kiinnostanut ilmiöt nimiltään: "hävitä huonommalleen" ja "pitää parempaansa alistettuna". Niinkuin OS/2 hävisi Windowsille niin on myös bash hävinnyt huonommalleen. Tottakai kun huonompaa on kehitetty päivittäin jo kolmenkymmenetä vuotta on se kehittynytkin paremmaksi kun bashia ei kehitetä juuri ollenkaan. Ja kun lisäksi opetetaan käyttämään bash:ia tehottomasti esimerkiksi jättämällä kertomatta että jos bashissa käyttää looppeja niin tehot laskevat; joskus vähän mutta joskus ne tekevät skriptistä käyttökelvottoman .

"Bash-loopittomia" ratkaisuja löytyy kaikkeen. Esimerkiksi muutama erikoistapaus - ensiksi matriisista etsiminen:
Koodia: [Valitse]
koe=(kaksiyksi kaksi kolme kaksikolmatta); echo -e ${koe[@]/%/\\n} | grep -nw kaksi | grep -o ^[0-9]*
tulostaa 2 niinkuin pitääkin ja nopeus kasvaa mielettömästi.

Vielä kammottavampi esimerkki on matriisien vertaaminen; totunnaisin keinoin hommaan sotketaan looppien lisäksi tiedostot. Mutta totunnaisia keinoja käyttäen matriisien vertaaminen onnistuu muistissakin:
Koodia: [Valitse]
mat1=(1 2 3); mat2=(1 2 3); [[ ! $(diff <(echo $(echo -e ${mat1[@]/%/\\n})) <(echo $(echo -e ${mat2[@]/%/\\n}))) ]] && echo matriisit ovat samanlaiset || echo matriisit ovat erilaiset
RAM:missa olevia muistikuvia vertaamalla toiminta on nopeampaa ja varmempaa ... ja vielä paljon epä-totunnaisempaa, esimerkiksi:
Koodia: [Valitse]
mat1=(1 2 3); mat2=(1 2 3); apu1="$(declare -p | grep "declare.*mat1"  | cut -d= -f 2- | tr -d [])"; apu2="$(declare -p | grep "declare.*mat2"  | cut -d= -f 2- | tr -d [])"; [[ $apu1 = $apu2 ]] && echo matriisit ovat samanlaiset || echo matriisit eivät ole samanlaisia
käsky ei välitä ovatko matriisit "tavallisia" tai assositiivisia eikä paljoa matriisien koostakaan. Ja osoitteiden vastaavuus tarkistetaan myös. Ja kaikkien matriisien arvoissa sekä assosiatiivisten matriisien arvoissa että osoitteissa saa olla välilyöntejä mikäli ne ovat heittomerkkien välissä - tai osoitteissa ei kavaita heittomerkkejäkään. Toiminta on moitteetonta, mutta häviää "nopeuskunkulle" hieman (mutta jotenkin varmemman oloinen).

Lopuksi vielä "nopeuskunkku" (Tämä tarkistaa osoitteetkin ja kelpaa assosiatiivisillekin. Tässäkään ei ole looppia):
Koodia: [Valitse]
mat1=({1..10000}); mat2=({1..10000}); [[ $(echo "${mat1[*]}") = $(echo "${mat2[*]}") && $(echo "${!mat1[*]}") = $(echo "${!mat2[*]}") ]] && echo matriisit ovat samanlaiset || echo matriisit ovat erilaiset
- luultavasti lisäksi melkein aina oikeellinen, mutta varmuutta siitä ei ole - aika näyttää.

-vertailun vuoksi se "normaali" menetelmä joka käyttää looppia mutta tarkistaa osoitteetkin:
Koodia: [Valitse]
mat1=({1..10000}); mat2=({1..10000});  for n in {1..10000}; do [[ "${mat1[$n]}" != "${mat2[$n]}" && "${!mat1[$n]}" != "${!mat2[$n]}" ]] && echo matriisit ovat erilaiset && break ; done; echo matriisit ovat samanlaiset
tämä on 10-200 kertaa hitaampi mutta toimii kyllä assosiatiivisillekin ja nimissä ja osoitteissa saa olla välilyöntejä. Mutta tuo hitaus: valinta ei tuota vaikeuksia kun pitää valita 0.3 sekuntia kestävän ja 60 sekuntia kestävän välillä.

- huomautus:  bash-matriisien osoite-alueella voi olla aukkoja - ominaisuus josta ei nykyään osata käyttää hyödyksi mutta kyllä sillä käyttönsä on. Mutta se tekee sen ettei pelkkä arvojen tarkistus riitä vaan osoitteet täytyy tarkistaa myös. 

Tottakai looppaaminen on välttämätöntä bash:issakin, mutta niin että bash käyttää käskyjä joissa on looppi sisällä: silloin tuo looppaaminen suoritetaan kun käsky on tulkattu C-kieliseksi. Ja joissakin käskyissä looppaaaminen on tarkoituksenakin, esimerkiksi:
Koodia: [Valitse]
seq -s* luku_josta_kertoma_lasketaan | bc
on nopein tapa laskea kertoma. Mutta siinäkään ei ole rakennetta: for ... ja looppaaminen tapahtuu C-koodisena. Mutta siiretään looppi BChen, ne ovat melkein yhtänopeita:
Koodia: [Valitse]
echo 'b=1;n=10000;while(n--)b=(b*(n+1));b' | bc
tai:
echo  "for (i = 1; i < 10000; i += 1)  i" | bc | sed ':a;{N;s/\n/*/};ba' | bc
**
Samoin etsintä matriisista:
Koodia: [Valitse]
function maksimi (){ eval echo \${$1[*]} | tr ' ' '\n' | sort -n | tail -1 ;}; mat=({999..1}); maksimi mat
function minimi (){ eval echo \${$1[*]} | tr ' ' '\n' | sort -nr | tail -1 ;}; mat=({999..1}); minimi mat

ei niissä looppeja tarvita ja niinpä ne ovatkin tosinopeita. Lisäksi niille passataan vain matriisin nimi. Niihinkin saa ujutettua bc:n jotta matriisin jäsenet voisivat olla myös ratkaisemattomassa muodossa:
Koodia: [Valitse]
function minimi (){ eval echo \${$1[*]} | tr '[]' '()' | tr ' ' '\n' | bc -l | sort -n | head -1 ;}; mat[1]='1+1'; mat[2]=s[.01]/c[.01]; minimi mat
jos bc: numeromäärä ei ole miellytä niin vahda sitä (numeromäärä saa olla mikähyvänsä positiivinen kokonaisluku) ja samalla kannattaa vaihtaa bc:n tulostustapaa:
Koodia: [Valitse]
function minimi (){ export BC_LINE_LENGTH=0; echo 'scale=566'>/tmp/delme; export BC_ENV_ARGS=/tmp/delme; eval echo \${$1[*]} | tr '[]' '()' | tr ' ' '\n' | bc -l | sort -n | head -1 ;}; mat[1]='1+1'; mat[2]=s[.01]/c[.01]; minimi mat
matriisin summan laskeminen:
Koodia: [Valitse]
function summa (){ summa=$(echo "${mat[@]/%/+}" | sed 's/+$//' | bc); echo 'summa='$summa ;}; mat=({999..1}); summa mat
matriisin summan ja keskiarvon laskeminen:
Koodia: [Valitse]
function summaJaKeskiarvo (){ echo "${mat[@]/%/+}" | sed 's/+$//' > /tmp/delme; summa=$( cat /tmp/delme | bc); lkm=$(cat /tmp/delme | tr -dc + | wc -m); echo 'matriisinsumma='$summa' keskiarvo='$(echo $summa/$lkm | bc) ;}; mat=({999..1}); summaJaKeskiarvo mat
**
Bashin käskykantaa voi lisätä funktioilla. Itseasiassa koneessasi on hirveä läjä jo määriteltyjä funktioita: annapa käsky: declare . Tosin noissa funktioissa ei ole sanaa: function )
- sitä olen aina ihmetellyt miksi ne ei edes vahingossa lisää joskus jotain hyödyllistä ?  Ja sitäpaitsi niille kaikille on käyttöä muttei kylläkään tässä ulottuvuudessa.
- funktiokutsuun kuluva aika on 45mikrosekuntia.
- muuttujat ovat yhteisiä kutsujan kanssa.

Aloinpa tehdä funktiota, joka etsii luetellusta joukosta minimin. Sen vaatimuksia:
- joukossa voi olla mielivaltainen määrä jäseniä eikä jäsenen pituutta rajoiteta
- jäsenissä voi olla etumerkki tai sitten ei
- desimaalilukujen tulee kelvata
- joukon jäsenissä voi olla tieteellistä esitystapaa (=exponentteja tyyliin: 1e3)
- eksponenttimerkki voi olla iso tai pieni e.
- jäsenet voi olla laskutoimituksia.
- myös sinit ja muut sellaiset tulee voida ratkaista. 
Tämmönen siitä tuli:
Koodia: [Valitse]
function minimi () { for n in $(seq $((${#@}+1))); do eval echo \$$n; done | tr '[]' '()' | sed 's/[eE]/*10^/g' | sed 's/^+//' | bc -l | tr -d '\\' | sort -nr | tail -1 ;}; minimi 1.1e3 s[.01]/c[.01] 1.2E3
**
Bash:issa kohtaa kummallisuuksia jatkuvasti, nyt tuli vastaan käsky:
Koodia: [Valitse]
function permutaatiot (){ eval echo $(printf "{%s,}" "$@"); }; permutaatiot {a..z}
käskynsuoritus kestää noin 2,5 minuuttia josta puolet näytölle kirjoittamista. Muodostuva datamäärä on noin 850 gigaa ja RAM:miin siitä ilmeisesti sijoitetaan 14G joten eipä ajankulu ole ihme - muodostettavien permutaatioiden lukumäärä on kyllä oikea elikä 67108863. En usko että muut kielet toimivat näinkään nopeasti. Muistitila jää varatuksi kunnes annetaan käsky: exec bash.
- lisäsinpä kirjaimen. Datamäärä nousi 1700 gigaan. RAM:mia kului 15,5G ja swap:pia 2.2G. Linux on kova peli ja bash osaa käskeä sitä.
**
Fifo:n ylläpitäminen levyllä on yksinkertaista:

Rivin lisääminen tiedoston huipulle: echo 'tämä on uusi rivi1' >> .fifo
Rivin lukeminen tiedoston pohjalta : apu=$(head -n 1 .fifo); echo $apu; sed -i '1d' .fifo
- kun rivin lukee niin se pitää poistaa, tämäntakia lukemisen perään tuo: sed -i '1d' .fifo
- tallettaa voi mitähyvänsä.
- pinoa ylläpidetään levyllä. Onhan se hidasta, mutta mitään ei häviä vaikka sammuttaisi koneensa.
- fyysisiä virheitä ei voi tulla ja loogisilta virheiltähän mikään ei suojaa.


Fifo voidaan toteuttaa matriisissakin - ja matriisi-toteutus on nopea. Siitä lienee viisainta esittää käyttökin:
Koodia: [Valitse]
#!/bin/bash
function lisää_pohjalle () { mat=("$@"); mat=($(echo -e ${mat[@]/%/\\n} | awk '{print NR" "$0}' | sort -k1 -nr  | sed 's/^[^ ]* //g')) ;}
function lue_pinnalta  () { eka=${mat[${#mat[*]}-1]}; unset mat[${#mat[*]}-1] ;} # kun jäsen luetaan täytyy se poistaa pinosta
unset mat; lisää_pinnalle {1..10000};  # lisätä voidaan yksi kerrallaan tai monta kerrallaan. Lisättävä voi olla mitä tyyppiä hyvänsä.
for n in {1..10000}; do lue_pinnalta; echo -n $eka' '; done
- tässävaiheessa ei ole tarkoitus tehdä jotain hyödyllistä, vaan tarkistella väitettä että bash on hidas ja kyvytön.  Eihän se 70mikrosekuntia per alkio ole yleensä nopeaa, mutta bash:ille kyllä on.



« Viimeksi muokattu: 18.12.16 - klo:21.20 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #225 : 24.12.16 - klo:10.39 »
Kielen saattamiseksi rappiolle sen kehittäjille täytyy uskotella että kielen parhaat ominaisuudet ovat kelvottomia. Esimerkiksi esitetään että goto käsky on iljettävä. Bash:in kehittäjät ja sen parhaat "tukijatkin" ovat uskoneet sen ja esittäävätkin bash:in muuttujat ainoastaan luku-arvoja säilyttäviksi. Mutta bash:in muuttujat pystyvät säilyttämään myös ohjaus-rakenteita. Esimerkiksi:
a=ls; $a 
kutsuu funktiota ls joka listaa ihan samallatavoin kuin käsky ls olisi kirjoitettu suoraan. Parametritkin sallitaan, esimerkiksi a="cat /etc/fstab"; $a
joka toimii ihan samoin kuin käsky esitettäisiin suoraan: cat /etc/fstab
Tai käsky: teksturi=nano; $teksturi teksti
toimii ihan samoin kuin käsky: nano teksti
tai peräti: trappi="trap LopetaSkriptiJaPalaaSkriptiajuriin SIGINT"; $trappi
toimii ihan samoin kuin kirjoitettaisiin suoraan:  trap LopetaSkriptiJaPalaaSkriptiajuriin SIGINT
joka painettaessa ctrl-c siirtää ohjelman suorituksen funktioon nimeltä: LopetaSkriptiJaPalaaSkriptiajuriin

Kutsuttavat funktiot voivat olla myös itsemääriteltyjä; esimerkiksi
function hupi () { echo vitsi ;}; a=hupi; $a
tulostaa sanan vitsi. Suurissa skripteissä tämä on erittäin käyttökelpoista; muuttujan arvoa muuttamalla voidaan määrätä minne ohjelmansuoritus hyppää; vanhat muistavat basicin - tämä on sama kuin basic:in "GOSUB X OF ..." - kuitenkin osoitteet käsitetään funktionnimiksi eikä numero-osoitteiksi. Ja hyppy-osoitteita voidaan muuttaa ohjelmallisesti.
- funktiokutsu on ilmeisesti yhtä nopea näinkin - jokatapauksessa erot ovat mikrosekunteja.
- monet eturivin ohjelmoijat sanovat että goto on ihan käyttökelpoinen monessa paikassa. Niinkuin esimerkiksi Torvalds.
- toinen keino laittaa bash aisoihin on uskotella että käskyä eval ei saa käyttää turvallisuussyistä. En luultavasti ymmärtäisi vaikka selitettäisiinkin kuinka se riski syntyy mutta ihmetyttää miksei kukaan ole yrittänytkään selittää - ilmeisesti kukaan muukaan ei ymmärrä ja kaikki vain apinoivat jonkun irvileuan aloittamaa huhua. Sillä on aivan varmaa että se on turvallisuusriski - ihan niinkuin kaikki muukin.
« Viimeksi muokattu: 24.12.16 - klo:12.01 kirjoittanut petteriIII »

matsukan

  • Käyttäjä
  • Viestejä: 2152
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #226 : 24.12.16 - klo:12.23 »
>Kielen saattamiseksi rappiolle sen kehittäjille täytyy uskotella että kielen parhaat ominaisuudet ovat kelvottomia. Esimerkiksi esitetään että goto käsky on iljettävä. Bash:in kehittäjät ja sen parhaat >"tukijatkin" ovat uskoneet sen ja esittäävätkin bash:in muuttujat ainoastaan luku-arvoja säilyttäviksi. Mutta bash:in muuttujat pystyvät säilyttämään myös ohjaus-rakenteita. Esimerkiksi:

Tämä viha goto käsky juontuu basic kieleen ja sen puutteisiin. goto käskyä käyttäen voi tulla tapauksia jossa goto käsky joutuu väärään paikkaan esim versionhallinan takia. Esim parin kolmen vuoden taakaa muistan Applen koodeissa  ssl bugin johtuneen juurikin silloin kun käytettiin goto käskyä eikä esim aaltosulkeita.

Voihan olla että esim kernelin syvimmissä c kielen rakenteissa on hyödyllistä joskus käyttää goto käskyä.  Tai joissakin bash scripteissä. Mutta esim c++ koodeissa, goto käskyä ei tulisi käyttää.
Pohjois-pohjanmaa
-- motto:  backupin tarve huomataan aina liian myöhään

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #227 : 24.12.16 - klo:20.36 »
Tämä viha goto käsky juontuu basic kieleen ja sen puutteisiin. goto käskyä käyttäen voi tulla tapauksia jossa goto käsky joutuu väärään paikkaan esim versionhallinan takia. Esim parin kolmen vuoden taakaa muistan Applen koodeissa  ssl bugin johtuneen juurikin silloin kun käytettiin goto käskyä eikä esim aaltosulkeita.

Voihan olla että esim kernelin syvimmissä c kielen rakenteissa on hyödyllistä joskus käyttää goto käskyä.  Tai joissakin bash scripteissä. Mutta esim c++ koodeissa, goto käskyä ei tulisi käyttää.

Enpä tiennytkään ja kiva että kerroit. Sillä aina uudet tiedot oikaisee käsityksiä.
**
Edellinen oli tarinaa muuttujista. Puhutaanpa muuttujista toisesta näkökulmasta:

Bash tuntee vain tekstijonot. Kuitenkin tulkille voi antaa ohjeita kuinka sen pitää tekstijono tulkita silloin kun se pitäisi tunnistaa numeroksi. Bash osaa tulkita tekstijonon ainoastaan etumerkillisenä kokonaislukuna alueelta -9223372036854775808-9223372036854775807; mikäli arvo on näiden alueiden ulkopuolella niin tulkinta antaa väärän tuloksen eikä asiasta edes urputeta. Mikäli luvussa  on desimaalipiste niin välittämättä declare:sta muiodostetaan tekstijono - vasta kun tekstijonoa yritetään käyttää bash:in omissa matemaattisissa laskuissa se aiheuttaa virheen samoinkuin mikätahansa muukin aakkonen. Sanotaan ettei bashin muuttujilla ole tyyppiä - no sanotaan niin, mutta jokatapauksessa bash:in voi määrätä seuraaviin tulkintoihin kirjoittamalla skriptin alkuun:
 
declare a ;    määrittelee että a on etumerkillinen kokonaisluku väliltä: -9223372036854775808-9223372036854775807
declare -i a ; määrittelee että a on kokonaisluku samalta lukualueelta: näennäisesti ihan sama.
                                    - kuitenkin:"declare -i a; a=1+2; echo $a"  tulostaa 3. Kuntaas: "declare a; a=1+2; echo $a" tulostaa 1+2
declare -r a                ; määrittelee että muuttujan a voi ainoastaan lukea. Siis käytännössä määrittely on: "declare -r a=1" ja jos myöhemmin määritelee arvon uudestaan aiheuttaa se virheen.
declare -a muuttuja  ; luo muuttujan joka tulkitaan matriisiksi
declare -A muuttuja  ; luo muuttujan joka tulkitaan assosiatiiviseksi matriisiksi. Nimenomaan pitää huomioida että tulkitaan - itse matriisi on aivan samanlainen.
declare -f muuttuja  ; luo muuttujan joka tulkitaan funktioksi
declare -x muuttuja  ; luo muuttujan joka siirtyy ilmanmuuta myös "lapsi-prosessien" käyttöön.
declare a=373         ; muuttujalle voidan antaa arvo samalla kun se määritellään
declare -ira a           ; samallakertaa voidaan antaa useampiakin määreitä.
- mitään määrittelyjä ei tarvitse tehdä, sillä bash on kova olettamaan.
- jos määritetään a=0.001 niin a ei ole numero vaan tekstijono.
- funktioissa voi määritellä että muuttuja tunnetaan vain funktion sisällä, määrittely on: local muuttuja
- kaikki bashin muuttujat ovat globaalialueella elikä ne tunnetaan kaikkialla. Tämä saa naurettavaan valoon sen huomautuksen jonka useimmat esittävät: bash:in funktiot eivät osaa palauttaa arvoja. Eihän niiden tarvitsekaan palauttaa sillä arvothan ovat muuttuneet muutenkin - puute se on sittenkin mutta sen kanssa voi elää. Globaalialueella toimiminen ei aiheuta ristiriita-tilanteita massiivisissakaan skripteissä mikäli skriptaaja osaa nimetä muuttujat oikein.function summaa { echo summa=$( eval echo \$\{$1\[\*\]\} | tr ' ' + | bc -l) ;}; mat=({1..100000}); time summaa mat
- bash-skripteissä voi laskea myös desimaaliluvuilla, mutta itse bash ei laskemiseen sovellu vaan laskut on tarkoitus suorittaa funktiossa nimeltään bc; esimerkiksi: echo 0.001+0.002 | bc    tai toisella tavalla: bc <<< 0.001+0.002
- ei merkitse mitää ovatko bc:n syöttöluvut numeroita tai tekstijonoja kunhan niissä on vain numeroita, mahdollisesti molemmilla luvuilla etumerkit ja mahdollisesti yksi desimaalipiste.   
- muuten bc:ssä numeroiden määrää ei mitenkään rajoiteta vaan muuttujassa saa kokonaisosassa olla numeroita vaikka ziljoona, sitten desimaalipiste ja taas ziljoona numeroa desimaaliosassa. Samoin käytettävissä on funktiokirjasto, joten bash:issa matematiikka on helppoa. Ei niin että bc olisi hyvä matematiikkaohjelma, mutta helppo se on noissa yksinkertaisissa laskuissa.

- sekä tavallisen että assosiatiivisen matriisin sisäinen esitys on samanlainen; kyse on siis kuinka matriisit tulkitaan:
- tavallinen matriisi:     
määrittely: declare -a mat1; mat1[1]=1; mat1[2]=2; mat1[3]=3   koneen muistissa on: declare -a mat1=([1]="1" [2]="2" [3]="3" )
- assosiatiivinen matriisi:
määrittely: declare -A mat2; mat2[1]=1; mat2[2]=2; mat2[3]=3   koneen muistissa on: declare -A mat2=([1]="1" [2]="2" [3]="3" )
- siis koneen muistissa kumpikin määrittely on melkein samanlainen
- assosiatiivisen matriisin osoitteet voivat kyllä olla tekstiäkin ja silloinhan tavallinen matriisi ei edes toimisi, mutta esitystapa olisi silloinkin melkein samanlainen.

- hexa-luvuille  ei ole declarea. Hexa-alueen laskutoimitukset esimerkiksi  : echo $((0xF + 0xF))  joka tulostaa 30 - ilmeisesti kääntää esin hexat desimaalialueelle, suorittaa laskutoimitukset ja tulostaa aina desimaalisena.
- oktaaliluville ei ole declarea. Oktaalialueen laskutoimitukset esimerkiksi: echo $((8#100)) joka tulostaa 64     - ilmeisesti kääntää esin hexat desimaalialueelle, suorittaa laskutoimitukset ja tulostaa aina desimaalisena.
- bc voi suorittaa laskut mielivaltaisessa lukujärjestelmässä ja tulostaa myös mielivaltaisessa lukujärjestelmässä: echo "ibase=8; obase=2;11" | bc
**
Koska bash on niin tekstijono-suuntautunut ovat siinä useimmat datan-käsittely-käskytkin tarkoitettu tekstijonoille - mutta toisaalta numeroitakin voi käsitellä tekstijonoina ja joskus siinä on järkeäkin:
muuttuja=apua; echo ${muuttuja^}  # muuta ensimmäinen suuri kirjain suureksi
muuttuja=apua; echo ${muuttuja^^} # muuta kaikki kirjaimet suuriksi
muuttuja=APUA; echo ${muuttuja,}  # muuta ensimmäinen kirjain pieneksi
muuttuja=APUA; echo ${muuttuja,,} # muuta kaikki kirjaimet pieneksi
muuttuja=APUA; echo ${muuttuja~}  # muuta ensimmäinen kirjan toisentyyppiseksi kuin se oli
muuttuja=APUA; echo ${muuttuja~~} # muuta kaikki kirjaimet toisentyyppiseksi kuin ne olivat

muuttuja=appuva; echo ${muuttuja/p/k}  # muuta ensimmäinen p-kirjan k-kirjaimeksi - toimii kirjainryhmillekin ja mikäli on määrätty ryhmä niin yksi ei kelpaa - ryhmät voivat olla erisuuria eli tämä on super-tr
muuttuja=appuva; echo ${muuttuja//p/k} # muuta kaikki p-kirjaimet k-kirjaimiksi   - toimii kirjainryhmillekin ja mikäli on määrätty ryhmä niin yksi ei kelpaa - ryhmät voivat olla erisuuria eli tämä on super-tr

- ja vastaavia loppumattomiin, väsyin jo katsellessani
**
Bash:in matematiikka toimii vain kokonaisluvuille ja on muutenkin niin vikaherkkää että sitä kannattaa käyttää hyvin harkiten. Salahaudat poislukien: on se ainakin nopeaa - bash:iksi tarkoitan:
echo $((1+1))  tulostaa 2 elikä summan. - * ja / toimivat myös.
echo $((5%2))  tulostaa 1 eli jakojäännöksen
echo $((5**2)) tulostaa 25 elikä potenssiin korotuksen
echo $((5^2))  tulostaa 7 elikä binääri-lukujen 101 ja 10 bitwise-XOR:in; siis kyseessä ei ole potenssiin korotus
echo $((2|4))  tulostaa 6 elikä binääri-lukujen 10 ja 100 bitwise-OR:in. Muutkin loogiset operaattorit tunnetaan.
a=1; echo $((~a))  tulostaa 2. Negaatiostahan tässä kyse on mutta tulokseen lisätään jostasin syystä aina 1. option-base hommeli vaikka bash:issa ei sitä ole?
a=1; echo $((a<<=2)) tulostaa 4 elikä a:n binääri-esityksen siirrto 2 paikkaa vasemmalle ja tulos muutettu desimaaliluvuksi
- voi laskutoimituksia laittaa samaan useampiakin: echo $((5**2-8/2)) tulostaa 21
- sulutkin otetaan huomioon: echo $(((2+2)*3)) tulostaa 12 kuntaas: echo $((2+(2*3))) tulostaa 8
- muuten nopein tapa laskea kertoma on: echo $(($(seq -s* 20))) # kylläkin vain luvuista alle 20

Bash toimii kuitenkin loistavasti desimaaliluvuilla rajoittamattoman monen desimaalin laskutarkkuuden omaavan ohjelman bc avulla jolla on kytkimellä -l käytettävissään funktiokirjasto. Esimerkiksi:
echo 0.001+0.002 | bc    tai toisin: bc <<< 0.001+0.002            # normaalit laskutoimitukset; samat kuin bash:issakin
echo "for (i = 4.00; i < 5.00; i += 0.01)  i" | bc                                # desimaalinen looppi
echo 1.0000002 '>' 1.0000001 | bc                                               # desimaaliluvut vertaillaan bc:ssä. Tai luvut aina viisainta verrata bc:llä sillä bc toimii yhtähyvin myös kokonaisluvuille.
echo $(echo "s(4*a(1)*90/180)" | bc -l)                                         # 90-asteen sini. Siksi kaava on niin inhoittavan näköinen kun bc toimii radiaaneissa ja ihmiset haluaa toimia asteissa.
**
Koska loopit ovat bash:in pahiksia niin aloin etsiä niille korvaavaa toimintaa: tässä tapauksessa oli kyse pino-rakenteen tutkiminen: inhoitti ihan esittää lopputulos jossa pinon hoitamiseen kului 20ms ja looppeihin 700ms.
Tässä esitetty kertoo jotenkin  kuinka  "loopittomia looppeja" tehdään. Menetelmä saattaa johtaa johonkin: vaikka bash on jo melkein kuollut eikä tämmöiset enää auta mitään, niin sikäli asia on mielenkiintoinen että muut tulkkaavat kielet taitaa jäädä jalkoihin.
- "looppi" on nopea: 11 mikrosekuntia per kierros ja jokaisella kierroksella suoritetaan tässä yksi funktiokutsu ja matriisi-operaatio. Liian hyvää ollakseen totta?
Koodia: [Valitse]
function lisää_perään () { mat+=("$@") ;}; unset mat; $(seq -s' 'lisää_perään' ' 10000 | sed 's/^[0-9]*//'); echo ${mat[*]//lisää_perään/}
**
Nämä loopittomat ratkaisut ovat todella nopeita, mutta meidät kaikki on opetettu tekemään bash-skriptit siten että ne mählivät kaikkialla. Ja kun on opetettu väärin niin vain harvoin pystyy heittämään yhden väärin opitun pois, ja ennenkuin skriptit toimivat nopeasti ne täytyy kaikki karsia pois.

Tämä loopiton skripti laskee summan matriisista jonka nimi mainitaan. Kun matriisin nimen vaihtaa niin mitään muuta ei tarvitse muuttaa.
- vaikka puhutaan summasta niin monet muutkin matematiikka-operaatiot ovat mahdollisia. Muodostettavan matriisin "koolla ei ole ylärajaa", se voi olla vaikka satatuhat jäseninen kuten ({1..100000}). Matriisi muodostetaan ainoastaan testausta varten - matriisi voidaan yhtähyvin määritellä myös: mat=(6*6 -1*6^2 .00000000000000000000000000000000000000000001)
siis desimaalien luvulla ei ole mitään rajaa ja jäsenet voivat olla ratkaisemattomassa muodossa - myös sinit sun logaritmit tunnetaan mutta tutustu bc-hen ennen kuin käytät niitä.
Koodia: [Valitse]
function summaa { echo summa=$( eval echo \$\{$1\[\*\]\} | tr ' ' + | bc -l) ;}; mat=({1..100000}); time summaa mat
- time:n antama suoritusaika on noin .14 sekuntia ja se vaihtelee senverran kun skripteillä yleensäkin. Mutta ajettaessa skripti päätteessä ensimmäisen kerran kestää noin 1.8 sekuntia sellaista aikaa jolloin ei tunnu tapahtuvan mitään. Aika kuluu mat-matriisin täyttämiseen. Kun matriisi on kerran täytetty sen uudelleenmuodostaminen melkein-samanlaisena on huomattavasti nopeampaa joten ajettaessa skripti myöhemmin samassa päätteessä tuo 1.8 sekuntia vähenee .2 sekuntiin. Mikäli skriptiä ajetaan kymmeniä kertoja alkaa matriisin uudelleenmuodostusaika taas kasvaa.
- "skriptin" voi kopioida foorumilta Ubuntun päätteeseen ja painaa enter.

Inhottavamman näköinen versio poistaa turhat etu-plussat, muuttaa pi:n arvoksi 3.14... ja muuttaa e:n arvoksi 2.71... silloin kun on kyse luonnollisen lukujärjestelmän kantaluvusta ja muutoin eksponenttimuotoon:
Koodia: [Valitse]
function summaa { echo summa=$( eval echo \$\{$1\[\*\]\} | sed 's/^+//;s/ /+/g;s/pi/4*a(1)/g;s/\be\b/e(1)/g;s/\Be\B/*10^/g' | bc -l) ;}; mat=(e+2*pi 1e3); time summaa mat
**
Siitä tämä BASH on mukava, että kukaan ei voi koskaan tehdä sitä parasta versiota - en minä etkä sinä. Toisin ilmaistuna: ainoastaan kun pysyy hiljaa ei puhu potaskaa. Siis ei ole mitään paineita: teet mitä vaan niin pieleen menee.

Taas uusi versio loopittomasta bash-skriptistä matriisin jäsenen arvoa vastaavan osoitteen etsimiseksi. Skripti on montakertaa nopeampi kuin aikaisemmat ja se toimii aina jokseenkin yhtänopeasti.
- matriisin nimi voi olla mikävaan ja samassa skriptissä voidaan kutsua hakua eri paikoista - ja kokonaisluvuilla, desimaaliluvuilla tai sanoilla muodostetuista matriiseista - sanoissa saa olla välilyöntejäkin.

BASH:issa matriisin jäsenten osoitteet voivat pomppia sinnetänne jättäen osoitteita väliinkin. Ja matriiseissa yleensäkin samaa arvoa voi löytyä monestakin eri osoitteesta. Lisäksi bash:issa matriisin ensimmäinen numero-osoite voi olla mikähyvänsä positiivinen kokonaisluku - ja assosiatiivisessa matriisissa osoite voi olla tekstiäkin. Näiden ominaisuuksien vuoksi eivät "normaalit" skriptit toimi hyvin etsittäessä jäsenen arvoa vastaavaa osoitetta.
Koodia: [Valitse]
#!/bin/bash
function arvoa_vastaava_osoite () { osoitelista=($(declare -p | grep dec.*$1= | grep -Po \[[[:alnum:]]*\]=\""$2"\" | grep -Po '[[:alnum:]]*(?=])')); }
unset mat # unset tuhoaa matriisin, mutta myös tiedon siitä oliko se tavallinen vaiko assosiatiivinen. Tämä siis hyväksyy myös assosiatiiviset matriisit.
unset osoitelista
mat=($(seq 10000000 | awk '{print rand()}')) # luodaan kymmenmiljoonainen matriisi noin kuusinumeroisista satunnaisluvuista.
arvoa_vastaava_osoite mat 0.500489
echo etsittävä: .500489' löytyi seuraavista osoitteista:'
echo -e ${osoitelista[*]/#/\\n} | sed 1d
echo; echo ja varmistukseksi:
for (( n=0; n<${#osoitelista[@]}; n++ )); do echo osoitteessa:${osoitelista[@]:$n:1}' on arvo:'${mat[${osoitelista[@]:$n:1}]}; done
- tuon kymmenmiljoonaisen matriisin luominen satunnaisluvuista kestää noin 12 sekuntia ja etsiminen sieltä 3.5 sekuntia elikä 0.35mikrosekuntia osoitteelta .  Osoitteitahan tulee monta sillä laskettaessa 6-numeroinen satunnaislukuluku 10 miljoonakertaa saadaan sama luku yleensä 6-14 kertaa.
- kyllä tämä toimii erinomaisesti pienienkin matriisien kanssa. Silloikin kun arvot ovat tekstijonoja ja assosiatiivisissa osoitteetkin saavat olla tekstijonoja.
**
Bash:in matriisit ovat tosiaan niin kummalliset että ainoa keino saada varmasti oikea käsitys matriisin kokoonpanosta on katsoa millaisen määrittelyn bash itse on siitä muistiin muodostanut - lisäksi se on ehdottomasti nopein keino. Esimerkiksi matriisien yhtäsuuruuden tarkistamiseksi on lukemattomia toinen toistaa etevämpiä keinoja mutta kaikki ne kompastuvat johonkin.
Mutta tämä keino ei kompastu mihinkään ja se sopii yhtähyvin yksinkertaisiin matriiseihin kuin hyper-monimutkaisiinkin, tavallisiin ja assosiatiivisiin. Ja se on bash:iksi salamannopea:
Koodia: [Valitse]
function esitysmuistissa () { echo "$(declare -p | grep -Po "(?<=declare -[aA] $1=).*")" ;}
mat1=({1..100}); mat2=({1..100}); [[ "$(esitysmuistissa mat1)" = "$(esitysmuistissa mat2)" ]] && echo matriisit ovat samanlaiset || echo matriisit ovat erilaiset


« Viimeksi muokattu: 07.01.17 - klo:12.51 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #228 : 07.01.17 - klo:18.33 »
Bash:ista löytyy jatkuvasti uusia piirteitä: ensin nuo "loopittomat-loopit" toivat nopeutta kymmenkertaisesti ja nyt tämä tapa käsitellä matriiseita - se nopeuttaa kaikenlaisia matriisinkäsittelyjä vielä enemmän eikä valehtele koskaan.
- eikä mikään näistä ole "uusi keksintö" vaan virtuoosit ovat tunteneet nämä aina.
- enää en vähättele bash:in kykyjä luettuani yhden virtuoosin kuukausi sitten kertomaa: esimerkiksi skripti joka koostuu yhdestä grep-komennosta on joissain tilanteissa nopeampi kuin C:llä saa aikaiseksi. Joskus harvoin muutenkin teet bashilla nopeamman skriptin kuin C:llä saat aikaiseksi  -  puhumattakaan muista kielistä.

Tässä esimerkki assosiatiivisen matriisin tulostamisesta silloin kun sekä arvossa että osoitteessa saattaa olla välilyöntejä; siitä ei mikään muu tulostustapa selviä:
Koodia: [Valitse]
#!/bin/bash
function tulostamatriisi () { tabs 25; echo -e osoite'\t'arvo; echo; declare -p | grep dec.*$1= | cut -d\( -f2 | tr -d []\) | sed 's/" /"\n/g;s/=/\t/g' ;}
declare -A mat

mat[alussa]=kiitosseisoo
mat[sutta ja sekundaa]="muodostetaan tauotta"
mat[surullista]="mutta totta"
 
tulostamatriisi mat
**
Olin jo kovin varma siitä että tämä muistikuvan tulkintaan perustuva menetelmä on aina super-nopea. Joten alkaessani tehdä muistikuvan tulkintaan perustuvaa matriisin levylle talletusta ja levyltä lukua odotin erinomaisia tuloksia. Miljoona-jäsenisen matriisin kirjoittaminen kävikin nopeasti, mutta lukeminen kesti 4.5 sekuntia. Pettyneenä tuohon tulokseen melkein lakkasin kehittämästä noita levy-operaatioita. Kunnes tulin mitanneeksi kuinka kauan muilla menetelmillä kestää: loopittomalla menetelmällä lukeminen kesti 2.5 tuntia ja tavallisella menetelmällä lukee vieläkin kun vuorokausi on kulunut - eikä niillä edes saa täysin moitteetonta lopputulosta.
**
Leikkaa-liimaa seuraavat neljä koodiriviä yksi kerrallaan päätteeseesi jotta toiminta selviäisi:

Tehdään satatuhat-jäseninen matriisi kokeiluja varten:
Koodia: [Valitse]
mat=($(seq 100000))
Talletetaan se levylle:
Koodia: [Valitse]
echo "$(declare -p | grep -P "declare -[aA] mat=.*=\".*")" > ~/.matriisi
Nollataan matriisi ja tarkistetaan onko se varmasti nollattu::
Koodia: [Valitse]
unset mat; echo ${mat[*]-matriisi on tyhjätty}
Luetaan se levyltä takaisin ja tulostetaan:
Koodia: [Valitse]
apu1=$( cat ~/.matriisi) && eval "$apu1" && echo ${mat[*]}
« Viimeksi muokattu: 11.01.17 - klo:12.33 kirjoittanut petteriIII »

nm

  • Käyttäjä
  • Viestejä: 16435
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #229 : 13.01.17 - klo:00.24 »
- enää en vähättele bash:in kykyjä luettuani yhden virtuoosin kuukausi sitten kertomaa: esimerkiksi skripti joka koostuu yhdestä grep-komennosta on joissain tilanteissa nopeampi kuin C:llä saa aikaiseksi. Joskus harvoin muutenkin teet bashilla nopeamman skriptin kuin C:llä saat aikaiseksi  -  puhumattakaan muista kielistä.

Jep. Tuollaiseen käyttöön shell-skriptit ovat mainioita. Ja muutenkin eri komentoriviohjelmien kutsumiseen ja orkestrointiin.

Tehdään satatuhat-jäseninen matriisi kokeiluja varten:
Koodia: [Valitse]
mat=($(seq 100000))
Talletetaan se levylle:
Koodia: [Valitse]
echo "$(declare -p | grep -P "declare -[aA] mat=.*=\".*")" > ~/.matriisi
Nollataan matriisi ja tarkistetaan onko se varmasti nollattu::
Koodia: [Valitse]
unset mat; echo ${mat[*]-matriisi on tyhjätty}
Luetaan se levyltä takaisin ja tulostetaan:
Koodia: [Valitse]
apu1=$( cat ~/.matriisi) && eval "$apu1" && echo ${mat[*]}

Tällaisia voi toki huvikseen kikkailla, mutta taulukoiden käsittely on kyllä aika tuskaista ja virhealtista Bashin tarjoamilla välineillä. Tässä tallennat taulukon tiedostoon bash-koodina ja suoritat koodin ladattaessa. Ei kovin kaunista, eikä erityisen hyvä ajatus silloin kun luettava tiedosto ei ole itse tuotettu. Siellä voisi olla mitä tahansa vihamielisen tahon lisäämiä komentoja, jotka iloisesti suoritat.

Oikeasti data kannattaisi tallentaa vaikkapa JSON-muodossa, jos halutaan että se on luettavissa selväkielisenä tekstinä. Komentorivillä JSON-datan käsittely onnistuu jq:lla, mutta se ei valitettavasti kuulu minkään jakelun perusasennukseen.

Huomattavasti parempi työkalu tällaisiin hommiin olisi Python.  :)

Koodia: [Valitse]
#!/usr/bin/env python3

import json

mat = [ x for x in range(1, 1000001) ]
with open("numeroita.json", "w") as f:
    json.dump(mat, f)

with open("numeroita.json", "r") as f:
    newmat = json.load(f)
    print(newmat)

Koodistakin saa jotain selvää ilman mustaa vyötä.   ;D
« Viimeksi muokattu: 13.01.17 - klo:00.28 kirjoittanut nm »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #230 : 13.01.17 - klo:11.20 »
Onneksi ei ole pelkoa siitä että juuri kukaan edes harkitsee isompien bash-skriptien tekemistä. Minä harrastan sitä siksi että bash:in parissa on mukava työskennellä koska lähes kaikki netissä oleva tieto siitä on ajalta jolloin netinkin kirjoituksissa oli paljon asiaa eikä juuri ollenkaan pahiksia - edes fork-bomb:ia en ole kohdannut.

Mutta ilmeisesti tuo eval-juttukin on saamassa korjausta: bash4.4:ssä voi säätää sen maksimin johon eval suostuu: voidaan määrätä että esimerkiksi muuttujissa oleva teksti ratkaistaan mutta siinä olevaa koodia ei suoriteta. Niin ainakin luulen:     /* Define to the maximum level of recursion you want for the eval builtin. 0 means the limit is not active. */ #define EVALNEST_MAX 0 

- korjaus: taitaakin vaatia koko bash:in uudelleenkääntämistä  ?

« Viimeksi muokattu: 13.01.17 - klo:13.20 kirjoittanut petteriIII »

nm

  • Käyttäjä
  • Viestejä: 16435
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #231 : 13.01.17 - klo:13.52 »
Onneksi ei ole pelkoa siitä että juuri kukaan edes harkitsee isompien bash-skriptien tekemistä. Minä harrastan sitä siksi että bash:in parissa on mukava työskennellä koska lähes kaikki netissä oleva tieto siitä on ajalta jolloin netinkin kirjoituksissa oli paljon asiaa eikä juuri ollenkaan pahiksia - edes fork-bomb:ia en ole kohdannut.

Kyllähän hyvää tietoa tuotetaan jatkuvasti. Yleiset ohjelmointikielet ja monet harrastajien kirjoittamat kirjastotkin on useimmiten dokumentoitu erittäin kattavasti ja kauniisti. Tietoa jaetaan asiantuntijafoorumeilla kuten Stack Overflow'ssa, jossa kokeneet koodaajat äänestävät huonot neuvot alas ja hyvät ylös. Skriptausongelmiinkin löytyy parhaiten apuja tuollaisilta foorumeilta.

Mutta ilmeisesti tuo eval-juttukin on saamassa korjausta: bash4.4:ssä voi säätää sen maksimin johon eval suostuu: voidaan määrätä että esimerkiksi muuttujissa oleva teksti ratkaistaan mutta siinä olevaa koodia ei suoriteta. Niin ainakin luulen:     /* Define to the maximum level of recursion you want for the eval builtin. 0 means the limit is not active. */ #define EVALNEST_MAX 0 

- korjaus: taitaakin vaatia koko bash:in uudelleenkääntämistä  ?

Yksikin taso on liikaa tällaisessa käytössä. :)

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #232 : 16.01.17 - klo:20.18 »
Matriisin lukemisessa tiedostosta tai kirjoittamisessa tiedostoon voi eval-käskyn korvata käskyllä: . joka liittää määrätyn tiedoston koodiin mutta ei suorita sitä. Mutta tämä edellyttää siirtymistä tutkimaan ympäristömuuttujia elikä muistikuvan käsittelyyn.
- muistikuvan selvitys: esimerkiksi teet skriptissäsi matriisin mat=(1 2 3). Bash tekee siitä ympäristömuutujiin muistikuvan: mat=([0]="1" [1]="2" [2]="3"). Saat sen nähdäksesi käskyllä:
declare | grep ^mat= .
- muistikuvan käsittely mullistaa kaikki matriisi-operaatiot, esimerkiksi nopeus nousee isojen matriisien käsittelyssä yleensä luokkaa 1000* . Liian vähän ja liian myöhään.
- luettaessa tiedosto tarkastetaan se ensiksi erillisessä prosessissa. Jos luettavassa tiedostossa on mitään muuta kuin kelvollinen matriisimäärittely tai matriisin jäsenissä on kiellettyjä merkkejä niin tiedostoa ei edes yritetä lukea vallitsevassa prosessissa. 
- vaikka menetelmä ei kompastu mihinkään niin kaikki toteutukset kompastuvat johonkin. Siis paranneltavaa on aina ja varsinkin varmistusta voi parannella loputtomasti.
- suoritusnopeus on verrannollinen matriisin merkkimäärän neliöjuureen. Vanhoilla menetelmillä suhde oli exponentiaalinen eivätkä ne suurilla matriiseilla edes toimineet.
- bash:in matriisin jokaisella jäsenellä on sekä osoite että arvo. Mikäli osoitteesta ei huolehdita niin numeeristenkaan matriisien käsittely ei toimi aina. Muistikuvassa osoitetieto on aina mukana mutta "normaali-toteutuksilla" hyvin harvoin sillä osoitteen huomioiminen hidastaa toimintaa puoleen.
- assosiatiivisilla matriiseilla on pakko ottaa osoite huomioon.
- jokaista matriisia voi käsitellä muistikuvansa avulla mitenkä hyvänsä ja nämä käsittelytavat ovat salamannopeita verrattuna vanhoihin menetelmiin ja ne ovat myös luotettavampia. Matriisin muistikuvan käsittelyyn perustuvat käsittelytavat ovat pienillä matriiseilla vain hieman "normaali-toteutuksia" nopeampia, mutta suurilla käytännön matriisella jopa tuhatkertaa nopeampia. Esimerkiksi matriisin lukeminen tiedostosta: miljoonajäsenisen matriisin lukeminen kestää muistikuvan avulla 5 sekuntia ja "normaali-toteutuksilla" monta tuntia ja tulos saattaa silti olla väärä. Mutta muistikuvan käsittely ei voi virheillä - toki senkin voi ohjelmoida väärin mutta se on oikaistavissa.
- toiminnan tarkistamiseksi tein tuhansia koe-ajoja. Tottakai minä olen yhtä rajoittunut kuin kukahyvänsä enkä ehkä ole huomannut testata jotakin. Mutta toisaalta kaikki testit ovat onnistuneet.
- talletus ja luku toimivat oikein vaikka matriisissa olisi kontrollimerkkejä mutta tulostus nikottelisi. Voisihan ne kontrollimerkit kieltääkin.

Tein matriisin levylle-talletuksen ja levyltä-lukemisen tutkimiseksi skriptin. Sen merkitykselliset funktiot ovat: matriisintalletus ja matriisinluku. Muulla osalla skriptiä saa helposti aikaan sellaisen tilanteen kuin haluaa noiden funktioiden tarkistamiseksi. 
Koodia: [Valitse]
 
#!/bin/bash
function matriisintalletus () { declare | grep ^$1= > $2 ;}
function matriisinluku () { ( [[ $(grep -Pv ^$1=\(.+\)$ $2) || $(grep -P '( /[[:alnum:]]|\brm\b|\$\()' $2) ]] && echo $2 on kummallinen && return 1 || return 0;) && (( ! $? ))  && . $2 ;}
function matriisinkirjoitusriviin () { tabs 30; echo -e 'osoite\tarvo'; declare | grep "$1"= | sed "s/$1=//" | sed 's/\" \[/\"\n\[/g' | tr -d \(\)[] | tr = '\t' ;}
function matriisinkirjoitusjonoon () { echo osoitteet:; declare | grep ^$1  | grep -o \[[0-9]*\] | tr '\n' ' ' | tr -d []; echo
                                       echo arvot:;     declare | grep ^$1= | sed "s/$1=//" | tr -d \(\)  | sed 's/\ \[/\n/g' | sed 's/.*=\"//' | tr -d \" | tr '\n' ' ' ;}
declare -A matriisinnimi
matriisinnimi[alussa]=kiitosseisoo
matriisinnimi[sutta ja sekundaa]="muodostetaan tauotta"
matriisinnimi[surullista]="mutta totta"

alkuhetki1=$(date +%s.%N); matriisintalletus matriisinnimi tiedosto; loppuhetki1=$(date +%s.%N)
unset matriisinnimi; declare -A matriisinnimi # unset hävittää myös tiedon oliko matriisi tavallinen vai assosiatiivinen joten tarvittaessa se täytyy palauttaa
alkuhetki2=$(date +%s.%N); matriisinluku matriisinnimi tiedosto; loppuhetki2=$(date +%s.%N)
echo;  matriisinkirjoitusriviin matriisinnimi; echo -n 'talletusaika='; echo $loppuhetki1-$alkuhetki1 | bc; echo -n '    lukuaika='; echo $loppuhetki2-$alkuhetki2 | bc

read -p 'paina enter nähdäksesi toisentyyppisen matriisin' ; clear
matriisinnimi2=($(seq 1000))
unset matriisinnimi2[55]
matriisinnimi2[500]=" / 9rmkkkk" # tätä muuttelemalla näkee millaisia tiedostoja jäseniä kartetaan.
alkuhetki1=$(date +%s.%N); matriisintalletus matriisinnimi2 tiedosto2; loppuhetki1=$(date +%s.%N)
unset matriisinnimi2
alkuhetki2=$(date +%s.%N); matriisinluku matriisinnimi2 tiedosto2; loppuhetki2=$(date +%s.%N)
echo; matriisinkirjoitusjonoon matriisinnimi2; echo; echo -n 'talletusaika='; echo $loppuhetki1-$alkuhetki1 | bc; echo -n '    lukuaika='; echo $loppuhetki2-$alkuhetki2 | bc
echo; echo huomioi että osoite:55 ja arvo:56 puuttuvat ja että arvon:501 paikalla on määräämäsi arvo.; echo
read -p 'paina enter jatkaakseesi'
**
Ilmeisesti muistikuvan hyödyntämiseen perustuvan funktion voi tehdä mihin tehtävään tahansa: olen tehnyt niitä jo kymmenkuntaan erityyppiseen tehtävään ja jokakerran funktion on saanut muodostettua heti. Tässä viimeinen:
Koodia: [Valitse]
#!/bin/bash
mat=($(seq 1000000))
function matriisintunnuslukuja () { declare | grep ^$1= | tr ' ' '\n'| cut -d ] -f1 | tr -d [ |\
awk '{sum=sum+$0; lkm++}END{print "matriisin summa:"sum" keskiarvo:"sum/lkm}' ;};  matriisintunnuslukuja mat
- muuten tavallisilla muuttujilla on myös muistikuvansa.
**
Nämä bash:in skriptit ovat juuri sitä mitä vanhat aivot tarvitsevat jotta dementia vähän viivyttelisi. Lisäksi bash on kuollut jo joten mitään paineita ei ole; koska ei voi onnistua niin ei voi epäonnistuakaan. Mutta sikäli vanhat bash-skriptit ovat haastavia että ne ovat niin hitaita ja rajoittuneita  että niitä täytyy parantaa ennenkuin pääsee tositoimiin. Kummassakin suhteessa parannusten täytyy olla "kymmenkertaisia".

Tein matriisin minimin ja maximin etsivän skriptin. Skriptin pitää toimia oikein olipa matriisi om muodostettu numeroista, tekstijonoista, tai numeroiden ja tekstin sekasotkusta. Tekstijonoissa täytyy saada olla välilyöntejä silloinkin kun ne ovat assosiatiivisen matriisin osoitteissa.
 
Yksi koitos etsimisen toimimiselle on muodostaa sellainen matriisi jonka jäsenmäärä on sama kuin satunnaisluku-generaattorin mahdollisten erilaisten arvojen lukumäärä, täyttää matriisi samalla generaattorilla tehdyillä satunnaisluvuilla ja etsiä sitten matriisin minimi ja maximi. Tämä testaa samalla satunnaislukugeneraattoria, sillä mikäli satunnaislukugeneraattori on hyvä niin tuloksen pitää olla eri ajokerroilla yleensä teoreettinen minimi ja maximi, harvemin poiketen yhdellä, vielä harvemmin poiketen kahdella.... Bash:in satunnaislukugeneraattori on hyvä 16-bittiseksi ja sillä onkin näin. Awk:in satunnailukugeneraattori on näiden tuloksien mukaan noin 26-bittinen joten sen tutkiminen vaatii matriisin koon olevan yli 33 miljoonaa mikä vaatii paljon RAM:mia. Heittoja täytyy olla sillä eihän ne muuten satunnaislukuja olisi eikä minimin etsintäkään toimisi.
- bash muodostaa satunnaislukunsa kokonaisluku-alueelle välille: 0-32767 ja awk muodostaa satunnaislukunsa desimaali-alueelle välille: 0-1.
- maximin ja minimin suhde kertoo sen kuinka monibittisestä satunnaislukugeneraattorista on todennäköiseti kyse.
Koodia: [Valitse]
#!/bin/bash
mat=($(for n in $(seq 32767 ); do echo $RANDOM; done ))        # Bash:in satunnaislukugeneraattori.
#mat=($(seq 33554432 | awk '{printf "%1.9f\n", rand()}'))    # awk:in satunnaislukugeneraattori # parempi: awk -v seed=$RANDOM 'BEGIN{srand(seed)}{printf "%1.9f\n", rand()}'))
# seuraava on etsii osoitteiden minimin ja maximit:
# function minimi (){ declare | grep ^$1= | cut -d \( -f2 | sed "s/\ \[/'\n'/g" | cut -d = -f1 | tr -d \)\[\] | sort -n | sed -e 1b -e '$!d' ;}; minimi mat
# seuraava etsii arvojen minimit ja maximit:
function minimi (){ echo matriisin minimi ja maximi: ; declare | grep ^$1= | cut -d \( -f2 | sed "s/\ \[/'\n'/g" | cut -d \" -f2 | sort -n | sed -e 1b -e '$!d';}; minimi mat
**
Edelliset esimerkit käsittelivät sellaista yksiulotteista matriisia joka on jo koneessa määriteltynä. Mutta levyllä olevaa kaksiulotteista matriisia voi käsitellä vähän samantyyppisesti niinkuin esimerkiksi tällä joka etsii kaksiulotteisen levy-matriisin määrätyn sarakkeen maximin ja minimin:
Koodia: [Valitse]
#!/bin/bash
function maxmin () { echo Tiedoston:$1 sarakkeen:$2 maximi ja minimi:;\
mat=($(cat $1 | cut -d' ' -f$2)); declare | grep ^mat= | cut -d \( -f2 | sed "s/\ \[/'\n'/g" | cut -d \" -f2 | sort -n | sed -e 1b -e '$!d' ;};\
maxmin tiedosto 2
- tämäkin on nopea jne. ja toimii myös tekstijonojen kanssa jotka voitaisiin sortata tekstijonossa olevien numeroiden mukaan.
- kutsu pitää olla samanlainen kuin muissakin kielissä: maxmin tiedostonimi käsiteltävän_sarakkeen_numero - jotta kutsumuodon tietäisi vaikkei olisi koskaan kuullutkasan.
- tuo maximin ja minimin etsiminen olisi helpointa toteuttaa awk:illa, mutta awk:in avulla saatavat tulokset ovat kovin "vähänumeroisia" ja vaikka se ei yleensä merkitse mitään niin yleiskäyttöisessä funktiossa se ei käy. Gawk:issa on mukana rajoittamattoman tarkkuuden kirjasto, mutta gawk ei ole Ubuntussa ilmanmuuta.
- jäsenten osoitteet ovat levyllä olevilla kaksiulotteisilla matriiseilla samanlaisia mielikuvituksettomia kuin muiden kielien matriiseissa. Eikä assosiatiivisuudesta ole puhettakaan.
- testattaessa tiedostoksi voidaan laittaa miljoonarivinen matriisi kirjoittamalla tiedoston paikalle <((seq 1000000)) . Sen minimi on 1 ja maximi 1000000. RAM:mista toimiminen nopeuttaa tässätapauksessa noin 40%.
- levyllä olevassa matriisissa saa olla tyhjiä rivejä, eikä rivien tai sarakkeiden täydy olla yhtäpitkiä muutenkaan.

Skriptistä voi tehdä myös version levyllä olevan kaksiulotteisen matriisin rivien käsittelemiseksi muuttamalla käsky: cut -d' ' -f$2  käskyksi: sed "$2q;d". Tätä uutta funktiota kutsutaan näin: maxmin tiedostonimi käsiteltävän_rivin_numero.

Levylle kirjoitetun kaksiulotteisen matriisin voikin aina käsittellä yhtähyvin kuin jo muistissa olevan. Levyllä olevan kaksiulotteisen matriisin käsittelyssä on paras unohtaa että matriisin jäsenillä on myös osoite.
Epäilemättä myös moniulotteisia matriiseja pystyy käsittelemään, mutta funktion tekeminen saattaisi olla vaikeaa.
**
Skriptiä kokeillakseen ei kannata tehdä omaan koneeseen skriptiä, vaan leikata skripti verkkosivulta ja liimata se oman koneen päätteeseen ja painaa enter.
**
Tähän asti nopein keino muodostaa numeerinen matriisi on ollut: matriisinnimi=($(seq 10))   ja sen muistikuva (jollaisia joskus näkee):
matriisinnimi=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9" [9]="10")

ZestyZapuksessa toimii kolme-neljäkertaa nopeampi tapa muodostaa numeerinen matriisi: readarray matriisinnimi <<< $(seq 10) mutta sen muistikuva on toistaiseksi ennennäkemättömän kummallinen:
matriisinnimi=([0]=$'1\n' [1]=$'2\n' [2]=$'3\n' [3]=$'4\n' [4]=$'5\n' [5]=$'6\n' [6]=$'7\n' [7]=$'8\n' [8]=$'9\n' [9]=$'10\n')
- toiminta tuntuu kuitenkin olevan kummallakin tavalla muodostetuilla matriiseilla sama
- readarray on toiminut kaiketi bash4:stä lähtien ja siten tämäkin. Mutta muistikuva muodostuu vasta nyt edes jotenkin. Kuinka ne aikaisemmat ovat voineet toimia kun muistikuvat ovat olleet vajaita?
- arvojen tulostaminenmuistikuvasta kummallakin tavalla muodostetulla matriisilla:
Koodia: [Valitse]
declare | grep matriisinnimi= | tr ' ' '\n' | sed 's/.*\=//g' | tr -dc '\n'[[:alnum:]]* | tr -d n$
**
Bash 4.4:ssä on mahdollistettu muistikuvien helppo lukeminen. Esimerkin muodossa esitettynä:
Koodia: [Valitse]
katti=(1 2);apu=${katti[*]@A}; echo "$apu" 
tulostaa: declare -a katti=([0]="1" [1]="2")
- siis kun tuon apu:n tallettaa niin koko matriisimäärittely menee levylle joten luettaessa myös matriisin tyyppi luetaan.
- muuten matriisin tyypin selvittämiseksi joutuisi käyttämään käskyä:
Koodia: [Valitse]
[[ "$(declare -p mat)" =~ "declare -A" ]] && echo asso matriisi; [[ "$(declare -p mat)" =~ "declare -a" ]] && echo tavallinen matriisi
**
Minkähän takia ohjekirjoissa ei ole yksinkertaisia esimerkkejä: lyhyt esimerkki kertoo koko homman mutta tuhatrivinen dokumentti vain sotkee.  Esimerkiksi matriisin tyypin ja muiden parametrien selvittäminen:
Koodia: [Valitse]
declare -Air mat=([0]="1" [1]="2" [2]="3"); echo ${mat@a}

mikä tulostaa Air . Koetapas hakea tämä "Bash-raamatusta"
**
Matriiseja sortatessa on totuttu jättämään osoite huomioimatta sillä osoitteen huomioiminen vaikeuttaa paljon ja muissa ohjelmointikielissä tieto-kato on usein merkityksetöntä.
Mutta bash:in matriisin osoitteissa on enemmän tietoa joten osoitteiden huomiottajättäminen kadottaa usein oleellista tietoa.

Assosiatiivisen matriisin sorttaamisessa osoitteet on pakko huomioida kaikissa kielissä.

Aikaisemmin ison matriisin sorttaaminen ottaen osoite huomioon on ollut niin sietämättömän hidasta että ongelma ratkaistiin yleensä olemalla sorttaamatta. Mutta sorttaaminen muistikuvan avulla on bash:iksi nopeaa, noin 2 sekuntia kuluu miljoonarivisen matriisin sorttaamisessa.

Osoitteen ottaminen huomioon luo seuraavankaltaisen ongelman: matriisin jäsenen osoite ja arvo kuuluvat yhteen ja kun toinen sortataan täytyy toisen seurata mukana. Ongelma on siis sama kuin sortatessa teksti jonkun kentän perusteella: rivit täytyy pitää yhtenäisenä.

Sorttatessa muistikuvan avulla on osoitteet helpompi ottaa huomioon. Eikä muistikuva ota kantaa siihen onko matriisi tavallinen vai assosiatiivinen, se ei välitä välilyönneistä ja funktion voi muodostaa käyttämättä eval-käskyä.

- nämä skriptini käsittelevät sellaisia asioita joilla ei ole enää käytännön merkitystä riippumatta siitä millä kielellä ne olisi tehty, mutta bash:illa tehtynä ne ovat todella surkeita. Mutta on mielenkiintoista yrittää löytää hyvää tuosta pahnanpohjimmaisesta. Sillä bash:in väitetään olevan kymmenkertaisesti huonompi kuin se todellisuudessa on. Halutaanko estää se että ihmiset hoitaisivat PC:nsä turva-ongelmat itse?
Koodia: [Valitse]
#!/bin/bash
function sorttaamatriisi () { declare | grep ^$1= | sed "s/$1=//;s/\s\[/\n/g" | tr -d \(\)[]\" | sort -n -t= -k$2 | tr = '\t' ;}

#testi1: declare -A mat; mat[a c]=3; mat[c a]="1 2"; mat[a b]=2 # testi 2: 
mat=($(seq 1000000 | awk '{print 1000000*rand()}'))

tabs 30; alkuhetki=$(date +%s.%N); sortattu=$(sorttaamatriisi mat 2); loppuhetki=$(date +%s.%N);
echo -e "osoite\tarvo"; printf "%s\n"  "$sortattu"; echo -n sorttaukseen kulunut aika sekuntia: ; echo $loppuhetki-$alkuhetki | bc
# sorttauskäsky on muotoa: sorttaamatriisi matriisinnimi sorttaus_kentän_numero 
# Sorttaus_kentän_numero -> 1=osoitteen mukaan ja 2=arvon mukaan
# sorttauksen parametreja täytyy edelleen määritellä halutuiksi; esimerkiksi -n sorttaa todellisen numeroarvon perusteella jne.


 

 
« Viimeksi muokattu: 20.02.17 - klo:04.51 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #233 : 22.02.17 - klo:13.31 »
Aina on väitetty ettei bash osaa palauttaa funktion parametria. Niin väitetään edelleenkin, vaikka parametrin palautustapoja on useitakin. Sillä bash ei toimi tässäkään asiassa niinkuin muitten kielien virtuoosit pitävät oikeaoppisena. Mutta eikö lopputulos ole se mikä ratkaisee? Jotkut parametrin palautusmenetelmät käyttävät kovalevyä ja toiset eivät; tässä yksi sellainen joka ei käytä kovalevyä, eikä se käytä eval-käskyäkään - ja tällä menetelmällä voi palauttaa halutun määrän parametreja:
Koodia: [Valitse]
#!/bin/bash
function palautaparametri () { read -p "kirjoitapa jotakin: " parametri
read $1 < <(echo "$parametri") # tämä käsky siirtää määrättävän parametrin muutetun arvon globaalialueelle. Ainoastaan tämä lause on tarpeen.
}

unset jotakin ; palautaparametri jotakin  # 'unset jotakin' vain varmistaa ettei echota jotakin joka on jäänyt edellisestä ajosta.
unset muuta  ; palautaparametri muuta    # 'unset jotakin' vain varmistaa ettei echota jotakin joka on jäänyt edellisestä ajosta.
echo $jotakin
echo $muuta
Menetelmä on ollut päivittäisessä käytössäni jo iäisyyden ja se on aina toiminut moitteetta.
- pääohjelmaan sijoitettu 'command substitution' palauttaa funktion tulostaman arvon. Command sustitution -> rakenne: $(funktion kutsu parametreineen)
- funktion 'returnvalue' kannattaa jättää asettamatta.
- tämä 'parametrien palautus' on toinen asia kuin:
Koodia: [Valitse]
function myfunc() { myresult='some value' ;}

myfunc
echo $myresult
sillä siinä ei ole kyse parametrista - toisinsanoen se palauttaa funktiossa määrätyn muuttujan eikä pääohjelman funktiokutsussa määrättyä muuttujaa.
**
Mikäli yritettäisiin käyttää edellisen kaltaisessa funktiossa parametrien arvoja ei se onnistuisikaan, sillä muuttujista on passattu vain nimet eikä arvoja - nimestä tosin saa arvon kirjoittamalla:
Koodia: [Valitse]
eval \$$nimi
mutta jos eval-käskyä ei haluta käyttää niin joko kutsuun on liitettävä myös arvot tai funktion alkuun liitettävä lause: 
Koodia: [Valitse]
parametri1=${!1}
ja käytettävä tarvittaessa näin saatuja arvoja.
- siis parametri1 on muuttujanimen $1 arvo. Usealle parametrille voidaan liittää oma lause.


Edellinen esimerkki muuttuisi seuraavankaltaiseksi:
Koodia: [Valitse]
#!/bin/bash
function palautaparametri () { parametri=${!1}   
read -e -p "editoi parametria: " -i "$parametri" parametri
read $1 < <(echo "$parametri") # tämä käsky siirtää määrättävän parametrin muutetun arvon globaalialueelle. Ainoastaan tämä lause on tarpeen.
}

jotakin=data0001; palautaparametri jotakin    # siis passataan muuttujan nimi; arvon jos passaa niin ei onnistu näin
muuta="78 56"       ; palautaparametri muuta  # arvoissa saa olla välilyöntejä mutta se on silloin laitettava lainausmerkkeihin niinkuin bash:issa muutenkin.   
echo editoitu arvo: "$jotakin"
echo editoitu arvo: "$muuta"
-editoitavassa saa olla mikä hyvänsä merkkiryhmä, vaikka yksinäinen kova lainausmerkki. Välilyöntejäkin saa olla.
**
Aikaisemmin esitetyissä sorttauksissa ainoastaan tulostetaan ja joskus tuloste ei riitä vaan matriisi täytyy luoda sortattuna uudestaan.:

Kuvaus vaikeuksista joita saattaa tulla:
1. Matriisin sorttauksessa voi sortata arvon tai osoitteen mutta kumpi sortataankin täytyy toinen hylätä -> oletetaanpa että on sortattu arvot. Sorttauksessa muodostuneen uuden matriisin osoitteilla ei  ole mitään korrelaatiota vanhojen osoitteiden kanssa joten miksi raahata matkassa niitä vanhoja osoitteita ? Sorttaustuloksesta muodostuvan uuden matriisin osoitteet alkavat nollasta ja kasvavat siitä monotonisesti yhdellä aivan kuin muissakin kielissä; jos siis sortattu matriisi talletetaan vanhalla nimellä niin vanhaan matriisiin ei ole paluuta; tilanteesta riippuen täytyy muodostaa uusi matriisi uudella nimellä joten talletusnimen saattaa joutua muuttamaan. Itse sorttausrutiinihan on sama kuin näytölle sorttavassakin.
2. funktion "function sorttaamatriisi" sort-käskyn parametreja täytyy joskus määritellä sopiviksi; esimerkiksi -g sorttaa yleensä parhaiten numeroarvon perusteella mutta -n on joskus parempi. Tekstuaalinen sorttaus vaatii määreeksi esimerkiksi -d jota saattaa joutua vahvistamaan määreellä -b. Muitakin muutoksia saattaa joutua tekemään.
Koodia: [Valitse]
#!/bin/bash
export LC_ALL=C # desimaalipiste on tällä sivulla piste eikä pilkku niinkuin normaalilla koodisivulla -> desimaalit sorttautuvat oikein samoinkuin tieteellinen esitysmuoto kytkimellä -g.
function sorttaamatriisi () { declare | grep ^$1= | sed "s/$1=//;s/\s\[/\n/g" | cut -d= -f2 | tr -d \(\)[]\" | sort -g ;} # sort-käskyn parametreja saattaa joutua muuttamaan.

# tässä lohkossa on koematriiseja toiminnan kokeilemiseksi. Viimeinen kommentoimaton unset:illä alkava matriisimäärittely jää voimaan.
unset mat
mat[7]=4
mat[1]=15
mat[4]=0.2
mat[3]=0.1
mat[5]=-7
mat[6]=-3
mat[2]=11
mat[8]=0.05
mat[9]=4e-2
mat[10]=6.1e-2
mat[11]=6.05e-2
# unset mat; mat=($(seq 1000 -1 1))
unset mat; mat=($(seq 100 | awk '{print 2*rand()-1}'))

echo matriisi sorttaamatta: ;echo ${mat[*]}
alkuhetki=$(date +%s.%N); mat=($(sorttaamatriisi mat)); loppuhetki=$(date +%s.%N) # "mat=" on talletusnimi ja sen sattaa joutua vaihtamaan.
echo matriisi sortattuna: ;echo ${mat[*]}
echo -n sorttaukseen kulunut aika sekuntia: ; echo $loppuhetki-$alkuhetki | bc
Sorttauskäskyn "sorttaamatriisi mat 1" selvitys: sorttaamatriisi matriisinnimi sorttaus_kentän_numero
ja tässä: sorttaus_kentän_numero -> 2=osoitteiden sorttaus ja 1=arvojen sorttaus

Sorttaaminen kestää noin 4-8ms 1000-jäsenisen matriisilla tai 2.4s miljoonajäsenisellä.
- tämä skripti ei sortatessaan välitä ollenkaan siitä onko sortattava luku kokonaisluku vai desimaaliluku.
**
Vaikka bash:ista toitotankin niin minäkään en osaa ottaa huomioon sitä ettei bash:issa yleensä skriptejä tehdä sillä monimutkaisetkin hommat hoituvat yhdellä rivillä. Äskettäin halusin saada selville mikä on kansiossa olevista tiedostoista runsas-rivisimmän rivien lukumäärä. Suunnittelin jo monirivistä skriptiä jossa olisi looppeja ja pelejä_ja_tokkaroita. Liian hidashan semmoisesta tulisi joten väsäsin yksirivisen:
Koodia: [Valitse]
find kansio_polkuineen -maxdepth 1 -type f | xargs wc -l | awk '{print $1}' | sort -n | tail -2 | head -1
Eikä sen tekemisessä ollut mitään muuta vaikeutta kuin hommaan ryhtyminen. Bash:in monimuotoisuudesta kertoo se että homman voi ratkaista paljon paremmin näin: tämä onkin ehdoton nopeuskunkku eikä hermostu edes binääritiedostoista.  Excludea sattaa joutua säätämään.
Koodia: [Valitse]
grep -n --exclude=*.* ^.*$ kansio_pollkuineen/*  | cut -d: -f2 | sort -n | tail -1
bash:issa jokainen tehtävä voidaan ratkaista lukemattomilla täysin erilaisilla  tavoilla, esimerkiksi edelliset voidaan kirjoittaa myös:
Koodia: [Valitse]
wc -l kansio_polkuineen/* | awk '{print $1}' | sort -n | tail -2 | head -1
Mutta jokaisella ratkaisulla on omat ominaisuutensa ja puutteensa. Tuosta wc-virityksestä saa kyllä paremman lisäämällä siihen osan joka hyväksyy vain tiedostot joissa on tekstiä:
Koodia: [Valitse]
wc -l $(file kansio_polkuineen/* | grep text | cut -d: -f1) | awk '{print $1}' | sort -n | tail -2 | head -1
****

Kun tekee mitähyvänsä niin kohtaa ennemmin tai myöhemmin ongelman: vähän aikaa sitten tein sitä-ja-sitä, mutta en muista tiedoston nimeä enkä edes kansiota. Ratkaisu: todennäköisesti se löytyy listaamalla mitä viimeisillä kerroilla on tiedostojärjestelmässä tehty:
Koodia: [Valitse]
find mistä_kansiosta_alkaen_etsitään -type f  -printf '%T@ %p\n' | sort -n | tail -40 | cut -f2- -d" "
joka listaa aikajärjestyksessä ne 40 asiaa joita on editoitu viimeksi. Esimerkiksi:
Koodia: [Valitse]
find ~/OMATSKRIPTIT -type f  -printf '%T@ %p\n' | sort -n | tail -40 | cut -f2- -d" "
--
Mutta joskus tulee tehtyä niin paljon hommia että tuloksia tulee niin paljon että niiden lukumäärää täytyy pienentää antamalla jotain rajoittavaa tietoa siitä mistä etsitään:
Koodia: [Valitse]
find mistä_kansiosta_alkaen_etsitään -type f  -printf '%T@ %p\n' | grep -v mistä_ei_etsitä | sort -n | tail -40 | cut -f2- -d" "
esimerkiksi:
Koodia: [Valitse]
find ~/OMATSKRIPTIT -type f  -printf '%T@ %p\n' | grep -v \/ARKISTO\/ | sort -n | tail -40 | cut -f2- -d" "
--
Tai voidaan määritellä että etsittävien tiedostojen nimistä jotakin: 
Koodia: [Valitse]
find mistä_kansiosta_alkaen_etsitään -type f  rajoittaminen_tiedostonimen_perusteella -printf '%T@ %p\n' | sort -n | tail -40 | cut -f2- -d" " 
Esimerkiksi:
Koodia: [Valitse]
find ~/OMATSKRIPTIT -type f  -regex '.*html$' -printf '%T@ %p\n' | sort -n | tail -40 | cut -f2- -d" "
joka rajoittaa listavat sen perusteella että peräliite täytyy olla html.
--
tai voidaan rajoittaa sen perusteella että etsittävässä tiedostossa tulee olla joku sana tai merkkiryhmä:
Koodia: [Valitse]
IFS=$'\n' unset apu; apu=($(find ~/OMATSKRIPTIT -type f  -printf '%T@ %p\n' | sort -n | tail -400 | cut -f2- -d" ")) ; for (( n=0; n<=${#apu[@]}-1 ; n++ )); do [[ $(cat ${apu[n]} | rajoitus_senperusteella_että_tiedostossa_on_määrätty_sana ) ]] && echo ${apu[n]}; done; unset IFS
esimerkiksi:
Koodia: [Valitse]
IFS=$'\n' unset apu; apu=($(find mistä_kansiosta_alkaen_etsitään -type f  -printf '%T@ %p\n' | sort -n | tail -400 | cut -f2- -d" ")) ; for (( n=0; n<=${#apu[@]}-1 ; n++ )); do [[ $(cat ${apu[n]} | grep -w joo) ]] && echo ${apu[n]}; done; unset IFS
--
- rajoitusmahdollisuuksia on kirjaimellisesti rajoittamattomasti. Yleensä nämä yksiriviset ovat nopeita mutta joitakin rajoitteita on erittäin vaikea saada nopeiksi sillä keinot niiden käyttämiseksi ovat tosivaikeita. Esimerkiksi tuon viimeisen esimerkin looppi tekee esimerkin hitaaksi ja jos keksii kuinka siitä pääsee eroon niin toiminta nopeutuu oleellisesti.
- eihän tämmöisiä kukaan tarkasti muista eikä se riitä että muistaa suurinpiirtein: täytyisi siis tehdä joku paikka josta näiden käskyjen rungot saa kopioitua. Kopioinnin perustella voisi nopeasti väsätä prototyyppi-ohjelman mihinhyvänsä; prototyypin joka toimii senverran nopeasti ettei varsinaista ohjelmaa tarvitse ehkä tehdäkään. Verkossa on tähän tehtävään on tehty lukemattomia verkkosivuja mutta huonon toteutuksensa takia ne kaikki vain sotkevat. 
« Viimeksi muokattu: 10.03.17 - klo:19.56 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 693
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #234 : 13.03.17 - klo:15.51 »
Ikuisuuskysymys ovat tiedostojen kopiot elikä duplikaatit. Kirjaimellisesti otettuna niiden etsiminen on yksinkertaista:
Koodia: [Valitse]
find mistä_kansiosta_alkaen_etsitään 2>/dev/null \( ! -regex ".*/\..*" \) -type f -exec md5sum '{}' ';' | grep -v mistä_ei_etsitä | sort | uniq --all-repeated=separate -w 33 | cut -c 35-
koodin selvennystä:
- 1: 2>/dev/null kehottaa jättämään "permission denied" viestit pois.
- 2: \( ! -regex ".*/\..*" \) kehottaa jättämään piilotiedostot väliin, sillä harvoin piilotiedostoja käytetään kun talletetaan jotakin joten ne sotkisivat vaan. Käsky on tarpeen vain kun etsitään kotikansiosta, mutta ei käsky haittaa vaikka etsittäisiin muualtakin joten se voi olla tuossa aina.

Käytännossä lause on seuraavanlainen kun etsitään kotikansiosta:
Koodia: [Valitse]
find ~ 2>/dev/null \( ! -regex ".*/\..*" \) -type f -exec md5sum '{}' ';' | grep -v /ARKISTO/ | sort | uniq --all-repeated=separate -w 33 | cut -c 35-
- bash-tulkki tekee käskystä virtuoosien optimoimaa C-koodia eikä mukana ole bash:ia juuri ollenkaan. Siis vain hyvin tehdyt C-koodiset toimivat nopeammin. Näitä nopeasti toimivia skriptejä tuntuu kokoajan tulevan lisää ja alkaa tuntua siltä että bash ei ole hidas mutta joku muu on.
- tuloksen tulkinta: tuloksessa on kahden tai useamman tiedoston ryhmiä joita erottaa tyhjä rivi. Ryhmän tiedostot ovat duplikaatteja elikä sisällöltään täysin samanlaisia vaikka nimet olisivat erilaisia.

Käskyllä saa nopeasti kuvan siitä kuinka paha koneen duplikaatti-tilanne on ja. Se löytää vain duplikaatit jotka ovat sisällöltään täysin samanlaiset - mutta siinä se ei erehdykään. Mutta eiväthän ne bitti-bitiltä samanlaiset ole kuin jäävuoren huippu. Toisenlaisia duplikaatteja voi olla rajattomasti - esimerkiksi videotiedostot voivat olla samasta asiasta mutta koodaus on toisenlainen, dokumentit voivat olla muuten samoja mutta toisesta on yksi kirjoitusvirhe korjattu .... Niiden löytäminen muilla keinoin on 10000 kertaa hitaampaa mutta yritetäänpä:
 ------
- toiminnan kehittäminen täytyy tehdä ympäristössä jonka koko on rajattu ja jossa on runsaasti "melkein-duplikaatteja". Skriptiajurin ARKISTO on paras mahdollinen.
Työ alkaa siitä että muodostetaan matriisi tiedostojärjestelmän halutusta osasta:
Koodia: [Valitse]
IFS= readarray mat < <(find ~/OMATSKRIPTIT/MATRIISIOPERAATIOITA/ARKISTO/SKRIPTIT/"matriisin jäsenen arvoa vastaavan osoitteen etsiminen matriisista" 2>/dev/null \( ! -regex ".*/\..*" \) -type f); unset IFS; printf "%s\n" "${mat[*]}"
- ei saa luottaa siihen että tuon käskyn matriisinkaltainen tulostus tarkoittaa sitä että kysymyksessä on matriisi, vaan vasta kun vaihdettaessa printf-käskyn merkki * merkkiin 0 tulostuu vain yksi rivi toimitaan varmasti matriisin kanssa. Jos tuloste ei muutu toimitaan muuttujan kanssa eikä jatko tule toimimaan. (siis muuttuja on sama kuin samannimisen matriisin jäsen 0)
------
Seuraavaksi muodostetaan jokaiselle parit jokaisen kanssa - huolehtien siitä että jokainen pari muodostuu vain kerran. Vertailtavien parien lukumäärä on mahdoton: esimerkkitiedostossa on noin 260 tiedostoa ja pareja siitä tulee noin 33800 - muuten jokaisella rivillä on kaksi rivinsiirtoa joten käsky : "wc -l" näyttää kaksinkertaista arvoa. Pareja muodostetaan noin .6 sekuntia. Esimerkki-skripti on seuraavanlainen:
Koodia: [Valitse]
IFS= readarray mat < <(find ~/OMATSKRIPTIT/MATRIISIOPERAATIOITA/ARKISTO/SKRIPTIT/"matriisin jäsenen arvoa vastaavan osoitteen etsiminen matriisista" 2>/dev/null \( ! -regex ".*/\..*" \) -type f); \
 unset IFS; unset apu; for (( n=0; n<${#mat[*]}-1; n++ )); do for (( m=n+1; m<${#mat[*]}; m++ )); do apu+=("${mat[n]}""${mat[m]}");  done; done; printf "%s\n" "${apu[*]}"
------
Sitten seuraa se aikaavievä osuus: tässä on sovellus wdiff joka vertailee tiedostoja - mutta sovellustahan voi vaihtaa. Mikäli toiminnon kokee mielekkääksi ja tarpeelliseksi kannattaa sitä kehittää edelleen - tulosteiden lukumäärää saa ilmanmuuta vähennettyä ja tehtyä mielekkäämmiksi jolloin toiminta lisäksi nopeutuu:
Koodia: [Valitse]
IFS= readarray mat < <(find ~/OMATSKRIPTIT/MATRIISIOPERAATIOITA/ARKISTO/SKRIPTIT/"matriisin jäsenen arvoa vastaavan osoitteen etsiminen matriisista" 2>/dev/null \( ! -regex ".*/\..*" \) -type f); \
 unset IFS; unset apu; for (( n=0; n<${#mat[*]}-1; n++ )); do for (( m=n+1; m<${#mat[*]}; m++ )); do apu+=("${mat[n]}""${mat[m]}");  done; done;  \
for (( n=0; n<${#apu[*]}; n+=2 )); do wdiff -s123  "$(printf "%s\n" "${apu[n]}" | cut -d$'\n' -f1)" "$(printf "%s\n" "${apu[n]}" | cut -d$'\n' -f2)"; echo; done
- sovelluksen wdiff ehkä joutuu aluksi lataamaan: sudo apt install wdiff . Kun nyt kehitys on suoritettu voi find-käskyn polkua muuttaa minnehyvänsä. Mutta muistaa kannattaa että toiminta-aika muodostuu erittäin suureksi mikäli annetulla polulla on paljon tiedostoja
*****
Myös tuossa parien muodostamisessa loopeista eroon pääseminen auttaa suunnattomasti. Bash osaa korvata muuttujan matriisilla joten toisesta loopista pääsee eroon helposti ja se auttaa jo paljon. Esimerkki skriptit; tässä tapauksessa ihan selviä käskyjä:
kahdella loopilla:
Koodia: [Valitse]
time ( unset IFS; unset apu;mat=($(seq 1000)); for (( n=0; n<${#mat[*]}-1; n++ )); do for (( m=n+1; m<${#mat[*]}; m++ )); do apu+=(${mat[n]}¤${mat[m]}) ; done; done; printf "%s\n" ${apu[*]} )
yhdellä loopilla:
Koodia: [Valitse]
time ( unset IFS; unset apu;mat=($(seq 1000)); for (( n=1; n<${#mat[*]}; n++ )); do printf "%s\n" ${mat[n]//${mat[n]}/${mat[*]:n}} | sed "s{.*{${mat[n-1]} &{g" ; done )
- arvolla 1000 ero on vain kaksinkertainen mutta jo arvolla 2000 ero on viisikertainen ja 3000:lla monikymmenkertainen ja sitten kaksilooppisen suoritusaika kasvaa mahdottomiin.
- varmaan toisestakin loopista pääsisi jotenkin eroon jolloin toiminta-ajat siirtyisivät millisekunti-alueelle.
- käsky osaa käyttää prosessorin kaikkia ytimiä, kylläkin tehottomasti niin että yhteistulos on vain noin 120%.
« Viimeksi muokattu: 21.03.17 - klo:06.16 kirjoittanut petteriIII »

Whig

  • Käyttäjä
  • Viestejä: 359
  • puppu-generaattori
    • Profiili
    • localhost
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #235 : 19.04.18 - klo:09.10 »
Onkohan mitenkään helpohkosti toteutettavissa "timeout" scriptiin?
Eli minulla on scripti joka toimii ainakin 90% tapauksista normaalisti mutta joissakin tapauksissa ei saa haluamaansa tietoa jota tjää sitten odottamaan kunnes se käsin lopetetaan.
Eli nyt olisi mielessä tuohon scriptiin "timeoutti" joka lähtisi esim. scriptin alussa laskemaan aikaa ja jos scripti on ollut ajossa yli 60sek niin tapetaan se (mahdollisesti logitetaan tms.) ja jatketaan matkaa jolloin kaikki muu ei jää odottamaan tuon scriptin valmistumista.

nm

  • Käyttäjä
  • Viestejä: 16435
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #236 : 19.04.18 - klo:09.26 »
Onkohan mitenkään helpohkosti toteutettavissa "timeout" scriptiin?
Eli minulla on scripti joka toimii ainakin 90% tapauksista normaalisti mutta joissakin tapauksissa ei saa haluamaansa tietoa jota tjää sitten odottamaan kunnes se käsin lopetetaan.
Eli nyt olisi mielessä tuohon scriptiin "timeoutti" joka lähtisi esim. scriptin alussa laskemaan aikaa ja jos scripti on ollut ajossa yli 60sek niin tapetaan se (mahdollisesti logitetaan tms.) ja jatketaan matkaa jolloin kaikki muu ei jää odottamaan tuon scriptin valmistumista.

Helpoin keino on coreutilsin timeout-komento, joka on vakiona käytettävissä nykyisissä Linux-jakeluissa.

Koodia: [Valitse]
timeout 60 jumittavakomento
Lokituksen voi tehdä exit statuksen perusteella.


Muita vaihtoehtoja löytyy googlaamalla. Esimerkiksi: https://stackoverflow.com/questions/687948/timeout-a-command-in-bash-without-unnecessary-delay

Whig

  • Käyttäjä
  • Viestejä: 359
  • puppu-generaattori
    • Profiili
    • localhost
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #237 : 20.04.18 - klo:07.14 »
Onkohan mitenkään helpohkosti toteutettavissa "timeout" scriptiin?
Eli minulla on scripti joka toimii ainakin 90% tapauksista normaalisti mutta joissakin tapauksissa ei saa haluamaansa tietoa jota tjää sitten odottamaan kunnes se käsin lopetetaan.
Eli nyt olisi mielessä tuohon scriptiin "timeoutti" joka lähtisi esim. scriptin alussa laskemaan aikaa ja jos scripti on ollut ajossa yli 60sek niin tapetaan se (mahdollisesti logitetaan tms.) ja jatketaan matkaa jolloin kaikki muu ei jää odottamaan tuon scriptin valmistumista.

Helpoin keino on coreutilsin timeout-komento, joka on vakiona käytettävissä nykyisissä Linux-jakeluissa.

Koodia: [Valitse]
timeout 60 jumittavakomento
Lokituksen voi tehdä exit statuksen perusteella.


Muita vaihtoehtoja löytyy googlaamalla. Esimerkiksi: https://stackoverflow.com/questions/687948/timeout-a-command-in-bash-without-unnecessary-delay

Kiitän. Ainakin tuolla timeout komennolla pääsee alkuun =) Ja jos vaikka kerrankin pitäisi scriptin yksinkertaisena eikä lähtisi kikkailemalla pilaamaan sitä.

Whig

  • Käyttäjä
  • Viestejä: 359
  • puppu-generaattori
    • Profiili
    • localhost
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #238 : 09.10.18 - klo:10.01 »
Minulla on txt tiedosto jossa on läjä URLeja jokainen omalla rivillään.
Miten tosta saisi kätevästi scriptillä tehtyä linkkejä?
Kokeilin n+1:llä tavalla lukea tiedoston rivi riviltä ja tehdä noista rivejä tyyliin <a href="$osoite">$osoite</a> mutta en saanut tuota toimimaan vaan aina jäi joko alku pois tai molemmat :-/

Eli riittäisi jos saisi jokaisen tuossa tiedostossa olevan urlin muutettua klikattavaksi linkiksi jossa linkki osana oli myös itse linkki.

nm

  • Käyttäjä
  • Viestejä: 16435
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #239 : 09.10.18 - klo:12.38 »
Minulla on txt tiedosto jossa on läjä URLeja jokainen omalla rivillään.
Miten tosta saisi kätevästi scriptillä tehtyä linkkejä?

Onnistuu vaikkapa tällaisella skriptillä:

Koodia: [Valitse]
#!/bin/sh

inputfile="$1"

if [ $# -lt 1 ]; then
    >&2 echo "\nusage: $0 urls.txt > urls.html\n"
    exit 0
fi

cat << EOF
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Linkkejä</title>
</head>
<body>
<ul style="list-style-type:none">
EOF

while IFS= read -r url; do
    printf '<li><a href="%s">%s</a></li>\n' "$url" "$url"
done < "$inputfile"

cat << EOF
</ul>
</body>
</html>
EOF

Käytetään siis tähän tapaan:

Koodia: [Valitse]
list-urls.sh linkit.txt > linkit.html