Tryb ścisły z set -euo pipefail
Włącz szybkie przerywanie po błędzie i dokładnie zrozum, które błędy każda flaga trybu ścisłego wykrywa, a które pomija.
Tryb ścisły z set -euo pipefail to bezpłatna lekcja DevOps Bootcamp na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej DevOps Bootcamp, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs DevOps Bootcamp zawiera 4 lekcji w sumie.
Dlaczego Bash domyślnie po cichu ignoruje błędy
Domyślnie Bash działa dalej, nawet gdy polecenia kończą się błędem. Prowadzi to do subtelnych i trudnych do zdiagnozowania awarii w skryptach produkcyjnych.
Rozważmy skrypt, który próbuje utworzyć kopię zapasową:
- Literówka w ścieżce powoduje niepowodzenie polecenia
cp - Bash ignoruje błąd i kontynuuje działanie
- Skrypt zgłasza powodzenie, mimo że dane nigdy nie zostały zarchiwizowane
To problem cichych błędów. Tryb restrykcyjny go rozwiązuje, sprawiając, że Bash zachowuje się jak język kompilowany: natychmiast zatrzymuje się, gdy coś pójdzie nie tak.
#!/usr/bin/env bash
# Without strict mode — dangerous default behavior
cp /important/data /backups/data # fails (path doesn't exist)
echo "Backup complete" # still prints — false confidence!
rm -rf /tmp/staging # still runs — potentially destructiveTrzy podstawowe flagi: set -euo pipefail
Tryb restrykcyjny włącza się, umieszczając następujący wiersz na początku każdego skryptu:
set -euo pipefail
Włącza to trzy odrębne zabezpieczenia:
-e— natychmiast kończy działanie, jeśli dowolne polecenie zwróci status różny od zera-u— traktuje niezdefiniowane zmienne jako błąd (zamiast rozwijać je do pustego ciągu)-o pipefail— potok kończy się niepowodzeniem, jeśli zawiedzie dowolne zawarte w nim polecenie, a nie tylko ostatnie
Razem tworzą standardowy nagłówek ochronny poważnych skryptów Bash. Każda flaga wykrywa inną klasę błędów.
#!/usr/bin/env bash
set -euo pipefail
echo "Strict mode is now active"
echo "Every command failure will abort the script"Zrozumienie set -e (errexit)
set -e (zapisywane również jako set -o errexit) powoduje natychmiastowe zakończenie skryptu, gdy polecenie zakończy się statusem różnym od zera.
Najważniejsze zachowania:
- Proste polecenia:
false,grep pattern file(brak dopasowania),ls /nonexistent— wszystkie powodują zakończenie - Kod wyjścia ostatniego polecenia w skrypcie staje się kodem wyjścia skryptu
- Polecenia w warunkach
ifsą wyłączone z tej zasady —-enie działa dla wyrażenia testowego - Polecenia zakończone przez
|| truerównież są wyłączone z tej zasady (zobacz następne sceny)
Proszę traktować -e jako pierwszą linię obrony przed cichym kontynuowaniem działania po błędzie.
#!/usr/bin/env bash
set -e
echo "Before failure"
ls /this/path/does/not/exist # exits here with code 2
echo "This line never runs"Zrozumienie set -u (nounset)
set -u (zapisywane również jako set -o nounset) sprawia, że Bash traktuje każde odwołanie do niezdefiniowanej zmiennej jako błąd krytyczny.
Bez -u literówka, taka jak $FLENAME zamiast $FILENAME, zostaje po cichu rozwinięta do pustego ciągu, przez co polecenia działają nieoczekiwanie — a nawet niebezpiecznie (proszę wyobrazić sobie rm -rf "$TMPDIR/", gdy $TMPDIR nie jest zdefiniowana).
Ważne wyjątki:
${VAR:-default}— bezpieczne podstawienie wartości domyślnej, nie powoduje zadziałania-u${VAR:+value}— rozwinięcie warunkowe, również bezpieczne"$@"i"$*"są wyłączone z tej zasady, gdy nie przekazano argumentów pozycyjnych
#!/usr/bin/env bash
set -euo pipefail
# Safe: provide a default for optional vars
OUTPUT_DIR="${1:-/tmp/output}"
LOG_LEVEL="${LOG_LEVEL:-info}"
echo "Writing to: $OUTPUT_DIR"
echo "Log level: $LOG_LEVEL"
# This would abort the script:
# echo "$UNDEFINED_VAR" # bash: UNDEFINED_VAR: unbound variableZrozumienie -o pipefail
Bez pipefail status wyjścia potoku zależy wyłącznie od ostatniego polecenia. Wcześniejsze błędy są po cichu ignorowane.
Przykład bez pipefail:
cat /missing/file | wc -lcatkończy się kodem wyjścia 1, alewc -lkończy się powodzeniem z kodem 0- Potok zwraca 0 — powodzenie! Mimo że dane zostały utracone.
Po włączeniu pipefail Bash zwraca kod wyjścia skrajnego prawego polecenia, które zakończyło się błędem. Dzięki temu błędy potoków stają się widoczne i możliwe do przechwycenia.
Uwaga: pipefail nie jest flagą literową — należy ją ustawić za pomocą -o pipefail.
#!/usr/bin/env bash
set -euo pipefail
# With pipefail: this aborts if grep finds nothing (exit 1)
# grep returns 1 when no match found
ps aux | grep "[n]ginx" | awk '{print $2}'
echo "If we reach here, nginx is running"Zamierzone błędy poleceń — używanie || true
Czasami poleceniu wolno zakończyć się błędem. Przy włączonym set -e trzeba jawnie określić, że dany błąd jest akceptowalny; w przeciwnym razie skrypt zostanie przerwany.
Idiomatycznym rozwiązaniem jest || true, które dodaje zawsze kończące się powodzeniem działanie awaryjne:
command || true— całkowicie ignoruje błądcommand || echo "Warning: step failed, continuing"— rejestruje ostrzeżenie i kontynuuje działaniecommand || { echo "fatal"; exit 1; }— niestandardowa obsługa błędu
Ten wzorzec jasno wyraża intencję w kodzie: zwykłe polecenie oznacza „to musi się udać”, a || true oznacza „to może się nie udać i jest to akceptowalne”.
#!/usr/bin/env bash
set -euo pipefail
# Remove temp dir if it exists — OK if it doesn't
rm -rf /tmp/my_workspace || true
mkdir -p /tmp/my_workspace
# Check if a service is running — OK if not
if systemctl is-active --quiet nginx 2>/dev/null || true; then
echo "nginx is active"
fi
# Grep that may find nothing — OK
grep 'ERROR' /var/log/app.log || true
echo "Done"Czego set -e NIE przechwytuje
set -e ma dobrze znane wyjątki i pułapki. Ich zrozumienie pozwala uniknąć fałszywego poczucia bezpieczeństwa:
- Polecenia w warunkach
if/while/until— wyrażenie testowe jest domyślnie wyłączone z tej zasady - Polecenia negowane za pomocą
!—! falsenie powoduje zakończenia - Ostatnie polecenie przed
||— na przykładfalse || handle_error - Status wyjścia podpowłoki w określonych kontekstach — na przykład
VAR=$(failing_command)w niektórych wersjach Bash - Wartości zwracane przez funkcje — liczy się tylko ostatnie polecenie w funkcji
Tryb restrykcyjny nie zastępuje jawnego sprawdzania błędów — jest siatką bezpieczeństwa, która wychwytuje większość przypadkowych błędów.
#!/usr/bin/env bash
set -euo pipefail
# These do NOT trigger -e:
if false; then echo "never"; fi # -e exempt in conditions
! false # negation exempts
false || echo "handled" # || exempts the left side
# This DOES trigger -e (no condition, no ||):
# false
echo "Script continues after exempted failures"Podpowłoki i funkcje w trybie restrykcyjnym
Ustawienia trybu restrykcyjnego są dziedziczone przez podpowłoki, ale w funkcjach i podstawieniach poleceń zachowują się w subtelny sposób.
Najważniejsze zasady:
- Funkcje dziedziczą
-e,-uipipefailz wywołującej powłoki - Niezerowa wartość zwracana przez funkcję powoduje zakończenie powłoki wywołującej (gdy ustawiono
-e) — chyba że wywołanie znajduje się w warunku lub po|| - Podstawienie polecenia
$(): w starszych wersjach Bash polecenie kończące się błędem wewnątrz$()może nie uruchomić-ew powłoce nadrzędnej; dla bezpieczeństwa należy przypisać wynik, a następnie użyć go osobno - Jawne podpowłoki
()dziedziczą wszystkie flagi
#!/usr/bin/env bash
set -euo pipefail
setup_workspace() {
local dir="$1"
mkdir -p "$dir" # fails here if permissions denied
cd "$dir"
echo "Ready in $(pwd)"
}
# Safe pattern: assign result then use it
TODAY=$(date +%Y-%m-%d) # capture separately
WORKDIR="/tmp/run_${TODAY}"
setup_workspace "$WORKDIR"
echo "Workspace: $WORKDIR"Łączenie trybu restrykcyjnego z przechwytywaniem błędów
Tryb restrykcyjny mówi Bashowi, kiedy ma się zatrzymać. trap dla ERR umożliwia wykonanie czyszczenia lub diagnostyki przed zakończeniem skryptu.
Typowy wzorzec wygląda następująco:
- Ustawienie trybu restrykcyjnego na początku
- Zdefiniowanie funkcji
cleanuplubon_error - Zarejestrowanie jej za pomocą
trap 'on_error' ERR - Opcjonalne przechwycenie również
EXITw celu zagwarantowania czyszczenia niezależnie od powodzenia lub błędu
Ważne: należy użyć set -E (wielkie E, nazywane również errtrace), aby pułapka ERR była dziedziczona także przez funkcje i podpowłoki — bez tego pułapki działają tylko w głównym ciele powłoki.
#!/usr/bin/env bash
set -Eeuo pipefail
on_error() {
local exit_code=$?
local line_number=${BASH_LINENO[0]}
echo "ERROR: command failed with code ${exit_code} at line ${line_number}" >&2
}
cleanup() {
echo "Cleaning up temporary files..." >&2
rm -rf /tmp/my_run_dir 2>/dev/null || true
}
trap on_error ERR
trap cleanup EXIT
mkdir -p /tmp/my_run_dir
echo "hello" > /tmp/my_run_dir/output.txt
cat /tmp/my_run_dir/output.txt
echo "Done"Lokalne wyłączanie trybu restrykcyjnego
Czasami blok kodu jest celowo „nieuporządkowany” — na przykład podczas sprawdzania dostępności opcjonalnych narzędzi lub uruchamiania starszych poleceń, które zwracają wartość różną od zera z powodów niezwiązanych z błędem. Można tymczasowo wyłączyć tryb restrykcyjny, a następnie go przywrócić.
Bezpieczny wzorzec:
- Należy zapisać stan za pomocą
set +e(wyłącza-e), uruchomić blok, a następnie ponownie włączyć tryb za pomocąset -e - Można też użyć podpowłoki
( set +e; ... ), aby flaga powłoki nadrzędnej nigdy nie została zmieniona - Flagi należy zawsze włączyć ponownie natychmiast po zakończeniu ryzykownego bloku — pozostawienie ich wyłączonych jest częstym źródłem błędów
Jeśli blok obejmuje wiele poleceń, należy preferować formę z podpowłoką, ponieważ automatycznie przywraca ona flagi przy zakończeniu.
#!/usr/bin/env bash
set -euo pipefail
# Probe for optional tools without aborting
HAS_JQ=false
(
set +e
command -v jq > /dev/null 2>&1
[[ $? -eq 0 ]] && echo "jq_found"
) && HAS_JQ=true || true
if [[ "$HAS_JQ" == "true" ]]; then
echo "jq is available — using JSON output"
else
echo "jq not found — using plain text"
fiKompletny szablon skryptu w trybie restrykcyjnym
Oto gotowy do użycia w środowisku produkcyjnym szablon, który łączy wszystkie omówione w tej lekcji dobre praktyki trybu restrykcyjnego:
set -Eeuo pipefail— wszystkie cztery flagi, w tymerrtraceIFS=$'\n\t'— bezpieczniejsze dzielenie słów (zapobiega dzieleniu po spacjach)- Pułapki ERR + EXIT na potrzeby diagnostyki i czyszczenia
- Jawne wartości domyślne dla opcjonalnych parametrów
readonlyilocaldo ograniczania zakresu zmiennych
Proszę kopiować ten szablon na początku każdego nietrywialnego skryptu Bash, aby od razu skorzystać z działania zgodnego z zasadą „zatrzymaj się przy pierwszym błędzie” oraz z możliwych do prześledzenia błędów.
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
# ── Constants ────────────────────────────────────────────
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
# ── Trap handlers ────────────────────────────────────────
err_handler() {
echo "[${SCRIPT_NAME}] ERROR on line ${BASH_LINENO[0]}: exit ${?}" >&2
}
cleanup() {
echo "[${SCRIPT_NAME}] Exiting" >&2
}
trap err_handler ERR
trap cleanup EXIT
# ── Defaults ─────────────────────────────────────────────
ENV="${1:-production}"
MAX_RETRIES="${MAX_RETRIES:-3}"
# ── Main ─────────────────────────────────────────────────
main() {
echo "Running in env=${ENV}, max_retries=${MAX_RETRIES}"
echo "Script dir: ${SCRIPT_DIR}"
}
main "$@"Sprawdzenie wiedzy: działanie pipefail
Sprawdź, czy rozumiesz, jak pipefail wpływa na kody wyjścia potoków.
Podsumowanie: tryb ścisły z set -euo pipefail
W tej lekcji dowiedział się Pan / dowiedziała się Pani, jak sprawić, aby skrypty Bash szybko i wyraźnie zgłaszały błędy dzięki trybowi ścisłemu.
Trzy flagi i zakres ich działania:
-e(errexit) — kończy działanie po napotkaniu polecenia o niezerowym statusie; nie obowiązuje w warunkach ani po||-u(nounset) — przerywa działanie przy odwołaniu do niezdefiniowanej zmiennej; dla opcjonalnych zmiennych należy używać${VAR:-default}-o pipefail— powoduje, że cały potok kończy się błędem, jeśli zawiedzie dowolny jego etap, a nie tylko ostatni
Uzupełniające praktyki:
- Należy dodać
-E(errtrace), aby pułapki ERR były propagowane do funkcji - Należy używać
trapdlaERRiEXITdo diagnostyki i sprzątania - Należy używać
|| true, aby celowo ignorować błędy - W podpowłokach można tymczasowo wyłączyć tę opcję za pomocą
set +ena potrzeby starszego kodu lub kodu testującego
Tryb ścisły nie jest cudownym rozwiązaniem — należy znać jego wyjątki — ale jest zdecydowanie najskuteczniejszym nawykiem podczas pisania niezawodnych, odpornych na błędy skryptów Bash.
Często zadawane pytania
Czy lekcja „Tryb ścisły z set -euo pipefail” jest bezpłatna?
Tak — pełny tekst „Tryb ścisły z set -euo pipefail” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu DevOps Bootcamp, przejdź na CoddyKit PRO. Kurs DevOps Bootcamp zawiera 4 lekcji w sumie.
Co nauczysz się w „Tryb ścisły z set -euo pipefail”?
Włącz szybkie przerywanie po błędzie i dokładnie zrozum, które błędy każda flaga trybu ścisłego wykrywa, a które pomija. Ćwiczysz DevOps Bootcamp z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć DevOps Bootcamp?
Nie wymagamy żadnego doświadczenia. DevOps Bootcamp w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Tryb ścisły z set -euo pipefail”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji DevOps Bootcamp?
Tak. Każda lekcja DevOps Bootcamp zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Tryb ścisły z set -euo pipefail
- Procedury trap do sprzątania i obsługi sygnałów
- Bezpieczne pliki tymczasowe i katalogi blokad
- Skrypty idempotentne i logika ponawiania z narastającym opóźnieniem