HyppÀÀ sisÀltöön

💡 Gallium

Osaat jo ajaa Hello World -skriptin, ja olet lukenut oikeaa tuotantokoodia. TÀssÀ luvussa harjoittelemme yksinkertaisia skriptejÀ, jotka eivÀt odota argumentteja tai kÀyttÀjÀn syötettÀ. Ne eivÀt myöskÀÀn luo tai tuohoa tiedostoja. Seuraavassa luvussa siirrymme ajamaan skriptejÀ Docker-kontissa, jolloin voimme kÀyttÀÀ hitusen riskialttiimpia skriptejÀ.

Olet kasannut itsellesi oppimateriaaleja, joten tÀmÀ paketti ei keskity esittelemÀÀn teoriaa. Alla on kuitenkin muutama tÀrppi, joista voi olla hyötyÀ. Varsinkin debug-taidot kannattaa ottaa haltuun.

Muuttujanimet

Muuttujien nimet voivat olla mitÀ tahansa, mutta niiden tulee alkaa kirjaimella tai alaviivalla ja niissÀ voi olla kirjaimia, numeroita ja alaviivoja. Muuttujanimet ovat case-sensitiivisiÀ, eli muuttuja ja Muuttuja ovat kaksi eri muuttujaa.

Bashissa kÀytetÀÀn usein kokonaan pienillÀ kirjoitettuja muuttujia ja eri sanat irrotetaan toisistaan snake_case-tyylillÀ. Esimerkiksi max_length on hyvÀ muuttujanimi.

Kussakin kielessÀ on varattuja avainsanoja (engl. reserved keyword). NiitÀ ei voi kÀyttÀÀ muuttujien niminÀ. NÀmÀ on listatuna alla.

if          then        else        elif        
fi          case        esac        for         
select      while       until       do          
done        in          function    time        
{           }           !           [[          
]]          coproc      
Klikkaa auki skripti, jolla lista muodostettiin
reserved.sh
#!/bin/bash

# Get all reserved keywords
keywords=($(compgen -k))

# Define the number of columns
columns=4

# Loop through and print in formatted columns
for ((i = 0; i < ${#keywords[@]}; i++)); do
    printf "%-12s" "${keywords[i]}"

    # Print a newline after every nth column
    if (( (i + 1) % columns == 0 )); then
        echo
    fi
done

# End the output with a newline
echo

Warning

Varattujen avainsanojen lisÀksi on lista Bashin sisÀÀnrakennettuja komentoja, jotka voit ylikirjoittaa, mutta tÀmÀ ei luonnollisesti kannata. Esimerkiksi cd ..-komennon pitÀisi vaihtaa hakemistopuussa yksi hakemisto ylöspÀin. Katso alta skripti, jossa kyseinen komento tulostaa vain .. by cd(), koska se on korvattu samannimisellÀ funktiolla kyseisen skriptin sisÀllÀ.

overwrite_cd.sh
#!/bin/bash -e
cd() {
    local var="$1";
    echo "${var} by cd()";
}

cd ..

TÀmÀ ei riko skriptin ulkopuolista cd-komennon toimintaa, paitsi jos sen tuo kyseiseen namespaceen source-komennolla. TÀmÀn jÀlkeen hakemiston muuttuminen muuttuu merkittÀvÀn hankalaksi:

🐳 Bash
$ source /app/overwrite_cd.sh 
.. by cd()

$ cd /etc/
/etc/ by cd()

$ cd $HOME
/root by cd()

Muuttujan asettaminen

Muuttujan asettaminen tapahtuu seuraavasti:

# ✅ Oikein
muuttuja="arvo"
muuttuja='arvo'
muuttuja=arvo
muuttuja="moni sanainen arvo"
muuttuja=5       # Merkkijono "5", ei varsinainen numero

# ⛔ VÀÀrin
muuttuja = "arvo"
muuttuja=moni sanainen arvo

LisÀksi on mahdollista kÀyttÀÀ declare-komentoa, joka on Bashin sisÀÀnrakennettu komento muuttujien mÀÀrittelyyn. declare-komento on hyödyllinen, jos haluat mÀÀrittÀÀ muuttujan tyypin.

# Kokonaisluku
declare -i muuttuja=5

# Joukko (array)
declare -a my_array=("eka" "toka" "kolmas")

# Joukko (assoasiative array)
declare -A my_dictionary=([key1]="value1" [key2]="value2")

Jos haluat tulostaa kaikki kÀyttÀmÀsi muuttujat, kirjoita declare -p.

Saavutettuja hyötyjÀ declare -<tyyppi>-kÀytöstÀ ovat mm. virheiden vÀlttÀminen ja koodin selkeyttÀminen. Vahvasti tyyppimÀÀriteltyÀ kieltÀ BashistÀ ei nÀin tule, mutta declare-komento auttaa hieman.

Aritmeettiset operaatiot?

Voit suorittaa kokonaislukumuuttujien avulla laskuoperaatioita esimerkiksi nÀin:

aritmeettinen.sh
#!/bin/bash
declare -i a=5 b=3 x=0
x=a*b
echo "Tulo: $x"

Vaihtoehtoinen tapa on let, joka kÀsittelee kaikkia =-merkin oikealla puolella olevia muuttujia lukuina.

aritmeettinen_let.sh
#!/bin/bash
a=5
b=3
let "x=a*b"
echo "Tulo: $x"
aritmeettinen_compound.sh
#!/bin/bash
a=5
b=3
(( x = a * b ))
echo "Tulo: $x"

Warning

Huomaa, ettÀ luvut ovat kokonaislukuja, mikÀ aiheuttaa sen, ettÀ esimerkiksi 5 / 3 palauttaa luvun 1. Jos jostain syystÀ haluat kÀyttÀÀ liukulukuja, sinun tulee kutsua jotakin ulkoista ohjelmaa, kuten bc-ohjelmaa. LÀhtökohtaisesti Bash ei kuitenkaan ole matemaattinen ohjelmointikieli vaan skriptikieli, joten jÀtÀmme nÀmÀ operaatiot muiden kielien ongelmaksi tÀllÀ kurssilla.

VianetsintÀ

Virheiden etsiminen on tÀrkeÀ osa ohjelmointia. Bashissa on muutamia tapoja, joilla voit helpottaa vianetsintÀÀ.

Set Builtin

Jos haluat debugata skriptiÀsi, voit kÀyttÀÀ set -x ja set -u -komentoja. EnsimmÀinen tulostaa jokaisen komennon ennen sen suorittamista ja jÀlkimmÀinen kaataa skriptin, jos kÀytÀt mÀÀrittelemÀtöntÀ muuttujaa. JÀlkimmÀinen kaatuu myös silloin, jos yritÀt esimerkiksi sijoittaa merkkijonon kokonaislukumuuttujaan. Tutustu myös muihin The Set Builtin optioihin. NÀistÀ varsinkin -e on hyödyllinen, joka kaataa skriptin, jos jokin komento palauttaa virheen.

muuttuja_set_none.sh
#!/bin/bash

declare -i luku=5

luku="kissa"
echo $luku
muuttuja_set_u.sh
#!/bin/bash -u
# ...
muuttuja_set_ux.sh
#!/bin/bash -ux
# ... 

Tip

Samat optiot voi antaa myös skriptiÀ ajaessa nÀin: bash -u muuttuja_set_u.sh.

Echo

Yksi luonnollinen tapa debugata lÀhes mitÀ tahansa ohjelmointikieltÀ on kÀyttÀÀ echo-komennon tulostusta. Voit tulostaa muuttujien arvoja ja tarkistaa, ettÀ ne ovat oikein.

Interaktiivinen

Huomaa, ettÀ BashiÀ voi ajaa skriptin lisÀksi myös interaktiivisesti komentoriviltÀ. TÀmÀ voi kuulostaa itsestÀÀnselvÀltÀ, mutta on helppoa unohtaa, ettÀ keskellÀ 200-rivistÀ skriptiÀ olevan rivin voi myös ajaa erikseen ihan vain kopioimalla sen ja liittÀmÀllÀ terminaaliin.

Declare

Ajoittain on tarpeellista katsoa, mitÀ muuttujia on mÀÀritelty ja mitÀ niiden arvot ovat. TÀmÀ onnistuu declare -p-komennolla. Se tulostaa kaikki mÀÀritellyt muuttujat ja niiden arvot - myös sellaiset, jotka Bash on mÀÀritellyt jossain sinun skriptin ulkopuolella. Sinun mÀÀrittelemÀt muuttuja-arvot ovat onneksi helposti listan lopussa.

declare_p.sh
#!/bin/bash
declare -A my_dictionary=([key1]="value1" [key2]="value2")
my_dictionary[key3]="kissa"

declare -p
đŸ–„ïž Host
# Komentoa lyhennetty - ks. runbash.sh alemmasta tehtÀvÀstÀ.
docker container run ... declare_p.sh
🐳 stdout
# ...
declare -x TERM="xterm"
declare -ir UID="0"
declare -- _=""
declare -A my_dictionary=([key2]="value2" [key3]="kissa" [key1]="value1" )

Tip

Jos haluat tulostaa tietyt muuttujat, sinun tulee antaa ne argumentteina.

declare -p muuttuja1 muuttuja2 ...

TehtÀvÀt

TehtÀvÀ: DevausympÀristö ja runbash.sh

EnsimmÀisenÀ tehtÀvÀnÀ luot itsellesi devausympÀristön. KÀytÀnnössÀ luot:

  • Hakemistorakenteen tehtĂ€vien vastauksia varten
  • Skriptin runbash.sh, joka joko:
    • Ajaa valitun skriptin kontissa
    • KĂ€ynnistÀÀ interaktiivisen Bashin kontissa
  • Varmistat, ettĂ€ kaikki on versionhallinnassa

Opettaja on antanut sinulle tyhjÀn repositorion tÀtÀ kurssia varten, ja se on esimerkiksi osoitteessa https://repo.kamit.fi/skriptiohjelmointi-2054/johnanderton. TyhjÀ repositorio sisÀltÀÀ ohjeet, kuinka voit luoda lokaalin repositorion ja alustaa sen main-haaralla sekÀ tyhjÀllÀ README.md-tiedostolla. Noudata GitLabin ohjeita. Kloonaa repositorio lokaatioon:

  • Ⓜ win: C:\Users\uname\Code\skriptiohjelmointi-2054\johnanderton
  • 🐧 linux: /home/uname/Code/skriptiohjelmointi-2054/johnanderton

Korvaa 2054 kuluvalla vuodella. Korvaa johnanderton omalla nimellÀsi, jossa kirjoitusa on sÀhköpostisi alku: xxxxxx@kamk.fi)

Miksi 2054?

Minority Report -elokuvan John Anderton seikkailee vuodessa 2054. KÀytÀn fiktionaalista vuotta, jotta tÀtÀ materiaalia ei tarvitse pÀivittÀÀ joka toteutuksen yhteydessÀ.

Luo repositorion sisÀllle seuraava rakenne:

johnanderton
├── README.md
├── bash
│   ├── README.md
│   ├── runbash.sh
│   └── scripts
│       ├── kaikki.sh
│       ├── skriptit.sh
│       └── tanne.sh
├── pwsh
│   └── .gitkeep 
└── python
    └── .gitkeep

Tiedosto runbash.sh:n luominen olisi hyvÀ tehtÀvÀ viikon pÀÀstÀ, mutta tarvitset sitÀ jo nyt, joten tarjoan sen valmiina. Voit ladata sen Githubista osoitteesta gh:sourander/skriptiohjelmointi/exercise-assets/scripts/runbash.sh. Lataa tiedosto ja sijoita se oikeaan hakemistoon.

TehtÀvÀ: Bash Hello World

Luo skripti hello.sh, joka tulostaa tekstin "Hello World".

Huomaa, ettÀ sijoita se oikeaan hakemistoon, kuten ~/Code/skriptiohjelmointi-2054/johnanderton/bash/scripts/hello.sh. Voit ajaa tiedoston Àsken lataamallasi apuri-skriptillÀ.

đŸ–„ïž Host
# Vaihda hakemistoon, missÀ on runbash.sh
cd ~/Code/skriptiohjelmointi-2054/johnanderton/bash/

# Aja
./runbash.sh scripts/hello.sh
🐳 stdout
Hello World
TehtÀvÀ: Turboahdettu Bash Hello World

Luo skripti, joka tulostaa absoluuttisen polun työhakemistoon ja siihen hakemistoon, missÀ skripti sijaitsee. Skriptin runko on alla:

hello_turbo.sh
#!/bin/bash

source /etc/os-release
distro_version=${VERSION:-"Unknown distribution"}

cwd_path=''  # IMPLEMENT
scr_path=''  # IMPLEMENT

printf "========= Turbo Hello World! =========\n"
printf "%-30s %s\n" "Current working directory:" "$cwd_path"
printf "%-30s %s\n" "Script directory:" "$scr_path"
printf "%-30s %s\n" "Kernel name:" "$distro_version"

Rivit, joiden perÀssÀ on kommentti # IMPLEMENT, vaativat sinulta toimia. LisÀÀ nÀihin toiminnallisuus. Testaa yllÀ olevan tehtÀvÀn neuvoilla. Kutsu skriptiÀ hello_turbo.sh ja katso, ettÀ se tulostaa oikeat tiedot.

HUOM! PelkkÀ src_path=$0 sattuu toimimaan, koska skripti ajetaan absoluuttisella polulla. TÀmÀn tulet kuitenkin huomaamaan vÀÀrÀksi vastaukseksi viimeistÀÀn seuraavaa tehtÀvÀÀ tehdessÀ. SelvitÀ, kuinka saat kÀÀnnettyÀ relatiivisen polun absoluuttiseksi.

Apuja absolutisointiin

GNU:n readlink-komento auttaa tÀssÀ. Ota selvÀÀ, minkÀ flagin ja parametrin avulla saat haluamasi tuloksen. Tarvitset tÀssÀ tiedonhakutaitoja:

# MitÀ ? ja ???? tilalle tulee?
src_path=$(readlink -? ????)
TehtÀvÀ: Interaktiivinen Bash

Harjoittele tÀssÀ tehtÀvÀssÀ interaktiivista Bashin kÀyttöÀ. TÀmÀ on tarpeellista, jos haluat luoda kontin sisÀlle esimerkiksi testitiedostoja, tai haluat tarkkailla, mitÀ ajettu skripti oikeastaan tekikÀÀn. Tarvitset tÀssÀ tehtÀvÀssÀ ylempÀnÀ mainitun runbash.sh-skriptin. Huomaa, ettÀ se löytyy /app/-hakemistosta kontin sisÀllÀ. TÀmÀ johtuu runbash.sh-skriptin rivistÀ: --mount type=bind,source="$(pwd)/${SCRIPT_DIR}",target=/app,readonly.

Jos muokkaat hello_turbo.sh-tiedostoa Host-koneella, sinun ei tarvitse poistua kontista ja kÀynnistÀÀ sitÀ uudelleen, koska polku on bindattu kontin sisÀÀn. Tallenna tiedosto ja aja se uudelleen kontissa - tiedosto on pÀivittynyt! Myös alla komennoissa luotava tiedosto /root/a/b/c/hello_turbo.sh-pÀivittyy samalla kertaa, koska se on symbolinen linkki eli pointteri alkuperÀiseen tiedostoon.

KÀynnistÀ istunto alla olevalla komennolla. Pois pÀÀset komennolla exit tai pikanÀppÀimellÀ Ctrl+D.

đŸ–„ïž Host
# Ilman parametriÀ skripti ajaa kontin vakio CMD:n, joka on
# haluamamme: /bin/bash
./runbash.sh

Vaihe 1: Aja missÀ oletkin

🐳 Bash
/app/hello_turbo.sh
🐳 stdout
========= Turbo Hello World! =========
Current working directory:     /
Script directory:              /app/hello_turbo.sh
Kernel name:                   24.04.1 LTS (Noble Numbat)

Huomaa, ettÀ vakiona työhakemisto on / eli juurihakemisto.

Vaihe 2: Aja toisaalla ja toisaalta

🐳 Bash
mkdir -p /root/a/b/c
ln -s /app/hello_turbo.sh /root/a/b/c/hello_turbo.sh
cd /root/a
./b/c/hello_turbo.sh

Huomaa, ettÀ hello_turbo.sh-skripti tulostaa nyt oikeat tiedot. Symbolinen linkki tosiaan sijaitsee hakemistossa /root/a/b/c/, eli src_path-muuttuja on mÀÀritelty oikein. Samaten cwd_path-muuttuja on mÀÀritelty oikein, koska kun ajoit skritin, olit hakemistossa /root/a.

🐳 stdout
========= Turbo Hello World! =========
Current working directory:     /root/a
Script directory:              /root/a/b/c/hello_turbo.sh
Kernel name:                   24.04.1 LTS (Noble Numbat)

Tip

TehtÀvÀ: Bash vianetsintÀ

Luo yllÀ esitellyt kolme skriptiÀ: muuttuja_set_{none,u,ux}.sh.

  1. Tarkastele, kuinka optiot vaikuttavat outputtiin.
  2. Kokeile myös vaihtoehtoista tapaa. Aja bash -ux muuttuja_set_none.sh.

Ajathan nÀmÀ kontissa aiempien tehtÀvien oppien avulla.