Vi liker alle å bruke det enkleste verktøyet som løser problemet, men av og til er ikke det nødvendigvis det optimale i det lange løp, eller når du skal kjøre det på flere forskjellige maskiner.

Si for eksempel at du har et shellskript som bygger prosjektet ditt. Du kaller det fra litt forskjellige mapper, så det første skriptet gjør er at den finner rotmappa til prosjektet, sånn at den er sikker på at arbeidsmappa er prosjektrota:

SCRIPT_PATH="$(readlink -f "$0")"
PROJECT_ROOT="$(dirname "${SCRIPT_PATH}")"
cd "${PROJECT_ROOT}"

Deretter, fordi vi har en eller annen bug med caching, fjerner vi alle byggeartifaktene vi har bygget tidligere. Det forsikrer oss om at vi ikke har noe gammalt ræl i bygget:

find build gen -type f \( -name '*.o' -o -name '*.a' \) -print0 \
  | xargs -0 -r rm

Så lager vi en signatur for bygget ved å legge inn byggedatoen i en fil:

BUILD_DATE="$(date -d 'now' +%F)"

cp version.template build/version.txt

sed -i "s/@VERSION@/${COMMIT_TAG:-dev}/" build/version.txt
sed -i "s/@BUILD_DATE@/${BUILD_DATE}/" build/version.txt

Og til slutt kjører vi bygget med hva enn slags byggekommando som prosjektet bruker.

Men det er et stort problem her. For meg fungerer dette skriptet kjempebra, men alle disse stegene vil krasje på Mac! Kallene til readlink, find, xargs, date og sed er alle avhengige av funksjonalitet som er støttet i GNU-versjonene (Linux), men som ikke finnes/er litt annerledes i BSD-versjonene (Mac). På samme måte er det også en haug med argumenter som fungerer på Mac og ikke på Linux.

Det finnes måter rundt alle disse problemene, men de er ikke like elegante. Og om du er godt kjent med Mac/Linux, blir du ofte overrasket når det ikke fungerer som ønsket på det andre systemet. Dette er kanskje mest frustrerende for de på Mac som ønsker å endre på kommandoen som kjører på en eller annen CI-maskin, men jeg kan bekrefte at det heller ikke er gøy å komme inn på et prosjekt der jeg først må debugge bygge- eller utviklingsskriptet før jeg kan sette i gang.

Python til unnsetning

Som tittelen hinter til, er Python 3 (heretter kun Python) et veldig godt alternativ til disse kompliserte shellskriptene. Det er fire grunner til det:

  • Python er installert på så og si alle maskiner
  • Mange er kjent med Python som språk
  • Python har et stort og standarisert standardbibliotek
  • Pythonkode er lettere å lese i etterkant

Godt kjent og allerede installert

Det viktigste argumentet i mine øyne er det at Python 3 er installert på så og si alle maskiner der ute.

I tillegg kjenner vi “alle” til Python. Alle har ikke brukt det i større prosjekter, men majoriteten av utviklere har alle enten brukt det i praksis, eller brukt et språk som ser nogenlunde lignende ut. Det gjør at det er lett å komme inn i.

Standardisert standardbibliotek

Så lenge du holder deg til standardbiblioteket vil Python fungere helt likt på alle maskiner du jobber med. Referanseimplementasjonen, CPython, er den 99.8% har installert og bruker. De 0.2% andre vet hva de gjør, men jeg vil bli overrasket om selv de opplever forskjeller: Alle implementasjoner jeg kjenner til jobber hardt med å ha identisk semantisk oppførsel som referanseimplementasjonen.

Den eneste tingen man må passe litt på er hvilken versjon av Python man lener seg på. I praksis er det kun relativt nye pakker og funksjoner som kan skape trøbbel: Om noen er på gamle versjoner av Python kan det hende metodene eller pakkene ikke er tilgjengelig. For vanlig bruk skjer det sjeldent.

Og bakoverkompatibilitet er noe Python tar alvorlig – som alle store språk seg hør og bør. PEP 387 gir retningslinjene for hvordan det eventuelt skjer, og det skjer sjelden. Men om noe ikke er deprecated, vil koden din kjøre helt fint i minst 5 år til uten noen problemer.

Selvfølgelig ønsker du å få nyss om det før koden faktisk stopper å fungere, så jeg anbefaler å legge inn

import warnings
warnings.simplefilter("default", DeprecationWarning)

på toppen av skriptet for å få advarsel om bruk deprecatede moduler og funksjoner i koden du kjører, og et tips om hva du heller bør gjøre. For eksempel, datetime.utcnow() er deprecated fra og med versjon 3.12, og advarslen du får om du skrur på DeprecationWarning er at du skal bruke datetime.now(timezone.utc) i stedet. Merk at dette antakeligvis er en såkalt “soft deprecation” som aldri vil forsvinne fra biblioteket. Men om de bestemmer seg for å fjerne den, sier PEP 387 at det (antakeligvis) skjer tidligst i 2028.

Stort standardbibliotek

Sammen med det faktum at standardbiblioteket alltid er der og ikke vil forsvinne med det første, så er den største grunnen til å bruke Python det at du kan gjøre alle de kommandoene jeg nevnte over med standardbiblioteket… samt mye mer.

Trenger du å lese eller skrive noe JSON/XML? Null stress. Må du hente noe data fra Internett? Det kommer en HTTP-klient bygget inn. Du har tilgang til gode tidstyper, gode datastrukturer, ja, til og med en sqlite3-pakke om du trenger det.

Utenom spesifikke programmer som protoc, openapi eller kompilatoren for språkene og rammeverkene du bruker, er det vanskelig å se for seg at du ikke kan lett gjøre noe med Python som du vanligvis gjør i et shellskript.

Lettere å lese

Å si at X er lettere å lese enn Y er ofte en ting jeg ikke liker. Det er ofte en greie som koker ned i at du jobber mye mer i X enn i Y, følgelig er X noe du er mer vant til.

Men å si at Python er lettere å lese enn shellskript har litt mer vekt bak seg. For det første, om du ikke bruker noen av disse ofte, er det mye mer kryptisk å jobbe med datatypene og strengoperasjonene i shellskripting framfor Python. For eksempel, her er måten du gjør om alle strenger i en liste til store bokstaver i Bash:

morgenhilsener=('hei' 'hallo' 'god dag')
energiske_morgenhilsener=()

for s in "${morgenhilsener[@]}"; do
  energiske_morgenhilsener+=( "${s^^}!" )
done

Om du ikke bruker det ofte, sier ikke "${morgenhilsener[@]}" deg mye. Men om du ikke har [@] med får du kun med det første elementet, og om du glemmer dobbeltfnuttene vil “god dag” bli lest som to elementer, og ikke en. Og hva gjør egentlig ${s^^}?

Forsåvidt må du ikke finne på å ha et komma etter elementene i listen, for

morgenhilsener=('hei', 'hallo', 'god dag')

er listen med elementene 'hei,', 'hallo,' og 'god dag'.

Til slutt skal det nevnes at dette fungerer ikke i ZSH, og om du bruker ZSH er du antakeligvis litt mer shellentusiast enn den jevne person. Og om entusiastene ikke kan bruke dette i sitt favorittshell, er det litt mindre sannsynlighet at du vil se det i bruk i skriptene de lager.

Som du ser er mange feil lette å gjøre om du ikke bruker Bash ofte.

Pythons ekvivalent ser sånn her ut:

morgenhilsener = ['hei', 'hallo', 'god dag']
energiske_morgenhilsener = [s.upper() + '!' for s in morgenhilsener]

Dette er ikke nødvendigvis enklere å forstå første gang du ser det: Hva [x for y in z] faktisk gjør er ikke selvsigende. Men når du vet hva det betyr, er det litt lettere å forstå hva greia er fordi s.upper() er litt lettere å skjønne enn ${s^^}.

En av tingene som gjør det litt mer objektivt sant at Python er lettere å lese, er det at metoder har navn i Python. Bash har et mønster på kommandoene sine, men det er likevel lettere for en litt rusten utvikler å skjønne hva s.removesuffix('.com') gjør, sammenlignet med ${s%.com}, eller hva len(morgenhilsener) gjør sammenlignet med ${#morgenhilsener[@]}.

Den andre tingen som gjør det mer sant er at Python har et større vokabular, og at man ikke like ofte trenger å lage egne funksjoner. Hva morgenhilsener.pop(1) gjør er kanskje ikke helt selvsigende første gangen du ser det (det fjerner elementet på indeks 1), men etter det gir det jo mening. Siden Bash ikke har det samme blir du nødt til å ty til ting sånn som dette:

unset 'morgenhilsener[1]'
arr=( "${morgenhilsener[@]}" )

og dette er i alle fall ikke noe du forstår på første gjennomlesning.

Kast ikke barnet ut med badevannet

Alle disse grunnene til å bruke Python skjer som regel når skriptet ditt vokser til en viss størrelse, eller du trenger å gjøre visse ting som er vanskelig og/eller uleselig i Bash. Erfaringsmessig tenker man ikke engang over at man kan bruke Python for å gjøre ting lettere, derav denne bloggposten.

Man trenger ikke bli alt for religiøs her: Om du gjør enkle ting i Bash er det helt ok å fortsette med det. Men hvis du sliter med å skjønne bæret av shellskriptet ditt neste gang du skal endre på det, håper jeg du husker at Python er et godt alternativ.