Bash scripting för utvecklare: Skript som inte kraschar i produktion
set -euo pipefail, citerade variabler och ShellCheck i CI. De vanor som skiljer ett bash-skript som håller från ett som lämnar servern i ett trasigt tillstånd klockan tre på natten.
De flesta utvecklare lär sig bash genom att kopiera rader från Stack Overflow tills skriptet slutar krascha. Det fungerar — tills det inte gör det, mitt i en deploy-pipeline klockan tre på natten, för att en variabel med mellanslag i sig aldrig blev citerad. Bash är inte ett "riktigt" programmeringsspråk på samma sätt som Python eller TypeScript, men det kör bakom nästan varje CI/CD-pipeline, Docker-build och serverdeploy som finns. Den här guiden går igenom det du faktiskt behöver för att skriva skript som inte faller ihop första gången indata skiljer sig från vad du testade lokalt.
Kör du redan pipelines med GitHub Actions? Vår guide till CI/CD går igenom hur bash-steg passar in där.
set -euo pipefail — den viktigaste raden i ditt skript
Bash fortsätter som standard att köra även efter att ett kommando misslyckats. Det är den enskilt vanligaste orsaken till skript som "verkar fungera" men lämnar servern i ett trasigt halvfärdigt tillstånd.
#!/usr/bin/env bash
set -euo pipefail
# -e: avsluta direkt om NÅGOT kommando misslyckas
# -u: krascha på oinitierade variabler istället för att tysta expandera till ""
# -o pipefail: en pipe (cmd1 | cmd2) misslyckas om NÅGOT steg i kedjan misslyckas,
# inte bara det sista
# Utan pipefail missar du fel som göms mitt i en pipe:
curl -sf https://api.exempel.se/data | jq '.saknas_fält'
# curl kan misslyckas tyst — jq körs ändå på tom indata och exit-koden blir jq:s, inte curl:sCitera dina variabler — alltid
Det vanligaste skriptfelet i produktion är ociterade variabler. Så fort ett filnamn eller en indata innehåller mellanslag, ett specialtecken eller är tom, bryts ett ociterat skript på oväntade sätt.
# ❌ Farligt — bryts om $file innehåller mellanslag, är tom, eller
# innehåller ett wildcard-tecken
rm $file
# Om $file råkar vara tom expanderas kommandot till "rm" utan argument
# (ofarligt här), men i "rm -rf $dir/$sub" blir en tom $sub farlig:
# rm -rf $dir/ raderar HELA katalogen istället för en subkatalog
# ✅ Alltid citerat
rm "$file"
rm -rf "${dir}/${sub}"
# Samma sak i loopar — annars delas strängar med mellanslag upp fel
for f in "${files[@]}"; do
echo "Bearbetar: ${f}"
done[[ ]] istället för [ ] — testkommandot som faktiskt beter sig som du väntar dig
# [ ] är POSIX test — äldre, kräver citering överallt, ingen && / || inuti
if [ "$namn" = "erik" -a -n "$stad" ]; then
echo "gammal syntax, lätt att göra fel med"
fi
# [[ ]] är bashs egen builtin — säkrare, tydligare, stödjer mönster
if [[ "$namn" == "erik" && -n "$stad" ]]; then
echo "matchar"
fi
# [[ ]] stödjer även glob- och regex-matchning direkt:
if [[ "$filnamn" == *.tar.gz ]]; then
echo "arkiv"
fi
if [[ "$version" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "semver-format"
fiFelhantering som faktiskt säger vad som gick fel
set -e avslutar skriptet, men berättar inte var eller varför. En trap på ERR ger dig kontext direkt i loggen istället för att gräva i vilken rad som exekverades sist.
#!/usr/bin/env bash
set -euo pipefail
trap 'echo "FEL på rad $LINENO: kommandot \"$BASH_COMMAND\" misslyckades" >&2' ERR
deploy() {
local miljö="$1"
echo "Deployar till $miljö..."
rsync -avz ./dist/ "server:/var/www/$miljö/"
}
deploy "produktion"
# Om rsync misslyckas: "FEL på rad 8: kommandot rsync ... misslyckades"
# istället för ett tyst avbrott utan förklaringSkriv testbar bash — funktioner, inte en enda lång fil
#!/usr/bin/env bash
set -euo pipefail
kontrollera_beroenden() {
local saknas=()
for verktyg in docker jq curl; do
command -v "$verktyg" >/dev/null 2>&1 || saknas+=("$verktyg")
done
if (( ${#saknas[@]} > 0 )); then
echo "Saknar: ${saknas[*]}" >&2
exit 1
fi
}
backup_databas() {
local namn="${1:?Databasnamn krävs}"
local tidsstämpel
tidsstämpel=$(date +%Y%m%d_%H%M%S)
pg_dump "$namn" > "backup_${namn}_${tidsstämpel}.sql"
}
main() {
kontrollera_beroenden
backup_databas "production_db"
}
main "$@"${1:?felmeddelande} är ett litet men värdefullt mönster — skriptet kraschar direkt med ett tydligt fel om ett obligatoriskt argument saknas, istället för att köra vidare med en tom variabel.
Kör ShellCheck innan du committar
Nästan alla vanliga bash-fallgropar — ociterade variabler, fel användning av [ ], glömda local-deklarationer — fångas automatiskt av ShellCheck. Det tar sekunder att köra och hittar buggar innan de når produktion.
# Installera och kör lokalt
shellcheck deploy.sh
# I CI — låt pipelinen fälla builden om skriptet har problem
- name: Lint shell scripts
run: shellcheck scripts/*.shChecklista för produktionsskript
set -euo pipefailhögst upp i varje skript som gör något viktigt.- Citera alla variabler —
"$var", aldrig$var. - Använd
[[ ]]istället för[ ]i bash- specifika skript. - Lägg en
trappå ERR så du ser exakt vilken rad som misslyckades. - Kör ShellCheck i CI — inte bara lokalt när du kommer ihåg det.
- Testa med tomma och konstiga indata — filnamn med mellanslag, tomma variabler, saknade argument.
Nordisk vinkel: bash lever kvar bakom molnet
Oavsett om du deployar till Hetzner, Vercel eller en svensk molnleverantör körs entrypoint-skriptet i din Dockerfile, din CI-pipeline och dina serverns cron-jobb fortfarande i bash. Team som outsourcar bash-kunskapen till "det där gamla skriptet ingen rör" ärver samma buggar varje gång en ny miljö sätts upp. Fem minuters ShellCheck-koll innan merge är billigare än en trasig deploy klockan tre på natten.
Källor och vidare läsning
- GNU Bash Reference Manual
Den officiella referensen för bashs inbyggda kommandon, testuttryck och syntax.
gnu.org/software/bash/manual/bash.html - Google Shell Style Guide
Konventioner för läsbar, underhållbar shell-kod — vad Google kräver internt för skript över 100 rader.
google.github.io/styleguide/shellguide.html - ShellCheck
Statisk analys för shell-skript — hittar ociterade variabler, felaktig testsyntax och andra vanliga buggar automatiskt.
shellcheck.net