Näytä kirjoitukset

Tässä osiossa voit tarkastella kaikkia tämän jäsenen viestejä. Huomaa, että näet viestit vain niiltä alueilta, joihin sinulla on pääsy.


Viestit - petteriIII

Sivuja: [1] 2 3 ... 30
1
Kaikenlaiseen tulee näköjään syyllistyttyä. Mutta en hölmöile ensikertaa joten on helppoa tunnustaa.

Minunmielestäni BASH:in viehätys onkin siinä että kaikessa on miljoona mahdollisuutta virheille - mutta virhetilanteita voi estää muodostumasta. Kaikkia mahdollisia virhepaikkoja ei ole mahdollista huomata mutta minkähyvänsä huomaamansa virhepaikan voi välttää.

Desimaalilukujen vertaaminen on sinälläänkin mukava apu nopeutensa tähden mutta sen varsinainen merkitys on siinä että se avaa oven nopeaan desimaalilaskentaan.

Matematiikkaohjelmat kuten bc viettävät vertailussa aikaa viisikertaa enemmän, mutta sen korvaukseksi niilä monia erikosiominaisuuksia vertailun tueksi. Joten monimutkaiset vertailut täytyy edelleen suorittaa  bc:llä.

Vertailufunktio toimii millisekunnissa vaikka onkin kooltaan hirviö. Funktion pituus ei vaikuta nopeuteen paljoakaan sillä koska käskyjä on vain yksi rivi niin se tulkataan kokonaisuudessan C-kieliseksi ohjelmaksi ja senpäälle aikaa kuluttaa vain yksi funktiokutsu. Tämmöinen vertailufunktio on nyt - mutta sitä täytyy korjata vielä montakertaa: 
Koodia: [Valitse]

function max () { a=${1//+/}; b=${2//+/}; b=${b//-./-0.}; a=${a//-./-0.}; [[ ${a%.*} == $a ]] && a=$a'.0';[[ $b{%.*} == $b ]] && b=$b'.0'; [[ ${a%.*} == '' ]] && a='0'$a; [[ ${b%.*} == '' ]] && b='0'$b; ((0${a%.*} == 0${b%.*})) && { ((${a//[^-]/}1${a##*.}${b##*.} > ${b//[^-]/}1${b##*.}${a##*.})) && echo $a || echo $b ;} || { ((${a%.*} >= ${b%.*})) && echo $a || echo $b ;} ;}

# kutsuesimerkkejä vaikeista vertailuista (muuten kun kummankin luvun etumerkin vaihtaa niin valitunkin pitää vaihtua):
max 2222222222222222.000000000000000001 2222222222222222.000000000000000002
# max .08 +.079
# max -.08 +.079
# max +.99 1

- vertailu tehdään bc:llä yksinkertaisimmillaan näin: bc <<< "$a > $b" joka tulostaa totuusarvon 0 tai 1.
- tekstijonona vertaaminen onnistuu sekin ihan hyvin. Sekin on nopea, hyväksyy desimaalit ja rajattomasti numeroita, ei välitä vaikka desimaalipiste olisikin pilkku eikä ole aina kovin kranttu vieraiden merkkienkään suhteen. Mutta se ei toimi oikein etumerkkien suhteen, ei ole niin nuuka missä se desimaalipiste on ja voi höpertää muutenkin, joten täytyy miettiä kaksikertaa missä sitä käyttää. Siis tarkoitan tämäntyyppisiä vertailuja:
[[ 2.19mW < 2.20mW ]] && echo 'eka on pienempi'

2
Muuttujien vertaaminen ei onnistu matemaattisella vertailulla:
Koodia: [Valitse]
a=2; b=1; (( $1 > $2 )) && echo 'eka on suurempi' # eikä mikään vaihtoehto onnistu tuolle > merkille.
# se onnistuu kylläkin kun kirjoittaa:
(( 2 > 1 )) && echo 'kaksi on suurempi kuin yksi'
Mutta BASH kykenee vertailemaan kokonaisluku- tai desimaalimuuttujiakin ihan peruskäskyillään, ensiksi yhtäsuuruus:
Koodia: [Valitse]
a=2.1; b=2.1; ((${a//./} ^ ${b//./})) || echo yhtäsuuria
# mutta: a=21.1; b=2.11; ((${a//./} ^ ${b//./})) || echo yhtäsuuria  # menee väärin. Korjaus tähän:
a=21.1; b=2.11; ((${a%.*} ^ ${b%.*})) || ((${a//./} ^ ${b//./})) || echo yhtäsuuria
seuraava vääriä tuloksia aiheuttava on .... ja sen korjaus .... Kyllä BASH kykenee kaikkiin korjauksiin mutta kaavat tulevat jatkuvasti pitemmiksi ja mahdottomammiksi muistaa ja loogiset operaatiot saattavat skriptintekijän sekaisin. Nopeus ei laske paljoakaan

***

Sitten varsinainen vertailu. Siitä kannattaa tehdä funktio. Ei se super-nopea ole, mutta eipä kuhnailekaan. Eiköhän siinä ole vielä parsimista, mutta tämmöinen se nyt on:
Koodia: [Valitse]
function onkoekasuurempi () { ((0${1%.*} ^ 0${2%.*})) || (( ${1//./} < ${2//./} )) && echo 0 || echo 1 ;}
#                                                                    ^
#                                            vertailumerkki on tässa | ja sitä voi muuttaa mieleisekseen
# ja sen kutsuesimerkki:
onkoekasuurempi -0.127 -0.126 
Muuten nopeudesta: BASH:in peruskometoja saa olla pitkä rivi ennenkuin  kokonaisus kestää edes yhtäkauan kuin paraskaan ulkoinen ohjelma. Esimerkiksi:
Koodia: [Valitse]
a=-1.01;b=-1.02; echo -e $a'\n'$b | sort -n | tail -1
on hidas ja toimiikin väärin kuten näet kun muutat miinukset plussaksi: plussapuolen suuremman iitseisarvon pitää nimittäin olla negatiivisen puolen pienemmän itseisarvo. Ja tuo pitkä hiviö on montakertaa nopeampikin.

Funktiokutsu vie hyvin vähän aikaa. Funktiokutsuun kuluvan ajan korvaa ylimäärin se että se tekee skriptin rakenteesta selväpiirteisen ja ongelmattoman. Funktiot on syytä tallettaa kirjastoon. Sillä nuo merkinnät ovat tosiaankin niin kummallisia että ellei niitä saa kopioitua jostakin niin käyttämättä jää.
Ja kirjaston toinen nimi on automaattikopioija.

Tämä johtaa aiheseen nimeltään desimaalimatematiikka jota BASH kyllä hallitsee myös. "salamannopeasti" lisäksi; se jää kyllä nähtäväksi pystynkö minä tekemään kaavat sille.

- kaavat ovat tosiaan kauheita vaikka ne suoritetaankin nopeasti. Ja salahautoja olisi runsaasti ja niiden kiertäminen hidasta. Pääsisihän sillä muutamaan millisekuntiin, mutta koska bc toimii ongelmitta jo viidessä milliskunnissa niin toistaiseksi viisainta on sittenkin käyttää seuraavantyylistä:
Koodia: [Valitse]
bc <<< "1.000001+5.27"

***

Vaikka pääohjelmassa ei voi kirjoittaa: (( $1 > $2 )) && ... niin funktiossa se on sallittu:
Koodia: [Valitse]
function max () { (( $1 > $2 )) && echo $1 || echo $2 ;}
 
# ja funktion kutsuesimerkki:
max 5 9

3
Ja samantapaisia hitaita menetelmiä tulee käytettyä monissa tehtävissä: ei kannata käskeä "cat tiedosto | grep jotakin". On nopeampaa toiminnallisesti ja helpommin käsittävääkin käskeä: "grep jotakin tiedosto" - ja sama koskee useimpia muitakin käskyjä: ei kannata käskeä: "cat tiedosto | tail -1" vaan kannattaa käskeä: "tail -1 tiedosto". 

Miksi noitä putkia on alunperinkään opetettu laittamaan jokapaikkaan? 

- awk ja sed ovat yksinään paljon tehokkaampia kuin BASH, mutta BASH:in apulaisiksi niistä ei aina ole sillä ne ovat ulkoisia käskyjä joten niiden lataaminenkin kestää. Niitä kannattaa käyttää vain kun niille annetaan ratkaistavaksi monimutkainen tehtävä.

***

Skriptien nopeuttamiseksi on miljoonia muitakin silkkaa BASH:ia olevia keinoa - eivätkä ne ole kummallisia niinkuin tuo LC_ALL=C joka ei aina edes tehoa. Ja mikäli jollekin toiminnalle löytyy BASH-käsky niin se on myös nopeampi kuin sed ja awk-viritykset. Toisaalta BASH kyllä kompastelee kaikissa esteissä. Esimerkiksi tämmöisiä löytyy:
Koodia: [Valitse]
apu=kilometri; echo ${apu//[a-l]/x}  # tulostaa: xxxomxtrx. Elikä tehdään BASH:issa se muunnos joka
# yleensä tehdään sed:issä. Ja BASH:kin tuntee regex:än, vaikka varsin rajoitetusti. 

apu=kilometri; echo ${apu//kilo/'~'} # tulostaa: ~metri elikä vaihdetaan sananosa eikä merkkejä.

apu=kilometri; echo ${apu#*ilo}                          # tulostaa:metri elikä toimitaan sananosilla
apu=kilometri; apu=$(echo ${apu%*tri}); echo ${apu#*ilo} # tulostaa:me sieltä välistä
# siis kun echotaan niin käsky cut on pahvia.
Samantapaisia on loputtomasti. Ja yleensä ne toimivat matriisienkin kanssa joten niitä voi soveltaa loopittomiin ratkaisuihin - jolloin jo nopeampi nopeutuu lisää. Tosin nuo BASH:illa tehdyt kompastelevat enemmän kun awk ja sed.

***

BASH:in toiminta virhe- ja poikkeustilanteissa:

Mikäli skiptissä viitataan johonkin jota ei ole olemassakaan oletetaan että kutsutaan funktiota jota ei ole määritelty ja kutsutaan automaattisesti funktiota: command_not_found_handle. Koska tuossa funktiossa voidaan tehdä mitävaan skriptintekijä määrää niin sen muodostaminen on skriptintekijän vastuulla. Käytännössä skriptintekijä haukkuu BASH:in kivenrakoon kun ei tiedä tuota funktiota määritellä. Muiden poikkeustilanteiden käsittelemiseen pätee seuraava:

Tehdäänpä koneessa mitähyvänsä niin käyttöjärjestelmä kyllä havaitsee sen ja asettaa vastaavan signaalin. BASH käy jokaise käskyrivin jälkeen tutkimassa määriteltyjen 78:n signaalin tilan ja tekee mitä trap-käskyllä on määrätty tuossa tilanteessa tekemään. Esimerkiksi seuraavankaltaisia trap-komentoja yleensä tehdään:
Koodia: [Valitse]
trap 'echo virherivi:${LINENO[@]};echo ${FUNCNAME[@]} ; read' ERR
trap 'rc=$?; echo "virhekoodi $rc rivillä $LINENO-${FUNCNAME[0]}" ' ERR
trap ' (( $? )) && echo ${FUNCNAME[0]}" palautti virheen:"$?'  RETURN
trap "echo kutsupino: ${FUNCNAME[@]}" RETURN
trap pomppaaTänne SIGINT                    # mitä tehdään kun painetaan CTRL-c (nimi eikä numero)
trap 'echo "Control-C disabled."' 2
trap jälkiensiivous SIGTERM                 # kun skripti loppuu tai tekee suoritusaikaisen virheen
trap 'echo "VARIABLE-TRACE> \$variable = \"$variable\""' DEBUG # muuttujanarvon seuraaminen
set -x; trap "echo hit_return;read x" DEBUG                    # käskyrivien suoritus yksi kerrallaan
- trap:peja voidaan kirjoittaa funktioisakin vaikka ne yleensä määritellään skriptin alussa. Esimrkiksi kun virhettä käsitellään voidaan virhekäsittelyn ajaksi määrätä virhekäsittely pois päältä käskyllä: trap - ERR ja palauttaa se vanhassa muodossaan käsittelyn jälkeen - jottei näyttö sotkeutuisi kovin pahasti.

-samoin trapeissa saa olla funktiokutsujakin.

- jos skriptinsuoritus päätyy virheeseen niin tulostuva virheviesti on BASH:issa normaaliakin kehnompi - mutta ei se virheen tarkka ilmoittaminen paljoa auttaisikaan. Nimittäin skriptintekijälle riittää tieto siitä tapahtuuko virhettä vai ei - ja missä se tapahtuu: silloin virheen syyn pitäisi olla melkoselvä.

- BASH ei lopeta skriptinsuorittamista ihan pienistä virheistä vaikka se asettaakin virheen kohdalla muuttujan $? . Yritetään väittää että tällainen toimintatapa ei ole hyväksyttävää ja neuvotaan laittamaan skriptin alkuun: set -e. Mutta siitä on joskus hyötyä ja joskus haittaa.

BASH-skripteissä ei enää käytetä rakennetta: if..then vaan se korvataan rakenteella:
käsky1 && käsky2 -> käsky2 suoritetaan vain mikäli käsky1 onnistuu, tai:
käsky1 || käsky2 -> käsky2 suoritetaan vain mikäli käsky1 epäonnistuu, tai:
käsky2 && käsky2 || käsky3 -> käskyryhmää käytetään siellä missä ennen oli if..then..else .
- usein käsky1 on ehto, esimerkiksi: [[ -f tiedosto ]], mutta ei sen tarvitse olla. Jokatapauksessa käsky: "set -e" olettaa tuon ensimmäisen olevan ehto ja eikä välitä siitä.
- mieti kaksikertaa mihin || milloinkin viittaa, minkä kanssa se OR-funktion muodostaa.

- useimpia muitakaan tulkille annettavia ohjeita ei kannata antaa muuten kuin virhettä metsästettäessä.

***

Skriptin debuggaamisessa ei ole mieltä - kuvaannollisesti huonompi yrittää korjata parempaansa. Hermotkin kiittää jos heität huonostitoimivan skriptin roskikseen ja teet uuden. Vaikka tuleehan siihen debuggaamiseen joskus syyllistyttyä.

Muutenkin on suositeltavaa välillä "hukata" joku skripti ja tehdä se sitten uudestaan sillä yleensä siitä tulee paljon parempi.

Skriptissä kaikkien käytettävien nimien tulee olla valittu niin ettei kommentointia enää juurikaan tarvita. Nimittäin kommentoidessasi skriptiä tajuat että jos se toimisi toisella tavalla nopeutuisi se paljon. Teet siis uuden version ja kommentointi alkaa alusta ja ennenkuin kommentointi on tehty niin taas huomaat että uusi olisi parempi ja ... Ja koska asiallinen kommentointi vie aikaa enemmän kuin koodaaminen niin hiukka idioottimaista se kommentointi on.

Maailmalla monet skriptit on suurella vaivalla kommentoitu pilalle. Sikäli pilalle että koodia ei ole helppo ymmärtää kun pääosa siitä on kommentointia. Kestää nimittäin kauan ennenkuin ymmärrät toisen kommentointia sillä toiselle ilmanmuuta selvä on toiselle vaikeaa joten sitä ei selitetä tarpeeksi - ja toisaalta toiselle vaikea on toiselle itsestäänselvää joten siitä läärätään turhaan ja loputtomasti.

Ihminen tajuaakin koodin sitä hitaammin mitä useammalla rivillä se on esitetty - ja lisää hitautta tulee jos koodi pursuaa näytön toiselle sivulle - varsinkin raamatun kokoinen skripti on vaikea ymmärtää vaikka siinä vain osoitettaisiin että 1+1=2 vielä tänäänkin. Paras esimerkki on jo vanhentunut käsky if..then..else jota ei pitäisi enää käyttää. Koska sitä kuitenkin käytetään niin minkätakia se täytyy jakaa monelle riville kun sen voi kirjoittaa yhdellekin?

***

Nopein keino skriptin nopeuttamiseksi on poistaa siitä tarpeettomia osia - usein poistaminen johtaa huomattavaan nopeutukseen. Sen havaitseminen mikä on tarpeetonta on valitettavasti kokeellista hommaa ja jotta se onnistuisi järjellisessä ajassa tarvitaan työkaluja - aivankuin niitä työkaluja tarvitaan kaikissa kielissä ja ne ovatkin lähes kaikissa kielissä jo perusversiossa - mutta BASH:ista ne on poistettu ja joudut itse luomaan ne uudestaan.

Samalla BASH:ista on poistunut paljon muutakin. Esimerkiksi BASH on pakotettu käyttämääm merkintätapoja joita ihminen ei kykene käyttämään tehokkaasti. Sama merkintätapojen kummallisuus pätee lopulta kaikkiin kieliin mutta niissä inhottavia merkintöjä ei tarvitse käyttää sillä ne on kätketty kirjastoihin.

Mutta paras keino skriptin nopeuttamiseksi on kirjoittaa skripti kokonaan uudestaan käyttäen parempaa toimintaperiaatetta jolloin nopeutta tulee usein paljon lisää, tulee vakautta ja vaikka mitä - kymmenenkertaa nopeampi on arkipäivää. Kannattaako se on toinen asia sillä BASH on sittenkin niin hidas ettei siitä isompaan käyttöön ole - tai hitaaksi BASH:ia ainakin monet väittävät.

Uudet menetelmät tuovat skripteihin uusia ominaisuuksia - esimerkiksi tässä esitetty muistikuva-menetelmä nopeuttaa oleellisesti useimpia isoja tehtäviä. Jos samalla hyödynnetään kuvattua uutta ja yksinkertaista menetelmää yhden tai useamman erityyppisen parametrin palauttamiseksi funktiosta päädytään johonkin aivan uuteen.

***

- koska tuo parametrien palauttaminen on niin perustavalaatuinen asia - ja koska näin loppuunajettuna en kykene asioita loogisesti esittämään niin tässä parametrien palauttaminen on esitetty niin ettei sitä voi ymmärtää väärin vaikka kuinka tahtoisi :
Koodia: [Valitse]
function pienlukujenkertoma () { let $2=$(echo $(($(seq -s* $1)))) ;}

pienlukujenkertoma 6 kuuskertoma
pienlukujenkertoma 20 kakskytkertoma

echo "6  kertoma: "$kuuskertoma
echo "20 kertoma: "$kakskytkertoma

- parametrien palauttamiseen on muitakin keinoja: esimerkiksi kutsurakenne $( ... ) - elikä palautetaan haluttu arvo näytön kautta; toiminta on erittäin nopeaa eikä jätä näyttöön mitään. Sillä on se puute ettei se toimi oikein kun palautettavaa on montariviä.
- tai peräti niin että funktio kirjoittaa kovalevylle ja kutsuja käy sitten lukemassa kovalevyltä. BASH:in hitauden huomioonottaen tämä on ihan käyttökelpoinen keino.
- nämä kaksi keinoa toimivat silloinkin kun funktiokutsu on muotoa: funktionimi () ( ... ) - siis kaarisulut aaltosulkujen tilalla. Kutsu on hitaampi koska se muodostaa ensin oman prosessinsa - mutta lähes kaikki on sitten ihan omaa joten mikään funktiossa tehty ei näy pääohjelmassa. Paitsi levyoperaatiot.

***

Ikuista ratkutusta:
Missään kielessä ei voi tehdä mitään merkittävää ilman kirjastoja. Olisi mielekästä alkaa uudestaan käyttää BASH:iin  kirjastoja sillä BASH:ista ei tulla pääsemään eroon vaikka halu onkin hirveä - ja kirjastoilla varustettuna se olisi ihan kelvollinen kieli - ja alkaa hiljokseen tuntua ettei se ole mahdottoman hidaskaan mikäli käskee oikein. Kuitenkin BASH:in käskyjä olisi syytä parantaa - ilmeisesti se olisi BASH:in kehittäjille helppoakin, mutta nähtävästi rintakarvoja puuttuu - en minäkään kyllä uskaltaisi käskyjä parannella.

***

Käsky sleep toimii millisekunnista ylöspäin eikä se ole kovin tarkkakaan. Tarkemman viiveen saat käskyllä: read -t .xxxxx  - muuttujaa ei tarvitse määrätä. Aika on sekunneissa joten toimitaan 10:stä mikrosekunnista ylöspäin. Pienillä viiveillä toimimista varten tein sitä testaavan skriptin - sadasta mikrosekunnista yhdeksään millisekuntiin:

Skriptissä on kaksi prosessia pyörimässä yhtäaikaisesti. Ensin omia aikojaan tiedoston lukuarvoa tasaisesti kasvattavava prosessi joka työnnetää taustalle käskyllä: & .

Ja sitten normaalina edusta-ajona toinen nopeampi looppi ottamaan tiedostosta ensin näyte, sitten suoritetaan tasaisesti kasvava viive jonka perään otetaan toinen näyte ja tulos on näyte2-näyte1. Tuloksien tulisi kasvaa tasaisesti, mutta koska BASH on ajallisesti horjahteleva niin eihän näin aina ole. Yleensä tämä ei edes tulosta kaikkia määrättyjä; esimerkiksi jos ensimmäinen prosessi kirjoittaa juuri kun toinen haluaa lukea niin BASH luovuttaa.
 
Suuntaus on kuitenkin selvä joten kyllä pienetkin viiveet toimivat ainakin suurinpiirtein - ehkäpä tarkastikin. Joten vaikka tällainen viive on tällähetkellä tarkoitukseton niin on mukava tietää että tulevaisuudessa tällainenkin viive on käytettävissä. 
Koodia: [Valitse]
#!/bin/bash
for (( n=0; n<=99999999; n++ )); do echo $n > file1; done & # tämän loopin pitää olla hitaampi

apu1=0; apu2=0; for m in {0001..0090}; do read -t 0.$m; apu1=$apu2;apu2=$(cat file1); [[ $apu1 && $apu2 ]] && echo -n $(($apu2-$apu1))' ' ; done
kill $! # tämä tappaa alussa määrätyn taustaprosessin - siis tuon jonka perässä on merkki &
rm -f file1

***

Kevennystä:
Voi sen sinin laskea näinkin:
Koodia: [Valitse]
b='$(echo "s(4*a(1)/180*$a)" | bc -l)'; a=30; eval echo $b
- siis muuttujaan laitetaan se kaava. Tekstijono se on kaavakin.

***

Yhdessä asiassa mistään kielestä ei ole BASH:in kilpailijaksi: yhdenrivin skriptit. Kooltaan ne ovat yleensä alle rivinmittaisia, mutta monet ovat kyllä jopa kolmenrivin mittaisia - mutta aina yksi kokonaisus.

Niitä käytetään sellaisenaan sillä päätteessä on tavallaan jo rivi: !#/bin/bash. Eikä noille yhdennrivin skripteille haeta koskaan suoritusoikeutta. Mikäli käytettävissä on kunnollinen varasto yhdenrivinskripejä ja niitä osaa käyttää tuleekin käyttäjästä legendaarinen.

Mutta noita yksirivisiä täytyy olla tuhansia ja ne täytyy olla järjestetty niin että löytää tarvitsemansa. Ja hyvin harvasta on siihen sillä kaikki me hallitsemme epäjärjestyksen.

Näyttää lisäksi siltä ettei ole mitään rajaa sille mitä yksirivisellä voi tehdä. Ja ne ovat hämmästyttävän nopeita.

Mutta on yksirivisillä varjopuolensakin: esimerkiksi ne toimivat usein vain jossakin tietyssä distrossa ja joskus ne tarvitsevat sellaisia järjestelmäasetuksia joita harvassa koneessa on.

***

Aikaisemmin käsityksenä oli että vaikka BASH:illa saakin nopeasti nopeasti tehtyä skriptin mihin vaan, niin siitä selviää tutkittavasta vain senverran että voi päättää kannattaako muilla kielillä tehdä kunnollinen ohjelma. Mutta usein BASH-skripti kelpaa kyllä lopulliseksikin versioksi. Esimerkiksi seuraava joka kelpaa pohjaksi monenlaiseen:

Graafisessa xy-esityksessä akseli voidaan esittää joko lineaarisena tai logaritmisena. Esitettäessä jotakin x-akselin ollessa logaritminen saadaan vähimmillä ponnistuksilla sujuva käyrä jos sen esityspisteet sijaitsevat x-akselilla tasavälein.

Koska pisteiden luomisessa tarvittava laskukaava on BASH:issa mahdoton muistaa tein siitä funktion: tämänjälkeen kun tarvitsee tasavälisiä esityspisteitä ei enää tarvitse laskea niitä vaan voi kutsua funktiota tekemään sen.

Mutta logaritmialueella pisteiden muodostamisessa tuli eteen periaatteellinen ongelma: epäilin että peräkkäisten pisteiden suhde on vakio eikä millääntavalla logaritminen. Tämä täytyi varmistaa tekemällä käyrä jossa peräkkäisten x-akselin pisteiden suhde on vakio - ja mikäli väite pitää paikkansa sijaitsevat pisteet tasavälein koko x-akselilla dekadien vahtuessakin - ja lisäksi exponentiaalisen käyrän kuvaaja on suora y-akselin ollessa lineaarinen:
Koodia: [Valitse]
#!/bin/bash
function luoaskellus () { # lineaarisen  asteikon kutsu: luoaskellus alku loppu askel
                         # logaritmisen asteikon kutsu: luoaskellus alku loppu *suhde 
                        # negatiivisilla arvoilla liikutaan imaginääriluvuissa. Siis nollaa ei saa ylittää
unset $1 # varmistus että matriisissa kaikki on tässä funktiossa muodostettua
[[ $(printf '%d' "'${4:0:1}'") != 42 ]] && apu=$(seq $2 $4 $3 | tr '\n' ' ' | tr , . ) || { echo "for (i = $2 ; i < $3; i *= ${4:1})  i" | bc -l > /tmp/delme; apu=$( cat /tmp/delme | tr '\n' ' ') ;}
apu2=($apu)                                # matriisi x-akselin pisteistä   
readarray $1 < <(printf "%s\n" ${apu2[@]})
let $5=${#apu2[@]} ;}                      # pisteiden lukumäärä   
 
function plotlogx () {
# tämä funktio tulostaa ainoastaan tiedostoon: ~/koe.eps eikä näytölle tule mitääm. Tiedosto avautuu tiedostoselaimella
gnuplot -p -e 'set terminal postscript eps color enhanced; set logscale x ;set xlabel "x"; set ylabel "logaritmi"; set output "~/koe.eps"; plot "~/koe"' ;}

luoaskellus askellus .000001 1000000 *1.5 askelluku
rm -f ~/koe; for (( n=0; n<=$askelluku-1; n++ )); do echo -n ${askellus[n]}' ' >> ~/koe; echo $(echo "l(${askellus[n]})") | bc -l >> ~/koe; done

plotlogx

4
Readarray toi BASH:iin kaksiulotteiset matriisit ja niitähän yleensä tarkoitetaan kun muissa kielissä puhutaan matriiseista. BASH:in vanhantyyppiset matriisit ovat yksiulotteisia ja niiden kanssa toimiminen on kaikinpuolin selväpiirteisempää - mutta se täytyy myöntää että puurot ja vellit menee nyt sekaisin. Seuraavissa tarkoitetaan aina yksiulotteisia matriiseja. Nimittäin readarray pahoinpitelee muistikuvaa joten kaksiulotteisten matriisien muistikuvien käsitteleminen tapahtuu vähän erilailla.

Kyllä ne matriisitkin palautuvat funktioista -> tässä funktioon menee vertailtavat matriisit ja tyhjä matriisi, se tyhjä täytetään siellä kahden muun matriisin eroilla ja sen muistikuva muodostetaan joten se on täytettynä funktiosta palattaessa - siis mitään ei palauteta mutta tulos on sama kun jos olisi palautettu.

Surkean hitaitahan nämä muistikuvan avulla tehdyt matriisinkäsittelyt ovat, mutta ne kestävät sentään järjellisiä aikoja - nämä kestävät vain sekunnin mutta looppi-versiot kymmeniä minuutteja. 

Myös matriisien eroavaisuuksia ilmoittavan funktion toiminta muistikuvaan tukeutuen on nopeampi kuin pääohjelmaan tehdyt viritykset. Tarkemmalta nimeltään skripti on: mitä toisessa matriisissa on sellaista mitä ensimmäisessä ei ole.

Tässä funktioon menee vertailtavat matriisit ja tyhjä matriisi, se tyhjä täytetään siellä kahden muun matriisin eroilla ja sen muistikuva muodostetaan - siten se on täytenä pääohjelmassa.
Koodia: [Valitse]
#!/bin/bash
function arraydiff1 () { readarray $3 < <(grep -Fxvf <(declare | grep ^$1= | tr ' '  '\n' | cut -d\" -f2 | sort) <(declare | grep ^$2= | tr ' '  '\n' | cut -d\" -f2 | sort)) ;}
matriisi1=({100000..1})
matriisi2=({-2..100002})
time arraydiff1 matriisi1 matriisi2 matriisi3
printf "%s\n" ${matriisi3[@]}


Tämä tulostaa kahden matriisin erot - jättäen pois yhteiset jäsenet:
Koodia: [Valitse]
#!/bin/bash
function arraydiff2 () { readarray $3 < <( comm -33 <(declare | grep ^$1= | tr ' '  '\n' | cut -d\" -f2 | sort) <(declare | grep ^$2= | tr ' '  '\n' | cut -d\" -f2 | sort)) ;}
matriisi1=({3..100000})
matriisi2=({0..99999})
time arraydiff2 matriisi1 matriisi2 matriisi3
printf "%s\n" "${matriisi3[@]}" | column -t


Vaikeista puuttuu enää se joka kertoo mitä yhteisiä jäseniä matriiseissa on:
Koodia: [Valitse]
#!/bin/bash
function arraydiff3 () { readarray $3 < <( comm  -12 <(declare | grep ^$1= | tr ' '  '\n' | cut -d\" -f2 | sort) <(declare | grep ^$2= | tr ' '  '\n' | cut -d\" -f2 | sort)) ;}
matriisi1=({3..5} {7..11})
matriisi2=({0..9} {11..53})
time arraydiff3 matriisi1 matriisi2 matriisi3
printf "%s\n" "${matriisi3[@]}" | column -t

***

Myös matriisin maksimi ja minimi kannattaa etsiä muistikuvan avulla:
Koodia: [Valitse]
#!/bin/bash
export LC_ALL=C # toiminta-aika muttuu 260->100ms
function matriisinmaxmin () {
declare | grep ^$1= | tr ' '  '\n' | cut -d\" -f2 | sort -n > /tmp/delme
readarray $3 < <(tail -1 /tmp/delme)
readarray $2 < <(head -1 /tmp/delme) ;}
# readarray lukee myös yksijäsenisen matriisin. Ja yksijäsenisen matriisi on sama kuin tavallinen muuttuja
# joten readarray lukee muuttujiakin.
 
# pääohjelma toiminnan kokeilemieksi: 180 188
unset maksimi minimi
matriisi=($(seq 100000 | awk -v seed=$RANDOM 'BEGIN{srand(seed)}{printf "%1.9f\n", rand()}')) 
time matriisinmaxmin matriisi maksimi minimi
echo minimi:$minimi'  maksimi:'$maksimi
LC_ALL=

***

BASH:in muistissa jo valmiiksi olevasta kaksiulotteisesta matriisista etsitään maksimi näin:
Koodia: [Valitse]
#!/bin/bash
# BASH:issahan voi olla kaksiulotteinen matriisi muistissakin ja mikäli näin on niin nopeinta on etsiä
# suoraan sieltä - tässä on kyse sellaisesta tapauksesta.
# Mutta mikäli se matriisi on jo valmiiksi levyllä niin nopeinta on etsitä heti levyltä.

function kaksiulotteisenmatriisinosoitettavankentänmaximi () { readarray $3 < <(declare | grep ^$1= | cut -d\( -f2- | tr "\'" '\n' | sed '/\[/d;s/\\n//g' | tr -d \) | cut -d' ' -f$2 | sort -n | tail -1) ;}
 
readarray matriisi < koe
kaksiulotteisenmatriisinosoitettavankentänmaximi matriisi 2 maximi
echo $maximi

*** 

Funktiotkin toimivat jokaisella suorituskerralla hieman erikauan. Tämä skripti tutkii transpose-funktiota ja nimenomaan sen suoritusaikojen vaihtelua odotettaessa mittausten välissä .1-20 sekuntia - nimittäin LINUX aiheuttaa tuskaa ainakin tällä alueella. Mutta pienin koodimuunnoksin tällä voi tutkia melkein minkätahansa funktion mitätahansa ominaisuutta. Tässä mittaus kestää monta tuntia mutta se on ihan liian lyhyt aika saada dataa josta tosiaan voisi sanoa jotakin.
Koodia: [Valitse]
#!/bin/bash
function transpose () {
numberoffields=$(($( declare | grep ^$1= | cut -d\[ -f2 | tr -dc [[:space:]] | wc -c ) -1 ))
readarray $1 <  <(for (( n=1; n<=$numberoffields; n++ )); do
  echo $(printf "%s" "${array[@]}" | cut -f$n -d' ' ) # & nopeuttaisi hieman mutta sillä on varjopuolensa
done)
}

< testa  readarray array
rm -f ~/koe
 
for n in $(seq .1 .1 20 | tr , .); do # mitattaessa muita funktioita x-akseli on usein logaritminen
# (esimerkiksi taajuus) ja pyyhkäisy saattaa silloin kannattaa vaihtaa "exponentiaaliseen" askellukseen
# jossa jokainen askel on aina esimerkiksi 5% suurempi kuin edellinen: 
# for n in $(echo "for (i = .001 ; i < 1000; i *= 1.05)  i" | bc -l ); do echo $n; done
# tällöin gnuplot:ille kannattaa kertoa että x-akseli on logaritminen: set logscale x 10

# suoritusaikoja tarkkaileva pääohjelma;
< testa readarray array # luetaan matriisi uudestaan jokakierroksella jotta alkuasetelma olisi aina sama
sleep $n
alkuhetki=$(date +%s.%N)
transpose array # tämän rivin paikalla olevan funktion ajankäyttöä tutkitaan. Sille voidaan antaa
                # parametrejä tai sitten ei.
loppuhetki=$(date +%s.%N)
echo $n' '$(echo $loppuhetki-$alkuhetki | bc) | tee -a ~/koe
done

gnuplot -p -e 'set terminal postscript eps color enhanced; set xlabel "odotusaika"; set ylabel "toiminta-aika sec."; set output "transpose.eps"; plot "~/koe"'
- skripti ei tulosta käyrää suoritusajoista vaan tekee käyrästä tiedoston nimellä transpose.eps. Sen voi lukea tiedostoselaimella - jos se ehdottaa lataamaan lisäpaketin niin lataa se.
- tai eihän se varsinainen käyrä ole vaan tuloste mittauspisteistä.

***

Muutaman mittauksen pohjalta ei mitään tarkempaa voi esittää mutta muutaman huomion tein:

Skriptien toimimisen nopeus heittelee aina - usein luokkaa 10%. Mutta todella ihmeellisiäkin toiminta-aikoja esiintyy - esimerkiksi kestoaika voi joskus olla kaksinkertainen - tai joissain harvoissa tapauksissa normaalia pienempikin.

Äskeisessä skriptissä määriteltiin suoritusaika nanosekunnin tarkkuudella vaikka todellinen suoritusaika vaihtelee usein peräti useita kymmeniä millisekunteja. Sinänsä tuo nanosekunnin tarkkuus on jokseenkin oikea - mutta se kertoo vain sen kuinkakauan suoritus kesti silläkertaa. Käytännössä sen tarkkuudesta jää hyötykäyttöön vain hieman enemmän kuin time-käskystä, sen tarkkudeksi voisi sanoa parisataa mikrosekuntia.

Mutta se kelpaa tilastollisiin tarkasteluihin - tosin noiden tarkastelujen oikea suorittaminen ja tulosten tulkinta on niin vaikeaa että tulokset jäävät usein epätarkoiksi. Mutta yhden asian ne kertovat luotettavasti: kuka on keskimäärin nopein ja antaa pienimmän hajonnan. Muuten tulokset ovat vain suuntaa-antavia.

***

Silloin kun BASH vielä oli elinkelpoinen koetettiin foorumeilla vastata kysymyksiin: voiko BASH:illa tehdä sitä-ja-tätä. Onhan se kiva ehdottaa jotakin, mutta itseasiassa noihin kysymyksiin on ainoastaan yksi vastaus: tapoja tuon tekemiseen on ainakin miljoona eikä kukaan tiedä sitä parasta tapaa - joten hyvä keino osoittaa itsensä idiootiksi on vastata. Mutta koska kaikkihan me idiootteja ollaan niin ...

Tosiasia kyllä on että BASH:in työkalut on päästetty rappeutumaan surkeaan kuntoon eivätkä sen hampaat pure enää juuri ollenkaan.

***

Aikoinaan tällä foorumilla oli juttua kuinka nopeasti kukin skriptikieli lukee tiedoston ja muuttaa siitä määrättavän sanan tai kirjaimen. Näistä normaaleista skriptikielistä nopein oli Python, mutta hyväksi kakkoseksi tuli BASH:in sed - itse BASH oli niin surkea ettei kukaan siitä edes puhunut.

Mutta senjälkeen BASH on nopeutunut suunnattomasti sillä se on saanut käskyn readarray joten tetävästä saa nykyään tehtyä loopittoman version. Ei sen nopeus ole vieläkään kuin vajaa puolet sed:n nopeudesta mutta toiminta-nopeus on sentään jo mielekäs. Ja BASH:issa on koneen muistissa tiedostosta tehty matriisi josta varmaan on joskus hyötyä. Matriisin jokaisen rivin lopussa on muuten ylimääräinen rivinsiirto - joskus se kannattaa poistaa. Tämmöinen se käsky on:
Koodia: [Valitse]
readarray matriisi < tiedosto; printf '%s' "${matriisi[*]//mikä/miksi}" > jokutoinentiedosto
- muuten tuo mikä voi olla myös yksinkertainen regex, esimerkiksi [[:upper:]] tai [0-9]

***

Kukaan ei pysty puhumaan BASH:ista niin ettei puheessa olisi paljonkin korjattavaa. Joten mikäli BASH:ia opettelee oppilaitoksissa kirjojen ja virtuoosien johdolla oppii sen normaalin kyvyttömyyden. Ja mikäli kokeilee ja kertoo kokemuksistaan päätyy usein naurettavuuksiin ja virheisiin. Mutta jos et ota aasinhattua päähäsi niin  BASH pysyy ikuisesti niin kuolleena kuin miksi virtuoosit ovat sen saattaneet.

***

BASH:issa on hyvät käskyt tekstijononkäsittelyyn, mutta niiden ulkoasu on useimmiten niin hankala muistaa ja naputella koneseen ettei niitä juuri kukaan käytä. Esimerkiksi määrittely missä pienisana sijaitsee isossasanassa:
Koodia: [Valitse]
apu=kemblefordmetri; echo ${apu%met*}
# joka tulostaa kembleford - ja siitä pääsemme funktioon nimeltään tekstijononalkupaikka:

function tekstijononalkupaikka () { apu=$(echo ${1%$2*}  | wc -m); (( $apu > ${#1} )) && echo ei_löydy || echo $(($apu-1)) ;}; tekstijononalkupaikka kemblefordmetri met
joka tulostaa 10. Oikeellisuuden tarkistus:
Koodia: [Valitse]
apu=kemblefordmetri; echo ${apu:10:3} tulostaa: met
- mikäli pienempää tekstijonoa ei isommassa ole niin palautusarvoksi tulee: ei_löydy 
- tekstijonon ensimmäisen merkin järjestysnumero on 0. 
- skannataan muuten lopusta alkuun - siis m on 10 eikä 2

***

BASH:in funktiot ovat samanlaisia kuin muissakin kielissä eli niitä voidaan kutsua samassa skriptissä eripaikoissa toisilla parametreilla. BASH:in funktiot tuntevat sekä arvo- että nimiparametrit. Niiden käyttäminen:
- arvoparametrit: kuulut joukkoon joka ajattelee "elämä ilman eval-käskyä on yksinkertaisempaa".
- nimiparametrit: haluat parametrit käsitelynjälkeen takaisin. Nimiparametrit eivät muuten ilmanmuuta johda eval-käskyn käyttöön.

Koska funktiot ovat perusosa BASH:ia on niiden kutsuminen erittäin nopeaa (noin 25mikrosec.)

Funktiot kannattaa sijoittaa kirjastoihin. Kirjasto on perusmuodossaan ihan tavallinen tiedosto josa on funktioita. Eikä kirjastossa ole lausetta: #!/bin/bash tai muita shebangeja. Kirjasto-tiedostossa saa olla funktioita kuinmonta vaan ja kirjasto-tiedostojakin saa olla kuinkamonta vaan - tai onhan niillä montakin rajaa: käyttäjän kyvyt tulevat ensin vastaan ja sitten koneesta loppuisi muisti. Kunkin tyyppin asioille kannatta tehdä oma kirjastonsa asianmukaisesti nimettyyn tiedostoon.

Jotta skriptissään saisi kirjastot käyttöön kirjoitetaan skriptissä riviä #!/bin/bash seuraaviksi riveiksi:
. ~/bash_kirjastot/ensimmäisen kirjaston nimi 
. ~/bash_kirjastot/toisen kirjaston nimi
. ~/bash_kirjastot/kolmannen ... ja niin monta kirjastoa kun tarvitsee - mutta yksikin kirjastonimi kyllä riittää.

Jotkut eivät halua käyttää kirjastoja. Silloin funktion voi kopioida oman skriptinsä alkuun sieltä kirjastotiedostosta.

***

Funktionimien oikea valinta on tärkeää useastakin syystä:
- kun nimi on kerran valittu tulee sen vaihtamisesta nopeasti vaikeaa olipa siinä kuinka idioottimainen virhe hyvänsä. Nimen täytyy kuvata hyvin sitä mitä funktio tekee - ei saa välittää siitä kuinka pitkä nimestä tulee, sillä tarvittavat nimet tulee jokatapauksessa aina leikata-liimata eikä niitä naputella.
- kun olettehnyt jotakin niin varmaankin haluaisi myöhemmin löytää sen mitä olet tehnyt - ja mikä sen parempi kuin nimi jonka osia voit aavistella. BASH:in merkinnät ovat pikkuisen kummallisia joten vaihtoehtona on etsiä jotain ?<=$2 kaltaista.

Sitä voi itsekseen pähkäillä laittaako nimiin ä:tä ja ö:tä. BASH nimittäin hyväksyy ne mutta editorit eivät aina tykkkää. Sitäipaitsi jos BASH jossain vaiheessa lopettaa toimimisen niiden kanssa niin se aiheuttaa melkoisen hämmingin.
Funktionimissä ei voi olla välilyöntejä edes lainausmerkeissä.

Ovatkoha nämä nimet oikein annettu? Tulevaisuus näyttää:
Koodia: [Valitse]
function hakuasanojenvälinenteksti () { echo "$1" | grep -Po "(?<=$2).*(?=$3)" ;}; hakuasanojenvälinenteksti "jb khavain1[@'123  4567890avain2k jnj" avain1 avain2
# tulostaa: [@'123  4567890
# jos jompaakumpaa avainta ei löydy ei myöskään tulosteta mitään

function avain1onhaussa_avain2täytyyollaperässämuttasitäeivalita () { echo "$1" | grep -Po "$2(?=.*$3)" ;}; avain1onhaussa_avain2täytyyollaperässämuttasitäeivalita "jb khavain111123  4567890avain2k jnj" avain1 avain2
# tulostaa: avain1

function avain1onhaussa_avain2eisaaollahetiperässä () { echo "$1" | grep -Po $2'(?!'$3')' ;}; avain1onhaussa_avain2eisaaollahetiperässä "jb khavain1avain2k jnj" avain1 avai2
# tulostaa: avain1 # mutta ei tulosta mitään kun avai2 muutetaan muotoon avain2

function avain1täytyyollahetiedessämuttasitäeivalita_avain2onhaussa () { echo "$1" | grep -Po "(?<=$2)$3" ;}; avain1täytyyollahetiedessämuttasitäeivalita_avain2onhaussa "jb khavain1avain2k jnj" avain1 avain2

# en edes yritä keksiä nimeä seuraavalle - jolloin siitä voisi tehdä funktion:
# -Poz '(?ism:^BEGIN.*?END)'   # i=älä välitä merkkikoosta, s=begin ja end voivat olla eri riveillä, m=merkin:^ käyttö sallitaan lukitsemaan begin rivin alkuun

Pieniten ja melkomerkityksettömien funktioiden nimeämistä ei saa väheksyä. Esimerkiksi kun joudut sanomaan mikä on kirjaimen A ascii-arvo niin sielusta lentää päreitä kun ei muista eikä kovin nopeasti saa siitä tehtyä skriptiäkään. Mutta funktion nimi on kaikenjärjen mukaan: merkkinumeroksi - ja se löytyy nopeasti.

Koodia: [Valitse]
function numeromerkiksi () { printf \\$(printf '%03o' $1) ;}

function merkkinumeroksi () { printf '%d' "'$1'" ;}

***

Ilman ohjeistusta skripti ei saa dataa käsitellessään olettaa millainen aakkosto on ollut siellä missä sen käsiteltäväksi annettu data on tehty. Eikä tulkki voi mitekään tietää ettei mitään kummallista merkkiä ole tulossa esimerkiksi kun skripti etsii jotain matemaattista.

Skriptikoodissa voidaankin antaa tulkille monenlaisia ohjeita, muunmuassa että käytetään yksinkertaisinta koodisivua skriptiä suoritettaessa. Skriptin alkuun laitetaan silloin lause: export LC_ALL=C. Silloin sriptin viimeiseksi lauseeksi on kirjoitettava export LC_ALL= . Skriptin toimintanopeus nousee silloin - yleensä vain vähän mutta joskus yli kaksinkertaiseksi.
- tuo C on kodisivuista yksinkertaisin ja ääkkösetkin temppuilee kun sitä käyttää.
- ongelmaa on aikoinaan ratkottu ja osin onnistuttukin mutta edelleen esimerkiksi nuo muistikuvaa hyödyntävät skriptit nopeutuvat yli kaksikertaa nopeammiksi silloinkuin käsitellään yksinomaan numeroita tai yksinkertaisia kirjaimia.

***

Yritin äskettäin mitata kuinka skriptin suoritusaika vaihtelee kerrasta toiseen. Teorihan on sellainen, että BASH:in tulkin tulkkaustuloksia säilytetään vähän aikaa - osasekunneista sekunteihin. Jos siis mitataan jonkun skriptin suoritusaika monta kertaa odottaen suoritusten välillä 0.1-20 sekutia niin suoritusajat plottaamalla pitäisi saada erittäin valaiseva käyrä. Ja niin saikin, mutta käyrässä oli aivan liikaa kohinaa jotta siitä olisi voinut varmuudella väittää mitään tarkempaa - eikä vuorokaudenkaan mittaus antanut kunnollista käyrää.

Teorian tarkistamisen sotki se että normaalisti skriptin suoritusaika heittelee luokkaa 10% ja joskus suoritusaika peräti kaksinkertaistuu. Toistin nyt saman mittauksen mutta määräsin skriptin alussa: LC_ALL=C. Se nopeutti hieman mutta ennenkaikkea peräkkäiset suorituskerrat kestivät lähes saman ajan. Ja käyrästä sai selvää jo muutamassa minuutissa: näyttää tosiaan siltä että hetkellistä talletusta tehdään ja nimenomaan että säilytettävät unohdetaan nopeasti.

Siis skriptit seikkailevat epämääräisiä aikoja koodisivullaan joten yksinkertaisin koodisivu on usein paras.

***

Toinen ohje minkä skripti voi tulkille antaa on IFS; se merkki jonka kohdalta lause jaetaan sanoiksi.

Kun skriptinsuoritus alkaa on IFS:n arvona: <välilyönti><tab><rivinsiirto> - perusmuodossaan IFS siis muodostuu kolmesta vaihtoehtoisesta merkistä - jako suoritetaan mikä niistä kohdataankaan. Tuo merkki jonka kohdalta jaetaan ei jää kummallekaan puolelle, vaan se häviää.

Mikäli skripti muuttaa IFS:ää olisi mukava jos sen arvo palautettaisiinkin etteivät muutkin pääse nauttimaan kummallisesti toimivasta päätteestä - tämä edellyttää että skriptissä on funktio jonka se suorittaa silloin kun skriptinsuoritus katkeaa virheeseen. Toki skriptin viimeiseksi lauseeksi laitetaan jokatapauksessa: unset IFS . Tuo lopussa suoritettava IFS:n palauttaminen tehdään lauseilla:
Koodia: [Valitse]
trap jälkiensiivous SIGTERM   
# trap kirjoitetaan skriptin alkuun alustuksien joukkoon. Kun kaikki alustukset on tehty tulevat funktiot'
# eikä niiden järjestyksellä ole väliä. Kirjastoja käytettäessä funktioita ei ole. Kirjastojen liitoskäskyt
# kirjoitetaan skriptin alkuun muiden alustuksieen joukkoon. Siis fnktioiden joukossa on :
function jälkiensiivous (){ unset IFS ;} # mukaan liitetään kaikki muukin siivottava

***

Alkaessaan harrastamaan  BASH:ia ihan jokainen saa eteensä "hyvän konstin" joka on jotain tällaista:
echo "aaa bbb ccc ddd fff" | awk '{print $3}' . Ja tuontapaisia tulee sitten tehtyä aina, sillä toimiiha se ja muistaahan sen helposti. Kitkerä kiitos väärästä opetuksesta. Sillä helposti sitä ei unohda ja totu käyttämään montakertaa nopeampaa menetelmää - tosin se on melko mahdoto muistettavaksi joten kopioipa tästä siitä funktio:
Koodia: [Valitse]
function echofieldno () { read -ra apu <<< "${2}"; echo ${apu[$1-1]}; unset apu ;}
# ja kutsuesimerkki:
teksti="aaa      bbb ccc ddd"     
echofieldno 3 "$teksti"
- todella pitkistä teksijonoista kentän tulostuminen kestää - kuten esimerkiksi tekstijonosta:
$(echo {100000..0}) - mutta silloinkin toiminta on parikertaa nopeampaa kuin awk:illa.

Tiedostoille sovitettuna käsky olisi ihan toisenlainen:
Koodia: [Valitse]
function catlineno () { tail -n $1 $2 | head -n 1 ;}
# ja kutsuesimerkki:
catlineno 292 /boot/grub/grub.cfg
- eihän tätä tarvita juurikoskaan. Mutta väärällä tavalla tässä millisekuntitehtävästä tulee minuutihomma ja BASH:in maine kasvaa.

5
Tuossa edellisessä funktiossa on pari puutetta jotka kaipaavat välitöntä korjausta: ensiksi on turha antaa ammuksia BASH:in tuhoajille tuolla eval-käskyllä jos voi olla antamatta - ja toisekseenkin tuo jokeri * on ongelmallinen ja teoriassa pitäisi käyttää viisikertaa hitaampaa jokeria @. Kumpikin korjautuu kun käsitellään BASH:in kirjanpitoa. Nimittäin käsky-yhdistelmä:

declare | grep ^matriisin_nimi=   
tulostaa sen, kuinka BASH pitää kirjaa matriisin_nimi nimisestä matriisista. Sen avulla voi tulostaa matriisin arvot:
Koodia: [Valitse]
mat=(1 kaksi 3 4 "viisi ja puoli" kuusi 7 8 9 0); declare | grep ^mat= | sed "s/mat=//;s/\s\[/\n/g" | cut -d= -f2 | tr -d \)\"
Siitä saa kasattua erittäin nopean sorttausfunktion jossa ei ole eval-käskyä eikä ongelmallista tähteä:
Koodia: [Valitse]
sorttaamatriisi () { readarray $1 < <(declare | grep ^mat= | sed "s/mat=//;s/\s\[/\n/g" | cut -d= -f2 | tr -d \)\" | sort -g) ;
Muistikuvan arvot voidaan erottaa myös jollakin sopivalla regex:ällä, esimerkiksi:
Koodia: [Valitse]
grep -Po '(?U)\".*\"' | tr -d \" tai:grep -Po '(?<==\").*?(?=\")'  tai:grep -Po '(?<=\")[0-9]*'

- nimenomaan huomioi ettei mitään tulosteta - eihän sitä pyydetäkään
- matriisin tekstijäsenet erottuvat näppärästi noilla regex:llä - välilyöntejäkin saa olla. Mutta käytännossä tekstissä saattaa olla kaikenlaista inhottavaa ja tiedostot ovat ihan kauheita. Tekstillä kannattaa käyttää sitä eval-versiota.
- pienin koodimuutoksin sortataan osoitteet. Varsinkin assosiatiivisten matriisien kanssa se on tarpeellista. Osoitteet saa erotettua vaikka käskyllä:
Koodia: [Valitse]
mat=({1..10}); declare | grep ^mat= | grep -Po '(?U)\[.*\]' | tr -d []

***
 
Muistikuvan avulla saa tehtyä matriiseille (=siten myös tiedostoille) monenlaista, esimerkiksi etsittyä maximia, minimiä, keskiarvoa tai jotain muuta odotusarvoa.Tai etsittyä pisimmän jäsenen pituus, suoritettua monenlaisia vertailuja ... samankaltaisia nopeita on lukemattomia.

Kyllähän nämä muistikuva-muutokset jäävät toiseksi kaikille: sed, awk, perl, python - siinä se BASH:in kirous onkin: professorit kehittävät vaikka mitä uudella nimellä koska siten saavat omankin nimensä esiin varsinkin painottaessaan että sitä heidän kehittämäänsä tarvitaan kun BASH ei kykene - tai eihän professorit voi enää tunnustaa että ovat joskus BASH:ia käyttäneetkään.

***

BASH loistaa sellaisten tutkimuksien tekemisessä joista alkuun epäillään että mahtaako niissä mitään tutkittavaa olla. Mutta BASH:illa tekee nopeasti riittäävän hyvän skriptin sen päättelemiseksi kannattaako tehdä tutkimukseen kunnon ohjelma.

Aloin noiden muistikuvaa käsittelevien regex:ien suhteen epäillä että regex:n ajankäyttö kasvaa lievästi exponentiaalisesti datamäärän kasvaessa ja tein siitä käyrän - asia on tärkeä siksi että regex:ää käytetään monissa kielissä. Epäily osottautui vääräksi - mutta sensijaan paljastui että regex:illä on vaikeuksia sisäisten datarakenteidensa kanssa - tai BASH:illa muistinvarauksessa. Siis tarkastelun kohteena on regex: grep -Po '(?<==\").*?(?=\")' ja rinnalla on normaali muistikuvaversio.

Käskyn koodi on tässä (ja se toimii heti kun kopioit sen päätteeseen - muista painaa return. Skriptin toimimisen jälkeen kotikansioon tulee tiedosto regex.eps. Sen saa näkyviin tiedostoselaimella - joskus se toimii vasta kun antaa ensin käskyn: sudo apt-get install evince):
Koodia: [Valitse]
rm -f regex.eps regex;
for (( n=1; n<=1000000; n=n+500 )); do echo $n; unset mat; mat=$(seq -s' ' $n); alkuhetki=$(date +%s.%N); declare | grep mat= | grep -Po '(?<==\").*?(?=\")' ; echo $n' '$(echo $(date +%s.%N)-$alkuhetki | bc ) >> regex; done; gnuplot -p -e 'set terminal postscript eps color enhanced; set xlabel "matriisin koko"; set ylabel "aika sec."; set output "regex.eps"; plot "regex"'
Käsky on pitkäpötkö. Mutta ei sitä kannata paloitella sillä se on suora ja yksioikoinen. Skriptin toiminta kestää viitisen minuuttia sillä dataa liikkuu kymmenkunta gigaa - skriptin toimiessa näytöllä pitää olla kasvava numero osoittamassa että jotain tapahtuu ja missä mennään.

***

BASH:in matriiseja inhotaan ja väheksytään monesta syystä. Yksi väite on se etteivät BASH:in matriisit mitään matriiseja olekaan vaan mitälie vektoreita.

Mutta tavallaan se ei pidä paikkaansa. Kirjoita tiedostoon nimeltä koe:
1 22 333
-4444 5.'$5e55 6666666
7777777 "8888 8888" 9999999999

ja anna sitten käsky:
Koodia: [Valitse]
readarray mat < koe; printf "%s" "${mat[@]}"
jolloin tulostuu:
1 22 333
-4444 5.'$5e55 6666666
7777777 "8888 8888" 9999999999

mutta BASH:in kehittäjät ovat unohtaneet yhden asian: BASH osaa erottaa siitä jäsenen vain kovin hankalasti:
Koodia: [Valitse]
set -- ${mat[rivi_numero]}; echo $jäsenen_numero. Siis esimerkiksi:
set -- ${mat[1]}; echo $2 -> tulostaa: 5.'$5e55
Hidasta ja hankalaahan tuommoinen on, mutta matriisista on silti kysymys.
- matriisin jäsenissä saa olla kaikkia merkkejä paitsi välilyöntejä sillä välilyönnit on varattu erottamaan jäseniä.

Vielä hankalampaa on kirjoittaa tuohon muisti-matriisiin. Esimerkiksi seuravankaltainen toimii:
Koodia: [Valitse]
set -- ${mat[rivi]}; mat[rivi]=${@:1:alkio-1}' uusi '${@:alkio+1}

***

Ei tuollaista "kaksiulotteista" matriisia tarvise editorissa tehdä ja lukea sitten "readarray"-käskyllä vaan onnistuu se ihan BASH:issakin - erikoismerkkejä ei kylläkään hyväksytä, mutta matriisin saa helpommin alkamaan ykkösestä:
Koodia: [Valitse]
rivi1=(1 22 333)
rivi2=(-4444 5.5e55 6666666)
rivi3=(7777777 "8888 8888" 9999999999)
mat=([1]=${rivi1[@]} [2]=${rivi2[@]} [3]=${rivi3[@]})

Siitä saa arvot kahdella tavalla:
set -- ${mat[rivino]}; echo $sarakeno
echo ${mat[rivino]} | awk "{print \$sarakeno}"
- esimerkiksi:echo ${mat[2]} | awk "{print \$2}" tulostaa: 5.5e55

***

Pitää täysin paikkansa että BASH:in matriisioperaatiot ovat niin omituisia että niitä kannattaa välttää mikäli niitä joutuisi koodaamaan. Mutta silti ne toimivat ja mikäli toiminta tapahtuisi kirjastoista noudetuista funktioissa niin koodista saisi selväpiirteistä, koodi nopeutuisi suunnattomasti ja tietoturvaankin saataisiin jonkinlainen ote.

Tai itseasiassa ei siihen kirjastoja tarvita: funktion/funktioita voi toki lisätä koodiinsa alkuun. Pitäisi vain olla paikka josta niitä funktioita voisi kopioida.

Kirjastojen merkitys on helppo todeta: tee millä kielellä hyvänsä jotain monimutkaisempaa ilman kirjastoja.

***
BASH on surkimus. Suurin syy taitaa olla se että BASH on tehty opetustarkoituksiin ja siihen on tosiaan laitettu paljon sitä opetettavaa. Siis sitä ei ole tarkoitettu ohjelmointiin eikä se siihen kunnolla sovikaan.

Jokaisen toiminnan voi koodata lukemattomilla täysin erilaisilla tavoilla - ja vain muutama niistä on lähes virheetön. On liiankin helppoa saada aikaiseksi skriptejä jotka toimivat vain kun niille annetaan dataa josta ne pitävät.

Kirjastot korjaisivat sen että skriptintekijän täytyy oppia BASH:in salat. Ja kirjastothan toimisivat, niitä ei vain ole kirjoitettu - tämä ei pidä täysin paikkaansa sillä netistäkin löytyy aikamonta.

Tämä kirjastojen puute koskee meitä tavallisia tallaajia - roistoilla on kyllä kirjastot: ja katsopas mihin se on johtanut. Olisikohan aika käyttää skripejä puolustukseenkin?
 
Eikä BASH:ia pidä esittää negatiivisessa valossa sillä se olisi oman oksan sahaamista. Nimittäin BASH:ista ei tulla pääsemään eroon ihan pian. Ja jos BASH tuhotaan jossakin distrossa menee tuo distro perässä.

***

Yleensä funktiot ovat tyyppiä: function () { ... }. Silloin funktiolla ja pääohjelmalla on samat muistikuvat - ja kummassakin paikassa on muistikuvaan sekä luku- että kirjoitusoikeus. Jos funktiossa muutetaan muistikuvaa muuttuu se pääohjelmassakin. Parametreja ei siis tarvise palauttaa jos muuttaa parametrin muistikuvaa - joka on yleensä huomaamattoman pieni toimenpide. Funktiossa voi muuttaa niin monen muuttujan tai matriisin muistikuvaa kun haluaa.

Levylle tallettaminen ja levyltä lukeminen kannattaa toteuttaa muistikuvan avulla. Siinä on kahdet menetelmät:

Moitteettomat menetelmät jotka sopivat myös assosiatiivisille matriiseille, noille äsken kuvatuille kaksiulotteisille elikä kun matriisin jäsenet ovat matriiseja:
Koodia: [Valitse]
function matriisilevylle () { declare | grep ^$1= > $2 ;}
# sitä kutsutaan: matriisilevylle matriisin_nimi tiedoston nimi
# "declare -p" on kolmekertaa nopeampi mutta se ei toimi assosiatiivisten matriisien kanssa.   
   
function matriisilevyltä () { . "$1" ;}
# sitä kutsutaan: matriisilevyltä tiedoston_nimi

# siis nämä soveltuvat myös assosiatiivisen matriisin talletukseen ja -lukuun.
# Siinä yhteydessä tarpeellinen funktio:
function tulostamatriisi () { echo "arvo     osoite"; paste <(IFS= eval printf "%s\\\n"  "\${$1[@]}") <(IFS= eval printf "%s\\\n" "\${!$1[@]}") ;}

Nopeat ja likaiset menetelmät jotka sopivat parhaiten yksinrivisille numeromatriiseille silloin kun osoitteesta ei välitetä:
Koodia: [Valitse]
function matriisilevylle () { declare | grep ^$1= | tr ' '  '\n' | cut -d\" -f2 > $2 ;}
# sitä kutsutaan: matriisilevylle matriisin_nimi tiedoston nimi
   
function matriisilevyltä () { readarray $1 < $2 ;}
# sitä kutsutaan: matriisilevyltä matriisin_nimi tiedoston_nimi

- funktioita käytetään kirjoittamalla ne skriptin alkuun - jos ei halua sotkeutua kirjastoihin.
- funktionimissä voi käyttää ä:tä ja ö:tä.

***

Yksinkertaisen tekstijonon saa matriisiksi helposti: matriisi=($tekstirivi). Mutta muunnos muuttuukin todella monimutkaiseksi mikäli tekstijonossa on heittomerkein suojeltuja lauseita joissa on välilyöntejä - käytännössä niitä usein on, ei  tämä ole keksitty erikoistapaus. Koko ratkaisukoodi on tässä sillä funktiokutsukin kaipaa selittämistä:
Koodia: [Valitse]
#!/bin/bash
function tekstijonostamatriisiksi () {
teksti=${@:1:$#-1} # lähtöpään funktiokutsu jakaa tekstijonon välilyöntien kohdalta aina uudeksi parametriksi. Tässä funktioon tulleista parametreista kootaan se munnettava tekstijono takaisin yhtenäiseksi ottaen huomioon että viimeisenä parametrina on sen matriisin nimi johon muunnos suoritetaan.   

# tehdään apumatriisi niistä heittomerkkien välisistä tekstijonoista:
apumatriisi=($(echo " $teksti" | tr \" '\n' | sed 's/^ .*//g;s/.*/\"&\"/g;/\"\"/d;s/\"\\n\"/\" \"/g;s/ /$£/g'))

# korvataan löydettyjen heittomerkkien välissä olevien tekstijonojen välilyönnit $£-merkkiryhmällä:
for (( n=0; n<=${#apumatriisi[@]}; n++ )); do apustring1=${apumatriisi[n]}; apustring2=$( echo ${apumatriisi[n]} | sed 's/$£/ /g'); teksti=$( echo ${teksti//$apustring2/$apustring1}) ; done
 
# muodostetaan tullesta teksijonosta pyydetty matriisi - jossa siis on $£-merkkiryhmät heittomerkkien välisten tekstijonojen välilyöntien paikalla:
apu=("$teksti")

# muodostetaan muistikuva nimellä: ${@:$#} (joka on sen matrisin nimi joka olisi tarkoitus muodostaa) korvaten samalla välilöynneillä merkkiryhmät: $£ :
readarray ${@:$#} < <(printf "%s\n" ${apu[@]} | sed 's/$£/ /g')
}



# kutsuva pääohjelma
echo jokumatriisi:
unset jokumatriisi # varmistaa että mitä tulostuukin niin se tulee tässä kutsutusta funktiosta
teksti='0 "1 2" "2 3" 4 5 "6 7" "8 9" 10 "11 12 13 14 15 16" 17 18 19 "20 21 22 23 24"'
tekstijonostamatriisiksi $teksti jokumatriisi
printf "%s" "${jokumatriisi[@]}"                 # tuloste on sama olipa kyseessä matriisi tai muuttuja
echo matriisissa on jäseniä: ${#jokumatriisi[@]} # joten täytyy testata montako jäsentä siinä on.

echo; echo jokutoinenmatriisi:
teksti='"koira kylmällä kalliolla" "^ _ $ / #" 2 "kissa kuumalla katolla" "Åålannista Öölannin kautta Ääniselle"'
tekstijonostamatriisiksi $teksti jokutoinenmatriisi
printf "%s" "${jokutoinenmatriisi[@]}"
echo matriisissa on jäseniä: ${#jokutoinenmatriisi[@]}

echo; echo jokumatriisi on arvoiltaan edelleen sama:
printf "%s" "${jokumatriisi[@]}"
- varmaankin joku awk- ja regex-velho saisi samasta aikaiseksi nopeasti toimivan version.

***

muistikuvan manipulointia tapahtuu normaaleissakin skripteissä aina kun jollekin muuttujalle tehdään jotakin: olkoonpa kyse tavallisesta muuttujasta tai matriisista - niin pääohjelmassa kuin funktiossakin. Eikä siinäkään ole uutta että funktiossa siirretään muistikuva osoittamaan jotain toista muuttujaa sillä toiminto on tunnettu aina; toiminto vastaa täysin parametrin palauttamista. Kysymys on siitä ettei haluta puhua BASH:ista mitään positiivista.

Se merkitsee vähän että nopeus nousee huomattavasti sillä nopeus on edelleen kelvottoman huono. Vaan nimenomaan se merkitsee että voidaan käyttää totunnaisempia merkintätapoja - ne BASH:in kummalliset merkinnät ovat funktioissa ja teoriassa käyttäjien ei tarvitse tehdä funktioita vaan noutaa ne kirjastoista.

***

Päätteeseen tulostuu usein tekstiä monta sivua - joskus jopa kymmeniä. Mutta kaikki se on tallessa näyttöbufferissa. Näyttöbufferia voi selata rullahiirellä  tai jos hiiri on tavallinen niin oikean reunan "hissillä". Kun on selannut jonnekin historian hämäriin niin kun näpäyttää enter palataan heti sinne mistä lähdettiin. Voit siis käydä katsomassa mitä aikaisemmin tulostettiin.

***

Tässä yksi mitättömän pieni ja merkityksetön funktio mutta kun samantapaisia alkaa olla tarpeeksi monta niin merkitystä alkaa tulla. Kyseessä on funktio joka kääntää matriisin.

Ainahan näitä matriisin kääntöohjelmia on ollut ja jos haluaa pysyä BASH:issa niin esimerkiksi awk-toteutukset ovat paljon nopeampia - minkätakia tämmöistä tehdä? Osittain osoituksena että kyllä BASH osaa kaksiulotteisiakin matriiseja sekä tehdä että käsitellä. 

Matriisissa saa olla tekstiä tai numeroita mutta välilyönnit on tarkoitettu erottamaan alkioita eivätkä lainausmerkit auta tässä yksinkertaisessa toteutuksessa:
Koodia: [Valitse]
#!/bin/bash
function transpose () {
numberoffields=$(($( declare | grep ^$1= | cut -d\[ -f2 | tr -dc [[:space:]] | wc -c ) -1 ))
readarray $1 <  <(for (( n=1; n<=$numberoffields; n++ )); do
  echo $(printf "%s" "${array[@]}" | cut -f$n -d' ' )
done) # > testb # tämä muodostaa levytiedoston käännetystä kun ensimmäisen kommenttimerkin poistaa
}

# Pääohjelma toiminnan kokeilemiseksi:
readarray array < testa
echo "matriisi alunperin:"
printf "%s\n" "${array[@]}"
echo "matriisi käännettynä:"
transpose array
printf "%s" "${array[@]}" | column -t
echo

- siis tiedostoon nimeltä: testa kirjoitetaan esimerkiksi:
kissa ja koira ovat ikuisia vihollisia 0 1 2 3 4 5 6 7 8 9 20 11 12 13 14
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 62 63

niin skripti tulostaa ajettaessa:
kissa       21  42
ja          22  43
koira       23  44
ovat        24  45
ikuisia     25  46
vihollisia  26  47
0           27  48
1           28  49
2           29  50
3           30  51
4           31  52
5           32  53
6           33  54
7           34  55
8           35  56
9           36  57
20          37  58
11          38  59
12          39  60
13          40  62
14          41  63

- toki matriisi voidaan muodostaa BASH:issakin käyttämättä tiedostoa. Mutta ehkä näin on selvempää
- skripti kääntää matriisin samaan matriisiin missä se funktioon toimitettiinkin ja tulostaa sen samalla.
- koodilisäyksillä tulostumisjärjestyksiä voi muuttaa mielinmäärin, suorittaaa sorttaamista, matematikkaa ...
- tuo loppussa oleva: " | column -t " kutsuu automaattiformatointi ohjelmaa.
- BASH:in kehittäjän ratkaisu readarray:n toimintaan aiheutti kylläkin paljon:
1. Tulostusasultaan matriisit ovat melkein normaaleja. Sen aikaansaaminen edellytti varmaan melkoisia ponnistuksia eikä kai seuraavista pitäisi valittaa:
2. readarray lisää näkymättömiä merkkejä - muunmuassa rivinsiirron ja muutenkin se murjoo muistikuvaa.
3. BASH:in koko sparse-matrix-käsitteen tilalle tuli melkein sama "osoitteeton" kuin muissakin kielissä.

6
Olen pitkään ihmetellyt päätteeni historian temppuilemista: esimerkiksi duplikaatit eivät poistu vaikka käskee, ja jokakerran saa pikkuisen ihmetellä että mitähän sieltä nyt tulee.

Luin netistä muutaman kommentin joita en purematta niele mutta ajateltavaa ne antaa:
- historia tykkää kyttyrää joistakin ei-ascii merkeistä
- kun historia alkaa temppuilla niin ainoa pätevä keino on tyhjentää historiatiedosto.

Tein siis historiatiedostosta kopion ja tyhjensin vanhan. Ainakin duplikaatit alkoivat poistua ja jotenkin toimintaan tuli jämäkkyyttä.

Apuahan tässä täytyy pyytää.

***

Tämmöistä tein:
Koodia: [Valitse]
Vanhankin .bash_history:n sai käyttöön kun poisti sen duplikaatit ulkoisella käskyllä:
cat ~/.bash_history | tac | awk '!a[$0]++' | tac > ~/koe . Ja koe-tiedoston tutkimisen jälkeen talletin sen .bash_history:n tilalle.

Samalla kirjoitin .bashrc:hen:
export HISTSIZE=100000
export HISTFILESIZE=100000

HISTCONTROL=ignoredups:erasedups
shopt -s histappend
PROMPT_COMMAND="history -n; history -w; history -c; history -r; $PROMPT_COMMAND"

Mutta myöntää täytyy että liian syvillä vesillä liikutaan.

***

 Kukaan ei kykene tekemään yhtäkään skriptiä jossa ei olisi parantamisen varaa - ja usein parantamista on paljon sillä pitkät ja hitaaat skriptit supistuvat nopeiksi muutaman sanan yksirivisiksi - mutta kannattaako sen vaatima työ on kyseenalaista - eikä niiden kehittämiseen kenenkään omat rahkeet riitä. Joten puhumalla skripteistä mitähyvänsä puhuu ilmanmuuta paljon myös palturia.

***

Väite etteivät BASH:in funktiot kykene palauttamaan parametrejaan on sinällään tosi. Useimmilla asian merkitys on hakusessa, mutta virtuooseilta on tahallinen ja ilkeämielinen unohdus että BASH osaa tehdä tuosta parametrin palauttamisesta turhaa. Ikuisesti on tunnettu seuraava esimerkki:
a=b;let $a=55;echo b -> tulostaa 55
Funktiomuotoon sovellettuna tämä on:
Koodia: [Valitse]
function koe () { let $1=55 ;}; koe a; echo $a
joka myös tulostaa 55
- siis passataan nimiparametri.
- $1 eteen lisätään joku käsky, esimerkiksi let, read, readarray, test, eval ...
- let toimii vain numeroiden kanssa.
- readarray toimii vähän erilailla kuin muut:
Koodia: [Valitse]
function koe () { readarray $1 < /boot/grub/grub.cfg ;}; koe a; printf "%s\n" "${a[@]}"
Väite kyvyttömyydestä palauttaa parametrejä ontuu muutenkin vähäsen sillä kun joku kirjoittaa näytölle - ei ole väliä  mikä - niin toinen voi käydä lukemassa sen sieltä; toiminta on erittäin nopea eikä se jätä näytölle jälkeä siitä että sitä on käytetty. Tosin se toimii ongelmitta vain kun tulostusta on yksi rivi mutta se riittääkin usein. Esimerkiksi
Koodia: [Valitse]
function koe () { echo töttöröö ;}; a=$(koe); echo $a           # tulostaa: töttöröö
function koe () { cat /boot/grub/grub.cfg ;}; a=$(koe); echo $a # koko teksti menee yhdelle riville ja se on usein ongelmallista.
Siis BASH:in funktiot eivät palauta parametrejään automaattisesti vaan se pitää määrätä.
Ja aina voi funktiossakin kirjoittaa levylle ja pääohjelmassa käydä lukemassa sieltä. Keino on ihan ongelmaton ja se hidastaa toimintaa olemattoman vähän - ajatuskin on kyllä inhottava.

Nopeasti toimiva matriisin sorttausfunktio numero-arvoille
=====================================
- sort-ohjelma sorttaa mitä sille tulostetaan - on aivan sama tulostetaanko sille tiedosto vaiko matriisi.
- matriisia ei muodosteta uudestaan funktiossa, vaan pääohjelmassa käydään näytöltä lukemassa funktioon tulostus ja muodostetaan matriisi uudestaan senperusteella - sentakia kutsu on omituinen mutta toiminta on paljon nopeampaa.
- 1900 jäsenisen matriisin sorttauksessa kuluva aika on noin 60ms
 
#!/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.
Koodia: [Valitse]

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.

#mat=($(seq 10000;seq 10000))
mat=($(seq -5700 1.5 5700;seq -5700 1.0  5700)) # noin joka neljäs on duplikaatti ja niiden pitää löytyä
#echo matriisi sorttaamatta: ;echo ${mat[*]}
alkuhetki=$(date +%s.%N)
mat=($(sorttaamatriisi mat))
loppuhetki=$(date +%s.%N)
echo matriisi sortattuna: ; echo "${mat[*]}"
echo -n sorttaukseen kulunut aika sekuntia: ; echo $loppuhetki-$alkuhetki | bc
(( ${#mat[*]} > 1 )) && echo kysessä on tosiaan matriisi
# duplikaattien lukumäärä
singlikaatit=$(echo "${mat[*]}" |  tr ' ' '\n' | sort | uniq | wc -w)
kaikki=$(echo "${mat[*]}" | wc -w)
echo duplikaatteja $((2*($kaikki-$singlikaatit)))
export LC_ALL=
- BASH:in sorttausrutiinit syövät usein duplikaatteja ja testatessa on syytä varmistaa että duplikaatteja ei syödä - esimerkiksi laskemalla ne ja vertaamalla siihen paljonko niitä pitäisi olla.
- jos luulee työskentelevänsä matriisin kanssa niin kannattaa testata työskenteleekö matriisin vaiko samannimisen muuttujan kanssa - jossa matriisin koko sisältö on on yhtenä tekstijonona. 
- nimenomaan kannattaa huomioida ettei tässä funktiossa tarvita eval-käskyä.
- tämä kykenee sorttaamaan vain numeroita - ja yksinkertaista tekstiä.
- toki parametrin voisi "palauttaakin". Mutta aikaa se vain tuhraa.

***

Verkkosivuilla esitetystä BASH-koodista ei juuri koskaan skriptin kokeilemisen takia tarvitse tehdä skripti-tiedostoa eikä edes lausetta #!/bin/bash välttämättä tarvita. Riittää kun maalaat koodin kokonaisuudessaan ja painat ctrl-c. Siten avaa pääte painamalla ctrl-alt-t ja sitten paina ctrl-shift-v . Lopuksi kannattaa painaa enter. Näyttö ei ole ihan siisti mutta asia selviää kyllä.

***

BASH:illa oli aikoinaan kirjastot ja niitä alettiin oppia hyödyntämään. Mutta BASH:ista halutaan eroon eikä se passannut ja niinpä tietoturvaan vedoten BASH:in kirjasto-osoitin poistettiin. Se ei tee kirjastojen käyttämisestä mahdotonta mutta koska entiset kirjastoja hyödyntävät skriptit lakkasivat toimimasta niin koko kirjastoidea haudattiin.

Mikäli kieleltä viedään sen kirjastot niin kielestä tulee kelvoton - niinkuin BASH:ista tuli kun siltä vietiin kirjastot. Ja BASH:issa kirjastot ovat vielä tärkeämpiä kuin muissa kielissä sillä BASH:in toiminnoista monet ovat kammottavaa merkkisotkua jota ei saa kasattua kohtuullisella työllä mitään kevollista edes asiaan vihkiytynyt.

Silloin kun BASH:illa oli kirjastot se olikin kiistaton kunkku. Mutta monikin haluaa kunkuksi kunkun paikalle ja sehän ei onnistu mikäli vanha kunkku on edes jotenkin kelvollinen. Siispä tietoturvaan vedoten BASH:in kirjasto-osoitin poistettiin. Se ei tee kirjastojen käyttämisestä mahdotonta mutta koska entiset kirjastoja hyödyntävät skriptit lakkasivat toimimasta niin koko kirjastoidea haudattiin.

Mutta mikäli ohjelmointikielen kirjastoista ei löydy jotain ominaisuutta ja joutuu koodaamaan itse tuon ominaisuuden kestää koodaaminen joskus iäisyyksiä ja koodista tulee suurinpiirtein aina hyvin hidas virhegeneraattori. 

Kuitenkin maailmalla on vieläkin monia BASH-kirjastoja - joitain löytyy netistäkin. Mutta tällähetkellä tilanne on sellainen että jos haluat olla aikaansaapa skriptaaja niin sinun on tehtävä omat kirjastosi - sillä jos hyvästä koodista ei ole kirjastofunktiota rönsyilee sen käyttö ikuisesti.

Seuraava isku BASH:in tuhoamisessa oli eval-käskyn lainsuojattomaksi julistaminen. Siinäkin käytettiin tekosyytä että eval-käsky on tietoturvariski - ja onkin ilmanmuuta selvää että se on tieoturvariski sillä koko ATK on pelkkää tietoturvariskiä. Kirjastofunktioista valtaosa tarvitsee eval-käskyä.

Tosiasiahan on että BASH on ohjelmointiin sopimaton varsinkin kun toiset heittelee kapuloita rattaisiin. Muiden hämärähommien lisäksi huomasin omituisen ilmiön: skripti toimi yhtänopeasti surkealla tabletilla kuin tehokkaalla pöytäkoneella. Millätavoin BASH:in nopeutta rajoitetaan? Sillä rajoittamistahan on pakko harrastaa moniajoympäristössä tai muuten joku´omii kaikki resurssit - mutta BASH:in suhteen koko raudan parantuminen viedään ilmeisesti toisiin käyttöihin.

Pahan iskun BASH:ille ovat antaneet BASH:in kehittäjät itsekin jättämällä opettamatta sen että BASH-kieliset loopit hidastavat BASH:in toiminnan mateluksi.

***
 
Sensijaan että tekee skriptiin BASH-kielisiä looppeja tulisi käyttää BASH:in monessa käskyssä sisällä olevaa looppia - ja on käskyissä hyödynnettäväksi nopeita mariisejakin. Käskyjen sisäisten looppien hyödyntäminen nostaa BASH:in toiminnan nopeammaksi. Nämä esimerkit ovat työskentelyä matriisin kanssa - mutta toisaalta kaikki on matriisia. Muuten looppien hidastava vaikutus kertautuu jos niitä hölvätään jokapaikkaan.

Kokeile itse:
Koodia: [Valitse]
matriisi=({1..1000000}); time printf "${matriisi[*]}"                      # kestää 1.2s
matriisi=({1..1000000}); time for n in {1..1000000}; do printf $n' '; done # kestää 7.5s vaikka looppi on nopein mahdollinen - tuommoiseen looppiin ei voi laittaa muutujaa, mutta loopit jotka hyväksyvät muuttujan ovat parikin sekuntia hitaampia.


- nopeuden muutokset ovat sittenkin pieniä. Mutta koko skriptin osalta nämä pinet erot usein kertautuvat eivätkä summaudu.
- ei BASH:in kanssa pidä toimia kovin suurten matriisien kanssa mutta tämä on vain osoitus että nekin toimivat. Suurten matriisien sivuvaikutukset ovat myös suuria siinä päättessä jossa toimitaan. Ehkä raja on jossain välillä:  20.000 - 200.000. Sen näkee myös liitteistä.

***

Kun käsitellään tiedostoja se ei onnistu kovalevyllä vaan siitä täytyy ensin tehdä käskyn sisällä matriisi RAM:miin. Eihän silloin ole kovin odottamatonta ole että totunnaisesti vain tiedostojen käsittelyyn käytetyt käskyt toimivat matriisienkin kanssa? Esimerkiksi matriisin sorttaus onnistuu ihan hyvin mikäli matriisi tulostetaan sinne sort-rutiiniin:
Koodia: [Valitse]
matriisi=({1..1000000}); time echo "${matriisi[*]}" | tr ' ' '\n' | sort -nr    # kestää 2.5 sek
tai saman tekee "käänteinen cat" tässä erikoistapauksessa
matriisi=({1..1000000}); time echo "${matriisi[*]}" | tr ' ' '\n' | tac 

Sitkeässä elää harhaluulo että funktion parametrin palauttamisesta olisi jotain hyötyä. BASH:issa funktioon lähetetään muuttujan nimi - siis matriisinkin tapauksessa vain nimi. Funktio käsittelee muuttujan. Funktiosta ei tarvitse palauttaa mitään sillä muuttuja on muuttunut BASH:in kirjanpidossa.

Seuraava matrisin sorttausfunktio joka sopii pelkästään numeroille ja tekstille jossa ei ole välilyöntejä ei palauta parametreja sillä se olisi tarpeetonta:
Koodia: [Valitse]
function sorttaamatriisi () { readarray $1 < <(eval echo \${$1[*]} | tr ' ' '\n' | sort -nr) ;}

- 20.000-jäsenisen matrisin sorttaaminen kestää noin 150ms  - täysin uskomatonta BASH:ista.
- eval-käsky mahdollistaa funktion tekemisen - readarray vain yksinkertaistaa koodia.

7
Tämä sinänsä ei haittaa jos vain Canonical tuottaa niin hyvä Desktop ympäristön että sitä pystyy käyttämään pelaamiseen ja webbiselailuun.

Mutta foorumit ovat vastanneet kuihtumalla. Ja ilman foorumeita Ubuntu ei toimi; tai sanotaanko että "tunteen palo" foorumeila on niin suuri mainosarvoltaan että yksi miljardööri ei sitä kykene sitä korvaamaan.

8
Ubuntun kehitysversio / Vs: Ubuntu 17.04 Zesty Zapus
« : 17.03.17 - klo:19.11 »
Minulla ei toimi kunnolla vieläkään kun se kuukausi sitten alkoi temppuilemaan.

Nyt olisi sitten hyvä sauma tehdä puhdas asennus ja testata, miten toimii. Vielä ehtii raportoida bugeja ennen lopullisia jäädytyksiä ja julkaisua.

Sen lisäksi se laskee satunnaisluvut jotenkin puoli-satunnaisesti.

Millaisella koodilla?

Taas kertaalleen sain oppia siitä ettei viimeisen-päälle päivitetty vastaa uutta. Oli olevinaan kiire enkä asentanut uutta vaan päivitin vaan jatkuvasti. Uuden dailyn asentaminen korjasi kaiken. Oppisinpa minäkin joskus.

- se satunnaisluku oli awk:in rand() joka ilmeisesti oli vaan taantunut siihen aikaan kun satunnaisluvun siemen täytyi asettaa.

9
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%.

10
Ubuntun kehitysversio / Vs: Ubuntu 17.04 Zesty Zapus
« : 07.03.17 - klo:14.42 »
Minulla ei toimi kunnolla vieläkään kun se kuukausi sitten alkoi temppuilemaan. Vähänväliä ilmoittaa "järjestelmä on tehnyt virheen".  Ja kun kytken apportin pois päältä niin päivitys kytkee sen takaisin. Sen lisäksi se laskee satunnaisluvut jotenkin puoli-satunnaisesti. Ei mitään radikaalia siis, mutta sen se tekee että en luota Zestyyn enkä käytä sitä koskaan. Toivottavasti betat toimii tai ainakin valmis versio.

11
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. 

12
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.


 

 

13
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ä  ?


14
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[*]}

15
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



16
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.

17
Laitealue / Vs: Usb tikku toimimaan Ubuntussa
« : 16.12.16 - klo:04.23 »
En ole tehnyt mitään. Tökkänyt tikun porttiin vaan, ja tota se löytyi.

Olet luultavasti tehnyt jotakin. Elikä tökännyt asennustikun USB-portttiin asentamisen jälkeen; se tapahtuu aikalailla huomaamatta ja tarkoittamatta - ja usein koetetaan tallettaa asennustikulle jotakin koska asennustikulla on paljon käyttämätöntä tilaa.

Mitä tapahtuu: asennustikku on root:in omistuksessa. Kun tikku laitetaan ensimmäisen kerran koneeseen muodostetaan sille liitoskansio /media/käyttäjänimi ja tämä kansio luodaan root:in omistukseen. Kun irroitat tikun niin tuon kansion sisältö häviää, mutta koska irroittaja olet sinä ei tuo root:in oikeuksilla oleva kansio poistu eikä se poistu edes bootatessa joten tikkusi liitoskansio jää root:in omistukseen maailman tappiin asti ja olet tuomittu käyttämään sudoa tikun kanssa. Tai: irroita tikku - varmista että kaikki tikut ovat irti - ja anna käsky: sudo chown -R $USER:$USER /media. Älä koskaan laita asennustikkua USB-porttiin.

18
 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.




19
Skriptiajuri sai lisää käynnistysviestejä. Nyt niissä on muunmuassa:

Skriptiajuri käynnistyy. Tässä koneessa on BASH:in versio: 4.4.5(1)-release.  Versio 4.0 toimii jotenkin ja ylemmät versiot kunnolla
tämän skriptiajuri-version päiväys on (vvvv-kk-pp): 2016-12-20
- ja lukemattomia korjauksia, toiminnan järkeistämistä, ja roskaakin on poistettu runsaasti minkä takia koko laski paljon  ... ja esimerkiksi siirrot tapahtuvat nykyään toisin jne.

- käynnistysviestit saa näkyviin kun skriptiajurin käynnistyttyä rullaa näyttöä hiiren rullalla tai oikean reunan hissillä. Versiopäiväyksen näkee myös painamalla nappia f3.
- foorumilla on myös kerrottu mikä on siellä olevan skriptiajurin versiopäiväys.
**
Skriptiä tehdessä täytyy käyttää versiohallintaa. Jokaisen version tulosteet täytyy säilyttää ja niiden hoitoon täytyy olla tulosteidenhallinta. Ja tulosteidenhallinnastta täytyy olla linkki versiohallintaan.
- mutta mitä ominaisuuksia lisätäänkin niin perus-käyttöön se ei saa vaikuttaa huonontavasti. Mitään ei saa muuttaa.

Tulosteiden säilyttäminen ei yleensä vaadi paljoakaan kovalevytilaa koska normaalisti tulosteiden koko ei ole suuri eikä tilaa kulu paljoakaan vaikka tulosteita olisi tallessa lukemattomia. Mutta joskus skripti tulostaa gigabittejä niin monta etteivät ne mahdu mihinkään ja ennenkaikkea tallettaminen kestää iäisyyksiä. Mutta automaattinen tulosteiden poisto ei toimi, käsipelin se täytyy tehdä. Tulosteiden hallinnassakin sen voi tehdä esimerkiksi niin että kun skriptiä kehittää kokoajan ei poista mitään, mutta kun skripti toimii hyvin niin poistaa sen tulosteista melkein kaikki.

Laitoin foorumille version: 2016-12-20 .
**
Tein estot ettei mahdottomia valintoja edes näytetä evätkä ne myöskään toimi. Huomioipa että kansion dokumentointi on oikeastaan mielekästä; dokumentissa voidaan kertoa tuhannella rivillä mitä kaikkea kansiosta löytyy.
**
Sain lopultakin ratkaistua kuinka tiedostot ja kansiot siirretään - se on melkoinen ongelma silloin kun täytyy siirtää kymmeniä tiedostoja ja kansioita samallakertaa mikäli halutaan arkistojen siirtyvän myös.
- ja normaali ilmiö: kun ratkaisu on valmis on se niin selväpiirteinen että muut keinot tuntuvat ihan idioottimaisilta.
**
Skriptiajuri toimii silloinkin kun ei saa bootattua graafiseen tilaan. Mutta ilmeisesti Gedit ei toimi - MidnightCommanderia voi kylläkin ajaa silloinkin. Ilmeisesti opiskelun paikka.
**
Pari viikkoa olen nyt tehnyt skriptiä joka osoittaa taas kertaalleen että bash esitetään paljon huonompana kuin se todellisuudessa on. Tehtävä on iso, tällähetkellä skriptistä on jo 600 versiota ja se toimiikin jo kuin unelma. Versioiden suuri määrä johtuu siitä että skriptiajuri on hyvä.

Esimerkiksi kun aamuisin herään ja boottaan koneeni niin skripti ilmestyy ruudulle heti kun skriptiajurin käynnistää. Ja kun muuttaa skriptiä tarkoituksella että taas paranisi niin yleensä jokin menee pieleen. Jolloin se vanha versio palautetaan. Palautuksia on tullut tehtyä jo 500 sillä palauttaminen on nopeaa.

Tai kun työnalla on lukemattomia muitakin skriptejä niin siirryäni editoimaan jotakuta  voinkin idean iskiessä keskenkaiken palata nopeasti tuohon isoon. Tai mihintahansa viimeaikoina editoituun skriptiin pääsee kahden napin painalluksella.
**
Skriptiajuri on tehnyt vallankumouksen sellaisten skriptien tekemisessä jotka maailman parhaat virtuoosit sanovat mahdottomiksi. Mutta täydellistä skriptiä ei kannata yrittää tehdä heti vaan kehityksen aikana valmistuu tuhat versioita ja koe-ajoja niille useampi tuhat. Versioita on alkuaikoina kyennyt tekemään päivässä kymmenen, nyt viisisataa.

Esimerkiksi monessa skriptissä on kehityksensä aikana ollut monta vanhojen uskomusten mukaan mahdotonta ominaisuutta. Jokaisen ominaisuuden kehittämisen aikana on välillä tehnyt kymmeniä versioita kunnes on tajunnut että jossain vaiheessa on mennyt vikaan. Milloin tämä vikaan meneminen alkoi ja mihin versioon tulisi pakittaa ja alkaa siitä lähtien uudestaan? Skriptiajurikaan ei osaa kertoa sitä, mutta koska sillä on kaikki vanha muistissaan niin voi kokeilla koska toimi sentään jotenkin.

- semmoista asiaa ei tunnu olevankaan kuin "ei voi toimia" vaan kaikki onnistuu jotenkin. Ja nimenomaan tulee kokoajan muistaa että bash on erilainen kuin muut kielet.
- esimerkiksi yleisesti käytetään bash:iin kiinteästi kuuluvista käskyistä alle yhtä prosenttia. Tämä johtaa siihen että 99%:iin käskyistä löytyy vain man-sivu, mutta niistähän ei saa selvää ennenkuin asia on muutenkin selvä sillä niissä on harvoin esimerkkejä. Eikä niitä esimerkkejä maailmaltakaan löydä. Eikä edes käskyjen nimiä löydä helposti; tai pikemminkin kun löytää niin löytää samanaikaisesti niin monta että masentuu. Ja myös nämä 99% ovat virtuoosien tekemiä ohjelmia joita ei käytetä koska niiden toiminta on vaikeasti tajuttavissa - nähtävästi virtuooseillekin.

20
Ubuntun kehitysversio / Vs: Ubuntu 17.04 Zesty Zapus
« : 06.12.16 - klo:13.47 »
Käytitkö samaa imagea/versiota asennuksissa? Ongelmasi vaikuttaa siltä, että imagessa oli vikaa, tai sitten vain ihan perinteisesti näppäimistöasettelu oli erilainen asennuksen aikana salasanaa määritettäessä kuin kirjautuessasi asennettuun järjestelmään.

Kyllä käytin ja kaikki muukin toiminta on ollut aivan samanlaista. Ja salasana on muuten jokakerran ollut oikea - kaikissa kokeili että hylkääkö se väärän salasanan ja niin se teki. Oikea salasana taas johti siihen että ruutu musteni välittömästi eikä pitkänkään odotusajan jälkeen näyttänyt mitään.
**
22.12  päivitys lopetti toimintansa heti kun olisi pitänyt alkaa asentaa paketteja. Päivitin sitten Synapticilla. Se päivittikin, mutta antoi ilmoituksen:
cannot open /etc/aptapt.conf.d/99synaptic to write APT::Install-Recommends
- asiasta on tehty useampiakin vikailmoituksia. Yhdessä oli huomautus: Probably somebody forgot to insert slash between /etc/apt and apt.conf.d

Sivuja: [1] 2 3 ... 30