Tuo tr ... on kyllä nopea ja elegantti, mutta en saa sitä käsittelemään ääkkösiä.
- erikoismerkkit ja välilyönnit hoituvat kun muuttaa tuon [:alnum:]:in [:print:]:iksi, mutta ääkkösiä sitä ei saa käsittelemään mitenkään.
- hieman tutkittuani asiaa se johtuu jostakin LC_TYPE jutuista, mutta kuulemma se on vielä pahempi kuin IFS ja jos kokeilee sillä saa järjestelmänsä helposti sekaisin.
Sensijaan tuo
for ((i=0; i<${#a};i++)); do [[ "$vm" != "${a:i:1}" ]] && echo -n "${a:i:1}";vm="${a:i:1}"; done;echo
käsittelee kaikkia on vielä nopeakin - riippuen tekstistä joskus nopeampi ja joskus hitaampi. Mutta ei kumpikaan sed:ille pärjää varsinkin kun sed toimii semmoisenaan tiedostoillekin nopeudenkaan kärsimättä.
Tuota nopeuden mittausta ihmettelen kaikkienkin skriptien kanssa; skriptiajuri nimittäin lisää aina ilmanmuuta kaikkiin skripteihin time-käskyn ja niin sen tuloksiin kiinnittääkin huomiotaan paremmin - kun määrää käsin time:n skriptin ajokäskyn eteen niin se antaa kyllä samat tulokset mutta sitä ei tule käytettyä aina. Tulokset saattavat vaihdella paljonkin kun nopeus on pieni,
esimerkiksi tulos voi vaihdella välillä 5-15 ms. Ja ajettaessa skripti päätteessä ensimmäisen keran on tulos usein paljon hitaampi kuin toistot - ihankuin tulkki oppisi tai jotakin olisi cacheissa ja joskus cachet nollautuisivat omia aikojaan.
- toki niin että sadasta mittauksesta 90 on 5-8 ms ja vain muutama on 12-15 ms.
- muuten ainoastaan tuo sed-versio osaa käsitellä sanan edessä olevia välilyöntejä.
**
Vastaan tuli sellainen ongelma että matriisiin perään piti liittää toinen matriisi poistaen samalla duplikaatti-rivit. Ratkaisuksi tuli:
#!/bin/bash
# matriisien yhteenliittäminen poistaen duplikaatit petteriIII 4.2.2015
# matriisien määrittelytapa on normaali ja merkkivalikoima on melkein rajoittamaton. Muuten tässä skriptissä on vain kaksi riviä asiaa
# ja loput ovat vain oikeellisuuden tarkastelua.
a[0]="kukkivat/' kummut" # matriisien arvot annetaan näin jotta alkuasetelma on varmasti yksiselitteinen. Erikoismerkit hyväksytään kun edessä on keno.
a[1]=3
a[2]="pähkäilyä komentorivi-komennoista!"
a[3]=5$
b[0]=1
b[2]="kukkivat/' kummut"
b[1]=2.57e9
b[2]=3
b[3]="pähkäilyä komentorivi-komennoista!"
b[4]=4
c=("${a[@]}"); c+=("${b[@]}") # matriisien yhteenliittäminen tapahtuu selväpiirteisimmin lisäämällä matriisi kerrallaan.
c=("$(echo -e "${c[@]/%/\\n}" | awk 'x[$0]++ == 0' )") # duplikaattien poistaminen
echo "${c[0]}" # tulostetaan yhteen-nivottu matriisi c tällätavalla ettei jää pienintäkään epäilystä että jokin toimii omituisesti
echo "${c[1]}"
echo "${c[2]}"
echo "${c[3]}"
echo "${c[4]}"
echo "${c[5]}"
echo "${c[6]}"
echo "${c[7]}"
**
Tein skriptin nimeltään: tagien välisen tekstin etsiminen tiedostosta . Sen nopeus perustuu awk:iin ja saattaa muut kielet häpeään - ei sikäli että se olisi edes yhtä nopea kuin muut vaaan siksi että se on jo kymmeniä vuosia maannut haudassa ja vieläkin se täytyy ottaa huomioon. Seuraavan haku-skriptin hitaus johtuu melkein kokonaan tiedoston lukemisesta minkä kesto- " aika lienee melko sama kaikilla kielillä. Sed puolestaan tarjoaa helpon muuteltavuuden - seuraavat ominaisuudet saa näillä muutamilla lisillä:
Teksti voi olla yhdessä pötkössä tai siinä saa olla rivinsiirtoja kaikkialla muualla paitsi etsittävissä sano:issa. Siten voidaan etsiä vaikka wget:in tulosteesta jos vain saadaan etsitävät sana:t määriteltyä.
Mikäli teksti on riveissä niin haku-sanait saavat olla samalla tai eri riveillä ja ensimmäisen sana:n edessä saa olla mitävain tai ei mitään samoinkuin toisen sana:n perässä saa olla mitävain tai ei mitään.
Tekstissä saa olla myös rivejä jotka eivät ole haku-sanojen välissä eivätkä ne sekoita olipa niitä kuinka monta vaan ja olipa niillä mitähyvänsä, vaikka tyhjää.
Sekään ei ole virhetilanne että tiedostossa ei ole etsittäviä sanoja ollenkaan.
Tulosteessa on löydettyjen rivien välissä tyhjä rivi. Mikäli samassa löydöksessä on useampi rivi niin niiden välissä ei ole tyhjää riviä jotta näkisi mitkä rivit kuuluvat yhteen. Samassa etsinnässä ryhmiissä riviluku voi siis vaihdella.
Ja ennenkaikkea: merkkivalikoimaa ei ole mitenkään rajoitettu.
Muuten kaikki väittää, ettei awk tunne BASH:in muuttujie ellei niitä sille erikseen esitellä. Mutta hyvin näyttää onnistuvan.
#!/bin/bash
function haku () {
# mikäli tyhjien rivien tulostuminen halutaan estää lisätään "löytöloitsujen" perään: | awk 'NF'
[[ $tag = [KkYy] ]] && < $tiedosto sed "s/$tag1/\n$tag1/g" | awk "/$tag1/,/$tag2/" | sed "s/.*$tag1//g" | sed "s/$tag2.*/\n/g" ||
< $tiedosto sed "s/$tag1/\n$tag1/g" | awk "/$tag1/,/$tag2/" | sed "s/.*$tag1/$tag1/g" | sed "s/$tag2.*/$tag2\n/g" ; }
[[ -f /tmp/haku ]] && read tiedosto tag1 tag2 tag <<< $(cat /tmp/haku)
read -ep "minkänimisestä tiedostosta etsitään: " -i " $tiedosto" tiedosto
read -ep "mistä sanasta haku alkaa: " -i " $tag1" tag1
read -ep "mihin sanaan haku loppuu: " -i " $tag2" tag2
read -ep "poistetaanko hakusanat tulosteesta: " -i " $tag" tag
echo $tiedosto $tag1 $tag2 $tag > /tmp/haku
echo
haku
**
Aloinpa tehdä testinkäsittely-funktioita. käytännössä senjälkeen kun nuo funktiot ovat koneessasi tuntee koneesi lisää käskyjä, esimerkiksi:
1. Sen paikan kertominen jossa teksti-palanen tekstirivillä on. Kutsumuoto:
etsi_paikka "kolmekymmentä merkkiä edessä_koti_perässämitävaan" koti
2. Tekstirivillä olevien reaalilukujen|kirjain-jaksojen|erikoismerkki-joukkojen|ip-osoitteiden ... irroittaminen. Kutsumuoto esimerkiksi:
etsi_luvut "mvkjngöejgn-2.2e-3 .33lfmbäamg"
- kirjoittaa luvut näytölle ja myös asettaa ne matriisin peräkkäisten jäsenien arvoiksi.
- lukujen välissä tulee olla jotakin - välilyöntikin kelpaa.
- integerit, desimaaliluvut ja järjestysluvut kelpaavat myös.
3. tekstirivillä rajoittimien välissä oleva tekstin tulostaminen. Kutsumuoto:
etsi_tekstit_tagien_välissä tag1 tag2 tiedostopolku
- siis se käy läpi koko tiedoston ja antaa joukon tuloksia.
- kirjoittaa tekstit näytölle ja myös asettaa ne matriisin peräkkäisten jäsenien arvoiksi.
- tagit saavat olla samalla tai eri riveillä eikä se vaikuta tulostukseen.
- merkkivalikoimaa ei rajoiteta, jopa kova lainausmerkki kelpaa vaikka yksikseen.
- etsittävä voi olla joko riveissä tai yhtenä pötkönä, joten vaikka wget-tulosteesta voi etsiä, sillä samalla rivillä voi olla monta tag-paria.
**
Yksi tärkeä ohjelma joka mielestäni on BASAH:ista puuttunut on sellainen haku jossa etsittävää ei täydy määrätä tarkasti vaan esimerkiksi sallien hakusanassa kahden merkin eron. Luuloa se oli, sillä kyllä BASH:issa sellainen on. Nimeltään se on agrep ja Ubuntussa se kuulemma jopa toimii.
**
Vihdoinkin sain selvityksen time-komennon "real/user/sys"-arvoista . Yksinkertaisessa koneessa: real on kokonaisaika, user on aika joka on vietetty user-prosesseissa ja sys on aika joka on vietetty sys-prosesseissa joten silloin: real=user+sys+"aika joka on kulunut odotellessa", noin suurinpiirtein. Mutta semmoista kuin "yksinkertainen kone" ei olekaan, varsinkin nykyään multi-prosessointi sotkee pahasti, sillä sys-ajaksi lasketaan kaikissa säikeissä kuluneiden aikojen summa. Siten sys saattaa olla ajoista suurinkin.
- aikojen suhteet kertovat aina vaan vähemmän siitä kannattaako skriptiä yrittää nopeuttaa.
**
Pähkäilin matriisien erilaisuuden tulostavaa skriptiä ja etsiessäni netistä vihjeitä parhaista tavoista kohtasinkin näpsäkän awk-skriptin ja ihmettelin taas kertaalleen senjälkeen että kun tehtävälle on jo löytynyt hyvä ratkaisu esitetään paljon huonompia eikä ole opittu mitään. Mutta toisaalta awk-skriptejä ei oikein käsitä joten tein BASH-toteutuksen jossa on se hyvä puoli että sen toiminnan käsittää kukahyvänsä ja kun siinä ole looppia on se melko kelvollinenkin:
diff <(echo -e ${matriisi1[@]/%/\\n} | awk '{$1=$1} !x[$0]++' | sort) <(echo -e ${matriisi2[@]/%/\\n} | awk '{$1=$1} !x[$0]++' | sort) | grep [\<\>] | tr -d [\<\>] | awk '{$1=$1}1'
- awk '{$1=$1} !x[$0]++' poistaa etu-välilyönnit ja toistot
Mutta itse asia on se, että jos BASH-skriptissä on looppi-käskyjä on se hidas ja alkeellisesti tehty, sillä BASH käsittelee aina joukkoja joten looppi on sijoitettu jokaisen käskyyn sisälle eikä sitä tarvitse itse erikseen määrätä. Varsinkin awk, sed, grep ja xargs ovat varsinaisia joukonkäsittelijöitä ja niissä on sisäiset loopit.
- no ei se looppien käyttäminen täysin kelvotonta ole sillä looppeja käyttävien skriptitien toiminta on helpompi ymmärtää. On elämäntehtävä oppia skriptaamaan ilman looppeja, minä itse en ainakaan ole oppinut.
**
automaatit olivat tehneet tosisuuren tiedoston, jossa monet rivit ja kappaleet olivat monta kertaa eikä ihminen saanut asioista enää mitään selvää. Duplikaattirivit oli helppo poistaa niinkin että kappaleiden väliin jäi yksi tyhjä rivi. Mutta tämä kasasi tuhansia näennäisesti tyhjiä rivejä tiedoston loppuun; näennäisesti tyhjiä rivejä sillä riveillä saattoi olla TAB:beja, kontrollimerkkejä ja mitä lie. Mutta kyllä siihenkin ratkaisu löytyi: koko toiminta kuvattuna:
cat ohje | awk '!NF || !x[$0]++' | awk '!NF {if (++n <= 2) print; next}; {n=0;print}'
- misiköhän noiden awk-lauseiden nopeus täytyisi laittaa ? Kun ne vievät aikaa tässä tapauksessa -200 millisekuntia.
- yleensäkin skriptien ajankäyttö tuntuu kummalliselta: mitatut ajat vaihtelevat ihmeellisesti ja suuremmat kokonaisuudet tuntuvat toimivan huomattavasti nopeammin kuin osiensa summa. Mutta toisaalta BASH esimerkiksi jakaa prosessorikuormaa eri ytimien kesken ilmanmuuta.
**
BASH:issa on huomattava määrä valmiiksi määriteltyjä funktioita. Selvitin funktion in_array kutsun:
a=(1 "2 3" 4 5); in_array "2 3" "${a[@]}" && echo joo || echo ei
Maailmalta löytyykin samanlaisia toteutuksia ziljoona, mutta itseasiassa skriptissä in_array on lukemattomia puutteita ja päälle päätteeksi looppi. Esimerkiksi seuraava karkea loopiton skripti olisi parempi:
matriisi=(1 glmnämthn 87 2 82 7 89 asd 81); etsittava=8[0-9]; echo -n 'löydöt muodossa: alkio/mitä_siellä_on: '; echo -e ${matriisi[@]/%/\\n} | grep -no $etsittava | xargs echo | tr : /
**
Perättäisten merkkien poistamiseksi tekstistä käsky: tr -s [[:print:]] <<< "$a" ei kelpaa, sillä vaikka se on tehokas niin se ei toimi skandien tai välilyöntien suhteen ja erikoismerkit saavat sen ihan sekaisin. Kaikki muu toimii kyllä käskyllä: cat tiedosto | tr -s [[:print:]] paitsi skandien kanssa se ei toimi sittenkään.
Toimiva toteutus on sed-skripti:
sed 's/\(.\)\1\+/\1/g' ohje
- se on huomattavast hitaampi kuin: tr -s [[:print:]] , vaan kaikki kelpaa: välilyönnit, erikoismerkit, skandit ....
- echo:a pehmeiden lainausmerkkien kanssa, printf:ää tai <<< ei voi käyttää mikäli tekstissä on erikoismerkkejä - tai toimivathan ne kun jokaisen erikoismerkin eteen kirjoitetaan keno.
Kyllä echo:takin voi mikäli lauseessa ei ole erikoismerkkejä.
**
- googlen regex-haku toimii ainakin jotenkin. Monessa muussakin haussa olen käyttänyt sitä mutta esimerkiksi haku: bash awk \$0=
löysi myös mitä pitikin.
**
itseopiskelu johtaa mielipuolisiin tilanteisiin. Olenko vielä unesta sekaisin kun luulen etten ole tämmöistä tavannut: kun näppäilen koneeseenii: a=echo; $a c niin se tulostaa: c
Edelleenkin olen sitä mieltä että tästä ei ole puhuttu missään. Vaikenemisella saattaa olla joku syykin, esimerkiksi se että tuommoinen johtaa erittäin tehokkaisiin itseään muuttaviin ohjelmiin jotka herraties miksi ovat täydellisessä pannassa.
Mutta nyt kun käyttäytyminen selvisi niin selityskin löytyi: BASH-skriptissä voi olla muuttuja missävaan. Kun tulkki kohtaa rakenteen: $muuttuja niin se korvaa rakenteen välittömästi sen arvolla ennenkuin tekee mitään muuta.
Nopeasti pieni esimerkki:
#!/bin/bash
function tehdään_mitä_määrätään () {
$@
}
tehdään_mitä_määrätään ls
read -p "paina return saadaksesi seuraavan tulosteen"
tehdään_mitä_määrätään ls -l
read -p "paina return saadaksesi seuraavan tulosteen"
tehdään_mitä_määrätään sudo blkid | awk '{print $1}' | tr -d : | sed 's,/dev/,,g' # tulosta kovalevyn tunnetut osiot