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

ajaaskel

  • Palvelimen ylläpitäjä
  • Käyttäjä
  • Viestejä: 3401
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #200 : 21.04.15 - klo:15.42 »
Olen äimänkäkenä ja joudun taas kysymään neuvoa: kohtasin verkkosivun joka väitti koodinsa olevan BASH:ia. Esimerkiksi yksi koodinpätkä oli tällainen:
Koodia: [Valitse]
function stack_destroy
{
    : ${1?'Missing stack name'}
    eval "unset _stack_$1 _stack_$1_i"
    return 0
}

Kysymys: mikä virka on tuolla kaksoispisteellä rivillä: 
: ${1?'Missing stack name'}

- kasasin skriptin ja koetin. Ilman kaksoispistettäkin toimi, mutta jonkinsortin virheen se silloin tekee. En siis edelleenkään tiedä kaksoispisteen merkitystä, mutta senverran kuitenkin että se on tarpeellinen.

Tuo kaksoispiste saa aikaan liki saman kuin  ohjaus --> /dev/null, lyhyempi kirjoittaa  mutta se ei liene suositeltava tapa jos tavaraa tulee paljon.  Tuolla on lisää siitä:

http://stackoverflow.com/questions/3224878/what-is-the-purpose-of-the-colon-gnu-bash-builtin

Edit: Tässä käytetty eri tarkoituksella, tarkemmin seuraavassa kommentissani tuolla alempana.
« Viimeksi muokattu: 22.04.15 - klo:10.00 kirjoittanut ajaaskel »
Autamme ilolla ja ilmaiseksi omalla ajallamme.  Ethän vaadi, uhoa tai isottele näin saamasi palvelun johdosta.

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #201 : 21.04.15 - klo:18.20 »
Tattista taas kertaalleen. Kestää kyllä pitkään ennenkuin nuo kaikki tajuaa edes jotenkin. Toisaalta masentavaa: en edes tiedä mistä kaikesta en tiedä mitään. Mutta yritetään ...kelataan kauan ... tämähän sanotaan jo antamasi verkkosivun alkuperäisesä viestissä ja senmukaan "selvennetty" selvitys taitaa olla:

: on käsky olla tekemättä mitään ja ${1?'Missing stack name'} on sen parametri

käskyä toteutettaessa BASH suorittaa ensin normaalin $1:n arvon määrityksen. Mikäli $1:llä on jokin arvo ei siis tapahdu mitään, mutta mikäli $1:llä ei ole arvoa niin näytetään oletusarvo joka tässä on: Missing stack name
« Viimeksi muokattu: 21.04.15 - klo:20.43 kirjoittanut petteriIII »

ajaaskel

  • Palvelimen ylläpitäjä
  • Käyttäjä
  • Viestejä: 3401
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #202 : 22.04.15 - klo:09.55 »
Kiitos "petteriIII", kommenttisi sai minut pysähtymään hetkeksi ja miettimään lisää.  Kaksoispiste yleisesti merkitsee NOP komentoa joka palauttaa aina exit-arvon 0 (true). Toiminta on hämärämpi kun sitä käytetään komentona parametrin kanssa ja sitä voi myös erikoisella (mutta aika rumalla) tavalla käyttää oikotienä koodin katkaisuun ajamalla tulkki virheeseen tahallisesti.

Lainaus
  : ${1?'Missing stack name'}

Tuon koodin pätkän merkitys on:  Jos parametri yksi on jäänyt antamatta ajetaan bash-tulkki virheeseen tuolla rivillä jolloin myös kaikki koodin suoritus loppuu siihen. Tuo syntaksi on kuvattu täällä:

http://www.tldp.org/LDP/abs/html/abs-guide.html#DEFPARAM     
Lainaus
${parameter?err_msg}, ${parameter:?err_msg}

    If parameter set, use it, else print err_msg and abort the script with an exit status of 1.

    Both forms nearly equivalent. The : makes a difference only when parameter has been declared and is null, as above.
mutta en pidä suositeltavana tapana, virheet pitää mieluummin ottaa hallitusti kiinni sen sijaan että kaadetaan koodi tulkin virheeseen.  Toiminnan oivaltamista hämärtää juuri tuo ruma toteutus jota ei ensimmäisenä osaa ajatella.

Bash-ilmaisutapojen moninaisuus ja eri merkitykset tilannekohtaisesti on haastava asia. Vie pitkähkön ajan muilla kielillä ohjelmoineeltakin oppia jollakin tavalla kattavasti eri ilmaisutavat muunnoksineen ja vielä enemmän mikä on hyvä lähestymistapa/rakenne jonkin asian ratkaisuun.   Silti se on hyvin mielenkiintoinen asia.     
« Viimeksi muokattu: 22.04.15 - klo:10.29 kirjoittanut ajaaskel »
Autamme ilolla ja ilmaiseksi omalla ajallamme.  Ethän vaadi, uhoa tai isottele näin saamasi palvelun johdosta.

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #203 : 04.05.15 - klo:18.49 »
BASH taipuu apu-ohjelmineen kummallisuuksiinkin, vaikka käskyjen muodostaminen on yleensä monivaiheisempaa kuin kehittyneemmillä kielillä. Esimerkiksi kun haluaa erottaa tekstijonosta tai tiedostosta siinä olevat kokonaisluvut, desimaaliluvut ja eksponenttiesitykset etumerkkeineen niin se onnistuu käskyllä:
Koodia: [Valitse]
| sed 's/[e.+-].[^[:digit:]]//g' | grep -o '[e.0-9+-]'*

esimerkiksi: echo 'o+oo-1234567ddd h.--[-- e"&<<-333.34e-444>>' | sed 's/[e.+-].[^[:digit:]]//g' | grep -o '[e.0-9+-]'*
- tai mikäli halutaan poistaa kaikki numerot: | sed 's/[e.+-].[[:digit:]]//g' | sed 's/[0-9]//g'
- luvut voi kyllä erottaa matriisiinkin, mutta erottamisen periaate selviää paremmin edellisestä
- siis ensiksi täytyy määritellä onko merkeillä: .+-e aakkosellinen vai desimaalinen merkitys

Hieman tähän samaan kuuluu: positive-negative-"look-ahead"-"look-behind":
Koodia: [Valitse]
| grep -Po 'tätä_haetaan(?=.*tämän_täytyy_olla_perässä_mutta_tätä_ei_valita)'  # haku- ja kelpuutus-sanojen välissä saa olla tekstiä
| grep -Po 'tätä_haetaan(?!tämä_ei_saa_olla_perässä)'                          # haku- ja esto-sanojen välissä ei saa olla tekstiä
| grep -Po '(?<=tämän_täytyy_olla_edessä_mutta_tätä_ei_valita)tätä_haetaan'    # haku- ja kelpuutus-sanojen välissä ei saa olla tekstiä
| grep -Po 'tätä_haetaan(?<!tämä_ei_saa_olla_edessä)'                          # haku- ja esto-sanojen välissä saa olla tekstiä
| grep -Po '(?<=teksti_edessä).*(?=teksti_perässä)'                            # tulostaa tekstin joka on hakuavaimien välissä. Myös: grep -Po '(?<=:).*(?=:)' onnistuu
| grep -o teksti_edessä.*tekstiperässä                                         # sama kuin edellinen, mutta teksti_edessä ja teksti_perässä kirjoitetaan myös

# oikeastaan haettavat voivat olla aivan mitä hyvänsä regexiä kaikissa noissa viidessä, joten voidaan kirjoittaa myös:
echo "AlvlökmgkrmgoerrkgkertgkeåthåäpthkB" |  grep -Po '(?<=^[A-Z]).*(?=[A-Z]$)'  # käsky sanoin kerrottuna: jos rivi alkaa ja loppuu suurella kirjaimella niin tulosta teksti noiden suurten kirjainten välistä

# tai voidaan etsiä verkkosivun tag:ien välistä ( tag=<< ja >> ):
echo "a c <<lvlökmgkrmgoerrkgkertgkeåthåäpthk>>d fb" |  grep -Po '(?<=<<).*(?=>>)'

# tai esimerkiksi hakea forecan sivuilta sääennuste; tässä rajoitetussa esimerkissä kymmenen päivän ennustuksen ylä-lämpötilat:
rm -f ~/tenday; wget http://www.foreca.fi/Finland/Helsinki/tenday; cat ~/tenday | grep -Po '(?<=abbr title="Vuorokauden ylin lämpötila">Ylin: <strong>).[0-9]'
- kun noilta verkkosivuilta tietoa hakevaa skriptiä muotoilee kannattaa kun hakee oikeaa paikka tehdä se editorissa; esim: rm -f ~/tenday; wget http://www.foreca.fi/Finland/Helsinki/tenday; gedit ~/tenday
**
tulosta kolmemerkkiset lohkot joiden kaikki merkit kuuluvat joukkoon [abc] ( määrä on siis {3}+1 )
Koodia: [Valitse]
echo jjjjaacaahhhhhbbbbbbbbhh | grep -P '([abc])(?1){3}'
# esimerkiksi tästä voi kehittää:
tulosta lohkot jotka muodostavat kolmesta perättäisestä a:sta tai neljästä perättäisestä joukon [bcd] jäsenestä
echo jjjjaaccachhhhhbccbbbbhh | grep -P '([a]{3}|[bcd])(?1){3}'
tulosta teksti joka on ensimmäisen ja viimeisen numeron välissä - mutta tekstissä ei saa olla erikoismerkkejä
Koodia: [Valitse]
echo jjhblbk17x7yhHYH17hjjhjhblhblb | grep -P '(?=(\d(?U)))\w+\1'
mikä muu e:n sisältävä ei kelpaa paitsi sellainen jossa e:tä seuraa 1
Koodia: [Valitse]
echo koe | grep -P '.*e(?!1)'
tulosta samoista numeroista muodostuneet numerosarjat (tai [a-z] tai ....):
Koodia: [Valitse]
echo gjjhbljhb555567jbjhbjhb | grep -P '([0-9])\1+'

Koodia: [Valitse]
echo "23:s" | grep -P '23(:s)?'         # valitsee: 23:s koska regex on tilassa greedy
echo "23:s" | grep -P '23(:s)??'        # valitsee: 23   koska regex on tilassa lazy
echo "23:s" | grep -P '(?U)23(:s)?'     # valitsee: 23   koska regex on tilassa lazy

echo "<EM>first</EM>" | grep -P '<.+?>  # kysymysmerkki tekee lazy:n elikä valitsee vain molemmat <EM> ja: <\EM>

echo adhd | grep -P 'h\Kd'              # tulosta h:n jälkeinen d
echo adhd | grep -P '(?<=h)d'           # tulosta h:n jälkeinen d
echo adhd | grep -P 'h\K\w+'            # tulosta h:n jälkeinen teksti       
**
- seuraava erottaa lohkon joka alkaa begin ja loppuu end. Mutta ennenkaikkea tästä ilmenee kuinka regex:lle annetaaan ohjeita (?...-ryhmissä :
i=älä välitä merkkikoosta, s=begin ja end voivat olla eri riveillä, m=merkin:^ käyttö sallitaan lukitsemaan begin rivin alkuun; joskus ^ kannattaa jättää pois
Koodia: [Valitse]
echo 'begin12345  67890endtyey kkk' | grep -Poz  '(?ism:^BEGIN.*?END)'
-tai BEGIN-END lohkojen erottaminen tiedostosta:
Koodia: [Valitse]
cat -e /boot/grub/grub.cfg  | grep -Poz  '(?ism:BEGIN.*?END)' | sed 's/END/&\n/g'

- toinen esimerkki ohjeiden antamisesta regex:ille:
Koodia: [Valitse]
echo AAA | grep -Po '(?i)a*'
**
Usein valitetaan että kirjoittaessaan BASH:ista osaavat ihmiset kirjoittavat toisille osaaville eivätkä välitä siitä ettei silläkeinoin koskaan kasva uutta sukupolvea BASH-koodaajia.
Syytöksessä on vähäsen tottakin, mutta toisaalta yli-dokumentoitu skripti on inhottava - täysin dokumentoimatonkin skripti on yleensä parempi. Mutta esimerkiksi seuraavankaltaisista esitetään usein pelkkä funktio eikä kerrota kuinka funktiota käytetään, mikä tekee funktiosta monille käyttökelvottoman. Vähäisen käyttö-opastuksen lisäksi funktiota kannattaa myös dokumentoida hieman:
Koodia: [Valitse]
#!/bin/bash
function verifyIP() { # Muodostetaan muotti jossa IP:t tehdään, tarkistetaan onko tämäkin IP tehty sillä ja palautetaan 0 mikäli IP oli tehty sillä muotilla ja muuten palautetaan 1 
local o='(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])'; [[ $1 =~ ^$o\.$o\.$o\.$o$ ]]; }

TarkistettavaIP=1.2.333.25; verifyIP $TarkistettavaIP && echo kelvollinen ip || echo kelvoton ip    # esimerkki kuinka funktiota kutsutaan

tai voidaan käyttää edellistä funktiota toisessa funktiossa ja kutsua kokonaisuutta seuraavasti:
Koodia: [Valitse]
#!/bin/bash
function verifyIP() {  # Muodostetaan muotti jossa IP:t tehdään, tarkistetaan onko tämäkin IP tehty sillä ja palautetaan 0 mikäli IP oli tehty sillä muotilla ja muuten palautetaan 1   
local o='(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])'; [[ $1 =~ ^$o\.$o\.$o\.$o$ ]]; }

function summaryIP() { # funktio kertoo osoitetussa tiedostossa olevat IP-osoitteet ja jokaisesta osoitteesta montako kertaa se on tiedostossa
while read ip x; do verifyIP $ip && echo $ip; done < "$1" | sort | uniq -c | sort -rn; }

summaryIP ~/IPlista    # IPlista-nimisen tiedoston jokaisella rivillä on IPosoite ensimmäisessä kentässä ja perässä voi olla muita kenttiä tai sitten ei.

# Joskus tulee esiin tilanteita joissa täytyy lukea tiedoston sijasta näytöltä. Esimerkiksi tuossa funktiossa: summaryIP se tehdään kirjoittamalla "$1" paikalle  "/dev/stdin" tai jotta molemmat
# tavat sallittaisiin niin "${1:-/dev/stdin}" jolloin vanha kutsutapa säilyy mutta lisäksi voidaan kutsua myös: cat ~/IPlista | summaryIP
« Viimeksi muokattu: 17.04.16 - klo:10.18 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #204 : 09.05.15 - klo:08.58 »
Netinssä olevista Atomic-group:in selvityksistä ei tule hullua hurskaammaksi. Olenkohan ymmärtänyt oikein päätyessäni seuraavaan:

ensimmäinen yritys on väärä joten poistin sen.
- alunperin sain pätevältä verkkosivulta "osittaisvihjeen" siitä ettei atomic-group:ista ole hyötyä yksinkäytettynä vaan sen tulee olla osana TAI-ryhmää. Hölmöyksissäni poistin tuon TAI:n koska toimihan se ilman sitäkin - mutta se toimi vajavaisesti mitä en huomannut. Väsäsinkin yritelmän 2:
Koodia: [Valitse]
echo abc | grep -P 'a(bc|b)c'     # tulostaa abc ( tuo | on looginen TAI )
echo abc | grep -P 'a(?>bc|b)c'   # ei tulosta mitään. Selvitys miksi: tuo (?>bc) tarkoittaa että ryhmää bc käsitellään kuten yhtä merkkiä. Siis siinävaiheessa
                                  # kun c:tä yritetään sovittaa niin ei ole enää mihin sovittaa, sillä tuo: (?>bc) on jo kuluttanut koko bc:n. 
echo abcc | grep -P 'a(?>bc|b)c'  # tulostaa abcc niinkuin pitääkin. On samantekevää mitä tuon |:n perässä on, vaikka ¤ kelpaa.





 
                               
« Viimeksi muokattu: 10.05.15 - klo:09.52 kirjoittanut petteriIII »

nm

  • Käyttäjä
  • Viestejä: 16232
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #205 : 09.05.15 - klo:14.49 »
Kysymyksestä olisi kannattanut avata uusi aihe, koska se ei koske erityisesti bashia ja shell-skriptausta vaan yleisesti säännöllisiä lausekkeita.

Netinssä olevista Atomic-group:in selvityksistä saa vain osittaisia vihjeitä. Olenkohan ymmärtänyt oikein päätyessäni seuraavaan:
Koodia: [Valitse]
echo abcc | grep -P 'a(?>bc)c'  # tulostaa: abcc
echo abc | grep -P 'a(?>bc)c'   # ei tulosta mitään. Selvitys miksi: tuo (?>bc) tarkoittaa että ryhmää bc käsitellään kuten yhtä merkkiä. Siis siinävaiheessa
                                # kun c:tä yritetään sovittaa niin ei ole enää mihin sovittaa, sillä tuo: (?>bc) on jo kuluttanut sen c:n.

(?>...) on siis ns. "Once-Only Subpattern" aka "Atomic group", jolla estetään backtrack-tyyppistä säännöllisen lausekkeen tulkkia palaamasta takaisin ja yrittämästä uudelleen pykälää vähemmän ahneella täsmäyksellä. Syntaksin avulla voidaan suunnitella säännöllisiä lausekkeita, jotka välttävät katastrofaalisen backtrackauksen hankalilla merkkijonoilla.

Antamasi esimerkit toimivat samalla tavalla, vaikka jättäisit (?>)-määritteen pois eli hakemalla pelkällä lausekkeella "abcc". Muutenkin "(?>bc)" vastaa aina lauseketta "bc", joka on yksiselitteinen ja täsmää vain merkkijonoon "bc".

Sen sijaan backtrack-eston vaikutusta voi havainnollistaa näin:

Koodia: [Valitse]
$ echo "abc" | grep -P '.+'
abc

$ echo "abc" | grep -P '.+c'
abc

$ echo "abc" | grep -P '(?>.+)'
abc

$ echo "abc" | grep -P '(?>.+)c'


Täsmättäessä lauseketta ".+c" merkkijonoon "abc", backtrackaava säännöllisen lausekkeen tulkki täsmää ensin ".+":n koko merkkijonoon "abc". Sitten se huomaa, että jäjellä oleva lauseke "c" ei enää täsmää merkkijonoon, mutta lopettamisen sijaan tulkki palaa takaisin ja täsmää ".+":n vain merkkijonoon "ab". Sitten loppuosakin täsmää ja tulkki päätyy oikeaan tulokseen, eli ".+c" täsmää merkkijonoon "abc".

Viimeinen esimerkkilauseke toimii kuitenkin toisin, koska backtrackaus on estetty atomic groupilla. Siinä ".+" täsmää taas ensin koko merkkijonoon "abc" ja lausekkeen "c" jää yli. Tulkkia on kuitenkin käsketty kokeilemaan ".+":aa vain yhden kerran, joten se ei voi palata takaisin ja yrittää uudelleen lyhyemmällä täsmäyksellä. Sen sijaan tulkki päätyy tulokseen, ettei täsmäystä löydy.
« Viimeksi muokattu: 09.05.15 - klo:14.52 kirjoittanut nm »

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #206 : 05.07.15 - klo:02.28 »
Aloinpa etsiä keinoa jolla saisi tietää onko kovalevy puolijohdetyyppinen vaiko mekaaninen. BASH:issa kaikkea voi viilata loputtomiin, mutta tällähetkellä skripti on seuraavanlainen:
Koodia: [Valitse]
#!/bin/bash
function Kovalevyntyyppi () { [[ $(udevadm info --query=all --name=/dev/$1 | grep DEVLINKS= | awk '{print $3}') ]] && echo 'levy:'$1' on puolijohdetyyppinen' || echo 'levy:'$1' on mekaaninen' ;}
# [[ -f /sys/block/$1/queue/rotational ]] && ( [[ $(cat /sys/block/$1/queue/rotational) -eq 0 ]] && echo 'levy:'$1' on puolijohdetyyppinen' || echo 'levy:'$1' on mekaaninen' ) || echo echo 'levyä:'$1' ei ole' # tämä on "virallinen" tapa määritellä levyntyyppi, mutta se ei toimi usb-levyjen kanssa

function ListaaLevyt () {
apu=$(ls -lF /dev/disk/by-id | grep ata- | grep -o sd.$); [[ $apu ]] && echo ata:lla liitetyt levyt: $apu && { for apuu in $apu; do Kovalevyntyyppi $apuu; done }
echo
apu=$(ls -lF /dev/disk/by-id | grep usb- | grep -o sd.$); [[ $apu ]] && echo usb:llä liitetyt levyt: $apu && { for apuu in $apu; do Kovalevyntyyppi $apuu; done }
}

ListaaLevyt


- ja tuloste esimerkiksi seuraavanlainen:

ata:lla liitetyt levyt: sda sdb
levy:sda on puolijohdetyyppinen
levy:sdb on mekaaninen

usb:llä liitetyt levyt: sdd sdc
levy:sdd on puolijohdetyyppinen
levy:sdc on puolijohdetyyppinen

**
Tulipa tarve mitata prosessori-ytimien kuormitusta. Siihen sopivaksi skriptiksi osoittautui:
- ytimien määrä otetaan automaattisesti huomioon
Koodia: [Valitse]
#!/bin/bash
apu=0; while true; do
  prosessorin_data=$( grep 'cpu'$apu /proc/stat)
  [[ ! $prosessorin_data ]] && break
  echo -n "ydin"$apu'  '; echo $prosessorin_data | awk '{usage=($2+$4)*100/($2+$4+$5)} END {printf "%2.2f %s\n", usage,"%"}'
  let apu++
done


Skriptin kokeilemiseksi aja se kun koneessa ei ole käynnissä mitään muuta (et siis saa roikkua netissä tämän kokeen aikana). Omassa neliytimisessä prosessorissani tulos oli seuraava:
- äskettäiset tapahtumat vaikuttavat, joten kannattaa odottaa minuutti ennenkuin ajaa skriptin. Tulos ei muten ole sama kuin mitä tulee järjestelmän valvonnassa.
ydin0  2.58%[[ $( apt-cache policy lm-sensors | grep "(ei mitään)" ) ]] && echo 'koneessasi ei ole pakettia  nimeltä: lm-sensors . Haen sen' && sudo apt-get install lm-sensors
[[ $( apt-cache policy plotutils | grep "(ei mitään)" ) ]] && echo 'koneessasi ei ole pakettia  nimeltä: plotutils . Haen sen' && sudo apt-get install plotutils
[[ $( apt-cache policy gv | grep "(ei mitään)" ) ]] && echo 'koneessasi ei ole pakettia  nimeltä: gv . Haen sen' && sudo apt-get install gv
n=0; echo "" >~/xy_arvot
while true; do
  apu=$(sensors -Au | grep -Po '(?<=_input: ).*' | tail -$(nproc) | awk '{t+=$0} END {print t/NF}') # lasketaan kaikkien ytimien lämpötilojen keskiarvo - niin monta kuin niitä onkin.
  echo -e $n' ' $apu  >> ~/xy_arvot 
  graph -T ps ~/xy_arvot > ~/plot.ps; gv ~/plot.ps &
  let n+=2; sleep 2 # mittausten välillä noin 2 sekuntia - ajan suhteellinenkin arvo on melko merkityksetön
  killall gv
done
ydin1  2.54%
ydin2  2.86%
ydin3  2.31%
 
Avaa sitten pääte (ubuntussa paina ctrl-alt-t) ja anna käsky: yes
- tuo käsky ajaa prosessoreissa prosessin joka tulostaa jatkuvasti: y.
Avaa sitten toinen pääte (ubuntussa paina taas ctrl-alt-t) ja aja kuormitusta mittaava skripti odotettuasi taas minuutin. Tuloksen pitäisi olla jotakin seuraavankaltaista:
ydin0  9.57%
ydin1  10.0%
ydin2  9.79%
ydin3  9.69%
- prosentit kasvavat mitä kauemmin odotat.
- tottakai skriptissä olisi parantamisen varaa taas loputtomiin; tämä on vain ensimmäiseksi mieleentullut.
**
Halusinpa saada mitatuksi prosessori-ytimen lämpötilan. Osoittautui että ytimen lämpötila on varsin epäselvä asia ja sen mittaava skripti olisi haastava tehtävä, mutta kestoltaan "ikuisuus-projekti". Tässä kuitenkin jonkinlainen versio joka suorittaa mittauksen kymmenen kertaa tulostaen Kovalevyn attribuuttien selville saamiseksi on lukemattomia konsteja. Niiden yhteinen piirre tuntuu olevan se, että ne vaativat sudo:a. Esimerkiksi käsky: blkid toimii näennäisesti ilman sudo:a, mutta lähemmin tarkasteltuna osottautuu että se vaatii sittenkin kunnolla toimiakseen sudo:a. Mutta toki konsti löytyy: otetaanpa esimerkiksi osiotyypin selvittäminen:
 
udevadm info -n /dev/sda1 -q property | grep -Po '(?<=ID_FS_TYPE=).*' 

- ja samalla periaatteella saa muitakin attribuuttejajokakerran mittaustuloksen ja tulostaa lopuksi mittaustulosten keskiarvon:
Koodia: [Valitse]
#!/bin/bash
[[ $( apt-cache policy lm-sensors | grep "(ei mitään)" ) ]] && echo 'koneessasi ei ole pakettia  nimeltä: lm-sensors . Haen sen' && sudo apt-get install "lm-sensors"
n=0
edellinen_tulos=0
while true; do
  mittaus[$n]=$(sensors -Au | grep -Po '(?<=_input: ).*' | tail -$(nproc) | tr "\n" " " | awk 'END {for(i=1;i<=NF;i++) t+=$i; print t/NF}')
  # mittaa kaikkien ytimien lämpötilat ja tulostaa niiden keskiarvon
  [[ ${mittaus[n]} != $edellinen_tulos ]]  && echo ${mittaus[n]} && edellinen_tulos=${mittaus[n]} && let n++
  [[ $n == 10 ]] && echo ${mittaus[*]} | awk '{for(i=1;i<=NF;i++) t+=$i; printf "%s %2.1f", "keskiarvo: ",t/NF}' && break 
done
**
Tässävaiheessa tulee tarve esittää mittaustulokset xy-käyrinä. Yksinkertainen alku on seuraavanlainen:
Koodia: [Valitse]
#!/bin/bash
# aluksi tarkistetaan onko koneessa tarvittava plotutils-ohjelma ja jos ei ole niin se ladataan
[[ $( apt-cache policy plotutils | grep "(ei mitään)" ) ]] && echo 'koneessasi ei ole pakettia  nimeltä: plotutils . Haen sen' && sudo apt-get install "plotutils"

# ensin muodostetaan data-tiedosto jossa tiedot ovat muodossa:  x0 y0 x1 y1 x2 y2 ...   
# x-arvoissa jokaisen arvon tulee olla suurempi kuin edellinen x-arvo
echo 0.0  0.0 1.0  0.2 2.0 0.0 3.0 -0.4 4.0 0.2 5.0 0.6 > ~/abc

# seuraava käsky muodostaa datasta kippuran tiedostoon: ~/plot.ps
graph -T ps ~/abc > ~/plot.psA potential source of a full filesystem are large files left open but have been deleted. On Linux, a file may be deleted (removed/unlinked) while a process has it open. When this happens, the file is essentially invisible to other processes, but it still takes on physical space on the drive. Tools like du will not see it.
**
Seuraavaksi edelliset nivotaan yhteen. Skripti kuvaa reaaliaikaisesti prosessorin kaikkien ytimien lämpötilojen keskiarvon kehitystä graafisesti. X-akselilla ovat sekunnit ja y-akselilla asteet. Piirros skaalataan ja akselit merkitään automaattisesti. Ohjelman aikana voi ajaa samanaikaisesti jotain ohjelmaa joka kuormittaa prosessoria. Millä kuormitatkin niin laita kuormitus päälle ensiksi sillä tämän grafiikka-ohjelman keskeyttäminen on haastava tehtävä. Kirjoittaudu ulos jos et muuten saa skriptiä loppumaan.
grep -Pzo '(?s)(?<=begin).*(?=.*end)
Ohjelma toimii nytkähdellen ja epäilemättä ohjelma on muutenkin omituinen sillä en hallitse BASH:in graafisia toimintoja, esimerkiksi on minulle on täysin epäselvää mitä ohjelmaa kannattaisi käyttää.   

- skripti on hiomaton jotta se olisi helpompi ymmärtää.
- äskettäinen 23.7.2015 päivitys muutti sensors-ohjelman toimintaa ja se vaati muutoksen tähänkin skriptiin.
Koodia: [Valitse]
#!/bin/bash
[[ $( apt-cache policy lm-sensors | grep -E '(ei mitään)|none)' ) ]] && echo 'koneessasi ei ole pakettia  nimeltä: lm-sensors . Haen sen' && sudo apt-get install lm-sensors
[[ $( apt-cache policy plotutils | grep -E '(ei mitään)|(none)' ) ]] && echo 'koneessasi ei ole pakettia  nimeltä: plotutils . Haen sen' && sudo apt-get install plotutils
[[ $( apt-cache policy gv | grep -E '(ei mitään)|(none)' ) ]] && echo 'koneessasi ei ole pakettia  nimeltä: gv . Haen sen' && sudo apt-get install gv
n=0; echo "" >~/xy_arvot
while true; do
  apu=$(sensors | grep -Po '(?<=Core [0-9]).*' | awk '{print $2}' | awk '{t+=$0} END {print t/NR}') # lasketaan kaikkien ytimien lämpötilojen keskiarvo - niin monta kuin niitä ytimiä onkin.
  echo $(echo $n/60 | bc -l)' ' $apu  >> ~/xy_arvot 
  graph -T ps ~/xy_arvot > ~/plot.ps; killall gv; gv ~/plot.ps &
  let n+=2; sleep 2 # mittausten välillä noin 2 sekuntia - ajan suhteellinenkin arvo on melko merkityksetön
done
**
Tavallisella Live-muistitikulla voi taas päivittää salasanaa antamatta kaikki linuxit missä koneessa hyvänsä, vaikka siinä olisi EFI asennus eikä se edes boottaisi. Skripti ei siis ainoastaan päivitä vaan myös palauttaa boottaus-kyvyn. 
- muuten UEFI testi: [[ -d /sys/firmware/efi ]] && echo 'kone käyttää UEFI:a' || echo 'kone ei käytä UEFI:a' .
- skripti löytyy paikasta: http://forum.ubuntu-fi.org/index.php?topic=31223.msg241294#msg241294
**
Teoriassa kone pitäisi päivittää aamun ensimmäisenä tehtävänä. Ja kehitysversio pitää päivittää monesti päivässä ellei halua uus-asentaa tilttiin mennyttä konetta. Mutta varusohjelmiston päivitys ei kelpaa, sillä se jättää oleellisia asioita tekemättä saavuttamatta ajallistakaan hyötyä.

Harkitsin taas kerran siirtymistä BTRFS:ään sillä päivitys-skripti toimii silläkin pienin muutoksin - ja olisihan tuo BTRFS:n snapshot myös sovellettavissa tähän nopeine epäonnistuneen päivityksen hylkäämisineen. Vaan ei se kumminkan auttaisi, sillä recovery-menusta taitaa puuttua kohta: boottaa siihen imageen joka viimeksi toimi. Mutta kieltämättä houkuttelee sillä ei se luultavasti olisi suurikaan työ.
**
Tästä on kyllä annettu tälläkin foorumilla hyvät ohjeet mutta esitetäänpä nyt asiat hieman toisin kuin ennen: aina kun systeemissä joku sovellu kaatuu kirjoitetaan siitä raportti ja annetaan käyttäjälle kaksi vaihtoehtoa: ohita ja:jatka raportti kehittäjille. Mutta tuo "ohita" valinta on salahauta: maailmantappiin jokakerran kun boottaat niin tuo kysymys esitetään uudestaan jos siitä ei ole kertaakaan raportoitu kehittäjille.
Onhan se pikkuisen nolo homma olla kertomatta kehittäjille että Ubuntusi on kaatunut - "käytät vain ilmaista Ubuntua mutta et edes viitsi auttaa kehittäjiä". Mutta monet ohjelmoijatkin käskevät kylmän tyynesti:
Koodia: [Valitse]
sudo rm /var/crash/*
jolloin vanhat raportit nollautuvat tai peräti: sudo gedit /etc/default/apport ja muuttamalla sillä ykkönen nollaksi jolloin raportteja ei enää ylipäätään muodostu.
Ja täytyy myöntää että minäkin syyllistyn moiseen. Koska tekeeköhän ne mitään periferiasta tulleille raporteille ?
**
Piti seurata pesukoneen tehonkulutusta ajan funktiona. Etsiskelin millaisia tiedonkeruu-laitteita Ubuntuun löytyy. Kalliitahan ne ovat ja niiden ubuntu-yhteensopivuus on niin ja näin. Mutta toisaalta: melkeinpä jokainen kone on äänitaajuusalueen oskilloskooppi käytettäessä pakettivarastossa olevaa xoscopea. Siitä en yrittänytkään selvittää kuinka työlästä on saada tarkkoja tuloksia, mutta halpaa se ainakin olisi. Xoscopen seuraava versio osaa muuten fourier-muunnoksen joten spektrinkin saisi.

Kerrotaanpa hieman xoscope:sta:
- bittisyys kuulemma riippuu äänikortista: useimmat äänikortit ovat 16-bittisiä. Äänikorttikin voi kuulemma olla DC-kytketty jolloin silloin saisi myös DC:n. 24-bittisellä äänikortilla siis saa paremman resoluution kuin ammatti-vehkeillä.
- herkkyyttä löytyy vielä yli 10* - siis herkempi kuin useat ammattivehkeet.
- käyttö on hankalaa vasta-alkajalle: esimerkiksi pyyhkäisynopeutta säädetään napeista 9 ja 0, triggausta napista + , herkkyyttä napeista { ja } - jos tarvitsee vaimennusta niin vaimentimen kyllä joutuu itekemään itse ...
- sisäänmeno on mikrofoni-sisäänmeno. Kaksikanavainenkin siis ryökäle, saisi mitattua cosini-fiin:kin. Esimerkkinä toistaiseksi vain "sormihurina" (kun mittapään elikä mikrofonisisäänmenon toisen kuuman tökkää sormensa): tulos on sellainen kuin odottaa sopiikin.
  Kuva avautuu kun kirjoittaudut foorumille:
**
Joskus käyttäjä poistaa vahingossa itseltään sudo-oikeuden ja sudo-oikeuttahan ei käyttäjä itse voi silloin palauttaa koska sudo-oikeuden palauttaminen vaatii sudo-oikeutta. Sudo-oikeus voidaan palauttaa "live-CD:llä tai jollakin muulla sudo-oikeuden omaavalla" siirtymällä chroot:illa sudo-oikeuden menettäneen käyttäjän kovalevy-osiolle ja liittämällä käyttäjä sudo-ryhmään.
- joskus käyttäjä eroaa vahingossa myös jostain muustakin ryhmästä johon olisi syytä kuulua joten ne kannattaa kaikki palauttaa samallakertaa. Kaikki tapahtumat skriptin-pätkänä:
Koodia: [Valitse]
# varmistetaan että käyttäjä kuuluu kaikkiin niihin ryhmiin joihin hänet on liitetty asennettaessa. Huomaa ettei tämä poista mitään.
# käyttäjä liitetään Ubuntua asennettaessa seuraaviin ryhmiin: adm cdrom sudo dip plugdev lpadmin sambashare. Käyttäjä tulee liittää myös ryhmään jolla on käyttäjän nimi.
# käsky: $(echo ${0%} | tr "/" " " | awk '{print $(NF-2)}') kertoo minkä nimisen käyttäjän tiedostoissa ollaan.
for apu in adm cdrom sudo dip plugdev lpadmin sambashare $(echo ${0%} | tr "/" " " | awk '{print $(NF-2)}'); do
  [[ ! $(groups | grep $apu) ]] && sudo addgroup $(echo ${0%} | tr "/" " " | awk '{print $(NF-2)}') $apu
done
**
Kun tekee käyttäjälle tarkoitettua skriptiä on syytä välttää sudo:n käyttämistä ellei se ole ihan pakko sillä ei ihmiset halua salasanaa kirjoittaa - kun ei käytä skriptiä niin eipähän tarvitse salasanaa kirjoitella ja onhan se pieni turvallisuusriskikin. Mutta toki sudo:n voi määrätä mikäli sudo on annettu jo aikaisemmin ja on A potential source of a full filesystem are large files left open but have been deleted. On Linux, a file may be deleted (removed/unlinked) while a process has it open. When this happens, the file is essentially invisible to other processes, but it still takes on physical space on the drive. Tools like du will not see it.edelleen voimassa.

Sen määrittely onko sudo vielä voimassa:
Koodia: [Valitse]
[[ $(sudo -n uptime 2>&1 | grep "load") ]] && echo 'sudo on vielä voimassa' || echo 'sudo ei ole voimassa'
**
Kovalevyn attribuuttien selville saamiseksi on lukemattomia konsteja. Niiden yhteinen piirre tuntuu olevan se, että ne vaativat sudo:a. Esimerkiksi käsky: blkid toimii näennäisesti ilman sudo:a, mutta lähemmin tarkasteltuna osottautuu että se vaatii sittenkin kunnolla toimiakseen sudo:a. Mutta toki konsti löytyy: otetaanpa esimerkiksi osiotyypin selvittäminen:
Koodia: [Valitse]
udevadm info -n /dev/sda1 -q property | grep -Po '(?<=ID_FS_TYPE=).*' 

- ja samalla periaatteella saa muitakin attribuutteja
- tai viimeksi lisätyn USB-laitteen idProduct:idVendor:
Koodia: [Valitse]
echo $(journalctl | grep usb.*New.*idVendor= | tail -1 | grep -Po '(?<=idVendor=)[0-9A-Za-z]*')':'$(journalctl | grep usb.*New.*idVendor= | tail -1 | grep -Po '(?<=idProduct=)[0-9A-Za-z]*')
**
Koska levylle kirjoittaminen on hidasta niin kirjoittaminen suunnataan aina RAM:miin, tarkemmin sanoen buffereihin. Buffereista tuo tieto kirjoitetaan käyttöjärjestelmän toimesta aikanaan levylle mutta saattaa kestää muutaman minuutinkin ennenkuin kaikki on varmasti kirjoitettu levylle. Mikäli kone sammutetaan aikaisemmin niin buffereissa ollut tieto katoaa. Kun tehdään ohjelmaa niin sen kiireettömiin kohtiin voi kirjoittaa käskyn: sync joka tyhjentää buffereista kirjoitettavat levylle.
- tämän "kiireettömän" kohdan valinta on vaikeaa sillä väärään paikkaan kirjoitettuna se aiheuttaa ohjelmassa hidatelua.
- paljonko bufferoitua ja kenties levylle kirjoitettavaa on:  grep ^Dirty /proc/meminfo
- ei sync-käsky teoriassa USB-muistitikulle riitä vaan lisäksi tarvitaan: umount .... ennenkuin sen voi irroittaa - helpompi irroittaa se nautiluksessa.
**
Luin netistä Linux-magazinea. Sikäli hyödyllinen homma että sain taas kertaalleen opastusta siitä kuinka BASH-skriptejä opetetaan tekemään väärin niidenkin toimesta joiden pitäisi tietää. Minkähän takia sillä kunnollisia BASH-virtuooseja on vielä jonkunverran? Jokatapauksessa opin senkin, että seuraava käyttäjälistan tulostus toimii ainakin Ubuntussa:
Koodia: [Valitse]
awk -F: '/home/&&/bash/ {print $1}' /etc/passwd
- muuten || on tai-funktio jos sitä tarvitset.
**
Ohjelmat muodostuvat joskus monesta itsenäisestä osasta joista joku/jotkut keräävät tietoa ja kirjoittavat sen jonnekin ja joku/jotkut lukevat sitä ja tulostavat sen sitten näytölle. Käsiohjauksella työ on yksinkertainen: ensin ajetaan ohjelma joka kerää tiedon ja kirjoittaa sen jonnekin ja kun se on lopettanut niin ajetaan ohjelma joka lukee niitä tietoja ja tulostaa ne näytölle. Mutta entäpä jos tuo tiedonkeruu voi alkaa joko hetikohta tai kuukauden kuluttua tai peräti pätkittäin? Tämä ongelma ratkeaa nimetyn-putken avulla. Toiminnan kuvaus yksinkertaisimmillaam:

Avaa ensimmäinen pääte (esimerkiksi painamalla ctrl-ALT-t) ja:
anna käsky: mkfifo NimettyPutki   
anna käsky: echo hilipatarallaa > NimettyPutki   # enterin painamisen jälkeen ei tapahdu enää mitään, edes uutta kehotetta ei tule

avaa toinen pääte (esimerkiksi painamalla ctrl-ALT-t) ja anna käsky: cat NimettyPutki
se tulostaa: hilipatarallaa
palaa ensimmäiseen päätteseen ja huomaat että se on tulostanut jälleen kehotteen.

- muodostuvaan NimettyPutki-nimiseen tiedostoon voi soveltaa kaikkia niitä toimia joita tiedostoon yleensäkin.
**
NimettyäPutkea voidaan käyttää esimerkiksi kun kaksi skriptiä toimivat eripitkään, mutta silti haluna olisi että ne toimisivat yhtämonta kertaa. Tämän aikaansaamiseksi nopeampi laitetaan odottaman hitaamman valmistumista. Kumpihyvänsä saa olla se hitaampi, tulostusjärjestys kylläkin muuttuu. 
- tottakai BASH:issa on toisia parempiakin keinoja mutta onhan tämä yksi menetelmä.

Tee skripti nimeltä: skripti1
Koodia: [Valitse]
#!/bin/bash
rm -f NimettyPutki && mkfifo NimettyPutki
while true; do
  Alkuhetki=$(date +%s.%N)
  sleep 1 # sleep:in tilalle voi laittaa minkähyvänsä toiminnon ja sen kuluttama aika tulostuu
  Loppuhetki=$(date +%s.%N)
  echo -e 'skripti1:n suoritusaika: '$(echo $Loppuhetki-$Alkuhetki | bc -l)'\n' > NimettyPutki # skripti1 lukitsee itsensä
done
Tee toinen skripti nimeltä: skripti2
Koodia: [Valitse]
#!/bin/bash
while true; do
  Alkuhetki=$(date +%s.%N)
  sleep 10 # sleep:in tilalle voi laittaa minkähyvänsä toiminnon ja sen kuluttama aika tulostuu
  Loppuhetki=$(date +%s.%N)
  echo 'skripti2:n suoritusaika: '$(echo $Loppuhetki-$Alkuhetki | bc -l)
  cat NimettyPutki # tämä vapauttaa skripti1:n
done

Toiminta:
Avaa pääte (paina ctrl-ALT-t) ja käske:         . skripti1
Avaa toinen pääte (paina ctrl-ALT-t) ja käske:  . skripti2
 
Alkaa tulostua noin kymmenen sekunnin välein jotain seuraavankaltaista:
skripti2:n suoritusaika: 10.005455598
skripti1:n suoritusaika: 1.005531242

skripti2:n suoritusaika: 10.005275533
skripti1:n suoritusaika: 1.004949491
**
Tutkin taas kerran kuinka tekstistä saa erotettua tagien välisen lohkon. Tapoja on lukemattomia ja todella erikoisia konsteja on vaikka minkälaisia. Muutamia yksinkertaisia:
Koodia: [Valitse]
grep -Pzo '(?s)begin.*end'                         # (?s) on ohje regex:älle siitä että . vastaa myös rivinsiirtoa.
grep -Pzo '(?s)(?<=begin).*(?=.*end)               # tagien paikoilla on tyhjä rivi
sed -n '/begin/,/end+/ { p }'                      # kirjoita lohkojen väliin tyhjä rivi. Siis esimerkiksi: cat /boot/grub/grub.cfg | sed -n '/BEGIN/,/END+/ { p }'
awk '/BEGIN|begin|<</,/END|end|>>/ {print $0}'     # | merkki on TAI   
- myös regex:t sopii: lspci -v | awk '/VGA/,/^$/'  # lohkon alku on VGA ja loppu tyhjä rivi
- tiedostoissa on joskus epäkelpoja merkkejä. Tällöin esimerkiksi:
cat /boot/grub/grub.cfg | tr -dc [[:graph:]]+'\n' | grep -Pzoi '(?sU)begin.*end' | sed "s/BEGIN/******* lohkon raja *******\nBEGIN/g"




« Viimeksi muokattu: 29.09.15 - klo:07.01 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #207 : 22.09.15 - klo:11.52 »
On jatkuva yllätyksen aihe kuinka nopea BASH on - taikka BASH on todella hidas mutta se saa asiat tehtyä nopeasti kun oikein käsketään. Taas kertaalleen sain kokea oikean käskytavan löytämisen vaikeuden kun yritin tehdä skriptiä joka etsii joukon kaikki kombinaatiot. Siinä muuten kului aikaa, tupakkia ja luntatakin piti. Lopulta skripti supistui muotoon:
Koodia: [Valitse]
function kombinaatio (){ eval echo $(printf "{%s,}" "$@"); }    # kutsu: kombinaatio a b c d . . . .
- skripti toimii nopeasti mutta mikäli nopeampaa tarvitaan niin kyllä se BASH:ilta onnistuu vaikka minulta ei.
**
BASH:issa oikean käskytavan löytäminen on vaikeaa siksi että ongelmat yritetään ratkaista samallatavoin kuin ne muissa kielissä ratkaistaan. BASH on hidas, ja jos toiminnan pakottaa BASH:ille niin kaikki tahmaa. Myös kertoman laskussa skripti kuluttaa tuhottomasti aikaa ja resursseja mikäli skriptin tekee väärin. Mutta oikentehtynä skripti on nopea, sen arvoalue on rajoittamaton ja sen kirjoitusasu on yksinkertainen. Koetapa laskea muillakeinoin "100000 kertoma" vaikka eihän siinä juuri mieltä ole. Matikassa vaan ohjelmat eivät saa rajoittaa. Nopea keino kertoman laskemiseksi:
Koodia: [Valitse]
function kertoma () { [[ $1 -eq 0 ]] && kertoma=1 || kertoma=$(seq -s* $1 | bc | tr -d '\\\n' ) ;}; time kertoma 555; echo $kertoma
- kertoma-funktion nimeksi kelpaa kyllä ! ja huutomerkki toimisi vaikka se olisi perässäkin. Mutta silloin skripti olisi paljon isokokoisempi.
**
Aina oppii. Seuraavat käskyt kertovat mistä on kyse:
Koodia: [Valitse]
echo "<<tulostuu>>ei saa tulostua<<tulostuu>>" |  grep -Po '(?<=<<).*(?=>>)'      # mikäli tag-ryhmiä on samalla rivillä useampia niin tuloste on väärä
mutta:
Koodia: [Valitse]
echo "<<tulostuu>>ei saa tulostua<<tulostuu>>" |  grep -Po '(?U)(?<=<<).*(?=>>)'  # tämä taas tulostaa oikein. Ungreedy voi kylläkin myös sotkea
tätä voi jalostaa edelleen sillä edellinen ei tulosta oikein mikäli tag:ien välinen teksti on monella rivillä. Silloin pitää kirjoittaa:
Koodia: [Valitse]
cat tiedosto |  grep -Poz '(?Us)(?<=<<).*(?=>>)'                                 
tai ehkäpä tulostaisi selvälukuisemmin:
Koodia: [Valitse]
cat tiedosto |  grep -Pozn '(?Us)(?<=<<).*(?=>>)' | sed 's/^1:/\n/g'

- tuo << ..... >> on vai esimerkki tageista: kyllä mikä teksti hyvänsä kelpaa tag:iksi
**
Aivan toisenlaisella lähestymistavalla haetaan ratkaisua samantapaiseen asiaan - tässä kylläkin on tarkoituksena tulostaa tiedoston ne lohkot joissa lukee jollakin rivillä määrättävä sana missäkohtaa tahansa:
Koodia: [Valitse]
awk -v RS='' '/sana/ {print $0"\n"}' tiedosto
- lohko elikä kappale on rivijoukko jota kummaltakin puolelta rajoittaa tyhjärivi.
- tässä tulostuvien lohkojen väliin tulostuu tyhjärivi
- kaikki ne kappaleet tulostetaan joissa on tuo sana. Jos jossakin kappaleessa ei ole tuota sanaa ei sitä myöskään tulosteta.
- on aivan sama mitä sanaa etsitään, kunhan se on lohkoissa joita halutaan eikä ole lohkoissa joita ei haluta.
- sanaksi kelpaa regex - tai voihan siitä käyttää nimitystä tag sillä siitä on kysymys
- mikäli halutaan että tulostetaan sittenkin kahden tagin välinen teksti niin käsky muuttuu seuraavankaltaiseksi:
Koodia: [Valitse]
sed "s/tag1/\n\ntag1/g;s/tag2/\n\ntag2/g" tiedosto | awk -v RS='' '/tag1/ {print $0"\n"}' | sed -e 's/tag1//g'
- tag:it saavat olla mielivaltaisia merkkejä, vaikka << ja >>
- tiedoston teksti saa olla yhtenä pötkönä ilman yhtäkään rivinsiirtoa, mutta saa siinä rivinsiirtojakin olla.
- tällaista käskyhirviötä on pakko käyttää mikäli koneen regex ei ole oikeanlainen ja regex on jokatapauksessa erittäin hidas.
- 100.000-rivisen tiedoston käsitteleminen kestää luokkaa 150ms jos tiedostossa on paljon tulostettavaa. Kauankohan kestäisi Pythonilla, C:llä ...?
**
BASH:in matikka on raivostuttavaa, mutta eihän sitä numeronmurskaukseen ole tehtykään. Yksi ikuisuusongelma on lukujen vertaaminen. Helpointa taitaa olla kun suosiolla käyttää jotain seuraavankaltaista:
Koodia: [Valitse]
[[ $(echo $a'>'$b | bc) = 1 ]] && teejotakin
niin ei tarvitse miettiä. Tosin omituisuutensa tuolla BC:lläkin on, esimerkiksi tieteellisessä esitystavassa eksponentiaation merkki on iso E.
**
Osa Linuxin käskyistä on niin nopeita että Time-käsky väittää niiden nopeudeksi nolla ja muilla menetelmillä saadut tulokset heittelevät mahdottomasti. Arvoista saa vakaita ajoittamalla käskyt monta kertaa ja ottamalla tuloksesta keskiarvon.
Tällätavoin menetetään tulosten absoluuttinen arvo, sillä silloinhan todennäköiseti mitataan cache:ja, buffer:eita tai onhan noita muitakin. Mutta yhtä asiaa ei menetetä: samantapaisten käskyjen
suoritusaikojen suhteellista eroa: esimerkiksi kun mitataan suoritusajat käskyille <echo a>" ja <printf "%s\n"> niin voi olla varma että niiden suhteellinen ero on oikea.
Koska tämä ratkaisee sen, kuinka skripti kannattaa kasata voi ajat esittää myös absoluuttisina joten seuraava pitää paikkansa:

Echo:kin on nopea kuten itse voit todeta käskyllä: merkkijono=$(cat /dev/urandom | base64 | head -c 6000); time echo $merkkijono
joka muodostaa ensin 6000-merkkisen sanan ja tulostaa sen sitten ajoitettuna: echo kestää koneen nopeudesta riippuen 0-2ms. Siitä tulee siis alle 0.15 mikrosekuntia kirjainta kohti - echo-käskyn kutsu kylläkin kestää noin mikrosekunnin.
- printf on hieman hitaampi.
- muuten 0 tarkoittaa: alle 0.5 -> TIME-käsky osaa pyöristää oikein.

yksinkertainen vertailu elikä esimerkiksi: [[ $a = 1 ]] && tee_jotakin  kestää noin 5 mikrosekuntia. Nopea on myös käsky:
echo ${muuttujanimi//jotakin/joksikin_muuksi} kestää luokkaa .1ms+7mikrosekuntia/kirjain joten onhan tuo yleensä nopeampi kuin sed joka vie putkituksineen noin 3ms.
- mutta kun ei käsitelläkään tekstijonoja vaan tiedostoja niin BASH käsky on:
Koodia: [Valitse]
readarray a < tiedosto && echo -e "${a[*]//mikä/miksi}"
 
ja vastava sed-käsky:
Koodia: [Valitse]
sed 's/mikä/miksi/g' tiedosto
ja BASH-käsky onkin lyhyillä tiedostoilla nopeampi ja pitkillä tiedostoilla hitaampi kuin sed-käsky.
- tästä selvisi sekin että * on merkittävästi nopeampi kuin @
- suoritettaessa perättäisten käskyjen ajoitusta täytyy ajoitettavista käskyistä muodostaa ryhmä joka täytyy laittaa aaltosulkuihin näin:
Koodia: [Valitse]
{ käsky1; .... ; käskyN;}  # huomaa laittaa tuo ; ennen viimeistä aaltosulkua
näin siksi että uuden prosessin muodostaminen kestää vajaan millisekunnin. Siis käskyjä ei pidä ryhmittää näin: ( käskyryhmä ) sillä tapa lisää millisekunnin suoritusaikaan ja vaikka se perii muuttujien arvot muodostajaltaan niin se ei palauta niitä - joskus harvoin tästä on kyllä etua. Käskyjen ryhmittäminen kannattaakin yleensä tehdä näin: { käskyryhmä; } sillä siten ryhmittäminen vie ylimääräistä aikaa noin 2 mikrosekuntia ja muuttujat myös palautetaan (tai pikemminkin ne ovat samoja eikä niitä tarvitse palauttaa).

Koneessa käskyjen suoritusajat ovat epämääräisiä; joskus käsky kestää 2ms ja joskus se kestää 5ms. Ja alta puoli-millisekuntia kestävät suoritusajat pyöristetään nollaksi. Eikä aika ole tuonut tähän helpotusta eikä varmaan tuokaan - syyn tähän voisi esittää useammallakin tavalla:
- minkä intel tuo sen softa vie
- Torvalds esitti homman näin: "Linux ydin on turvonnut". Siis syyllinen on käyttöjärjestelmä eikä BASH. Saattaa käydä jopa niin että epämääräisyys alkaa kasvamaan sillä samalla kun ydin turpoaa niin sen ylläpitäjät vähenee eivätkä kerkiä tekemään aikaisemmin tehtyyn uusien lisäyksien vaatimia muutoksia.

Käskyn suoritusaikaa ei voi mitata mittaamalla kuinka kauan perättäisten samojen käskyjen suoritus kestää, sillä käskyä suoritettaessa sen koodi kännetään cacheen josta se toteutetaan.
- siis tällätavoin ei saa käskeä: for (( n=1; n<=1000; n++ )); do käsky; done
sillä kun seuraava käsky on sama kuin edellinenkin niin sen koodi on jo cachessa ja suoritus on tosinopea. Käskyn suoritusaika tuleekin mitata käskemällä esimerkiksi:
for (( n=1; n<=1000; n++ )); do { käsky; sleep .001}; done jolloin käsky-koodi joudutaa jokakerralla kääntämään uudestaan - loppuajasta täytyy sitten vähentää tuo "sleep .001":n aika.
- noita aikaisemmin esitetettyjä käskyjen suoritusaikoja on korjattu tämän mukaisiksi.


 
« Viimeksi muokattu: 19.10.15 - klo:07.28 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #208 : 09.10.15 - klo:20.26 »
Mitä ovat ne BASH:in valmiiksitehdyt funktiot joista saa listan käskyllä: declare ? Senverran olen katsonut että niitä voi käyttää mikäli "arvaa" kuinka niitä kutsutaan huolimatta siitä ettei niissä ole sanaa: function.
Listan pelkistä otsikoista saa käskyllä: declare | grep ()  . Löytyykö niille jostain käyttöohje ?

Niitä on 190klp. Taitavat olla paketin bash-completion määrittelemiä; bash-completion on tehty jottei systeemien ylläpitäjät joutuisi kirjoittamaan niin paljon.
Tosin ei pidä sekoittaa pakettia bash-completion toimintoon bash-completion. Tosin kyse ei ole täysin eri asiasta muttei siis samastakaan.
https://wiki.debian.org/Teams/BashCompletion  .
**
Aloinpa ratkaista ongelmaa johon ei edes kunnolisia osittaisratkaisuja osattu esittää. Lopetin tuon iäisyys-projektin aika nopeasti, silä sain iäisyys-projektista jo sen minkä takia siihen ryhdyinkin, nimittäin huomion että parhaan tuloksen saa kun käyttää mahdollisimman yksinkertaisia menetelmiä, esimerkiksi perus-käskyjä.
- paras tulos: nopein, koodiltaan selväpiirteinen eikä vääriä positiivisia- eikä negatiivisia harha-iskuja.
- se ongelma yksinkertaisissa ratkaisuissa on, että ihminen haluaa ajatella monimutkaisesti ja ei edes suostu ajattelemaan yksinkertaisesti; ihminen haluaa myöskin käyttää voimaa silloinkin kun siitä on haittaa.

Esimerkiksi kysypä kuinka tulostetaan tiedoston viimeinen rivi. Vastauksena tarjotaan ilmanmuuta: cat tiedosto | tail -1 . Kuitenkin helpompaa olisi käskeä: tail -1 tiedosto . Sen suoritusaikakin on pikkuisen nopeampi.
- mutta yleensä kysymyksellä tarkoitetaankin: kuinka tulostetaan tiedostosta viimeinen rivi jolla on tekstiä? Sen saa helpoiten käskyllä:
Koodia: [Valitse]
awk '{ if (NF > 0) n = $0 }END{print n}' tiedosto
Se on muuten nopeinkin.
**
Matriisi voidaan määritellä kahdella tavalla:
Koodia: [Valitse]
MatriisinNimi=(5 6)                      # jolloin BASH määrää itse indexit alkaen nollasta. Toinen tapa:
MatriisinNimi[0]=5; MatriisinNimi[1]=6   # indexit voi tällöin määrätä itse ja aivan mielivaltaisesti.
- kummallakin esitystavalla BASH:in sisäinen esitystapa on samanlainen:
Koodia: [Valitse]
MatriisinNimi=([0]="5" [1]="6")- BASH:issa ei ole kaksi- tai moniulotteisia matriiseja. Niitä voi tavallaan kyllä kuvata joko tavallisessa- tai assosiatiivisessa yksiulotteisessa matriisissa.
- matriisia voi käyttää vaikka sitä ei määrittelisikään, mutta jos matriisin määrittelee niin se tehdään näin: declare -a matriisi    # siis indexi-rajoja ei määritellä.
- BASH tuntee myös matriisin jonka osoite voi olla tekstiä. Tällainen assosiatiivinen matriisi määtitellään näin: declare -A matriisi.   
- matriisin jäsen tulostetaan esimerkiksi: echo ${matriisi[indexi]}: siis esimerkiksi: echo ${matriisi[2]
- matriisin kaikki jäsenten arvot voi tulostaa käskyllä:
Koodia: [Valitse]
echo ${a[@]}           # jolloin ne kirjoitetaan peräkkäin välilyöntien erottamina
echo -e ${a[@]/%/\\n}  # jolloin  arvot tulostuvat alekkain. Tätä voi käyttää testaamiseen: mikäli kyseessä on sittenkin tekstijono niin se tulostuu edelleen yhteen riviin.
- $muuttuja on sama kuin ${muuttuja[0]}.
- Matriisin käsittelemiseksi voi käyttää ohjelmaa nimeltään awk. awk:in toimintaa on paras kuvata esimerkeillä:
Koodia: [Valitse]
Rivisummien laskeminen  : cat matrix | awk 'BEGIN {j=0} {for(i=1;i<=NF;i++) t+=$i; print "rivin",j" summa: ",t; t=0 j++}'
Sarakesummien laskeminen: cat matrix | awk  'BEGIN {}{for(i=1;i<=NF;i++) s[i]+=$i} END {for(i=1;i<=NF;i++) print "sarakeen",i," summa=" s[i]}'
- mihin tahansa muuhunkin hommaan voi tehdä awk-skriptin. Ne toimivat nopeasti. Awk:iin tottunut koodaaja kodaakin sillä erittäin nopeasti, mutta
  toisaalta awk-koodaus on jäämässä unhoon vielä nopeammin kuin BASH.
- awk osaa myös tieteellisen esitysmuodon, desimaalit, sinit sun logaritmit ... esimerkiksi:
Koodia: [Valitse]
awk 'BEGIN{print .11e1+2.2}'    # siis tämä lause kirjoitetaan ihan yksikseen
**
BASH:ista kertovat oppaat eivät edes mainitse BASH:in tärkeintä käskyä enkä ole tajunnut että asiahan kaipaa selvittämistä: kun BASH:in koodi-rivillä lukee yksikseen: $muuttuja  niin se tulkitaan käskyksi: mene suorittamaan funktiota jonka nimi on muuttujassa ja suoritettuasi funktion palaa tätä käskyä seuraavan käskyyn. Kun BASH:ia alkaa käyttämään ei funktioita tehdä itse joten käskyllä ei olekaan alussa oikein mitään mieltä. Mutta kun funktioita alkaa käyttämään tulee käskystä nopeasti peruspilari.

Mikäli muuttuja on lisäksi assosiatiivisen matriisin jäsen niin saadaan samalla tehtyä 'koodimuunnos'. Lisäksi muuttujan eteen voidaan laittaa ehto: [[ ehto ]] && $muuttuja
- myös sisäisiä funktioita voi kutsua: apu=ls; $apu. Muuten parametreja annettaessa pitää olla lainausmerkit: apu="ls *"; $apu
**
Ei ole koskaan tullut mieleen että se ettei find-ohjelma hyväksy regex:iä ei tarkoita sitä ettei find-ohjelma tuntisi regex:iä. Mutta findille täytyy kertoa se: find $HOME -regex '.*[a-z]'
- käsky selväsanaisena: etsi kotikansiostasi tiedostot joiden nimissä on ainakin yksi pieni kirjain. 
**
Samanlaisten rivien poistamiseen BASH:issa on käsky uniq, mutta tekstinkäsittelyyn se on jokseenkin sopimaton. Sensijaan kannattaa käyttää seuraavia awk-käskyjä:
Koodia: [Valitse]
awk '!x[$0]++'                                                # poistaa tiedostosta duplikaattirivit
awk '{ gsub(/^[ \t]+|[ \t]+$/, ""); print }' | awk '!x[$0]++' # poistaa ensin välilyönnit ja tab:it rivien alusta ja lopusta - ja sitten vasta poistaa duplikaattirivit
awk '!NF || !x[$0]++'                                         # poistaa tiedostosta duplikaattirivit jättäen tekstin tyhjät rivit rauhaan
**
Dokumentti muodostuu kappaleista. Eri kappaleissa saa yleensä olla samanlaisia rivejä joten vain kussakin kappaleessa sen omat duplikaatti-rivit voi poistaa. Tämänvuoksi dokumentin jokainen kappale täytyy erottaa omaan tiedostoonsa, suorittaa niille duplikaattien poisto ja koota sitten tiedostot yhteen. Tämä täytyy tehdä kahdessa vaiheessa sillä ensimmäinen vaihe tehdään tilanteesta riippuen erilailla:
1. Kun kappaleen alkurivillä on jotakin kappaleelle ominaista; esimerkiksi koodissa sana BEGIN.
Koodia: [Valitse]
sed  '/kappaleelle_ominaista/ilisätty_rivi' tiedosto_jossa_kappaleet_ovat > /tmp/delmee02. Kun kappaleiden välissa on tyhjä rivi:
Koodia: [Valitse]
sed 's/^$/lisätty_rivi/g' tiedosto_jossa_kappaleet_ovat > /tmp/delmee0
Näin aikaansaatu aputiedostosta kappaleet sitten jaetaan omiin tiedostoihinsa:
Koodia: [Valitse]
awk -v RS="lisätty_rivi" '{ print $0 > "/tmp/delmee"NR }' /tmp/delmee0
kappaleet liitetään yhteen tiedostoksi( perättäiset tyhjät rivit supistetaan yhdeksi):
Koodia: [Valitse]
rm -f /tmp/delmee; n=1; while true; do [[ -f /tmp/delmee$n ]] && { awk '!NF || !x[$0]++' /tmp/delmee$n > apu; cat -s /tmp/delmee apu >> /tmp/delmee ; ((n++));} || exit; done
lopuksi siistitty tiedosto on paikassa: /tmp/delmee
- roskanpoisto puuttuu ja epämääräisyyksiäkin on mutta toimii silti.
**
BASH_REMATCH:ista puhutaan paljon mutta ymmärrettäviä esimerkkejä ei juurikaan esitetä. Tarkoitan sellaista esimerkkiä jossa tulostetaan BASH_REMATCH, BASH_REMATCH[1], BASH_REMATCH[2], BASH_REMATCH[3] ... sotkematta asiaan mitään muuta jotta asiasta tulisi kunnollinen rautalankamalli jota ei voi käsittää väärin.

Yhden esimerkin löysin ... no jaa, tein ziljoona korjaus-yritystä ennenkuin sain toimimattomasta toimivan. Ja tämänjälkeen alkoi löytyä muitakin  kun tiesi mitä pitää etsiä. Esimerkki on tämmöinen:
Koodia: [Valitse]
#!/bin/bash
  [[ "012 a34567b89cdefgh" =~ ([a-z])[0-9]*([a-z])[0-9]*([a-z]) ]] && {
      # REMATCH tehdän kohdassa:[[ "012 a34567b89cdefgh" =~ ([a-z])[0-9]*([a-z])[0-9]*([a-z]) ]]."
      # Regex muodostuu useasta regex:stä. Tässä esimerkissä on kolme regex:ää ympäröity kaarisuluilla."
      # mikäli sanassa mikään ei täytä koko regexää niin ei tulosteta mitään; siis kaikille regex:än BASH_REMATCH-kentille pitää löytyä arvo.
      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]}" ;}

Esimerkiksi:[[ "teksti_112233.txt" =~ (.*[[:punct:]])([0-9][0-9])([0-9][0-9])([0-9][0-9])(.*) ]] && echo ${BASH_REMATCH[1]}${BASH_REMATCH[4]}${BASH_REMATCH[3]}${BASH_REMATCH[2]}${BASH_REMATCH[5]}  # tulostaa: teksti_332211.txt
**
Yleiskäyttöinen lukurutiini. Kokeilua varten lisäsin monia ominaisuuksia joiden kanssa on hyvä kokeilla mihin tämä pystyy; käytössähän voi turhat ominaisuudet poistaa.
- mitäpä olet mieltä määrittelystä: eval eval      # siis peränjälkeen kaksi eval:ia.
Koodia: [Valitse]
#!/bin/bash
# petteriIII 31.10.2015
# declare -A jotakin # mikäli halutaan muodostaa assosiatiivisia matriiseja täytyy tämä lause olla. Mutta jos assosiatiiviset alkavat toimia niin tavalliset lakkaavat toimimasta.

function tekijät () { apu=$( factor $1 ); echo $apu | grep -Po '(?<=:).*' ;}

function permutaatiot () { eval echo $(printf "{%s,}" "$@"); }    # kutsu-esimerkki: permutaatio a b c d

function TulostaMatriisi () {
[[ $(eval echo \${$1[*]}) ]] && {
echo -n $1':n arvot     : '; eval echo \${$1[*]}   # arvojen väliin tulostuu välilyönti
echo -n $1':n osoitteet : '; eval echo \${!$1[*]}  # osoitteiden väliin tulostuu välilyönti.
} | column -t; echo ;}


function lue_näppis {
local -n array=$1 # tämä lause määrää että kun tehdään array:lle jotakin niin se siirtyy myös siihen jonka nimi on tullut parametrissa $1.
                  # jos array:ssa ei ole kuin jäsen 0 niin se ei eroa mitenkään array-nimisestä tavallisesta muuttujasta - se voidaan tulostaakin käskyllä: echo $array.

echo "testata voi kirjoittamalla jonkun seuraavista:"
echo '- tekstijonon jossa saa olla välilyöntejä tai toisia tekstijonoja, esim: 1 2 "3 4" 5 . Jos matriisin jäsenessä on välilyöntejä on jäsen kirjoitettava lainausmerkkien väliin.'
echo '- tekstijonon voi määritellä myöskin:{1..5} tai:{1..5}{1..5} tai: {$alku..$loppu}'
echo '- numerot käsitellään tekstijonoina joten niissä voi olla mitävaan'
echo '- matriisi kirjoitetaan näin:(1 2 3) mikäli tyydytään automaattisesti määrättäviin osoitteisiin, mutta mikäli osoitteet halutaan antaa itse täytyy kirjoittaa: ([0]="1" [1]="2" [2]="3")'
echo '- matriisin voi määritellä näinkin: ({1..5})  tai: ({1..5}{6..10})  tai: ({1..5}1 2 3) mutta tuollaisissa määritelmissä alkaa tulla eteen se että kone tulkitsee ja sinä vikiset.'
echo '- pääohjelman muuttujia voi käyttää, joten mikäli alku ja loppu ovat määriteltyjä niin ihan ihan käypä käsky on: ({$alku..$loppu}{$loppu..$alku})'
echo '- tulosteessa tekstijonon erottaa matriisista siitä, että mariisin jäsenistä tulostetaan myös osoitteet.'
echo '- assosiatiivinen matriisi täytyy aina kirjoittaa näin: ([bbb]="eee" [ccc]="fff" [aaa]="ddd" )'
echo '- permutaatiot saa tietää tämänmuotoisella käskyllä: permutaatiot a b c d e'
echo '- luvun tekijät saa tietää tämänmuotoisella käskyllä: tekijät 256'
echo '- myös tiedoston sisällöstä voi muodostaa matriisin:  /etc/fstab tai:/boot/grub/grub.cfg'
echo '- vanhoja vastauksia voi selailla nuolinäppämillä ja ctrl-r toimii normaalisti.'
echo '- editointi toimii kokoajan'
echo 'kirjoita jotakin: '

HISTFILE=~/.user_history # tämän skriptin toiminta-ajaksi siirretään BASH:in oma historia-toiminta tälle skriptille. Esimerkiksi "ctrl r" toimii niinkuin kaikki muukin.
set -o history 
read -e array ; echo $array >> ~/.user_history     
HISTFILE=~/.bash_history
set -o history

[[ $(echo $array | grep tekijät) ]] && array=($($array))      # array saa arvot funktion tekijät      kutsusta - parametrit ovat kutsussa
[[ $(echo $array | grep permutaatiot) ]] && array=($($array)) # array saa arvot funktion permutaatiot kutsusta - parametrit ovat kutsussa
[[ ${array:0:1} = '(' && ${array:${#array}-1} = ')'  ]] && { apu=$(echo $array | grep -o {.*}); apuu=$(eval echo $apu); array=$(echo $array | sed "s/$apu/$apuu/g"); eval array=$array ;}
[[ ${array:0:1} = '{' && ${array:${#array}-1} = '}'  ]] && array="$( eval eval echo $array)"
[[ ${array:0:1} = '/' ]] && readarray array < $array && echo -e "${array[*]}"
}

# pääohjelmassa oltaessa:
alku=1; loppu=5
lue_näppis jotakin # mikäli halutaan lukea jo olemassa oleva matriisi niin sen nimi tulee paikalle:jotakin tässä ja seuraavassa lauseessa jos seuraavaa lausetta edes tarvitaan.
[[ $(echo ${!jotakin[@]} | cut -sd' ' -f 2) ]] && TulostaMatriisi jotakin || echo $jotakin # [[ $(echo ${jotakin[@]} | cut -sd' ' -f 2) ]] on testi siitä onko kyseessä matriisi
# jos on luettu tiedosto on sen sisältö matriisissa jonka nimi tässä on:jotakin , mutta sen tilalle voi kirjoittaa halutun matriisin nimen.
**
Tein numeroiden syöttämistä varten skriptin jota voi käyttää kun näpytellee PC:lle numeroita sen näppäimillä. Se tarkistaa syötettäessä kokoajan että syötettävä merkki on sellainen että se on mahdollinen luvun silläkohdalla ja mikäli ei ole niin ei hyväksy merkkiä. Esimerkiksi kirjaimimia ja erikoismerkkejä ei hyväksytä koskaan, e:n perässä ei voi olla desimaalipistettä ...
Koodia: [Valitse]
#!/bin/bash
# numeron lukeminen näppäimistöltä siten ettei merkkejä kirjoitettaessa merkkiä hyväksytä ellei merkki ole mahdollinen silläkohtaa.
function aloitus () {
numero=''
sallitutmerkit=+-.e0123456789
clear
alku=1 ;}

aloitus
while true; do
#stty echo # oli koe - onko merkitystä
  read -rsN 1 merkki
  [[ $(echo $merkki | od | awk '{print $2}') = 005177 ]] && aloitus && continue # BS:n painaminen pyyhki näytön ja aloittaa alusta
  merkki=$(echo $merkki | tr , .)                                       # desimaalipilkkua ei hyväksytä vaan muutetaan se pisteeksi 
  [[ -z $merkki ]] && exit                                              # enteriä painettu
  [[ $merkki = . && $alku -eq 0 ]] && continue                          # pistettä ei hyväksytä e:n jälkeen
  [[ $merkki = e && ! $(echo $numero | tr -dc 012346789) ]] && continue # e poistetaan mikäli yhtään numeroa ei vielä ole annettu
  [[ $( echo $merkki | tr -d $sallitutmerkit) ]] && continue            # mitään merkkiä ei hyväksytä ellei se ole sallittu. Testi jotenkin vuotaa, siksi ensimmäinen poisto 
  (( alku++ ))
  sallitutmerkit=$(echo $sallitutmerkit | tr -d +-)                     # + ja - sallitaan vain alussa
  [[ $merkki = . ]] && sallitutmerkit=$(echo $sallitutmerkit | tr -d .) # pistettä ei tämänjälkeen hyväksytä.
  numero=$numero$merkki
  [[ $(echo $numero | tr -dc e ) ]] && sallitutmerkit=$(echo $sallitutmerkit | tr -d e. ) && alku=0 # vain yksi e sallitaan; ja kun e on annettu ei desimalipistettäkään enää hyväksytä
  [[ ${numero:${#numero}-1:1} = e ]] && sallitutmerkit="+-"$sallitutmerkit                          # heti e:n jäljessä voi taas olla yksi + tai -
  clear; echo -n $numero
done
**
Taas kertaalleen jouduin tekemään uuden testin IPv4-osoitteille sillä monimutkaisemmat regex:ät tökkivät, BASH_REMATCH ei innosta ja yleensä testit lisäksi vuotavat. Hyvä IP-testi onkin ihan perus-käskyistä väsätty ja sellainen mikä ei päästä läpi mitään jossa on fyysistä vikaa muttei myöskään anna vääriä hälytyksiä:
- testi ei välitä siitä mikä merkki toimii erottimena aliosoitteiden välissä mikä on ehkä hyväkin. Mutta se ei myöskään tarkista onko osoitteessa turhia merkkejä. Mutta toisaalta ipv4 onkin jo historiaa.
Koodia: [Valitse]
function ipv4testi () { for n in {0..255}; do echo $1 | grep -wo 0*$n; done | [[ $(wc -l) = 4 ]] ;}; ipv4testi 255:0:012:56 && echo ip hyvä || echo ip kelvoton
**
IPv6-osoitteen tarkastaminen:
Täydellinen IPv6-osoite on esimerkiksi: 2001:0db8:0000:0000:0000:ff00:0042:8329
- siten IPv6-osoitteessa voi olla korkeintaan 7kpl :-merkkien eroittamaa ryhmää ja hexdigitejä kussakin ryhmässä korkeintaan 4
- ryhmiä eroittava merkki on aina : joten muita merkkejä ei saa olla kuin : ja hexdigit:ejä

IPv6-osoiteen tarkistamisessa yhdistetään nuo kolme vaatimusta:
Koodia: [Valitse]
function ipv6testi () { [[ $(echo $1|tr -d :[[:xdigit:]]) = '' && $(echo $1|tr -dc :|wc -c)<8 && $(echo $1|grep -o '[[:xdigit:]]*'|wc -L)<5 ]];}; ipv6testi 9901:0db8::ff:42:29  && echo kunnossa || echo tökkii
- siis tuon: 9901:0db8::ff:42:29 tilalle kirjoitetaan testattava ipv6-osoite
- huomioi että vain fyysiset virheet selviävät näin ja loogiset virheet jäävät.

Tarkistuksessa täytyy lisäksi huomioida:
- jokaisesta ryhmästä etunollat voi poistaa: 2001:db8:0:0:0:ff00:42:8329
- perättäiset 0:t voi yhdistää kahdeksi :-merkiksi: 2001:db8::ff00:42:8329
- esimerkiksi loop-back osoite: 0000:0000:0000:0000:0000:0000:0000:0001 voidaan kirjoittaa ::1
   
 


 
« Viimeksi muokattu: 19.01.16 - klo:22.23 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #209 : 04.11.15 - klo:03.11 »
Tiedoston listaaminen korostaen kaikki siinä olevat ipv4-osoitteet

Paras ratkaisumenetelmä ipv4-osotteiden löytämiseksi on awk-perustainen, eikä regex-perustainen.
- täydellinen regex jolla voi tarkistaa ipv4:n on "kolme riviä pitkä hirviö". On kylläkin olemassa sovelluksia jotka tarkistavat ipv4-osoitteen käyttäen lyhyitä regex:iä. Sama tarkastus mikä tässäkin on, eikä edes nopeampi. Eikä niitäkään  voi käyttää tiedoston seulomiseen vaan ne vaativat tiedoston seulomiseen  samantapaiset kilkkeet kuin tässäkin on.

Koodia: [Valitse]
#!/bin/bash
# tarkoituksena on listata kotikansiosta tiedosto-niminen tiedosto siten että siinä olevat ipv4-osoitteet ovat tulostettu punaisella muun tekstin ollessa mustaa. 51
export GREP_COLOR="01;31" # red; 32=green; 33=yellow; 34=blue; 35=purple

# ensiksi funktio joka tulostaa sille syötetyn tekstin mikäli se on kelvollinen ipv4-osoite
function tulosta_kelvollinen_ipv4 () { [[ $(echo $1 | grep [a-zA-Z]) ]] || echo $1 | awk -F: 'NF==4 && $1<=255 && $1>=0 && $2<=255 && $2>=0 && $3<=255 && $3>=0 && $4<=255 && $4>=0' ;}

# etsitään tiedostosta ne numerosarjat jotka saattaisivat olla ipv4 osoitteita ja talletetaan niistä muodostuva lista kovalevylle:
grep -Po '[[:alpha:]]*[0-9]*[:0-9]*' ~/tiedosto > /tmp/delme1

# tarkistetaan listan jäsenet yksitellen ja tehdään kovalevylle uusi lista hyväksytyistä:
echo '' > /tmp/delme2; while IFS=$'\n' read -r rivi; do tulosta_kelvollinen_ipv4 $rivi >> /tmp/delme2; done < /tmp/delme1

# listataan tiedosto merkiten siihen ipv4-osoitteiksi hyväksytyt:
grep --color -E $(cat /tmp/delme2 | tr '\n' \|) ~/tiedosto
read -p 'paina enter jatkaakseesi'

**
Tiedoston listaaminen korostaen kaikki siinä olevat ipv6-osoitteet

Ipv6-osoitteiden suodattaminen tiedostosta onnistuu samantapaisella keinolla kuin ipv4:nkin. Ja tätä menetelmää onkin pakko käyttää, sillä ipv6:lle ei ole olemassa mitään kunnollista regex:ää. Se raaka-lista joka tässä muodostetaan regex:än avulla tiedostossa mahdollisesti olevista ipv6-osoitteista käsittää useampia harha-iskuja kuin oikeaan osuneita.
- muuten vaikka näissä menetelmässä käytetäänkin kovalevy-operaatioita eivät ne hidasta toimintaa, sillä skriptin kannalta toimitaan kokoajan RAM:missa olevissa buffereissa.
- ipv6 osoitteissa voi lopussa olla /64 mikä merkitsee kuulumista joukkoon. Tarkemmin: http://www.tutorialspoint.com/ipv6/ipv6_subnetting.htm. Tällaisista merkitään vain osoiteosa
Koodia: [Valitse]
ja
#!/bin/bash
# tarkoituksena on listata kotikansiosta tiedosto-niminen tiedosto siten että siinä olevat ipv4-osoitteet tulostetaan punaisella muun tekstin ollessa mustaa.
export GREP_COLOR="01;32" # green; 31=red; 33=yellow; 34=blue; 35=purple

# ensiksi funktio joka kertoo onko sille syötetty varmasti ipv6-osoite:                       
function ipv6testi () { [[ ! $(echo $1| tr -d :[[:xdigit:]]) ]] && [[ $(echo $1|tr -dc :|wc -c) = 7 || $(echo $1 | grep :: ) ]] && [[ $(echo $1|grep -o '[[:xdigit:]]*'|wc -L) < 5 ]] ;}


#etsitään tiedostosta ne jotka saattaisivat olla ipv6 osoitteita ja talletetaan ne levylle:
grep -Pow '::[:[:xdigit:]]*|[:[:xdigit:]]*' ~/tiedosto > /tmp/delme1

# tarkistetaan listan jäsenet yksitellen ja tehdään kovalevylle uusi lista hyväksytyistä:
echo '' > /tmp/delme2; while IFS=$'\n' read -r rivi; do ipv6testi $rivi && echo $rivi >> /tmp/delme2; done < /tmp/delme1

# listataan tiedosto merkiten siihen ipv6-osoitteiksi hyväksytyt:
grep --color -E $(cat /tmp/delme2 | tr '\n' \|) ~/tiedosto
read -p 'paina enter jatkaakseesi'
**
Myöd BASH:issa tekstilohkoja voi käsittellä mitenkä hyvänsä jos pystyy pukemaan tehtävän sanoiksi. Näitä tämäntyyppi:siä käskyjä on lukemattomia, niiden muodostaminen on hidasta eikä ole mahdollista muistaa niistä oikeastaan mitään. Käskyvarasto onkin täysin pakollinen ja täytyy olla jokin keino löytää sieltä haluamansa. Jos käskyvapetterurastoa ei ole niin käy juuri niin kuin BASH:ille on käynyt. Muut kielet porskuttaa juuri sentakia että niissä nuo varastot on hoidettu edes jollaintavoin. Seuraavat käskyesimerkit eivät oikeastaan ole esitetty senvuoksi että ne olisivat hyödyllisiä, vaan senvuoksi että tuo käskyvaraston pakollisuus selventyisi. Käskyesimerkkkejä:

1. Mikäli teksti muodostuu lohkoista niin todennäköisemmin haluttaessa poistaa samanlaiset rivit halutaan poistaa toisinnot vain kunkin lohkon sisällä. Siihen sopiva käsky on:
ensin jaetaan tiedosto ali-tiedostoiksi tyhjien rivien kohdilta:
Koodia: [Valitse]
awk -v RS='' '{ print $0 > "/tmp/delmee"NR }' ~/koe2
sitten käsitellään muodostetut ali-tiedostot ja kootaan tulos taas yhteen:
Koodia: [Valitse]
echo '' > /tmp/delmee_kokonaisuus; n=1; while true; do [[ -f /tmp/delmee$n ]] && awk '{$1=$1} !x[$0]++' /tmp/delmee$n >> /tmp/delmee_kokonaisuus || exit; ((n++)); done
   
- lopputulos on tiedostossa: /tmp/delmee_kokonaisuus

2. Kun halutaan tulostaa tiedostosta /boot/grub/grub.cfg ne BEGIN- ja END-rajoittimien väliset lohkot, joiden jollakulla rivillä lukee: source niin käsketään:
Koodia: [Valitse]
sed -n '/BEGIN/,/END+/ { p }' /boot/grub/grub.cfg | awk -v RS='' '/source/ {print $0"\n"}'
]
**
BASH:ista puuttuu käskyjä - tai ei oikeastaan puutu vaan käskyt on määritelty hassusti: esimerkiksi loopeista puuttuu helppokäyttöisin käsky: kertaa x tee jotakin
Tottamaar on hölmöä ohjelmoida looppi näin:
Koodia: [Valitse]
function Kertaa () { MontakoKertaa=$1; shift; for n in $(seq $MontakoKertaa); do $@; done;}; Kertaa 1000000 echo $((1+1)
- toki funktion voi laittaa koneessaan tiedostoon bashrc jolloin se on aina käytettävissä.
**
Oishan se kiva tietää mitä omassa koneessa tapahtuu? Yksi tähänsopiva käsky:
Koodia: [Valitse]
clear; echo -n 'viimeksi käsitellyt 40 kotikansiosi tiedostoa esittämättä piilotiedostoja:';find ~ -type
f  -printf '%T@ %p\n' | grep -v '/\.'| sort -n | tail -40 | cut --output-delimiter='  ' --complement -c 11-$((29+${#USER}))
**(dd of=~/kansio1)
Vaikka loopit ovat oleellisia rakennusosia myös BASH-skripteissä ei BASH-koodiin saa looppeja tehdä, vaan aina tulee käyttää sitä "automaatti-looppia" joka on awk, sed, grep ja useissa BASH-käskyissä sisäänrakennettuna. Sillä BASH-koodilla toteutetut loopit hidastavat skriptiä usein kymmenesosaansa ja koodia saa näpytellä lisää monta riviä.
Myös nämä "automaatti-loopit" osaavat hyödyntää regex:iä.

Esimerkiksi tiedoston jokaisen rivin ensimmäisen kentän tulostaminen:
Koodia: [Valitse]
awk '{print $1}' tiedosto

tai tiedostossa jokaisella rivillä jokaisen numeron muuttaminen merkiksi: ¤:
Koodia: [Valitse]
sed 's/[0-9]*/¤/g' tiedosto
tai kun halutaan tulostaa matriisin pisimmän jäsenen pituus voi toteutus olla seuraavanlainen:
Koodia: [Valitse]
function matriisin_pisin_jäsen () { echo $1 | tr ' ' '\n' | wc -L ;}; matriisi=(aaaa a aaaaa aa); matriisin_pisin_jäsen "$(echo ${matriisi[*]})"
**
Miksi vältän pakettivaraston ohjelmien käyttämistä:

Kaikki pakettivaraston ohjelmat ovat samanlaisia käskyjä ja jotkut niistä on vain ladattu jo boottaamisen yhteydessä - sekin vielä vaihtelee että mitkä paketit ovat milloinkin sellaisia että Ubuntun kehittäjät pitävät niitä tarpeeksi arvokkaina ladattaviksi boottaamisen yhteydessä. Elikkä usein monien pakettivaraston pakettien käyttämistä ei edes voi välttää.

Mutta suuri osa niistä ohjelmista joita ei ole ladattu koneeseen jo boottaamisen yhteydessä ovat jotenkin huonoja: esimerkiksi ne ainoastaan esittävät käyttöjärjestelmän keräämiä tietoja teoriassa paremmassa ulkoasussa. Mutta joskus esitys onkin huononnus ja paketti lisäksi toimii virheellisesti. Tällaisia paketteja tarkoitan sillä jos pakettivaraston ohjelma ei toimi oikein niin asialle ei ole tehtävissä mitään. Esimerkiksi levyn sarjanumeron esittäminen: käsky: lshw on todella hidas ja pakettivaraston miljoona muuta tähän tarkoitettua ohjelmaa toimivat kaikki poikkeuksetta jossain suhteessa väärin ja esittävät tiedot omalla tavallaan eikä niiden esitystapaa voi helposti muuttaa. Koska käyttöjärjestelmä kerää jokatapauksessa kaiken tarvittavan tiedon ei "itsetehdyn" skriptin tarvitse tehdä muuta kuin esittää ne kerätyt tiedot. Esimerkiksi tuo sarjanumeron esittäminen:
Koodia: [Valitse]
(dd of=~/kansio1)

function levyid () { apu=$(ls -lh /dev/disk/by-id | grep -v wwn- | grep $1$ | awk '{print $9}'); echo Liitäntä:${apu%%-*}; apu2=${apu#*-}; echo Levytyyppi:${apu2%_*}; echo Sarjanumero:${apu##*_};}; levyid sda
- samantien skripti esittää muutakin asiaankuuluvaa.
- pakettivaraston ohjelmien räätälöiminen on yleensä mahdotonta, mutta koska skriptit ovat aina lähdekoodia on niiden mukaeleminen aina mahdollista.
**
Käyttöjärjestelmä ei kirjoita kovalevylle suoraan, vaan kirjoitettava suuntautuu ensiksi RAM:miin ja sieltä käyttöjärjestelmän toimesta levylle. Siten vaikka levylle tietoa siirtyy levylle aina jokseenkin samalla nopeudella niin levylle kirjoittavan ohjelman kannalta levylle kirjoittaminen on alkuunsa nopeaa mutta jatkuessaan kauemmin saattaa laskea todella pieneksi. Tästä saa selvän kuvan seuraavan käskyn tulosteesta:
Koodia: [Valitse]
cat /dev/zero | pv > ~/delme

tämä kirjoittaa kotikansion tiedostoon nimeltään: delme eikä sen luomisesta tarvitse huolehtia. Siirron lopettamiseksi sulje pääte. Muuten minulla on nopea SSD-levy SATA2 liittimessä ja sillä kirjoitusnopeus muuttuu vielä mielenkiintoisemmin kuin pyörivällä levyllä. USB:lle kirjoitetaan minun koneessani käskyllä:
Koodia: [Valitse]
cat /dev/zero | pv > /media/petteri/tikku/delme
- katso nautiluksella mikä polku johtaa koneessasi muistitikulle - alustetun muistitikun tulee olla kiinni USB:ssä.
- muuten kaikki muutkin tiedonsiirtoa vaativat toiminnot toteutetaan samalla tavalla, esimerkiksi verkosta lataaminen ja paperille tulostaminen.
- anna vaadittaessa se käsky: sudo apt-get install pv
- kovalevy itsekin harrastaa tämänkaltaista toimintaa - silläkin on RAM:mia (levy spekseissä nimenä on Cache) ja se kirjoitaa ensin sinne ja sieltä toinen prosessi siirtää sen levylle.

**
prosessorin yhdelle ytimelle saat sadan prosentin kuorman käskyllä:
Koodia: [Valitse]
dd if=/dev/zero of=/dev/null
prosessorin kahdelle ytimelle saat kummallekin täyden kuorman käskyllä:
Koodia: [Valitse]
dd if=/dev/zero of=/dev/null | dd if=/dev/zero of=/dev/null
- samalla menetelmällä saa täyden kuorman niin monelle ytimelle kuin haluaa. Kuormat tippuu pois kun sulkee päätteen. Jos on varomatun niin prosessori saa lämpöhalvauksen,
- kaikille ytimille saat täyden kuorman käskyllä:
Koodia: [Valitse]
(dd of=~/kansio1)
apu=''; for i in $(seq $(grep -c ^processor /proc/cpuinfo)); do apu=$apu' '$(echo -n dd if=/dev/zero of=/dev/null' | '); done; eval "${apu:0:${#apu}-2}"
- mutta mikäli siirto tapahtuu samalla levyllä voi kåyttää seuraavantyyppistä "salaman-nopeaa" käskyä:

mkdir kansio1 kansio2; for i in img_[0-9]*\.jpg; do ln $i kansio1/$i; ln $i kansio2/$i; done; rm img_[0-9]*\.jpg
 

Toki muitakin keinoja prosessorin rasittamiseksi löytyy. Esimerkiksi käsky:
Koodia: [Valitse]
yes $(yes)
ei kylläkään kuormita ytimiä sataa prosenttia, mutta se alkaa myös syödä RAM:mia ja jos onni on oikeellaan niin se alkaa kuluttamaan swap:piakin. Varsin nopeasti se kylläkin tekee prosessorin olon niin tuskaiseksi että prosessori tappaa koko pääte-prosessin. Mutta kerkiäähän JärjestelmänValvonnassa kuitenkin seurata tapahtumia minuutin verran.

Prosessorin kuormittamiseen on syy: se kertoo aina kuinka järjestelmä kestää rasitusta ja sen avulla voit tarkistaa muuttuuko ytimien kellotaajuus ja toimiiko lämpötilan mittaus.
**
kopioitaessa yksi suurikokoinen tiedosto moneen paikkaan samallakertaa kannattaa käyttää seuraavankaltaista käskyä:
Koodia: [Valitse]
pv tekstiä | tee >(dd of=~/kansio1) >(dd of=~/kansio2) | dd of=/dev/null
- kopioitavia tiedostoja voi lisätä kuinkamonta vaan ja silti kopioinnin edistymisen osoitus koskee koko joukkoa. Uusi kansio johon kopioidaan lisätään lisäämällä ryhmä: >(dd of=~/kansioN)
- kopiointi voidaan suunnata samaĺla kertaa myös USB:lle (ja kaiketi myös verkkoon?)
- kopionti kylläkin kestää yhtä kauan kuin normaalistikin, sillä kopiointia rajoittaa se että usein kaikkien tiedostojen kopiointi kulkee samaa väylää pitkin ja tuo väylähän kyllästyy kopioitaessa mitä tiedostoa hyvänsä. Mutta nopeus kyllä kasvaa mikäli kopiot kulkevat eri väyliä pitkin: (joku monista sata-väylistä)/USB/verkko .... 
**
Kun joutuu korjaamaan konetta jonka koti-kansiossa on sekaisin suuri määrä jpg-kuvia ja tavallisia tiedostoja niin ne kuvat kannattaa siirtää KUVAT-kansioon ja poistaa ne koti-kansiosta. Koska sama sekasotku muodostuu varmasti uudelleen niin kannattaa varautua siirtämään uudet kuvat KUVAT-kansioon sillointällöin. Käytännössä siirto-käsky on esimerkiksi tällainen:
Koodia: [Valitse]
rsync --remove-source-files --info=progress2 -t ~/img_[0-9]*\.jpg ~/KUVAT
**
Jos ne lukemattomat kuvat täytyy siirtää moneen paikkaan niin se onnistuu seuraavantyyppisellä käskyllä:
Koodia: [Valitse]
for i in img_[0-9]*\.jpg; do cp $i kansio1; mv $i kansio2; done
- siihen on hankala saada minkäänlaista etenemisen osoitusta
- mutta mikäli siirto tapahtuu samalla levyllä voi käyttää seuraavantyyppistä "salaman-nopeaa" käskyä tiedostojen siirtämiseksi toiseen/toisiin kansioihin:
Koodia: [Valitse]
mkdir kansio1 kansio2; for i in img_[0-9]*\.jpg; do ln $i kansio1/$i; ln $i kansio2/$i; done; rm img_[0-9]*\.jpg
- siis silloinkin kun mv vie aikaa kaksi tuntia niin edellinen vie 100 ms. Vaikka kyse onkin "silmänkääntö-tempusta" niin käyttäjän kannalta tämä on silti verinen-totuus.
« Viimeksi muokattu: 26.12.15 - klo:13.03 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #210 : 31.12.15 - klo:23.29 »
Aloinpa tonkia muistoja; matikanopettaja oli aikoinaan ihastunut senkaltaisiin ongelmiin kuin "pienimmän yhteisen jaettavan" laskemiseen. Jotkut ovat tehneet siitä BASH-ohjelman joka on kooltaan kunnioitettava ja toiminnaltaan hidas. Samaan tuntuu pääsevän yhdellä nopeatoimisella käskyllä:
Koodia: [Valitse]
L1=10;L2=24;echo $(( $L1*$L2/$(comm -12 <(factor $L1 | tr ' ' '\n' | grep -v :) <(factor $L2 | tr ' ' '\n' | grep -v :))))
- siis PYJ lasketaan luvuista L1 ja L2.
- tämä on ontuen toimiva periaate ja siitä puuttuvat pillit ja viheltimet.
- tätähän BASH on: kokeiluja täysin hyödyttömillä asioilla, monia nöyryyttäviä epäonnistumisia ja itsensä voittamista.
**
samankaltaisesti lasketaan myös "suurin yhteinen tekijä":
- myös tähän lisäsin koodin joka lisää 1:n luvun tekilöiden joukkoon
Koodia: [Valitse]
L1=110;L2=11;echo $(comm -12 <(echo 1;factor $L1 | tr ' ' '\n' | grep -v : | sort) <(echo 1;factor $L2 | tr ' ' '\n' | grep -v : | sort)) | tr ' ' \* | bc
**
jokainen Ubuntu-BASH tuntee ilmanmuuta käskyn: factor   joka jakaa sille annetun luvun alkutekijöihinsä.
 
- ilmeisesti linuxia kasaamassa on ollut todellisia virtuooseja. Ja jotkut ovat saaneet kiksejä kun ovat uskotelleet näille virtuooseille että joku hölmö asia on ehdottoman tärkeä. Tätä paljon pahempi on laskentaohjelma bc:n Besselin funktiot. Tärkeitähän ne saattaa olla, mutta niiden käyttäminen ja tuloksien tulkinta vaativat hyvää matemaatikkoa. Vaikka onhan se niinkin että aika on rapauttanut hyvää hommaa.
« Viimeksi muokattu: 02.01.16 - klo:18.51 kirjoittanut petteriIII »

Tomin

  • Palvelimen ylläpitäjä
  • Käyttäjä / moderaattori+
  • Viestejä: 11433
    • Profiili
    • Tomin kotisivut
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #211 : 01.01.16 - klo:06.17 »
Aika näppärää, tosin tuo ei toimi ihan kaikissa tilanteissa:
Koodia: [Valitse]
$ L1=10;L2=11;echo $(( $L1*$L2/$(comm -12 <(factor $L1 | tr ' ' '\n' | grep -v :) <(factor $L2 | tr ' ' '\n' | grep -v :))))
bash: 10*11/: lauseoppivirhe: odotettiin operandia (virheellinen avainsana on ”/”)
$ L1=45;L2=11;echo $(comm -12 <(factor $L1 | tr ' ' '\n' | grep -v :) <(factor $L2 | tr ' ' '\n' | grep -v : )) | tr ' ' \* | bc
$
Oikea vastaus olisi yksi, mutta sitä ei tulosteta, koska factor ei anna ykköstä vastauksena. Kaikki laskut joissa on alkulukuja heittävät virheen tai eivät palauta mitään. Tosin silloin vastaus on ilmeinen ilman skriptiäkin.

Muokkaus: Näköjään tuo epäonnistuu vielä dramaattisemmin, jos L1 = L2:
Koodia: [Valitse]
$ L1=10;L2=10;echo $(( $L1*$L2/$(comm -12 <(factor $L1 | tr ' ' '\n' | grep -v :) <(factor $L2 | tr ' ' '\n' | grep -v :))))
bash: 10*10/2
5: lauseoppivirhe lausekkeessa (virheellinen avainsana on ”5”)
$
Lisäksi kannattaa testata myös tapaus, jossa nuo kaksi lukua ovat samat alkuluvut.

Muokkaus 2: Hmmh:
Koodia: [Valitse]
$ L1=45;L2=150;echo $(( $L1*$L2/$(comm -12 <(factor $L1 | tr ' ' '\n' | grep -v :) <(factor $L2 | tr ' ' '\n' | grep -v :))))
bash: 45*150/3
5: lauseoppivirhe lausekkeessa (virheellinen avainsana on ”5”)
$

Taitaa tarvita vielä vähän lisää koodia ja testitapauksia. :)

Muokkaus 3: Tämä toimii vähän paremmin tuon suurimman yhteisen tekijän laskemiseen, mutta sekin tekee vielä väärin:
Koodia: [Valitse]
$ L1=45; L2=150;echo $(comm --nocheck-order -12 <(uniq <(factor $L1 | sed 's/^[0-9]*:/1/' | tr ' ' '\n'; echo $L1)) <(uniq <(factor $L2 | sed 's/^[0-9]*:/1/' | tr ' ' '\n'; echo $L2))) | tr ' ' \* | bc
15
$ L1=110; L2=11;echo $(comm --nocheck-order -12 <(uniq <(factor $L1 | sed 's/^[0-9]*:/1/' | tr ' ' '\n'; echo $L1)) <(uniq <(factor $L2 | sed 's/^[0-9]*:/1/' | tr ' ' '\n'; echo $L2))) | tr ' ' \* | bc
1
$
Alemman pitäisi olla 11.

Muokkaus 4: Aika hauskaa:
Koodia: [Valitse]
$ L1=11; L2=11;echo $(comm --nocheck-order -12 <(uniq <(factor $L1 | sed 's/^[0-9]*:/1/' | tr ' ' '\n'; echo $L1)) <(uniq <(factor $L2 | sed 's/^[0-9]*:/1/' | tr ' ' '\n'; echo $L2))) | tr ' ' \* | bc
11
$ L1=10; L2=10;echo $(comm --nocheck-order -12 <(uniq <(factor $L1 | sed 's/^[0-9]*:/1/' | tr ' ' '\n'; echo $L1)) <(uniq <(factor $L2 | sed 's/^[0-9]*:/1/' | tr ' ' '\n'; echo $L2))) | tr ' ' \* | bc
100
$
Alempi on väärin. Ehkäpä annan tämän nyt olla. ;D
« Viimeksi muokattu: 01.01.16 - klo:06.58 kirjoittanut Tomin »
Automaattinen allekirjoitus:
Lisäisitkö [RATKAISTU] ketjun ensimmäisen viestin aiheeseen ongelman ratkettua, kiitos.

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #212 : 01.01.16 - klo:09.23 »
Pikainen paikkaus. Mutta eiköhän vielä ilmene kaikenlaista - ja lopuksi varmaankin löytyy toinen ratkaisu joka on nopea ja virheetön:
Koodia: [Valitse]
L1=10;L2=11;echo $(($L1*$L2/$(echo $(comm -12 <(echo 1;factor $L1 | tr ' ' '\n' | grep -v :) <(echo 1;factor $L2 | tr ' ' '\n' | grep -v : )) | tr ' ' \* | bc))) | bc
kuvittelin että tässä on korjaus myös tilanteille joissa luvuilla on useampia yhteisiä tekijöitä kuten esimerkiksi: 6 ja 24. Se lasketaan kyllä oikein mutta samalla urputetaan vaikka syytä ei ole. Kai se virhe tässä minun laskussani on, mutta epäilen kyllä myös että käsky: comm horisee omiaan sillä muodostuva matriisi on moitteeton.
**
Ei tämä vieläkään ihan kunnossa ollut: Kun comm urputtaa: "tiedosto 2 ei ole lajitellussa järjestyksessä" niin se tarkoittaa että järjestys:
1
2
5
11 
ei kelpaa vaan järjestyksen tulee olla se koneen mielestä järjestyksessä oleva:
1
11
2
5   
Siis käskyyn täytyy lisätä vielä käsky: sort :
Koodia: [Valitse]
L1=110;L2=11;echo $(($L1*$L2/$(echo $(comm -12 <(echo 1;factor $L1 | tr ' ' '\n' | grep -v : | sort) <(echo 1;factor $L2 | tr ' ' '\n' | grep -v : | sort)) | tr ' ' \* | bc))) | bc
tai jos haluaa päästä eroon tuosta bc:stä:
Koodia: [Valitse]
L1=24;L2=6;echo $(($L1*$L2/$(($(echo $(comm -12 <(echo 1;factor $L1 | tr ' ' '\n' | grep -v : | sort) <(echo 1;factor $L2 | tr ' ' '\n' | grep -v : | sort)) | tr ' ' \* )))))
**
Jatkuvasti paheneva ongelma on se että kovalevyllä on saman tiedoston kopioita siellä-täällä ja kopioiden nimet voivat vaihdella. Tai yksi kopio voi olla jpg-muodosssa ja toiset joissain muussa muodossa. Tai kopioituun tekstiin on on voitu lisätä yksi välilyönti tai kuvassa jotakin pikseliä muutettu - ei ole mitään mahdollisuutta tehdä ohjelmaa joka osaisi erottaa onko kohde ainut kappale vai onko siitä lukemattonia melkein samoja kopioita. Asialle on kuitenkin pakko tehdä jotakin.

Mutta ennenkuin tekee mitään voi tarkastaa kuinka paha tuo ongelma omassa koneessa on, sillä pelkkä tarkastaminen ei tuhoa mitään. Esitettävä komentorivi-käsky ei läheskään kaikkia duplikaatteja löydä, mutta onhan tämä nopea keino ylimalkaiseen tarkistamiseen. Tarkistuskäsky on:
Koodia: [Valitse]
find ~/ -not -path '*/\.*' -type f 2>/dev/null  -exec md5sum '{}' ';' | sort | uniq --all-repeated=separate -w 66 | cut \
-c 35- > /tmp/delme; apu=0; cat /tmp/delme | while read rivi; do [[ -f "$rivi" ]] && apu=$(($apu+$(echo $(du -b "$rivi" \
| awk '{print $1}'))/2)); echo $apu; done
ja se ilmoittaa likimääräisesti duplikaattien viemän kovalevytilan sanoina
- tiedostot merkitään samoiksi kun niiden sisällöt ovat bitti-bitiltä samat. Kopion nimestä ei välitetä eikä siitä missä kansiossa kopio on. Tiedosto voi olla koodattu tai kryptattu mitenkä vaan kunhan kopio on muodostettu samalla tavalla.
- kopiotiedostojen nimiä voit tarkistella käskyllä: gedit /tmp/delme. Siellä on tiedostonimi-ryhmiä. Jokaisen ryhmän edessä ja perässä on tyhjä rivi. Jokaisessa ryhmässä on niin monta riviä kuin samansisältöisiä tiedostoja on - siis yksittäisiä rivejä ei ole.
- leikkaa-ĺiimaa käsky täältä päätteeseesi ja paina RETURN. Käskynsuoritus saattaa kestää useampia minuutteja.
- ~ paikalle voi laittaa tarkemman määrityksen siitä mistä duplikaatteja etsitään - esimerkiksi: ~/KUVAT
- tulostuva luku on duplikaattien viemä talletustila byteinä.
**
Edellä esitetty etsintämenetelmä toimii tekstitiedostojenkin kanssa, mutta tekstitiedostoja editoidaan usein ja vaikka tekstistä korjaisi vain yhden kirjaimen on sen md5sum  täysin erilainen eikä esitetty etsintä-menetelmä toimi, vaan etsimiseen täytyy käyttää käskyä:
Koodia: [Valitse]
grep "tiedostoille ominainen sana tai rivi" mistä_kansiosta_alkaen_etsitään -R | sed "s#:#\n#" | sed 's#^\/#\n&#'
- tulosteen 1 rivi on: sen tiedoston nimi josta etsitty löytyi
- tulosteen myöhemmät rivit ovat kuvaus siitä mitä löydetyllä rivullä on
- lopuksi tulostetaan tyhjä rivi jotta tuloste olisi helppolukuinen
**
- Kun teksti-tiedostoja on todella paljon löytää etsimänsä tiedoston parhaiten kun määrää sieltä hakutermiksi monta tekstinpätkää - samalta tai eri riveiltä - tekstinpätkät voivat olla yksi tai monta välilyöntien eroittamaa sanaa. Tässä käsky-versiossa kaikkien määriteltyjen tekstien kirjainten tulee olla täysin samat kuin on kirjoitettu, mutta sanojen edessä ja jäljessä saa olla tekstiä. Hakutermien pitää löytyä oikeassa järjestyksessä mutta sensijaan ei ole väliä sillä kuinka monta rivinsiirtoa niiden välissä on ja onko lohkon sisällä muutakin tekstiä. Esimerkiksi kun etsii kovalevyltä runoa:

marilla oli karitsa
sen villa lumivalkoinen

minne mari menikään
karitsa meni perässään

mutta ei muista siitä kuin palasia sieltä-täältä niin voidaan käskeä esimerkiksi näin:
Koodia: [Valitse]
grep -RPzl  '(?s)'"marilla".{0,99\}"karitsa".{0,99\}"villa lumivalkoinen".{0,99\}"perässään" mistä_kansiosta_etsintä_aloitetaan
**
- mikäli halutaan erottaa tiedostosta siinä olevat esimerkiksi BEGIN-END lohkot onnistuu se seuraavalla käskyllä:
cat -e /boot/grub/grub.cfg  | grep -Poz  '(?ism:BEGIN.*?END)' | sed 's/END/&\n/g'
**
Vihaan selityksiä sillä totuus on kaikille erilainen. Mutta pakko tässä on nyt selitellä:

Linuxissa on erinomaiset haku-työkalut. Niiden käyttö jää erittäin pieneksi sillä jotta haku-työkaluja voisi käyttää niin tarvitaan jotakin mistä hakea ja sitä ei yhdessäkään koneessa paljoakaan ole ennenkuin itse hakee. Esimerkiksi internet on täynnä esimerkkejä joten sieltähän niitä voi hakea - tosin vain jotkut noista esimerkeistä toimivat ja niistäkin harvoissa on mitään sellaista mistä kannattaa ottaa oppia. Kuitenkin niitäkin on rajattomasti ja niitä on syytä koneeseensa hakea jos haluaa jotain oppia.
- näinhän monet toimivatkin. Mutta toiminta hiipuu ajankuluessa sillä oikeastaan kukaan ei ole saanut ratkaistua ongelmaa: kuinka vanhasta roskasta pääsee eroon, joten kone täyttyy esimerkeistä joista "on imetty kaikki mehu" ja ne ovat enää taakka. Mutta ratkaisuksi ei kelpaa se että heittää kaiken vanhan roskikseen sillä joukossa on myös sellaisia kulta-jyviä joita ei mistään löydä.
- osittaisratkaisuna kannattaa harkita mihin kansioon internetistä hakemansa sijoittaa. Haetusta on heti tehtävä skripti ja dokumentti.
- vielä suurempi vaikeus on asioiden nimeäminen sillä tiedon löytää parhaiten sen nimellä. Ja jos nimeät jonkun alussa väärin muuttuukin nimen vaihtaminen jatkossa nopeasti mahdottomaksi. Ja väärin nimettyä on vaikea löytää.

Kerätyistä esimerkeistä voi sitten käydä katsomassa kuinka jokin asia on toteutettu seuraavankaltaisella käskyllä:
Koodia: [Valitse]
IFS=$'\n'; for n in $(find mistä_kansiosta_alkaen_etsitään -type f -not -path "*mistä_ei_etsitä*"); do cat -e "$n" | grep -Poz  '(?ism:case.*?esac)' | sed 's/esac/&\n/g'; done; unset IFS
- siis käskyssä käydään rekursiivisesti läpi kaikki tiedostot määrätystä kansiosta alkaen mutta ei etsitä jostain ja tulostetaan niistä kaikki case-esac lohkot.
- käskyn käyttö ei rajoitu BASH-skripteihin eikä edes ohjelmointiin
- toki itsekin vasta opettelen ja esimerkeissäni on joskus paljonkin mätää. Mutta kaikesta saa toimivan, siihen voimme luottaa.
**
Rekursiivinen haku kertoo missä saattaa olla jotakin - siis se on samantapainen kuin googlen antana hakulista.
Esimerkiksi haluttaessa tuloste niistä tiedostoista joissa puhutaan IPv4 osoiteista käsketään:
Koodia: [Valitse]
grep '(([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' mistä_kansiosta_alkaen_etsitään -PRw | grep -v ".*mistä_kansiosta_tuloksia_ei_esitetä.*" | sed "s#:#\n#" | sed 's#^\/#\n&#'
- tulosteessa on jokaisesta osumasta kaksi riviä: ensimmäisellä on polku ja toisella: <rivino:>löytynyt rivi
- nimittäin koneelle on mahdotonta sanoa onko 1.1.1.1 ipv4-osoite, kirjan kappale tai jotakin ihan muuta.
- jokaista löytöä vastavan kaksi-rivisen tulosteen perässä on tyhjä rivi eroittamassa löytöjä.
**
Maksimin laskemiseksi awk on nopea ja se käsittää positiiviset, negatiiviset, kokonaisluvut, liukuluvut ja tieteellisen esitysmuodon. Mutta desimaaleja se hyväksyy vain jotain 19. Toteutukseltaan se on:
Koodia: [Valitse]
a=(1 12 3 4 5 6e2 7 88 9); echo -e ${a[@]/%/\\n} | awk 'BEGIN {maksimi=$1} {n if ($1>maksimi) maksimi=$1 } END { print maksimi }'
Kun tarvitaan enemmän desimaaleja niin seuraava askel onkin rajoittamaton tarkkuus, siis desimaalien luku määräytyy automaattisesti niin suureksi kuin tarvitsee. Mutta laskenta on kymmenen-kertaa hitaampi. Mutta taas kaikenlaiset luvut hyväksytään. Sen toteutus on:
Koodia: [Valitse]
function max () { apu=$1; for n in $@; do [[ $(echo $apu'>'$n | bc) -eq 0 ]] && apu=$n; done; echo $apu ;}; a=(3.33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 3.33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333334); max ${a[*]}
**
BASH:in regex valitsee suurimman sopivan palasen jollei muuta määrätä:
Koodia: [Valitse]
echo a9b8c7d6e | grep -o [0-9].*[0-9]   
tämä tulostaa: 9b8c7d6

Mikäli halutaan valita pienin palanen niin käsky muuttuu:
Koodia: [Valitse]
echo a9b8c7d6e | grep -oP [0-9].*?[0-9]tämä tulostaa kaksi palasta:
9b8
7d6
 
-tätä ? lisämäärettä voidaan käyttää  * , + , ? ja {} merkkien kanssa (hienoilla sanoilla: tekee greedystä lazyn ).
- merkkiryhmä {} on ainoastaan 'placeholder' find-käskyssä - ei siitä voi olla kyse. Ja netin yksikään hakukone ei osaa sitä etsiä. Turhauttavaa.
**
Pitipä tulostaa tiedostosta siinä olevat ipv4-osoitteet ja osoitteiden edessä oleva osoitteen merkityksen kuvaus. Ratkaisu-esimerkiksi:
Koodia: [Valitse]
echo "osoitteen 1 kuvaus%1.1.1.1 osoitteen 2 kuvaus:2.2.2.2 osoitteen 3 kuvaus 3.3.3.3 'osoitteen 4 kuvaus'4.4.4.4 osoitteen 5 kuvaus'5.5.5.5'"\
 | grep -Po '.*?\b(([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b'
- ipv4-regex on muuten mielestäni täydellinen.

- tiedostoa käsittelevä versio:
Koodia: [Valitse]
grep -Po '.*?\b(([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b' <<< $(cat tiedosto | tr -d '\n')
- tuloste ei muutu vaikka osoitteen kuvaus alkaisi yhdellä riivillä jä loppusi toisella
**
Edellinen lause löytää kyllä kaikki osoitteet ja osoitteiden kuvaukset, mutta osoitteiden kuvauksiin jää usein niin paljon asiaankuulumatonta että tulosteesta tulee hyvin vaikeaselkoinen. Yksi menetelmä liirum-laarumeiden rajoittamiseksi on muodostaa tuloksista matriisi jonka jäseniä sitten tulostettaessa rajoitetaan.
Jotta esityksestä saisi selvemmän on se parasta esittää kunnollisena skriptinä:
Koodia: [Valitse]
#!/bin/bash
# Matriisin muodostaminen tiedoston ipv4-osoitteista ja niiden kuvauksista:
IFS=$'\n'
matriisiosoitteista=($(grep -Po '.*?\b(([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b' \
<<< $(cat TIEDOSTONIMI | tr -d '\n'))); unset IFS

# Matriisin jäsenien tulostaminen rajoittaen jokaisen jäsenen pituus rajoittamalla siinä olevan ipv4-osoitteen kuvaus yhteen lauseeseen
# (siis ipv4-osoitetta edeltävään pisteeseen jos pistettä kuvauksessa onkaan):
for (( n=0; n<=${#matriisiosoitteista[@]}; n++ )); do
  jasen=${matriisiosoitteista[$n]}; pisteita=0; for (( pisteenpaikka=${#jasen}; pisteenpaikka>1; pisteenpaikka-- )) ; do
  [[ ${jasen:$pisteenpaikka:1} = \. ]] && (( pisteita++ )); [[ $pisteita = 4 ]] && break; done; echo ${jasen:$pisteenpaikka}
done

« Viimeksi muokattu: 03.02.16 - klo:15.36 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #213 : 26.01.16 - klo:08.28 »
Tieteellisessa esitysmuodossa olevien lukujen kunnollinen vertaaminen osoittautui vaikeaksi - awk toimii hyvin ja nopeasti mutta se on rajoitettu 19 numeroon. Ja matematiikka ei toimi jos sallitaan rajoituksia - varsinkaan sellaisia joita ei tarvitse sallia.
Tällä skriptillä ei ole rajoituksia verrattavien lukujen suhteen vaan se toimii kaikkien ratkaistujen reaalilukujen kanssa riippumatta numeroalueesta, desimaalien määrästä tai esitysmuodosta. Tieteellisen esitysmuodon lukujen vertailu toimii suuruusluokkaan  e-50 asti ja jos se ei riitä niin sehän on muutettavissa. Tieteellisessä esitysmuodossa voi käyttää pientä tai isoa E:tä.
Sovellustavasta riippuen skriptejä voi käyttää myös kattona tai lattiana (katosta ei mennä yli ja lattiasta ei mennä ali).
Koodia: [Valitse]
function minimi () { [[ $(echo "scale=66; ${1//[Ee]/*10^}>${2//[Ee]/*10^}" | bc) -eq 0 ]] && echo $1 || echo $2 ;}; minimi 1.0000003e-50 1.0000002E-50
Koodia: [Valitse]
function maximi () { [[ $(echo "scale=66; ${1//[Ee]/*10^}<${2//[Ee]/*10^}" | bc) -eq 0 ]] && echo $1 || echo $2 ;}; maximi 1.0000003e-50 1.0000002E-50

- vertailu muuten onnistuu olipa numerojärjestelmä mikähyvänsä alle 10-kantainen. Sisääntulevan suurin numerojärjestelmä on 16 ja sillä vertailu:
Koodia: [Valitse]
function minimi () { [[ $(echo "ibase=16; ${1^^}>${2^^}" | bc) -eq 0 ]] && echo $1 || echo $2 ;}; minimi ffffe ffffd 
- voi nuo hexat kirjoittaa isollakin.

Kaikilla lukujärjestelmillä on desimaalit. Mutta bc:ssä lasketaan hexadesimaali-numeroiden desimaalit väärin . Esimerkiksi lasku: "echo 'ibase=16; .F' | bc" tulostaa .9 vaikka pitäisi tulostaa .9375 . Ja lasku ei oikaistu millään muulla kuin käymässä laskemassa desimaalien kokonaisluku-arvosta; siis kummanssakin verrattavassa kokonaisosa ja desimaalit lasketaan eritavoin. Lisäksi muutamat varmistukset ja päädytään seuraavaan aikaavievään laskuun (tätä laskua siis sovelletaan vain kun lasketaan hexadesimaali-lukuja joissa saattaa olla desimaaleja. ):
Koodia: [Valitse]
#!/bin/bash
# lisäys: | tr -d '\\\n'   bc:n perässä tekee bc:n tulostuksesta yhden numeron jossa ei ole kenoja eikä rivinsiirtoja
function minimi () {
apu1=${1^^}; desi1=${apu1##*.}; [[ $(echo $apu1 | grep .) ]] || desi1=0; koko1=${apu1%%.*}; koko1=${koko1:-0}; len1=${#desi1} # verrattavan1 paloittelua
sign1=1; [[ ${koko1:0:1} = - ]] && sign1=-1 && koko1=${koko1:1} && [[ $koko1 = '' ]] && koko1=0                               # verrattavan1 paloittelua   

apu2=${2^^}; desi2=${apu2##*.}; [[ $(echo $apu2 | grep .) ]] || desi2=0; koko2=${apu2%%.*}; koko2=${koko2:-0}; len2=${#desi2} # verrattavan2 paloittelua
sign2=1; [[ ${koko2:0:1} = - ]] && sign2=-1 && koko2=${koko2:1} && [[ $koko2 = '' ]] && koko2=0                               # verrattavan2 paloittelua

verrattava1desimaalisena=$(echo "scale=99; $(echo "scale=99;ibase=16; $koko1" | bc | tr -d '\\\n' )"+\
$(echo "scale=99;ibase=16; $desi1" | bc | tr -d '\\\n' )/$(echo "scale=99;16^$len1" | bc | tr -d '\\\n' ) | bc | tr -d '\\\n' )

verrattava2desimaalisena=$(echo "scale=99; $(echo "scale=99;ibase=16; $koko2" | bc | tr -d '\\\n' )"+\
$(echo "scale=99;ibase=16; $desi2" | bc | tr -d '\\\n' )/$(echo "scale=99;16^$len2" | bc | tr -d '\\\n' ) | bc | tr -d '\\\n' )
echo -e $verrattava1desimaalisena'\n'$verrattava2desimaalisena # tmän rivin eteen voi kirjoittaa merkin # kun tulosteesta haluaa siistimmän.

[[ $(echo $verrattava1desimaalisena*$sign1'>'$verrattava2desimaalisena*$sign2 | bc ) -eq 0 ]] && echo $1 || echo $2 ;};
minimi -.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb -.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc
**
Kyllä BASH itsekin kykenee näihin vertailuihin - tosin se kykenee ilman apua vertaamaan vain alle 14 numeroa pitkiä positiivisia ja negatiivisia hexadesimaalilukuja. Niissä voi olla 14 desimaaliakin, mutta tieteellinen esitysmuoto ei onnistu:
Koodia: [Valitse]
function minimi () {
apu1=${1^^}; desi1=${apu1##*.}; [[ $(echo $apu1 | grep .) ]] || desi1=0; koko1=${apu1%%.*}  # verrattavan1 paloittelua
[[ ${koko1:0:1} = - ]] && [[ ${koko1:1:1} = '' ]] && koko1=-1                               # verrattavan1 paloittelua   
apu2=${2^^}; desi2=${apu2##*.}; [[ $(echo $apu2 | grep .) ]] || desi2=0; koko2=${apu2%%.*}  # verrattavan2 paloittelua
[[ ${koko2:0:1} = - ]] && [[ ${koko2:1:1} = '' ]] && koko2=-1                               # verrattavan2 paloittelua
desimaalienvertailuoperaattori='<'; [[ ${koko1:0:1} = - ]] && [[ ${koko2:0:1} = - ]] && desimaalienvertailuoperaattori='>' 
if [ $koko1 = $koko2 ]; then (( $((16#$desi1)) $desimaalienvertailuoperaattori $((16#$desi2)) )) && { echo $1; return ;} ||  { echo $2; return ;}; fi
(( $((16#$koko1)) < $((16#$koko2)) )) && { echo $1; return ;} ||  { echo $2; return ;} ;}
minimi -fffffffffffffff.ffffffffffffffd fffffffffffffff.fffffffffffffff

- kyllä tällä voi verrata kymmenjärjestelmässäkin.
**
Mutta tosiaan awk-sovellus on helpoin ja nopein myös maximin/minimin etsimisessä:
Koodia: [Valitse]
echo '.ffffffffffffffd .fffffffffffffff' | awk '{ if ($1<$2) print $1 ;else print $2}'
Pilkunviilaus osoitti että siinäkin on virhetoiminto:
Koodia: [Valitse]
echo 'f d' | awk '{ if ($1<$2) print $1 ;else print $2}'    # tulostaa d
echo '-f -d' | awk '{ if ($1<$2) print $1 ;else print $2}'  # tulostaa -d vaikka pitäisi tulostaa -f
- desimaaliluvuilla toiminta on moitteetonta. Hexadesimaaliluvuilla täytyy käyttää käskyä:
Koodia: [Valitse]
echo 'dddd ffff' | awk '{ if ($1>0 && $2>0) if ($1<$2) print $1 ;else print $2; else if ($1>$2) print $1 ;else print $2 ;}'
- siis siinä vaihdetaan vertailumerkki arvosta: "<" arvoon: ">" mikäli molemmat luvut ovat negatiivisia.
**

**
Koneesi ipv6-osoitteet saat käskyllä:
Koodia: [Valitse]
ip -6 addr
   
Kun saat käsiisi jotain jonka pitäisi olla ipv6-osoite voit testata sen käskyllä:
Koodia: [Valitse]
function ipv6testi () { [[ $(echo $1|tr -d :/[[:xdigit:]]) = '' && $(echo $1|tr -dc :|wc -c)<7 && $(echo $1|grep -o '[[:xdigit:]]*'| \
wc -L)<5 ]] && echo osoite on kelvollinen || echo osoite on kelvoton ;}; ipv6testi 1::1111:1/128 # esimerkkiosoite

Kyllä ipv6:lle on regex-kin ja sitä kannattaa käyttää, sillä se on nopea ja ilmeisesti virheetönkin; kunpa se vain ei olisi noin pitkä:
echo 1:1:1:1:1:1:1:1111 | grep -P "^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:)))(%.+)?\s*$"


 
« Viimeksi muokattu: 07.02.16 - klo:04.32 kirjoittanut petteriIII »

nm

  • Käyttäjä
  • Viestejä: 16232
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #214 : 07.02.16 - klo:14.16 »
Kyllä ipv6:lle on regex-kin ja sitä kannattaa käyttää, sillä se on nopea ja ilmeisesti virheetönkin; kunpa se vain ei olisi noin pitkä:
echo 1:1:1:1:1:1:1:1111 | grep -P "^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:)))(%.+)?\s*$"

grep -P:n kanssa toimii tällainen viritys:

Koodia: [Valitse]
^\s*(?>(?>([a-f0-9]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f0-9](?>:|$)){8,})((?1)(?>:(?1)){0,6})?::(?2)?)|(?>(?>(?1)(?>:(?1)){5}:|(?!(?:.*[a-f0-9]:){6,})(?3)?::(?>((?1)(?>:(?1)){0,4}):)?)?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?4)){3}))\s*$
Lähde: http://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses/1934546#1934546

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #215 : 08.02.16 - klo:08.57 »
grep -P:n kanssa toimii tällainen viritys:

Koodia: [Valitse]
^\s*(?>(?>([a-f0-9]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f0-9](?>:|$)){8,})((?1)(?>:(?1)){0,6})?::(?2)?)|(?>(?>(?1)(?>:(?1)){5}:|(?!(?:.*[a-f0-9]:){6,})(?3)?::(?>((?1)(?>:(?1)){0,4}):)?)?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?4)){3}))\s*$
Lähde: http://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses/1934546#1934546

Toimii loistavasti, tattista.
**
Halusin tietää onko satunnaislukujen muodostaminen edelleenkin huonoa. Helppo tapa tulostaa satunnaisluku väliltä: 0-joku_pienehkö_numero  on käsky:
Koodia: [Valitse]
echo $(( RANDOM*joku_pienehkö_numero/32768 ))
- satunnaislukujen muodostamisessa 16-bittisyys ei tunnu olevan pahin heikkous. Jokatapauksessa annetaan sitten 31-bittinen versio; se on aina hitaampi mutta ehkäpä se on joskus myös parempi:
Koodia: [Valitse]
echo $((((RANDOM<<15)|RANDOM)*40/1073741824))

Muodostettujen satunnaislukujen laatua voi tarkastella käskyllä : 
Koodia: [Valitse]
unset apu; declare -A apu; for n in {1..100000}; do let apu[$(( RANDOM*40/32768 ))]++ ; done ; echo ${apu[*]} | awk 'BEGIN {RS=" "}{sum+=$1; sumsq+=$1*$1}END {printf "%s\n", "keskipoikkeama="sqrt(sumsq/NR - (sum/NR)^2)"    keskiarvo="sum/NR}'
- tässä joku_pienehkö_numero on 40 ja muodostettavien satunnaislukujen määrä on  100000. Tarkoituksena on seurata kuinka niiden muuttaminen vaikuttaa kun käskyä jatkuvasti toistetaan.


Koska epäilin että maximi- ja minimiarvot saaattavat olla liian usein samoja niin tein niiden tarkistusta varten skriptin joka tuloste on seuraavankaltainen: 
assosiatiivisen matriisin keskipoikkeama=172.567    keskiarvo=25000
assosiatiivisen matriisin minimi        =24632 ja sitä vastaava osoite:14
assosiatiivisen matriisin maximi        =25352 ja sitä vastaava osoite:15

skriptistä tuli seuraavanlainen:
Koodia: [Valitse]
#!/bin/bash
# petteriIII 10.2.2016
unset asso; declare -A asso
function MatrixMin () { eval echo \${$1[*]} | awk 'BEGIN { min="";RS=" "}{ if (min=="") min=$1; if ($1<min) min=$1 }END{ print min}' ;} # toimii tavallisille sekä yksi- että "moniulotteisille" assosiatiivisille matriiseille.
function MatrixMax () { eval echo \${$1[*]} | awk 'BEGIN { max="";RS=" "}{ if (max=="") max=$1; if ($1>max) max=$1 }END{ print max}' ;} # toimii tavallisille sekä yksi- että "moniulotteisille" assosiatiivisille matriiseille.
for n in {1..100000}; do let asso[$(( RANDOM*40/32768 ))]++ ; done ; echo ${asso[*]} | awk 'BEGIN {RS=" "}{sum+=$1; sumsq+=$1*$1}END {printf "%s\n", "assosiatiivisen matriisin keskipoikkeama="sqrt(sumsq/NR - (sum/NR)^2)"    keskiarvo="sum/NR}'
min=$( MatrixMin asso)
echo -n 'assosiatiivisen matriisin minimi        ='$min' ja sitä vastaava osoite:';for (( n=1; n<=${#asso[@]}; n++ )); do [[ ${asso[$n]} = $min ]] && echo $n && break; done
max=$( MatrixMax asso)
echo -n 'assosiatiivisen matriisin maximi        ='$max' ja sitä vastaava osoite:';for (( n=1; n<=${#asso[@]}; n++ )); do [[ ${asso[$n]} = $max ]] && echo $n && break; done

- epäilys osoittautui vääräksi. Tosin laskin vain kymmenkunta sadanmiljoonan ryhmää koska homma on niin hidasta.
- syy skriptin esittämiseen on se että nuo käskyt: MatrixMin ja: MatrixMax rikkovat  vanhoja luuloja taas hieman lisää.
- jonkun_pienen_luvun ollessa yli 15000 tuloksissa alkaa esiintymään kummallisuuksia. Kerrostumista?
---------
Mutta kaikkien tämänkaltaisten tehtävien tulosten tarkistamisessa plottaaminen on niitä parhaita keinoja. Plottasinkin seuraavan skriptin mukaani:
Koodia: [Valitse]
#!/bin/bash
unset matriisi
for (( n=1; n<=1000000000; n++ )); do let matriisi[$(( RANDOM*40/32768 ))]++ ; done # 1000 000 000 eli miljardi vastaa aikana yli 8 tuntia.
minimi=$( echo ${matriisi[*]} | awk 'BEGIN {minimi=9e99} { if ($1<minimi) minimi=$1 } END { print minimi }' )
for x in {1..39}; do echo $x" "$( echo "100*(${matriisi[$x]}-$minimi)/$minimi" | bc -l ); done | gnuplot -p -e 'set terminal pngcairo size 350,262 enhanced font "Verdana,10"; set output "satunnaisluvut.png"; set ylabel "poikkeama %"; set xlabel "satunnasluku"; plot "/dev/stdin" with lines'
- 16-bittisellä satunnaisluvun kehittämistavalla ovat kehitetyissä satunnaisluvuissa havaittavaa toistoa joka selviää jo miljardilla mittauksella. 31-bittisellä satunnaisluvun kehittämismenetelmällä toistoa ei voi havaita.
- 16-bittinen satunnaisluku -> $(( RANDOM*40/32768 ))
- 31-bittinen satunnaisluku -> $((((RANDOM<<15)|RANDOM)*40/1073741824)) . 31-bittinen on vain mitättömän vähän hitaampi kehittää.

- satunnaislukuja tehdään miljardi, joten yksi mittaus kestää noin 8 tuntia.
- tulokset liitteenä tämän postauksen lopussa
- sekin selvisi, että konetta voi täysin hyvin käyttää samanaikaisesti muihin hommiin, myös ajaa toisia skriptejä.
---------
Skriptien toimintaa tutkittaessa verrataan usein niiden tekstimuotoisia tulosteita. Tällöin tulosteet täytyy suunnata näytön lisäksi myös tiedostoon. Skriptiin ei lisätä mitään sillä aina ei skriptiin voi kirjoittaa mitään tai se voi olla työlästä ja pitäisi perua myöhemmin. Skriptin tulostuksen voi suunnata näytön lisäksi myös tiedostoon lisäämällä skriptin käynnistyskäskyn perään > >(tee tuloste) . Siis esimerkiksi:
Koodia: [Valitse]
. ~/mun_skriptini > >(tee tuloste)      tai: bash mun_skriptini > >(tee tuloste)
jolloin tulostus löytyy myös kotikansion tiedostosta: tuloste josta se kannattaa heti kopioida muiden tulosteiden perään tai jotakin. Tulosteessa on joskus kirjoituskelvottomia merkkejä jolloin se on näennäisesti lukukelvoton mutta kyllä gedit silti osaa sen muut merkit lukea.
- tulosteet kannattaa lisätä heti tulostusten yhteenvetoon käskyllä:
Koodia: [Valitse]
cat <(echo -e '\nseuraava tuloste '$(date)) tuloste >> yhteenveto
**
Aloinpa taas kerran pähkäillä kuinka todeta kahden tiedoston ero. Käsky diff toimii toki ihan hyvin, mutta sen tuloksia joutuu miettimään eikä sen tuloksia ole helppo jatko-käsitellä joten käytän awk:ia.
Samantien voi käyttää awk-käskyä jonka tuloksia ei voi käsittää väärin sillä se kirjoittaa tulosteeseen mitä on verrattu:
Koodia: [Valitse]
clear;awk '{apu=FILENAME; if (!FILENAME++) nimi=nimi apu " "}END{printf "\nmitä sellaista on " substr(nimi,1,index(nimi, " ")-1)":ssä \
mitä ei ole"substr(nimi,index(nimi, " "),99)"\b:ssa\n"} NR == FNR {file1[$0]++; next} !($0 in file1) ' tiedosto2 tiedosto1
- vaihda perästä: tiedosto2 tiedosto1  muotoon:tiedosto1 tiedosto2 jos ei vertaa oikeinpäin.
- tiedostojen nimet saavat olla mitävain, vaikka: mutterivarasto lista_nyt_tarvittavista_muttereista

Siihen kuinka katsotaan "mitä samanlaista tiedostoissa on" sopii käsky:
Koodia: [Valitse]
clear;awk '{apu=FILENAME; if (!FILENAME++) nimi=nimi apu " "}END{printf "\nmitä samanlaista on tiedostoissa: " substr(nimi,1,index(nimi, " ")-1)"  ja: \
"substr(nimi,index(nimi, " "),99)"\n"} NR == FNR {file1[$0]++; next} ($0 in file1) ' tiedosto2 tiedosto1

--
Ehkäpä kannattaisi lähteä ratkaisemaan tätä tiedostojen vertailua toista kautta. Alku olisi seuraava:
Koodia: [Valitse]
grep -Fxvf tiedosto1 tiedosto2
- nimittäin grepistä on myös repoista löytyvä versio agrep joka hyväksyy määrättävän kokoisia eroja.
--
Matriisejakin voi "vertailla"
- soveltaen myös joukkoja, onhan bash:issa matriisi=(joukko) 
- nämä skriptit on viisainta esittää täydellisinä esimerkkeinä sillä kun nämä alkaa sotkeutua niin loppua ei tule
- jäsenet voivat olla numeroita, tekstiä tai heittomerkkien ympäröimiä lauseita

1. mitä ensimmäisessä matriisissa on sellaista mitä ei toisessa ole
Koodia: [Valitse]
#!/bin/bash
matriisi2=( a1 a2 "nämä ovat heittomerkeissä" x a2 a4 a5 a6 a\nb a7 a8 x2 ) # a2 on kaksi kertaa
matriisi1=( x2 a3 a4 b5 a6 "nämä ovat heittomerkeissä" "rivi jossa on välilyöntejä täytyy laittaa heittomerkkien väliin")
grep -Fxvf <(echo -e ${matriisi1[@]/%/\\n} | awk '{$1=$1} !x[$0]++' | sort) <(echo -e ${matriisi2[@]/%/\\n} | awk '{$1=$1} !x[$0]++' | sort)
2. mitä toisessa matriisissa on sellaista mitä ei ensimmäisessä ole
Koodia: [Valitse]
#!/bin/bash
matriisi1=( a1 "nämä ovat heittomerkeissä" x a2 a4 a5 a6 a\nb a7 a8 x2 ) # a2 on kaksi kertaa
matriisi2=( x2 a3 a4 b5 a6 "nämä ovat heittomerkeissä" "rivi jossa on välilyöntejä täytyy laittaa heittomerkkien väliin")
grep -Fxvf <(echo -e ${matriisi1[@]/%/\\n} | awk '{$1=$1} !x[$0]++' | sort) <(echo -e ${matriisi2[@]/%/\\n} | awk '{$1=$1} !x[$0]++' | sort)
3. mitä yhteistä kahdella matriisilla on
Koodia: [Valitse]
#!/bin/bash
matriisi1=( a1 "nämä ovat heittomerkeissä" x a2 a4 a5 a6 a\nb a7 a8 x2 ) # a2 on kaksi kertaa
matriisi2=( x2 a3 a4 b5 a6 "nämä ovat heittomerkeissä" "rivi jossa on välilyöntejä täytyy laittaa heittomerkkien väliin")
grep -Fxf <(echo -e ${matriisi1[@]/%/\\n} | sort) <(echo -e ${matriisi2[@]/%/\\n} | sort)
4. mitä jäseniä kahdella matriisilla yhteensä on: (se ei aina ole sama kuin matriisien summa)
Koodia: [Valitse]
#!/bin/bash
matriisi1=( a1 "nämä ovat heittomerkeissä" x a2 a4 a5 a6 a\nb a7 a8 x2 ) # a2 on kaksi kertaa
matriisi2=( x2 a3 a4 b5 a6 "nämä ovat heittomerkeissä" "rivi jossa on välilyöntejä täytyy laittaa heittomerkkien väliin" )
matriisi3=("${matriisi1[@]}" "${matriisi2[@]}")
echo -e ${matriisi3[@]/%/\\n} | awk '{$1=$1} !x[$0]++' | sort
5. mitä jäseniä kuuluu vain jompaankumpaan matriisiin muttei kumpaankin
Koodia: [Valitse]
#!/bin/bash
matriisi1=( a1 a2 "nämä ovat heittomerkeissä" x a2 a4 a5 a6 a\nb a7 a8 a2 x2 ) # a2 on kaksi kertaa
matriisi2=( x2 a3 a4 b5 a6 "nämä ovat heittomerkeissä" "rivi jossa on välilyöntejä täytyy laittaa heittomerkkien väliin")
comm -3 <(echo -e ${matriisi1[@]/%/\\n} | awk '{$1=$1} !x[$0]++' | sort) <(echo -e ${matriisi2[@]/%/\\n} | awk '{$1=$1} !x[$0]++' | sort) | awk '{$1=$1}1'
# tässä tehtävässä grep olettaisi että suurempi matriisi on ensimmäisenä. Ei käy sillä tässä vaatimus johtaisi vain väärinkäsityksiin.
# tätä voi käyttää myös matriisien yhtäsuuruuden tarkistamiseen - tai pikemminkin sen tarkistamiseen onko matriiseissa samat jäsenet sillä jäsenien järjestyksellä ei ole väliä.
**
Skriptin osia voidaan erottaa omaksi kokonaisuudekseen suluilla - esimerkiksi kun haluaa painottaa missä järjestyksessä suluissa olevat tehtävät tulee suorittaa - konekin tarvitsee niitä joskus mutta ohjelmoija voi määrätä niitä selventääkseen toimintaa itselleen. Siis ihan niinkuin matematiikassakin. Mahdolliset sulkutyypit ovat: ( tehtävä; uusi tehtävä; uusi tehtävä ... ) ja:{ tehtävä; uusi tehtävä; uusi tehtävä ... }. Niiden erot:

1. { ... } suoritetaan tehtävät samassa prosessissa kuin missä ollaan joten se on paljon nopeampi. Globaali-alue on yhteinen, joten parametrit siirtyvät kumpaankin suuntaan. Myös toiminnan sivuvaikutukset jäävät rasittamaan omaa prosessia.

2. ( ... ) ensin luodaan uusi prosessi ja ajetaan käskyt siinä. Onhan se on paljon hitaampaa, mutta sivuvaikutukset nollautuvat kun palataan omaan prosessiin. Koska globaali-alue on uuden prosessin oma niin parametrien palautus ei toimi - funktioon päin parametrit kyllä siirtyvät ihan hyvin.

- käytännössä funktio luodaan aina seuraavasti: functio funktion-nimi () { ... }. Mutta tuo äsköinen pätee funktioihinkin. Siten:
apu=0; function nimi() { apu=1 ;} ; nimi; echo $apu  # tulostaa 1
mutta:
apu=0; function nimi() ( apu=1 ;) ; nimi; echo $apu  # tulostaa 0 . Yksi keino saada apu:n arvo palautettua on palauttaa se kovalevy-tiedoston kautta:
apu=0; function nimi() ( apu=1; echo "$apu">/tmp/delme ;) ; nimi; apu=$(cat /tmp/delme); echo $apu
- onhan tämä hidasta ja inhan näköistä, mutta kutsuva prosessi ei kärsi vaikka kutsuttu hölmöilisi.


Esimerkiksi seuraavassa funktio asettaa muuttujan arvon globaali-alueella. Funktion ja pääohjelman globaali-alueiden tulee siis olla samat jos halutaan niiden välistä tiedonsiirtoa; elikä on pakko käyttää rakennetta { ... } :
Koodia: [Valitse]
#!/bin/bash
function lue_näppäimistöltä () { echo "annapa $1"; read $1 ;}

# samassa paikassa luetaan kaksi muuttujaa jotta olisi selvää että palautetaan oikean muuttujan arvo. 
lue_näppäimistöltä muuttuja1
lue_näppäimistöltä muuttuja2

echo -e "\n\nmuuttujalle1 annettu arvo: "$muuttuja1
echo "muuttujalle2 annettu arvo: "$muuttuja2
 
- lopputulos on siis sama kuin palautettaisiin parametri.

Rakennetta ( ... ) käytetään esimerkki matematiikassa:
Koodia: [Valitse]
echo $((1+2*3))  tai: echo $(((1+2)*3))


Kumpaa hyvänsä sulutusta tulee käyttää monimutkaisemmissa logiikkalauseissa sillä muuten helposti toimitaan toisin kuin luullaan ja selventäähän se tapahtumia jokatapauksessa. Mutta päänsärkyä niistä monimutkaisuuksista vaan saa ja kannattaa mieluummin jakaa monimutkainen moneksi yksinkertaiseksi, esimerkiksi:
Koodia: [Valitse]
a=0; b=1; \
[[ $a == 1 && $b == 1 ]] && echo sekä a että b ovat ykkösiä;  \
[[ $a == 0 && $b == 1 ]] && echo a on nolla ja b on yksi;  \
[[ $a == 1 && $b == 0 ]] && echo a on yksi ja b on nolla;  \
[[ $a == 0 && $b == 0 ]] && echo sekä a että b ovat nollia


Siitä päänsärkyä aiheuttavasta ja monimutkaisesta ratkaisusta esimerkki:
Koodia: [Valitse]
a=0; b=1; [[ $a == 1 ]] && { [[ $b == 1 ]] && echo sekä a että b ovat ykkösiä ; : ; } || [[ $b == 0 ]] && echo sekä a että b ovat nollia
- muuten selvitys kohdasta: { [[ $b == 1 ]] && echo $c ; : ; } -> tuo : johtuu bash:in vaatimuksesta että sulkujen sisällä olevan lauseke ei saa olla pelkkä ehdollinen teko, vaan siinä täytyy tehdä jotain todellistakin, vaikka suorittaa nollakomento. Tuo perässä oleva ; taas johtuu siitä että ennen sulkevaa sulkua pitää olla joko ; tai rivinsiirto.
- tässävaiheessa kuvannollisesti päätä särki jo niin paljon että piti lopettaa.


 


« Viimeksi muokattu: 26.04.16 - klo:15.32 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #216 : 20.02.16 - klo:07.00 »
Bash kehittyy pikkuhiljaa. Esimerkiksi aikaisemmin tiedostoon kirjoitetun 2D-matriisin sarake- ja rivikeskiarvojen laskemiseen oli pakko käyttää awk:ia sillä bash-totetus oli tuhat kertaa hitaampi ja suurien matriisien laskeminen kesti kymmeniä minuutteja. Mutta awk:in desimaalien lukumäärä on riittämätön, joten on pakko käyttää bc-laskentaohjelmaa. Bc:n ohjaamisessa tulee helposti tehtyä skriptistä etana.
- nämä skriptit toimivat pienillekin matriiseille hyvin, mutta vasta suurten matriisien laskennassa ne loistavat.
- bc:n tuloon voi syöttää lähes rajoittamattoman pituisen laskukaavan; gigantti-kokoisen matriisin laskennassa dataa saattaa olla sata a4-arkillista.

Nämä toteutukset ovat parhaimmillaankin 3-kertaa hitaampia kuin vastaavat awk-sovellukset. Mutta tuloksessa on nyt 66 desimaalia ja haluttaessa kuinka monta vaan.

Skriptien nopeus johtuu siitä ettei niissä ole käytetty bash:in looppi-käskyjä, vaan bash:in käskyihin sisäänrakennettuja looppeja. Myös sed:issä ja awk:issa on sisäänrakennettu looppi. Nämä sisäänrakennetut loopit ovat nopeita. Seuraavat skriptit ovatkin tosinopeita bash-skripteiksi: 
Koodia: [Valitse]
function sarakekeskiarvo() { lkm=$(cut -c -1 $1 | wc -w); echo $(echo 'scale=66;('$(cut -d ' ' -f$2 $1 | tr '\n' + | sed 's/+*$//')')/'$lkm | bc -l);}; sarakekeskiarvo tiedosto1 2
# lkm=$(cut -c -1 tiedosto | wc -w)    # viimeisen sellaisen rivin rivino jolla on jotakin välilyöntiä suurempaa
# cut -d ' ' -f$2 $1                   # erotetaan haluttu sarake
# | tr '\n' +                          # vaihdetaan rivinsiirrot + merkeiksi
# sed 's/+*$//                         # poistetaan perästä turhat + merkit
- sarakekeskiarvo lasketaan sarakkeesta 2

Koodia: [Valitse]
function rivikeskiarvo() { lkm=$(sed -n $2p $1 | wc -w); echo $(echo 'scale=66;('$(sed -n $2p $1 | tr ' ' + | sed 's/+*$//')')/'$lkm | bc -l);}; rivikeskiarvo tiedosto1 2
- rivikekeskiarvo lasketaan riviltä 2
------
Linux on levykäyttöjärjestelmä ja teoriassa matriisit ovat levyllä. Mutta toki on mahdollista suorittaa käsittely RAM:mista. Siihen soveltuu esimerkiksi seuraava:
Koodia: [Valitse]
function rivikeskiarvo() { readarray apu < $1; lkm=$( echo "${apu[@]}" | wc -l ); echo '('$(echo ' '"${apu[@]}" | cut -d ' ' -f3 | tr '\n' + | sed 's/+*$//g')')/'$(($lkm-1)) | bc -l  ;}; time rivikeskiarvo tiedosto1 2
- tuota alussa olevaa: "readarray apu < $1" tarvitaan vain jotta esimerkki toimisi ensimmäiselläkin kerralla ja sitä ei tarvita mikäli matriisi on jo muistissa.
- toiminta RAM:missa on kymmenkunta kertaa hitaampaa kuin toiminta levyllä. Piti nähdä ennenkuin uskoin. Ettei vaan jossain olisi matoja?
- osoittautukin ettei käsky: "readarray" tee normaalia matriisia vaan lisää tekemäänsä matriisiin jotakin.
- mutta se ei muutu että RAM:missa olevaa matriisia voi käsitellä - luultavasti lukemalla matriisi oikein ja tekemällä mutoksen aiheuttamat lisämuutokset nopeuskin nousisi mutta skriptistä tulisi tosipitkä.
- linuxin normaalit käskyt toimivat siis myös luettaessa RAM:mista.
**
Tuosta readarrayn omintakeisesta toiminnasta:
kirjoita levytiedostoon nimeltä koetiedosto:
1 2
3 4
Lue se sitten muistiin vanhalla menetelmällä ja tulosta heti:
Koodia: [Valitse]
i=0; while IFS=$'\n' read -r rivi; do matriisi[i]="${rivi}"; ((++i)); done < koetiedosto; echo ${matriisi[@]}
se tulostaa: 1 2 3 4
käske sitten:
Koodia: [Valitse]
i=0; while IFS=$'\n' read -r rivi; do matriisi[i]="${rivi}"; ((++i)); done < koetiedosto; echo "${matriisi[@]}"
se tulostaa: 1 2 3 4
käske sitten:
Koodia: [Valitse]
readarray matriisi < koetiedosto; echo ${matriisi[@]}
se tulostaa: 1 2 3 4
Käske sitten:
Koodia: [Valitse]
readarray matriisi < koetiedosto; echo " ${matriisi[@]}"
Se tulostaakin:
 1 2
 3 4
- siis ero tulee kun krjoitetaan tulostettava lainausmerkkeihin. Oikeastaan aika näppärääkin - mutta miksei siitä kerrota missään?
------
matriisin lukeminen tiedostosta vanhalla luotettavalla menetelmällä:
Koodia: [Valitse]
i=0; while IFS=$'\n' read -r rivi; do matriisi[i]="${rivi}"; ((++i)); done < tiedosto
tai:
i=0; cat tiedosto | while read rivi; do matriisi[i]="${rivi}"; ((++i)); done
matriisin lukeminen tiedostosta uudemmalla menetelmällä. Mutta tuo readarray tekee siis vähän muutakin:
Koodia: [Valitse]
readarray matriisi < tiedosto
matriisin lukeminen tekstijonosta:
Koodia: [Valitse]
matriisi=(1 2 3 4 5) 
matriisin lukeminen toisesta matriisista:
Koodia: [Valitse]
matriisi=("${apu[@]}")
matriisin lukeminen toisesta matriisista jättäen ne rivit pois joilla on valittava teksti jossainkohtaa (tässä esimerkissä 3.3):
Koodia: [Valitse]
matriisi=("${apu[@]/*3.3*/}")
matriisin lukeminen toisesta matriisista vaihtaen millähyvänsä rivillä määrättävän tekstin:
Koodia: [Valitse]
matriisi=("${apu[@]//menuentry/menuhih}")
joskus koodista tulee sangen sottaista:
matriisi2=("${matriisi[@]//*jotakin*/'rivi poistettu\n'}"); echo -e " ${matriisi2[@]}"
tai kun yritetään "negatiivista jokeria" elikä:
matriisi2=("${matriisi[@]//!(*jotakin*)/'rivi poistettu\n'}"); echo -e " ${matriisi2[@]}"
niin tulos ei ole ihan sitä mitä odotetaan - mutta tämähän onkin vain tutkijan apuväline eikä fakta
matriisien liittäminen: 
Koodia: [Valitse]
matriisi=("${apu[@]}" "${apu2[@]}")
tai:
matriisi+=("${apu[@]}")
 
**
Aloitinpa taas kertaalleen tutkia kuinka matemaattiset laskut suoritetaan "myrkyttämättä" ympäristöä. Ei siitä oikeastaan mitään oleellisesti uutta ilmennyt, mutta taas kertaalleen laskut nopeutuivat, kofor n in $(seq 1 $(cat tiedosto2 | awk NF | awk 'END{print NF}')); do  echo -n sarakkeen:$n' summa on:'; echo $(cat tiedosto2 | awk -v n=$n '{printf "%s",$n"+"}' | sed 's/sin[0-9]*/s(&)/g;s/sin//g;s/[Ee]/*10^/g;s/+*$//g' ) | bc -l; doneodi väheni romahtamalla ja uusien laskutapojen lisäämisestä tuli oleellisesti helpompaa. Kyllä tämä silti kaipaisi mahdottoman paljon tarkennuksia koodiin. Mutta kunhan tätä ideaa saa vielä parannettua niin mihinkähän päätyy:
Koodia: [Valitse]
#!/bin/bash
function laske () {
apu=$(echo $1 | sed 's/e/'$(echo "e(1)" | bc -l)'/g') #  grep -Po '(?<=\+)\+|-|\*|\/|\^|\%') tr -d
luku=$(echo $apu | tr -d '![[:alpha:]]' | sed 's/^+//g;s/++/+/g;s/--/+/g;s/-+/-/g;s,\/+,\/,g;s/\*+/\*/g;s/\^+/\^/g') # laskumerkit bc:n sääntöjen mukaisiksi
case $(echo $apu | tr -d '[0-9].+-') in 
 sin) echo "s($luku)" | bc -l;;
 cos) echo "c($luku)" | bc -l;;
 tan) echo $(echo "s($luku)/c($luku)") | bc -l;;
 sqrt) echo "sqrt($luku)" | bc -l;;
 \!)   echo $(seq -s* $luku | bc | tr -d '\\' | tr -d "\n");; 
 *)  [[ $(echo $luku | grep '\^.*\.') ]] && echo $(echo "e($(echo $luku | awk -F^ '{print $2}')*l($(echo $luku | awk -F^ '{print $1}')))" | bc -l) || echo "$(echo $luku)" | bc -l;;           
esac
}

# kokeita, niitä on paljon ja ne muuttuvat kokoajan
laske +2--2 # automaatti-järjestelmistä tällaisia laskuja tulee usein niin hölmö kuin lasku ihmisen mielestä onkin
laske !99
laske 2^2   # tuloksen pitää olla tasan 4 mikäli lasku kulkee oikeaa tietä - siis ei samaa kuin desimaali-exponenttiset             
laske 2.000001^2
laske 2^2.000001
toisaalta onhan tuolla parempikin viritelmä:  http://forum.ubuntu-fi.org/index.php?topic=47408.msg364003#top
**
- muuten bc:lle voi syöttää matriisinkin. Esimerkiksi:
Koodia: [Valitse]
a=10; b=0; for n in $(seq $a -1 $b); do echo "obase=2; $n" | bc; done

**
Yksinkertaisilla BASH-skripteillä voidaan suorittaa nopeasti kaikenmoisia tehtäviä. Otetaanpa esimerkiksi matriisin sarakesummat: miksi edes yrittää ratkaista jonkun rivin summaa kun voi ratkaista ne kaikki ja valita sitten lopputuloksesta oikean? Eipähän käytä vahingossa väärän sarakkeen summaa.
- muuten BASH ei  tehtävien määrän kasvaessa hidastu paljoakaan mikäli käyttää käskyjen sisäisiä looppeja sillä nehän ovat C-kielisiä - ja oikein koodattuja liäksi.
Koodia: [Valitse]
for n in $(seq 1 $(cat tiedosto2 | awk NF | awk 'END{print NF}')); do  echo -n sarakkeen:$n' summa on:'; echo $(cat tiedosto2 | awk -v n=$n '{printf "%s",$n"+"}' | sed 's/sin[0-9]*/s(&)/g;s/sin//g;s/[Ee]/*10^/g;s/+*$//g' ) | bc -l; done
Kovalevylle kirjoitetun matriisin sarakesummat lasketaan riippumatta siitä mitä matriisissa on: desimaalilukuja tavallisessa tai tieteellisessä esitysmuodossa, ratkaisemattomia sinejä tai mitä halutaankin, desimaaleja voi olla sisäänmenossa ja ulostulossa niin monta kuin haluaa ...
Matriisissa voi olla mielivaltaisen monia sarakkeita tai rivejä ja se otetaan automaattisesti huomioon. Ja toiminta on lisäksi nopeaa.
- tuloste on tähän tyyliin:
sarakkeen:1 summa on:2351232
sarakkeen:2 summa on:39168.0
sarakkeen:3 summa on:40320


Vastaava sarakesummien awk-skripti on kyllä kertaluokkaa nopeampi, mutta sen desimaalien määrä on maksimissaan vain noin 19:
Koodia: [Valitse]
awk NF tiedosto2 | awk '{for(i=1;i<=NF;i++) s[i]+=$i} END {for(i=1;i<=NF;i++) print "sarakkkeen: "i" summa=" s[i]}'
ja rivisummien:
Koodia: [Valitse]
awk '{for(i=1;i<=NF;i++) t+=$i; print "rivisumma riviltä:"NR,t; t=0}' tiedosto2

Pienin muutoksin skriptin tulosteesta saa  tämäntyyppisen:
sarakkkeen:1  summa=2041  keskiarvo=291.571   keskipoikkeama=697.466
sarakkkeen:2  summa=34      keskiarvo=4.85714   keskipoikkeama=2.70547
sarakkkeen:3  summa=35      keskiarvo=5              keskipoikkeama=2
 
skripti on tällainen:
Koodia: [Valitse]
awk NF tiedosto2 | awk '{for(i=1;i<=NF;i++) {sum[i]+=$i;sumsq[i]+=$i*$i}} END {for(i=1;i<=NF;i++) print "sarakkkeen:"i" summa=" sum[i]" keskiarvo="sum[i]/NR" keskipoikkeama="sqrt(sumsq[i]/NR - (sum[i]/NR)^2)}' | column -t
- tuo: "column -t" skriptin perässä uudelleen-formatoi niin hyvin että formatointi ei ihmiseltä onnistu koskaan yhtähyvin.
- hidashan tämä on: jos otat lämpötila- ja tuuli lukemia sekunnin välein vuodenverran niin huonolla koneella käsittely kestää noin 20sekuntia. Niin kestää muuten luku- ja talletuskin.

Mennyttä aikaa ? Eivät ongelmat nykyään tuolla tasolla ole? Kyllä ne ongelmien osatehtävät ovat, ainoastaan ongelmien lukumäärä on huomattavasti suurempi; eikä näin omaan käyttöön esiintulevissa ongelmissa kasvuakaan ole tapahtunut merkittävästi. Mikäli jokaisen pienen osa-onghelman ratkaisun löytäisi heti niin jopa BASH pystyisi ratkaisemaan sen isommankin ongelman..
« Viimeksi muokattu: 03.03.16 - klo:06.41 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #217 : 03.03.16 - klo:07.04 »
- ihan ensiksi lataa gnuplot-ohjelma:
Koodia: [Valitse]
sudo apt get install gnuplot

Yksi kouriintuntuva menetelmä määrätynlaisen tiedon kuvaamiseksi on piirtää se. Kuvataanpa hommaa esimerkillä:
- jokainen esimerkki toimii ihan sellaisenaan ja niistä voi ottaa mallin omaan skriptiinsä: ensin datan muodostaminen ja sitten sen plottaaminen. Kumpikin vain yhdellä rivillä. Dataa kannattaa tutkia editorilla että saisi kuvan siitä kuinka oma data on muodostettava.
- ettet heti heittäisi kirvestä heti järveen niin annapa käsky:
Koodia: [Valitse]
gnuplot -e "plot sin(x)"
# tai peräti:
gnuplot -p -e 'set terminal postscript eps color enhanced; set output "sini.eps"; plot [-5:5] [-1.5:1.5] sin(x),  cos(x)' # kirjoittaa tiedoston kotikansioon
# ehkäpä 3D kiinnostaa:
gnuplot -p -e 'splot [0:3] [1:4] [-1:5] x*y-2'


- sitten muodostetaan koe-tiedosto (noin yksi jakso sini-käyrää):
Koodia: [Valitse]
for x in {1..628}; do echo $x" "$(awk -v x=$x 'BEGIN{print sin(x/100)}') >> /tmp/tiedosto; done
saadaksesi ensin varman kuvan siitä millainen tiedosto muodostui niin tulosta se ensin tekstinä:
Koodia: [Valitse]
cat /tmp/tiedosto
seuraavaksi plotataan se:
Koodia: [Valitse]
cat /tmp/tiedosto | gnuplot -p -e 'plot "/dev/stdin"'
- x-arvona on /tmp/tiedosto:n sarake1  ja y-arvona sarake 2 . Oletuksena on että x- ja y-arvojen välissä on välilyönti Jos muita sarakkeita on jätetään ne huomioimatta.
- ilmaantuvan piirroksen vasemmassa alakulmassa päivitetään hiiriosoittimen koordinaatteja
- tällä esitystavalla on helppo plotata matriisejakin ; siis levyltä ei lueta mitään:
Koodia: [Valitse]
for x in {1..628}; do mat[x]=$(echo $x" "$(awk -v x=$x 'BEGIN{print sin(x/100)}')) ; done; echo -e ${mat[@]/%/\\n} | gnuplot -p -e 'plot "/dev/stdin"'

- voi myöskin tehdä funktion plot_matrix. Siinä passataan vain matriisin nimi joten sitä voi käyttää mille hyvänsä matriisille:
Koodia: [Valitse]
function plot_matrix () { eval echo \${$1[*]} | tr ' ' '\n' | gnuplot -p -e 'plot "/dev/stdin" with lines' ;}; \    # tällä rivillä on itse funktio
for x in {1..628}; do apu[$x]=$(awk -v x=$x 'BEGIN{print sin(x/100)}'); done; plot_matrix apu                       # tällä rivillä on esimerkki matriisin muodostuksesta

Onhan tuolla gnuplotilla noita krumeluurejakin, esimerkiksi:
Koodia: [Valitse]
cat /tmp/tiedosto | gnuplot -p -e 'set title "sinikäyrä"; set ylabel "y"; set xlabel "x";plot "/dev/stdin"'
tai kirjoittaa piirros kovalevylle:
Koodia: [Valitse]
cat /tmp/tiedosto | gnuplot -p -e 'set terminal postscript eps color enhanced; set output "sini.eps"; plot "/dev/stdin"'
tai UbuntuSuomen foorumille kelpaavana:
gnuplot -p -e 'set terminal pngcairo size 350,262 enhanced font "Verdana,10"; set output "koe.png";set pointsize 1.0; set title "osumat"; set ylabel "y"; set xlabel "x";plot "~/fooruminstatistiikka" using 6 with lines'
- noita set-käskyjä voi laittaa niin monta kuin haluaa

ja tulosta voi tarkastella esimerkiksi käskyllä:
Koodia: [Valitse]
evince sini.eps

Mikäli x- ja y-pisteet ovat toisissa sarakkeissa kuin 1 ja 2 niin silloin käsketään:
Koodia: [Valitse]
cat /tmp/tiedosto | gnuplot -p -e 'set pointsize 1.0; set title "sinikäyrä"; set ylabel "y"; set xlabel "x";plot "/dev/stdin" using 2:3'



Kun halutaan plotata samaan kuvaan kaksi käyrää niin siihen on useampiakin menetelmiä, tässä kaksi:
Koodia: [Valitse]
# Käyrien datat ovat omissa tiedostoissaan:
rm -f /tmp/tiedosto1; for x in {1..628}; do echo $x" "$(awk -v x=$x 'BEGIN{print sin(x/100)}') >> /tmp/tiedosto1; done
rm -f /tmp/tiedosto3; for x in {1..628}; do echo $x" "$(awk -v x=$x 'BEGIN{print cos(x/100)}') >> /tmp/tiedosto3; done
cat /tmp/tiedosto1 <(echo -e '\n') /tmp/tiedosto3 > /tmp/tiedosto4
gnuplot -p -e 'plot "/tmp/tiedosto4" using 1:2 index 0, "" using 1:2 index 1'

# tai kun käyrien datat ovat samassa tiedostossa:
rm -f /tmp/tiedosto1; for x in {1..628}; do echo $x" "$(awk -v x=$x 'BEGIN{print sin(x/100)}')" "$(awk -v x=$x 'BEGIN{print cos(x/100)}') >> /tmp/tiedosto1; done
gnuplot -p -e 'set pointsize 1.0; set title "sini- kosini-käyrät"; set ylabel "y"; set xlabel "x";plot "/tmp/tiedosto1" using 2 title "Sini" with lines, "/tmp/tiedosto1" using 3 title "Kosini" with lines'
# - kun on tuontapainen määritelmä: "using 2" eikä "using 1:2" niin se tarkoittaa että x-arvona käytetään pisteen järjestysnumeroa.
# - koodi koostuu hieman eritavalla kuin aikaisemmissa esimerkeissä ettei siitä tulisi kamalan pitkä.
# - käyrien värit tulevat automaattisesti.Piirroksen oikeassa yläkulmassa kerrotaan mikä väri vastaa mitäkin käyrää. Värejä voi olla 8?
# - tämän käyrän pirros esitetty sivun alaosassa

-----
Edellisessä esimerkissä plotattavat arvot sopivat erinomaisesti automaatti-asetuksia varten. Mutta joskus plotattavat arvot ovat sellaisia, että niiden perusteella tapahtuvat automaatti-asetukset olisivat huonoja ja silloin voi kyseisen asetuksen määrätä. Muutama yleisin:
Koodia: [Valitse]
 
set xrange [-2*pi:2*pi]                                   # säätää x-alueen
set yrange [-1.5:1.5]                                     # säätää y-alueen

set xtics ('-2π' -2*pi, '-π' -pi, 0, 'π' pi, '2π' 2*pi)   # säätää akselimerkintöjä
set ytics 1                                               # säätää akselimerkintöjä
set tics scale 0.75                                       # säätää akselimerkintöjä
set xtics axis                                            # samassa esityksessä voi olla käyriä joilla on eri x. Mikä x-akseleista näkyy
set ytics axis                                            # samassa esityksessä voi olla käyriä joilla on eri y. Mikä y-akseleista näkyy

noborder                                                  # määrää ettei käyrän ympärille piirretä rajoja 

set format x "%2.0f"                                      # x-akselin merkintöjen esitysmuoto
set format y "%2.0f"                                      # y-akselin merkintöjen esitysmuoto
- tämä kaivannee esimerkkiä:
datan teko: rm -f /tmp/tiedosto1; for x in {1..628}; do echo $(awk -v x=$x 'BEGIN{print (x/314)}')" "$(awk -v x=$x 'BEGIN{print sin(x/100)}')" "$(awk -v x=$x 'BEGIN{print cos(x/100)}') >> /tmp/tiedosto1; done
datan plottaus: gnuplot -p -e 'set format x "%2.1f'pi'"; set pointsize 1.0; set title "sini- kosini-käyrät"; set ylabel "y"; set xlabel "x";plot "/tmp/tiedosto1" using 1:2 title "Sini" with lines, "/tmp/tiedosto1" using 1:3 title "Kosini" with lines'

- joskus täytyy muuttaa muutakin kuin set-lauseita: esimerkiksi joskus haluat kaksi käyrää samaan piirrokseen jotta saisit käsityksen siitä kuinka toisen käyrän arvot korreloivat toisen käyrän arvojen kanssa, mutta toisen käyrän esimerkiksi y-arvot ovat niin pienellä alueella ettei sen käyrän muutoksia näe samasta piirroksesta ihmis-silmin tai peräti toisen offset sekoittaa koko piirroksen. Tällaista tilannettta kuvaa esimerkki:
Koodia: [Valitse]
datan muodostus: rm -f /tmp/tiedosto1; for x in {1..628}; do echo $(awk -v x=$x 'BEGIN{print (x/314)}')" "$(awk -v x=$x 'BEGIN{print sin(x/100)}')" "$(awk -v x=$x 'BEGIN{print cos(x/100)/100+40}') >> /tmp/tiedosto1; done
datan plottaus: gnuplot -p -e 'set format x "%2.1f'pi'"; set pointsize 1.0; set title "sini- kosini-käyrät"; set ylabel "y"; set xlabel "x";plot "/tmp/tiedosto1" using 1:2 title "Sini" with lines, "/tmp/tiedosto1" using 1:3 title "Kosini" with lines axes x1y2'
- siis eron tekee tuo lopussa oleva: axes x1y2. Jos jätät sen pois et saa arvoista mitään selvää.

-------------------
Kuinka gnuplotilta kysytään neuvoa siitä kuinka käskyt rakennetaan:
- ensin mennään päätteeseen ja käsketään: gnuplot
- sitten annetaa käsky: help . Sieltä tulee niin paljon asiaa että sekoittaahan se vaan alkuunsa. Mutta annapa käsky: help plot. sieltä tulee taas paljon asiaa, mutta muun muassa:

gnuplotin syntaksista:
       plot {<ranges>}
            {<iteration>}
            {<function> | {"<datafile>" {datafile-modifiers}}}
            {axes <axes>} {<title-spec>} {with <style>}
            {, {definitions{,}} <function> ...}
- sitten vain antamaan käskyjä tyyppiä: help plot ranges
- siis vikana on ihan sama kuin Linuxissa kaikkialla: saat niin paljon apua että melkein tukehdut siihen. Mutta pari vuotta sitkeää yrittämistä niin kyllä se siitä.
----
gnuplot:illa on myös kyky interpolointiin. Muodostaakseesi koetiedoston anna ensin käsky:
Koodia: [Valitse]
echo -e '-111 -0.07\n-24 0.09\n62 0.12\n69 0.2\n86 0.7\n99 0.74\n101 0.69\n105 0.2\n120.403 -0.5848\n170 -0.353\n247.891 -0.105295\n321 0.0925' > /tmp/delme
ja sitten anna käsky:
Koodia: [Valitse]
gnuplot -p -e 'plot "/tmp/delme" u 1:2 smooth cspline, "/tmp/delme" with points'
- interpoloitu käyrä on punainen jatkuva kuvaaja. Vihreät pisteet ovat annettuja pisteitä.
- csplinen lisäksi tunnetaan:  unique, frequency, cumulative, cnormal, kdensity, acspline, bezier ja sbezier.
---
gnuplotilla on enemmän ominaisuuksia kuin jaksat edes lukea. Kyse onkin lähinnä siitä onko gnuplot ajantasalla.
gnuplotin versio 5.02 on tehty tammikuussa 2016. Ubuntun versio on 4.6.4 sillä Canonical ei suosi sitä että ihmiset tekevät itse.



« Viimeksi muokattu: 30.03.16 - klo:14.14 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #218 : 08.03.16 - klo:09.18 »
Nyt en malta olla kertomatta commandlinefu.com:mista löytyneestä fibonac-sarjan jäsenien laskumenetelmästä:
Koodia: [Valitse]
echo 'n=92;m=(n+1)/2;a=0;b=1;i=0;while(m){e[i++]=m%2;m/=2};while(i--){c=a*a;a=c+2*a*b;b=c+b*b;if(e[i]){t=a;a+=b;b=t}};if(n%2)a*a+b*b;if(!n%2)a*(a+2*b)' | bc | tr -d '\\\n'
- tuon f(92):n laskeminen kestää 5 millisekuntia minun kelvottomalla läppärilläni - siis se on nopeampi kuin awk-skripti mikäli awk-skriptissä edes riittäisivät numerot.
- sillä voi laskea mielivaltaisen suuren fibonac:cin - kunhan sen numerot vain mahtuvat RAM:miin.  Tuo raja 92 on naurettavan pieni ja 92000 on parempi.
- se tuntuu laskevan oikein - tosin äkkiseltään löysin netistä fibonacit vain 1000:n asti. Ja oikeellisuushan on ehdottomasti tärkeintä.
- sen toimintaa ei käsitä, Mutta sama vikahan on jopa C-koodillakin: et tiedä mitä mielettömyyksiä se milloinkin tekee riehuessaan kirjastoissaan. Eikä kirjastot tiedä mitä raudan firm-ware ohjaa raudan tekemään.

Tässähä ei varsinainen matematiikka olekaan BASH:ia, vaan skripti-kieli bc:tä. Tämä on ensimmäinen varsinainen bc-skripti minkä olen kohdannut.

tai kuinka sen ottaa - täytyy myöntää etten ole mikään menestystarina tässä skriptaamisessa. Varmaankin tie tämmöisen ymmärtämiseen kuulkee niiden define-lauseiden kautta joista kyllä toitotetaan mutta joita en ole koskaan saanut toimimaan; yksi toimiva esimerkki riittäisi.

Lopultakin sain aikaan toimivan esimerkin tuosta define:stä:
Koodia: [Valitse]
#!/bin/bash
function eksponentiaatio () { echo "kantaluku=$1; eksponentti=$2

define int(kantaluku) { auto oldscale; oldscale = scale; scale = 0; kantaluku /= 1; scale = oldscale; return kantaluku ;}
 
# eksponentin ollessa desimaaliluku lasketaan eksponentiaatio kaavalla:  e^(eksponentti*log(kantaluku))
define power(kantaluku,eksponentti) { if (eksponentti == int(eksponentti)) return kantaluku^int(eksponentti) else return e(eksponentti*l(kantaluku)) ;}

power (kantaluku,eksponentti)" | bc -l ;} # bc:n funktiokutsut laitetaan tänne

eksponentiaatio 4 2.000001
**
Bc:lle voikin syöttää ohjelmakoodia. Olisikohan syntaksi C:n kaltainen ? Sillä:
Koodia: [Valitse]
echo 'n=93;n=(n+1)/2;n+1' | bc
tulostaa 48 niinkuin olettaa voikin
- ja voi se ohjelmakoodi tulostaa itsekin, kuten tässä jonka tarkoitus on muuttaa tulosteet muodosta: .1 muotoon 0.1 ja muodosta: -.1 muotoon -0.1
Koodia: [Valitse]
echo "x=-.1; if(x==0) print \"0.0\" else if(x>0 && x<1) print 0,x else if(x>-1 && x<0) print \"-0\",-x else print x" | bc

Myös tarkistus siitä onko luku tosiaan luku on parasta suorittaa kysymällä se bc:ltä:
Koodia: [Valitse]
[[ $(echo kirjoita_luku_tähän | bc 2>/dev/null) ]] && echo kyseessä on numero || echo kyseessä ei ole numero
- tarkistus bc:llä on aina selvä eikä tarvitse pähkäillä minkäsorttista lukua tarkistaa. Regexillä täytyy sillä:
Koodia: [Valitse]
luku=-9999; [[ $(echo $luku | grep -E '^[-+0-9][0-9]+$') ]] && echo "kokonaisluku"
luku=-1.9999; [[ $(echo $luku | grep -E '^[-+0-9]*\.[0-9]*$') ]] && echo "desimaaliluku"
luku=-0.9e-999; [[ $(echo $luku | grep -Po  '[-+]?[0-9]*\.?[0-9]+[eE][-+]?\d+') ]] && echo "tieteelisen esitysmuodon luku"


tai toisen asteen yhtälön juuret laskettuna bc:llä:
Koodia: [Valitse]
echo 'a=1; b=2; c=1; x1=(-b+(sqrt(b^2)-4*a*c))/(2*a) ; x2=(-b-(sqrt(b^2)-4*a*c))/(2*a); print "ensimmäinen juuri=";x1; "ja toinen juuri=";x2' | bc
- matikkani on unohtunut ja sitä en tiedä onko oikein; mutta korjaamallahan se lutviutuu. Mutta oikeastaan tässä vain selvennetään minkätyyppisien ongelmien ratkaisuun bc kykenee.
- siis tässä ei ole kyse toisen asteen funktion ratkaisemisesta vaan siitä että bc kykenee tulostamaan tekstiäkin.

kertoma laskettaisiin define määritelmin näin:
Koodia: [Valitse]
#!/bin/bash
function kertoman_laskeminen () { echo " x=$1; scale=0
define kertoma (x) { if (x <= 1) return (1);
                     return (kertoma(x-1) * x);}
kertoma (x)" | bc -l | tr -d '\\\n' ;}   
time kertoman_laskeminen 1000
- kertoman laskemisessa defineä ei kuitenkaan kannata tehdä, sillä ihan yhtä nopeaa on laskea näin: echo $(seq -s* x | bc | tr -d '\\\n')
- tosi tässä esitetty define on rekursiivinen. mutta muoto:
Koodia: [Valitse]
define kertoma (x) { m=1; for(i=1;i<x;i++) m=m*i; return (m);}
ei nopeuta yhtään.
- kertomat luvuista alueella 1-20 saa nopeimmin laskettua käskyllä: echo $(($(seq -s* luku)))

Tämmöisiähän 30-vuotta sitten oli kädessäpidettävissä funktiolaskimissa:
- kun annetaan rajat x1 ja x2 ja määritellään funktio niin integroidaan niiden ja x-akselin rajoittama pinta-ala ja piirretään määrityksistä myös käyrä:
Koodia: [Valitse]
#!/bin/bash
function tee_kippura () { echo "
define f(x) { return (e(-(x^2)));}
for(i=0;i<1;i=i+0.01) {print i;574372;f(i)}" | bc -l > /tmp/delme ; sed -i ':a;N;$!ba;s/574372\n/ /g' /tmp/delme ;}

function itegroi_pa () { echo "
define simpson(a,b,n) {
auto h,sum_even,sum_odd;
h=(b-a)/(2*n);
sum_even=0;
sum_odd=0;
for (i=1;i<=n;i++) sum_odd=sum_odd+f(a+(2*i-1)*h);
for(i=1;i<n;i++) sum_even=sum_even+f(a+2*i*h);
return ((f(a)+f(b)+4*sum_odd+2*sum_even)*h/3);}

define f(x) { return (e(-(x^2)));}

simpson(0,1,10)" | bc -l ;} # integroi PA:n väliltä 0-1. Ja 10:n palasta? arvo 100=> lasku kestää hieman kauemmin mutta arvo on oikeampi?

tee_kippura
itegroi_pa
gnuplot -p -e 'set pointsize 1.0; set title "PintaAla"; set ylabel "y"; set xlabel "x";plot "/tmp/delme" using 1:2 title "Raja" with lines'
- hankalakäyttöinenhän tuo vielä on eikä kannata kehittääkään sitä sujuvaksi sillä periaatehan tässä kiinnostaa.
**
Totuusarvot (Boolean) ovat yhtä vanhat kuin tietokonekin ja kyllä ne BASH:iinkin kuuluvat. Mutta nimitykset true ja false ovat tehty vasta senjälkeen kun BASH oli jo tehty ja niinpä BASH puhuu totuusarvoista numeroarvoina 1 ja 0. 
- itseasiassa mikähyvänsä nollasta eroavan totuusarvo on false (arvo on "nurinkurinen" koska käskyn ollessa toimiva sen virhekoodi on 0. Mikrosoft alkaessaan tehdä ohjelmisto-ympäristöä harkitsi kuinka parhaiten heittää kapuloita Linuxin rattaisiin ja BOOLEAN osastollakin kaikki käännettiin ympäri. Onnistuikin sabotaasissaan, ryökäle.)

Ja tämä tuntuu myös BOOLEAN matematiikassa: BASH:issa toimitaan numeroarvojen kanssa. Käytännössä tämä tarkoittaa sitä, että BASH:issa totuusarvot lasketaan kaarisuluissa ja hakasuluissa toimitaan tekstijonojen kanssa. Esimerkiksi: 
(( 0 )) && echo true || echo false  # tulostaa false: koska (( 0 )) on false niin suoritetaan || perässä oleva käsky.
[[ 0 ]] && echo true || echo false  # tulostaa true, koska katsottiin onko verrattavassa mitään tekstiä ja se ei merkitse mitään että teksti sattuu olemaa 0.

Esimerkkejä boolean-operaattorien käytöstä:
#!/bin/bash
a=1; b=1
(( $a & $b )) && echo "true" || echo "false"  # Muuttujien ja  (=and) . Tuo yksinäinen & kirjoitetaan usein &&, mutta siinä taidetaan sotkea kaksi asiaa vaikka se toimiikin)
(( $a | $b )) && echo "true" || echo "false"  # Muuttujien tai (=or)
(( $a ^ $b )) && echo "true" || echo "false"  # Muuttujien poissulkeva tai (=xor; tosi vain jos muuttujien totuusarvot ovat erilaisia)(siis BASH:issa ^=XOR ja **=potenssi)
- muuuttujan boolean-arvo muuttuu päinvastaiseksi kun sen eteen kirjoitetaan merkki: !
**
BASH:issa on myös ihan hyvät bitti-operaatiot. Eipä niitä juurikaan käytetä, mutta asianharrastajat kyllä tekevät niillä ihmeitä. Muutama esimerkki:

var=1; echo $((var <<= 2))     # tulostaa:4 . Siis desimaaliluvun 1 binääriesitystä 1 siirretään kaksi pykälää vasemmalle ja oikealta tulee nollia. Lopputulos esitetään desimaalisena.
tai:
var=4; echo $((var >>= 2))     # tulostaa: 1 . Pitää pitää mielessä ettei BASH:issa ole desimaalilukuja - ja BASH ei pyöristä vaan leikkaa. Samat desimaali/binääri/desimaali muunnokset kuin edellisessäkin.

var=5; ((var |= 7)); echo $var # tulostaa 7, sillä tuo yhtäkuinmerkki myös muuttaa var:n arvon.
« Viimeksi muokattu: 31.03.16 - klo:06.33 kirjoittanut petteriIII »

petteriIII

  • Käyttäjä
  • Viestejä: 657
    • Profiili
Vs: Ohjeita shell-skriptaukseen (bash)
« Vastaus #219 : 11.03.16 - klo:17.34 »
Lämpökameran datan plottaaminen gnuplot-ohjelmalla:
- syy siihen miksi laitan tämän tänne kun suurinpiirtein sama on verkkisivulla: http://gnuplot.sourceforge.net/demo/heatmaps.html
on se, että poikkeuksetta kaikilla verkkosivuilla olevat ohjeet ovat virtuoosien tekemiä toisia virtuooseja varten ja ne eivät koskaan toimi meille taviksille ihan noinvain, sillä niissä jätetään selvityksen "ensimmäinen pylväänväli" pois; ja sitäpaitsi ne eivät muutenkaan aina toimi Ubuntulla ihan semmoisenaan.

Koodia: [Valitse]
#!/bin/bash
# lämpökameran datan tulee olla seuraavassa muodossa: x-pixeli_no     y-pixeli_no    lämpöä_kuvaava_numero 
ja juuri niinkuin seuraavassa on ettei seuraisi vaikeuksia. Seuraavassa esimerkissä arvot kirjoitetaan tiedostoon

echo -e "
0 0 5
0 1 4
0 2 3
0 3 1
0 4 0

1 0 2
1 1 2
1 2 0
1 3 0
1 4 1

2 0 0
2 1 0
2 2 0
2 3 1
2 4 0

3 0 0
3 1 0
3 2 0
3 3 2
3 4 3

4 0 0
4 1 1
4 2 2
4 3 4
4 4 3" > /tmp/delme

# mitenkä tiedosto onkin muodostettu niin se plotataan seuraavalla käskyllä:
gnuplot -p -e 'plot "/tmp/delme" using 2:1:3 with image'

« Viimeksi muokattu: 20.03.16 - klo:13.55 kirjoittanut petteriIII »