Onneksi vanhoja käskyjä ei ole tuhottu - luulisin että haluja on ollut mutta kykyjä ei. Jokatapauksessa ne ovat seuraavankaltaisia tekstijonojen käsittelyssä ( mutta niitä on siis 'miljoonia' ja tässä vain pieni näyte):
merkkijono=123; echo "${merkkijono:mistä:montako merkkiä} # määrät voi ilmoittaa numeroina tai muuttujina
merkkijono=123; echo "${merkkijono:: -montakolopustapoistetaan}" # tulostaa: 12 (tai:echo "${merkkijono%?}"
merkkijono=123; echo "${merkkijono%3}" # ainoastaan perässä oleva 3 poistetaan mutta mikätahansa muu saa jäädä
merkkijono=12343; echo "${merkkijono%?43}" # tulostaa: 12 (tai:echo "${merkkijono%[0-9]43}")
merkkijono=123; echo "${merkkijono: -montakolopusta tulostetaan}"
merkkijono=123; echo "${merkkijono:0:montakoalusta tulostetaan}" # eli: "${merkkijono:monennestako aloitetaan:kuinka monta}"
merkkijono=123; echo "${merkkijono:montakoalusta poistetaan}"
merkkijono=xabyabz; echo ${merkkijono/ab/acc} # tulostaa: xaccyabz -> korvaa vain kerran alusta - korvaava ja korvattava voivat olla eripitkiä
merkkijono=xabyabz; echo ${merkkijono//ab/acc} # tulostaa: xaccyaccz -> korvaa kaikki
merkkijono='9 8 7 5 4'; echo ${merkkijono//8*5/} # tulostaa: 9 4 -> tässä * on jokerimerkki, eikä kertomerkki
merkkijono='9404'; echo ${merkkijono//$((8*5))/} # tulostaa: 94 -> nyt * on kertomerkki
merkkijono='9 8 7 5 4'; echo ${merkkijono%% *} # tulostaa: 9
merkkijono='9 8 7 5 4'; echo ${merkkijono% *} # tulostaa: 9 8 7 5 ja echo ${merkkijono% * *} tulostaa: 8 7 5
merkkijono='9 8 7 5 4'; echo ${merkkijono##* } # tulostaa: 4
merkkijono='9 8 7 5 4'; echo ${merkkijono#* } # tulostaa: 8 7 5 4
merkkijono='987km/t'; echo ${merkkijono#*[!0-9]} # tulostaa: 987 elikä ensimmäiseen ei-numeroon asti joten sama toimii millä kielellä vaan.
merkkijono=$( skripti joka tulostaa monta riviä); echo ${merkkijono##*[[:cntrl:]]} # tulostaa viimeisen rivin.
merkkijono='9 8 7 5 4'; echo ${merkkijono% * *} # tulostaa: 9 8 7 . Tämmöisiä koottuja voi muodostaa ohjelmallisestikin
merkkijono='9 8 7 5 4'; a=' '; echo ${merkkijono%%$a*} # tulostaa 9 - elikä jako voidaan tehdä myös muuttujan arvon avulla
- jakopisteen merkkien lista on esimerkiksi [- .:/] ja mikätahansa niistä kelpaa -> merkkijono='9:8:7:5:4'; echo ${merkkijono%%[- .:/]*}
- esimerkki jossa jakopisteenä on sana:
jakavasana=next; merkkijono='9next8next7next5next4'; [[ "$merkkijono" =~ "$jakavasana" ]] && echo ${merkkijono%%"$jakavasana"*} # tulostaa: 9
Ja vaikka merkintä on pitkä on se edelleen nopea.
- luulisinpa että se 'vallankumouksellisen hyvä' idea siitä että jokainen käsky käy läpi kymmen laajennosta ennen tulkkausta onkin vanhojen käskyjen perua. Tosin niille laajennus-sääntöjen täytynee olla aivan toisenlaisia - harmittaa kun tietää että niiden sääntöjen täytyy olla jossakin - mutta tuskin sellaisen asian sääntöjä löytää josta ei juuri koskaan puhuta. Mutta jonkun pöytälaatikossa ne on.
---
Joten tästä ryhmästä voidaan muodostaa seuraavat funktiot (siis määritellä uudet käskyt):
otaeka () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono%%$erottaja*} ;}
# kutsuesimerkki: otaeka '9 8 7 5 4' tai: otaeka '9 8 7 5 4' erottavasana
#
# siis mikähyvänsä muuttujan:erota merkeistä voi toimi erottajana.
# muuttuja:erottavasana sensijaan voi olla tosiaan sana. Sitä ei tarvise kutsussa olla mutta jos se on niin sitä käytetään muutta jos ei ole niin sitten käytetään merkkejä.
# rakenne:x if-then-else vanhentui - vastaava merkintä nykyään on tuo tässäkin oleva: [[ $2 ]] && 'joko hypi' || 'tai tärise'. Ja koko ehto on aina samalla rivillä.
## on kommentin lisä-selvennys. Joten ### on seli-seli-seli
## [[ $2 ]] on se if - elikä rakenne [ $2 ] vaheni myös. Se on ehto: onko-olemassa - mikähyvänsä teksti on jotakin joten myös merkki 0 on jotakin - ainoastaan '' on ei-mitään
## (( $2 )) olisi matemaattinen if: onko matemaatista arvoa vai onko se 0. Vain numeroita voi testata näin.
## && on then ja || on else
function otavika () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono##*$erottaja} ;}
function poistaeka () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono#*$erottaja} ;}
function poistavika () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono%$erottaja*} ;}
# kutsuesimerkki ja muut kommentit näissä kolmessa ovat samat kuin otaeka:ssa. Nämä yksittäiset käskyt ovat tosi-nopeita.
----
Ja näistä saa kasattua käskyn poimijoku:
otaeka () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono%%$erottaja*} ;}
poistaeka () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono#*$erottaja} ;}
poimijoku () { merkkijono=$1; apu=$1; for (( n=1; n<=$(($2-1)); n++ )); do apu=$(poistaeka "$apu"); done; otaeka "$apu" ;}
Hidashan tämmöinen poimijoku on ja turhan monimutkainenkin mutta saihan sen tekemisestä oppiakin. Kymmenenkertaa nopeampi ja yksinkertaisempi on:
poimijoku () { apu=($1); unset IFS; echo ${apu[$2-1]}; unset IFS ;} # IFS kertoo mitkä merkit tulkitaan merkitsevän sananväliä - joka oletuksena on välilyönti.Se on aina joku yksi merkki annetusta listasta - siis tavallaan kokeillaan kunnes joku kelpaa. Esimerkkikutsu:
poimijoku () { IFS='-.:/¤ ' ; apu=($1); unset IFS; echo ${apu[$2-1]} ;}; time poimijoku '987654321-87654321 7654321.54321:4321/321&21¤1' 7 # tulostaa 1
mutta IFS on sikäli huono ettei se voi olla sana - ja toisekseenkin leikit IFS:n kanssa menevät joskus pahasti pieleen kun unohtaa lopusta: unset IFS:n. Seuraava hyväksyy erotusmerkiksi sanan - mutta se on hidas:
poimijoku () { apu=$(echo ${1//$2/' '}); apu=($apu); echo ${apu[$3-1]} ;}
# esimerkkikutsu:
poimijoku '987654321next87654321next7654321next54321next4321' poimijoku next 3
# apu=($apu) ei ole väkivaltaa sillä jokainen BASH:in tavallinen muuttuja on samalla myös matriisi joka on määritelty näin.
# kohdassa: echo ${apu[$3-1]} lasku: $3-1 on merkinnältään normaali eikä $(($3-1)) niinkuin BASH:issa muuten -> sulut tämän aiheuttavat.
---
Myös matriisien käsittelyyn on 'miljoona' vanhaa käskyä. Kaikki vanhat käskyt ovat rakenteeltaan merkki-sotkua ja kestää hieman ennenkuin oppii mikä sotku viittaa mihinkin. Käskyjä on niin paljon ettei niiden kaikkien täsmällistä muotoa kukaan voi muistaa vaan skriptatessa aikaa kuluu paljon taulukoiden selaamiseen editorissa - sillä merkki-sotkujen googlaaminen on vaikeaa mutta editorien ctr-f toimii loistavasti - ja BASH:in varsinaiset hakuun tarkoitetut työkalut ovat aivan omaa luokkaansa.
Esimerkiksi hyvin pieni otos matriisien käsittely-käskyistä:
a=(alpha beta gamma); echo ${#a[@]} # tulostaa: numeron 3 (matriisin jäsenien lukumäärä. tyhjät rivit pois: echo -e ${mat[*]/%/\\n} | sed -e '/^$/d' | wc -l
a=(alpha beta gamma); echo ${#a[0]} # tulostaa: numeron 5 (matriisin ensimmäisen jäsenen pituus)
a=(alpha beta gamma); echo ${#a[1]} # tulostaa: numeron 4 (matriisin toisen jäsenen pituus)
a=(k kkk kk); echo -e ${a[@]/%/\\n} | wc -L # tulostaa: 4 -> matriisin pisimmän jäsenen pituus+1 -> 4 sillä matriisin jokaisen sanan edessä on IFS
a=(alpha beta gamma); echo ${a[1]:2} # tulostaa: ta
a=(alpha beta gamma); echo "${a[@]#a}" # tulostaa: lpha beta gamma
a=(alpha beta gamma); echo "${a[@]%a}" # tulostaa: alph bet gamm
a=(alpha beta gamma); echo "${a[@]//a/f}" # tulostaa: flphf betf gfmm
a=(alpha beta gamma); echo "${a[@]/#a/f}" # tulostaa: flpha beta gamma
a=(alpha beta gamma); echo "${a[@]/%a/f}" # tulostaa: alphf betf gammf
a=(alpha beta gamma); echo "${a[@]/#/a}" # tulostaa: aalpha abeta agamma
a=(alpha beta gamma); echo "${a[@]/%/a}" # tulostaa: alphaa betaa gammaa
a=(alpha beta gamma); echo "${a[@]/#/oho}" # tulostaa: ohoalpha ohobeta ohogamma
a=(alpha beta gamma); echo "${a[@]/%/oho}" # tulostaa: alphaoho betaoho gammaoho
a=(alpha beta gamma); echo -e "${a[@]/#a/}" # tulostaa: lpha beta gamma
a=(aaa1 bbb2 ccc3); echo ${a[*]//[0-9]*/5} # tulostaa: aaa5 bbb5 ccc5
a=(1 2 3 4 5 6); echo ${a[$((${#a[@]}-1))]} # tuostaa: 6 joka on matriisin viimeinen rivi - siis tavallaan matriisin: tail -1
a=({1..99999}); time printf "%s\n" "${a[@]}" # tulostaa matriisin kaikki-jäsenet kukin omalle rivilleen. printf:n formaatilla saat aikaan ihmeitä.
a=({1..99999}); echo -e ${a[*]/#/\\n} # tulostaa matriisin jäsenet kukin omalle rivilleen - melkein sama kuin edellinen
a=(alpha beta gamma); echo ${a[*]} # tulostaa alpha beta gamma yhteen riviin
a=(alpha beta gamma); echo ${a[*]:1} # tulostaa jäsenestä 1 alkaen: beta gamma
a=(alpha beta gamma); echo ${a[*]:1:1} # tulostaa jäsenen 1: beta
a=(alpha beta gamma); echo ${a[1]} # tulostaa beta
a=(alpha beta gamma [5]=delta); echo ${!a[*]} # tulostaa 0 1 2 5; elikä mitkä indexit (osoitteet) ovat käytössä (jäsenet 3 ja 4 ovat määrittelemättä)
a=(alpha beta gamma [5]=delta); echo ${a[@]:2:3} # tulostaa: gamma delta (matriisin a jäsenestä 2 kolme jäsentä eteenpäin ja vaikka 3 ja 4 puuttuvat niin kyllä ne lasketaan)
a=(alpha beta gamma);a+=("lisätty");echo ${a[@]} # tulostaa: alpha beta gamma lisätty (elikä jäsenen lisääminen matriisiin)
a=(alpha beta gamma); echo "${a[@]:(-2)}" # tulostaa: beta gamma (elikä kaksi viimeistä jäsentä); siis: viimeinenjasen=${a[@]: -1}
IFS= a=(alpha "beta 2" gamma delta); printf "%s\n" ${a[@]:1}; unset IFS # tulostaa alekkain: beta 2 / gamma / delta - siis 2 samalla rivillä kuin beta
***
Yksi menetelmä etsiä tekstistä jotakin on muodostaa tuosta etsittävästä muotti ja ajaa teksti muotista läpi jolloin kaikki muotin mukainen tarttuu muottiin. Käytännössä toimitaan seuraavasti:
- etsittävän rakennetta kuvataan oliolla nimeltä regex: regex on muotti jonka määräysten mukaan etsittävän tyyppiset muuttujat on koottu - esimerkiksi päivämäärän muotti on: [0-9]+:[0-9]+:[0-9]+
- tässä paikassa merkki + merkitsee: yksi tai useampi samanlainen kuin on edessäkin.
- monille merkeille tulkki antaa asiayhteydestä riippuvan erikoismerkityksen - useimmilla merkeillä on aina sama merkitys mutta esimerkiksi merkillä: * niitä on monia. Jos ei haluta tulkin antavan merkille erikoismerkitystä kirjoitetaan sen eteen kenoviiva - siis esimerkiksi: \*
- kun tekstin jokaista sanaa sovitellaan tuohon muottiin niin kun muotti sopii niin kyse on päivämäärästä.
- regex:ää on totuttu käyttämään jonkun apu-ohjelman avulla - esimerkiksi grep:in avulla ja kyllähän sillä omat etunsa onkin, mutta BASH kykenee useisiin regex:iin itsekin ja silloin kun BASH kykenee niin grep jää surkeasti kakkoseksi.
- myös tuo regex-maailma on valtavan suuri - katsopa verkkosivua:
https://www.regexbuddy.com/. Ja samatyyppisiä sivuja on monia muitakin.
haku kokonaisuudessaan:
teksti="höpinää 17:06:23 lisää höpinää"; regex="[0-9]+:[0-9]+:[0-9]+"
[[ "$teksti" =~ $regex ]] && echo "tekstissä on päiväys:$BASH_REMATCH"
- tuosta regexään:n sopivasta muodostetaan aina BASH:issa muuttuja $BASH_REMATCH.
---
Mutta päiväys voidaan jakaa osiin: tuntiin, minuuttiin ja sekuntiin - ja mikäli regex on jakamista silmäälläpitäen muodostettu niin kukin näistä osista talletetaan omaan BASH_REMATCH[numero] nimiseen muuttujaan. BASH_REMATCH:ia selvitetään kaikkialla juurta jaksain - ja ehkäpä joku saa niistä selityksistä selvääkin. Mutta esimerkkejä ei esitetä; siis sellaista yksinkertaista skriptiä jossa tulostetaan BASH_REMATCH, BASH_REMATCH[1], BASH_REMATCH[2], BASH_REMATCH[3] ... jotta asiasta tulisi rautalankamalli jota ei voi käsittää väärin - tai oikeastaan että asian voisi nopeasti edes käsittää. Koetetaanpa nyt esitää asia ymmärrettävästi:
- BASH_REMATCH:ia käytettäessä regex:ät on jaettu suluilla osiin ja jokainen sulkupari muodostaa uuden muuttujan nimeltään: BASH_REMATCH[1], BASH_REMATCH[2], BASH_REMATCH[3] ... ja niiden kokonaisuuden nimeltä: BASH_REMATCH. Esimerkiksi päivämäärää kuvaava regex jaetaan:
([0-9]+):([0-9]+):([0-9]+)
Esimerkki:
teksti="höpinää 17:06:23 lisää höpinää"; regex="([0-9]+):([0-9]+):([0-9]+)"
[[ "$teksti" =~ $regex ]] && echo "tekstissä seuraava palanen täytti koko regex:än: $BASH_REMATCH - ja sen osat: tunti:${BASH_REMATCH[1]} minuutti:${BASH_REMATCH[2]} sekunti:${BASH_REMATCH[3]}"
- mikäli koko regex ei täyty niin ei tulosteta mitään - elikä 2013:06 ei tulosta mitään.
- siis jokaisesta regex:än (....)-kentästä muodostuu uusi BASH_REMATCH[numero] - ja numerointi alkaa ykkösestä.
- muuten teksti voi olla matriisikin:
${teksti[@]}
- silloin tosin saadaan vain ensimmäinen joka sopii.
- kieliasetukset vaativat skriptiin omat muutoksensa - näillä määrityksillä tämä toimii Suomessa.
toinen esimerkki:
[[ "012 a34567b89cdefgh" =~ ([a-z])[0-9]*([a-z])[0-9]*([a-z]) ]] && {
echo "- kolmeriviä selitystä: REMATCH:it tehdäån kohdassa:[[ "012 a34567b89cdefgh" =~ ([a-z])[0-9]*([a-z])[0-9]*([a-z]) ]]."
echo " Regex muodostuu useasta regex:stä. Tässä esimerkissä on kolme regex:ää ympäröity kaarisuluilla - huomio että"
echo " [0-9] on kaksikin kertaa BASH_REMATCH:in ulkopuolella vaikka kuuluvatkin regex:ään - siis valintajoukkoon ne kuuluvat mutta eivät BASH_REMATCH:iin."
echo "BASH_REMATCH on koko regex alue= "$BASH_REMATCH
echo "BASH_REMATCH[1] (elikä regex:än ensimmäisessä ()-osassa olevan regex:n hyväksymä arvo)= ${BASH_REMATCH[1]}"
echo "BASH_REMATCH[2] (elikä regex:än toisessa ()-osassa olevan regex:n hyväksymä arvo)= ${BASH_REMATCH[2]}"
echo "BASH_REMATCH[3] (elikä regex:än kolmannessa ()-osassa olevan regex:n hyväksymä arvo)= ${BASH_REMATCH[3]}" ;}
kolmas esimerkki, kenttien kääntäminen - vaikkapa Suomalaisen päiväyksen muuttaminen jonkun muun maan esitystapaan sopivaksi:
[[ 1.2.2013 =~ ([0-9]+).([0-9]+).([0-9]+) ]] && echo ${BASH_REMATCH[3]}.${BASH_REMATCH[2]}.${BASH_REMATCH[1]}
- kyse on nopeudesta. Työ olis helpompaa awk:lla tai semmoisilla mutta myös hitaanpaa. Ero on on millisekunneissa mitättömän pieni eikä muiden hitauteen edes vihjata missään - mutta itseasiassa nopeusero on yli kymmenkertainen.
---
Esimerkiksi matriisien tarkistaminen regex:ällä senvaralta että siinä olisi kirjaimia:
function tarkista_matriisi () { [[ $@ =~ [[:alpha:]] ]] || (( $BASH_REMATCH )) && echo matriisissa ensimmäinen vastaantuleva kirjain:$BASH_REMATCH || echo "matriisisssa ei ole kirjaimia";}; matriisi=({1..100000}); matriisi[5000]=7A7e; time tarkista_matriisi ${matriisi[@]}
- tuo [[:alpha:]] on tässä se regex. Regex:t voivat olla tällaisia pieniäkin mutta esimerkiksi IP6-verkko-osoitteen tarkistamiseen sopiva regex on monen rivin hirviö - mutta silti erittäin nopea.
Tavanomaisempi funktio samaan tarkistukseen - ja se ilmottaa kaikki merkit eikä hermostu miinus-merkistä, plus-merkistä eikä desimaalipisteestä - mutta isommalla matriisilla aikaa alkaa kulua paljon:
function tarkista_matriisi () { apu=${@//[-+.,0-9]/}; [[ $apu > ' *' ]] && echo löytyi seuraavat merkit:$apu ;}; matriisi=({-500..+500}); matriisi[5000]=7A7e; matriisi[2]=c; time tarkista_matriisi ${matriisi[@]}
- koska tekstijono on saman-nimisen matriisin jäsen 0 niin voi näillä tarkistaa tekstijononkin.
- + on monisäikeinen juttu - yleensä sitä ei edes tarvita tuossa: [-+.,0-9]