0Pricing
DevOps Bootcamp · Lekcja

Budowanie i dołączanie wielokrotnego użytku bibliotek Bash

Porządkuj współdzielone pomocnicze funkcje w plikach bibliotek .sh możliwych do dołączenia, z osłonami dołączania i prefiksami funkcji określającymi przestrzeń nazw.

Budowanie i dołączanie wielokrotnego użytku bibliotek Bash to bezpłatna lekcja DevOps Bootcamp na CoddyKit. To lekcja 2 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.

Czym jest biblioteka Bash?

W inżynierii oprogramowania biblioteka to zbiór funkcji wielokrotnego użytku, z których może korzystać wiele programów. Bash obsługuje tę samą koncepcję za pomocą skryptów powłoki możliwych do wczytania.

Zamiast kopiować funkcje pomocnicze do każdego skryptu, można umieścić je w osobnym pliku .sh i wczytać go za pomocą polecenia source (lub jego skróconej postaci .). Wszystkie funkcje, zmienne i aliasy zdefiniowane w tym pliku stają się dostępne w bieżącej sesji powłoki skryptu wywołującego.

  • Promuje zasadę DRY (Don't Repeat Yourself)
  • Centralizuje poprawki błędów — wystarczy poprawić je raz, aby skorzystali z nich wszyscy wywołujący
  • Skraca poszczególne skrypty i ułatwia ich czytanie
  • Umożliwia spójne podejście w całym zespole do rejestrowania, obsługi błędów i logiki narzędziowej

Dobrze zorganizowany projekt Bash zazwyczaj zawiera katalog lib/ ze współdzielonymi plikami, zgodnie z konwencjami znanymi z języków wyższego poziomu.

Polecenie source i operator kropki

Istnieją dwa równoważne sposoby wczytania pliku biblioteki do bieżącego środowiska powłoki:

  • source /path/to/lib.sh — jawna i czytelna forma
  • . /path/to/lib.sh — skrócona forma zgodna z POSIX

Oba sposoby wykonują plik w bieżącym procesie powłoki, a nie w podpowłoce, dlatego każda zdefiniowana w nim funkcja i zmienna natychmiast staje się częścią środowiska skryptu po wywołaniu.

Często stosuje się wzorzec polegający na zlokalizowaniu biblioteki względem skryptu wywołującego za pomocą $BASH_SOURCE, dzięki czemu projekt pozostaje przenośny niezależnie od miejsca instalacji.

#!/usr/bin/env bash
# main.sh — load a library relative to this script's own location

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib/utils.sh"

echo "Library loaded. Calling greet..."
greet "World"

Tworzenie pierwszego pliku biblioteki

Plik biblioteki to zwykły plik .sh, zawierający wyłącznie definicje funkcji (oraz czasami stałe). Nie powinien wykonywać na najwyższym poziomie kodu powodującego skutki uboczne — jest przeznaczony do wczytywania, a nie do bezpośredniego uruchamiania.

Najważniejsze konwencje:

  • Rozpocznij od komentarza shebang opisującego przeznaczenie biblioteki
  • Definiuj wyłącznie funkcje — bez logiki main na najwyższym poziomie
  • Używaj return wewnątrz funkcji (nigdy exit, które zakończyłoby działanie wywołującego)
  • Przechowuj plik w podkatalogu lib/ projektu
#!/usr/bin/env bash
# lib/utils.sh — General-purpose utility functions

# Print a greeting message
greet() {
    local name="${1:-stranger}"
    echo "Hello, ${name}!"
}

# Print a timestamped log line to stderr
log_info() {
    echo "[INFO]  $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
}

# Print an error message and return a failure code
log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
    return 1
}

Strażnicy dołączania: zapobieganie wielokrotnemu wczytaniu

Gdy kilka skryptów wczytuje tę samą bibliotekę lub gdy biblioteka wczytuje inną bibliotekę, która jest również wczytywana przez główny skrypt, funkcje mogą zostać zdefiniowane wielokrotnie. Powoduje to niepotrzebne zużycie czasu i może prowadzić do trudnych do wykrycia błędów, jeśli treść funkcji zostanie zmieniona w trakcie działania.

Rozwiązaniem jest strażnik dołączania — zmienna pełniąca rolę flagi. Przy pierwszym wczytaniu zmienna jest nieustawiona, więc plik kontynuuje działanie. Przy każdym kolejnym wczytaniu strażnik jest już ustawiony, dlatego plik natychmiast zwraca sterowanie.

Jest to odpowiednik #pragma once z C/C++ lub wzorców if not already imported stosowanych w innych językach.

#!/usr/bin/env bash
# lib/utils.sh — with include guard

# Guard: if already sourced, do nothing
[[ -n "${_LIB_UTILS_LOADED:-}" ]] && return 0
_LIB_UTILS_LOADED=1

greet() {
    local name="${1:-stranger}"
    echo "Hello, ${name}!"
}

log_info() {
    echo "[INFO]  $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
}

log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
    return 1
}

Prefiksy funkcji z przestrzenią nazw

Bash ma jedną globalną przestrzeń nazw funkcji. Jeśli dwie biblioteki zdefiniują funkcję o nazwie log lub init, druga definicja po cichu nadpisze pierwszą.

Standardowym zabezpieczeniem jest prefiks przestrzeni nazw: każda funkcja w bibliotece otrzymuje prefiks będący skróconą nazwą biblioteki, po którym występują dwa dwukropki (::) lub podkreślenie. Na przykład biblioteka narzędzi do obsługi ciągów używa str::, a biblioteka plików — file::.

  • Kolizje stają się bardzo mało prawdopodobne
  • Kod wywołujący sam wyjaśnia swoje działanie — str::trim dokładnie wskazuje, gdzie znajduje się funkcja
  • Łatwiej wyszukiwać użycia: grep 'str::' main.sh natychmiast pokazuje wszystkie wywołania biblioteki do obsługi ciągów
#!/usr/bin/env bash
# lib/str.sh — String utility library (namespaced)

[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1

# Trim leading and trailing whitespace
str::trim() {
    local s="$1"
    s="${s#"${s%%[![:space:]]*}"}"
    s="${s%"${s##*[![:space:]]}"}"  
    echo "$s"
}

# Convert string to uppercase
str::upper() {
    echo "${1^^}"
}

# Convert string to lowercase
str::lower() {
    echo "${1,,}"
}

# Check if a string contains a substring
str::contains() {
    [[ "$1" == *"$2"* ]]
}

Organizowanie katalogu lib/

W miarę rozrastania się projektu pojedynczy plik utils.sh staje się nieporęczny. Należy podzielić odpowiedzialności na wyspecjalizowane pliki bibliotek w katalogu lib/:

  • lib/log.sh — funkcje pomocnicze do rejestrowania (log::info, log::warn, log::error)
  • lib/str.sh — operacje na ciągach znaków (str::trim, str::upper)
  • lib/fs.sh — funkcje pomocnicze systemu plików (fs::require_dir, fs::safe_rm)
  • lib/net.sh — sprawdzanie sieci (net::wait_for_port, net::is_online)

Pojedynczy plik ładujący bootstrap (lib/bootstrap.sh) może wczytać wszystkie te pliki we właściwej kolejności, dzięki czemu każdy skrypt potrzebuje tylko jednego wywołania source.

#!/usr/bin/env bash
# lib/bootstrap.sh — Load all project libraries in dependency order

[[ -n "${_LIB_BOOTSTRAP_LOADED:-}" ]] && return 0
_LIB_BOOTSTRAP_LOADED=1

_BOOTSTRAP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

source "${_BOOTSTRAP_DIR}/log.sh"
source "${_BOOTSTRAP_DIR}/str.sh"
source "${_BOOTSTRAP_DIR}/fs.sh"
source "${_BOOTSTRAP_DIR}/net.sh"

log::info "All libraries loaded."

Kompletna biblioteka logowania

Logowanie jest najczęściej współdzielonym elementem skryptów. Dedykowany plik lib/log.sh centralizuje formatowanie danych wyjściowych, poziomy logowania i kody kolorów.

Dzięki takiej bibliotece każdy skrypt w projekcie wyświetla spójne, opatrzone znacznikami czasu i kolorami komunikaty bez powielania kodu formatowania.

#!/usr/bin/env bash
# lib/log.sh — Coloured, levelled logging library

[[ -n "${_LIB_LOG_LOADED:-}" ]] && return 0
_LIB_LOG_LOADED=1

# Colour codes (disabled when not writing to a terminal)
_LOG_RED=''; _LOG_YEL=''; _LOG_GRN=''; _LOG_RST=''
if [[ -t 2 ]]; then
    _LOG_RED='\033[0;31m'
    _LOG_YEL='\033[0;33m'
    _LOG_GRN='\033[0;32m'
    _LOG_RST='\033[0m'
fi

_log::_print() {
    local level="$1" colour="$2"; shift 2
    printf "%b[%s]%b %s  %s\n" \
        "$colour" "$level" "$_LOG_RST" \
        "$(date '+%H:%M:%S')" "$*" >&2
}

log::info()  { _log::_print 'INFO ' "$_LOG_GRN" "$@"; }
log::warn()  { _log::_print 'WARN ' "$_LOG_YEL" "$@"; }
log::error() { _log::_print 'ERROR' "$_LOG_RED" "$@"; return 1; }
log::fatal() { _log::_print 'FATAL' "$_LOG_RED" "$@"; exit 1; }

Biblioteka funkcji pomocniczych systemu plików

Skrypty, które modyfikują pliki i katalogi, często powtarzają te same kontrole bezpieczeństwa: czy ten katalog istnieje? Czy ta ścieżka umożliwia zapis? Czy za chwilę nie usunę czegoś ważnego?

Scen tralizowanie tych kontroli w lib/fs.sh sprawia, że każdy korzystający z biblioteki skrypt jest bezpieczniejszy i czytelniejszy. Proszę zauważyć, że każda funkcja w przypadku niepowodzenia używa return 1 zamiast exit, zachowując możliwość eleganckiej obsługi błędu przez kod wywołujący.

#!/usr/bin/env bash
# lib/fs.sh — Filesystem helper library

[[ -n "${_LIB_FS_LOADED:-}" ]] && return 0
_LIB_FS_LOADED=1

# Ensure a directory exists; create it if not
fs::require_dir() {
    local dir="$1"
    if [[ ! -d "$dir" ]]; then
        mkdir -p "$dir" || { echo "[fs] Cannot create directory: $dir" >&2; return 1; }
    fi
}

# Remove a file only if it exists (no error on missing)
fs::safe_rm() {
    local target="$1"
    [[ -e "$target" ]] && rm -rf -- "$target"
    return 0
}

# Assert that a file exists and is readable
fs::require_file() {
    local file="$1"
    [[ -f "$file" && -r "$file" ]] || {
        echo "[fs] Required file missing or unreadable: $file" >&2
        return 1
    }
}

Wersjonowanie biblioteki za pomocą stałej

Gdy biblioteki są współdzielone między wieloma projektami lub dystrybuowane w zespole, ważne staje się ustalenie, jaka wersja biblioteki jest ładowana w czasie działania. Prostą konwencją jest eksportowanie z każdej biblioteki stałej wersji.

Kod wywołujący może wtedy podczas uruchamiania sprawdzić, czy używana jest co najmniej wymagana wersja, i wcześnie wykryć niezgodności zamiast później szukać przyczyny tajemniczych awarii. Zmienna strażnika pełni jednocześnie funkcję ciągu wersji, łącząc dwie role w jednej zmiennej.

#!/usr/bin/env bash
# lib/str.sh — versioned example

# Guard doubles as the version identifier
[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
readonly _LIB_STR_LOADED='1.3.0'

# Caller can validate the version
str::version() { echo "$_LIB_STR_LOADED"; }

# ---- Utility functions ----
str::trim() {
    local s="$1"
    s="${s#"${s%%[![:space:]]*}"}"
    s="${s%"${s##*[![:space:]]}"}"  
    echo "$s"
}

str::repeat() {
    local str="$1" count="$2" result=''
    for (( i=0; i<count; i++ )); do result+="$str"; done
    echo "$result"
}

Samodzielny przykład: używanie wielu bibliotek

Ta scena przedstawia realistyczny skrypt, który ładuje dwie biblioteki i używa funkcji z każdej z nich. Proszę zauważyć, że główny skrypt pozostaje przejrzysty — wyraża zamiar, podczas gdy wszystkie szczegóły implementacji znajdują się w bibliotekach.

Skrypt można uruchomić jako samodzielny plik, ponieważ definiuje biblioteki w swoim kodzie za pomocą dokumentów here-doc zapisywanych do plików tymczasowych. W rzeczywistym projekcie każda biblioteka znajdowałaby się we własnym pliku w katalogu lib/.

#!/usr/bin/env bash
# Standalone demo: inline libs written to /tmp, then sourced
set -euo pipefail

# --- Create a temporary lib/log.sh ---
TMPDIR_LIBS="$(mktemp -d)"
trap 'rm -rf "$TMPDIR_LIBS"' EXIT

cat > "${TMPDIR_LIBS}/log.sh" <<'LIBEOF'
[[ -n "${_LIB_LOG_LOADED:-}" ]] && return 0
_LIB_LOG_LOADED=1
log::info()  { echo "[INFO]  $*"; }
log::error() { echo "[ERROR] $*" >&2; return 1; }
LIBEOF

cat > "${TMPDIR_LIBS}/str.sh" <<'LIBEOF'
[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1
str::upper() { echo "${1^^}"; }
str::trim()  { local s="$1"; s="${s#"${s%%[![:space:]]*}"}";
               s="${s%"${s##*[![:space:]]}"}"  ; echo "$s"; }
LIBEOF

# --- Source both libraries ---
source "${TMPDIR_LIBS}/log.sh"
source "${TMPDIR_LIBS}/str.sh"

# --- Main logic ---
log::info "Libraries loaded successfully."
raw_input="   hello from bash libraries   "
trimmed="$(str::trim "$raw_input")"
log::info "Trimmed: '${trimmed}'"
log::info "Uppercased: '$(str::upper "$trimmed")'"

Najlepsze praktyki i typowe pułapki

Przed udostępnieniem biblioteki zespołowi proszę przejść przez poniższą listę kontrolną:

  • Strażnik dołączania — każda biblioteka musi go mieć; proszę udokumentować nazwę zmiennej strażnika na początku pliku
  • Brak efektów ubocznych na najwyższym poziomie — nigdy nie używać cd, echo ani nie modyfikować stanu globalnego poza treścią funkcji
  • Używać local dla wszystkich zmiennych wewnątrz funkcji — bez local każde przypisanie przenika do zakresu kodu wywołującego
  • Zwracać wynik, nigdy nie kończyć działania powłoki — exit w pliku załadowanym za pomocą source kończy działanie całej powłoki wywołującej
  • Weryfikować dane wejściowe — sprawdzać wymagane argumenty i zwracać znaczący kod błędu, gdy ich brakuje
  • Dokumentować za pomocą komentarzy — opisywać działanie każdej funkcji, jej parametry i zwracaną wartość
  • Unikać set -e w plikach bibliotek — kod wywołujący może mieć własną strategię obsługi błędów; proszę pozostawić decyzję jemu

Sprawdzenie wiedzy: strażniki dołączania

Proszę rozważyć projekt, w którym main.sh ładuje zarówno lib/bootstrap.sh, jak i lib/log.sh, a lib/bootstrap.sh również wewnętrznie ładuje lib/log.sh. Jaki jest główny cel strażnika dołączania w lib/log.sh?

Podsumowanie lekcji: właściwe używanie bibliotek Bash

Omówiono wszystkie najważniejsze techniki tworzenia bibliotek Bash wielokrotnego użytku i korzystania z nich. Oto najważniejsze informacje:

  • Ładować za pomocą source lub . — wczytuje to plik do bieżącej powłoki, natychmiast udostępniając jego funkcje
  • Używać $BASH_SOURCE do rozwiązywania ścieżek bibliotek względem skryptu wywołującego, dzięki czemu projekty pozostają przenośne
  • Strażniki dołączania ([[ -n "${_GUARD:-}" ]] && return 0) zapobiegają wielokrotnemu definiowaniu elementów, gdy wiele plików ładuje tę samą bibliotekę
  • Prefiksy przestrzeni nazw (log::, str::, fs::) eliminują kolizje nazw funkcji między bibliotekami
  • Katalog lib/ z ukierunkowanymi plikami o pojedynczej odpowiedzialności ułatwia utrzymanie dużych projektów
  • Moduł ładujący bootstrap (lib/bootstrap.sh) zapewnia każdemu skryptowi pojedyncze wywołanie ładowania całego ekosystemu
  • Nigdy nie używać exit ani efektów ubocznych na najwyższym poziomie w plikach bibliotek — powinny się tam znajdować wyłącznie definicje funkcji i stałe

Stosując te wzorce konsekwentnie, sprawią Państwo, że skrypty powłoki będą równie modułowe i łatwe w utrzymaniu jak kod napisany w dowolnym języku wyższego poziomu.

Często zadawane pytania

Czy lekcja „Budowanie i dołączanie wielokrotnego użytku bibliotek Bash” jest bezpłatna?

Tak — pełny tekst „Budowanie i dołączanie wielokrotnego użytku bibliotek Bash” 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 „Budowanie i dołączanie wielokrotnego użytku bibliotek Bash”?

Porządkuj współdzielone pomocnicze funkcje w plikach bibliotek .sh możliwych do dołączenia, z osłonami dołączania i prefiksami funkcji określającymi przestrzeń nazw. Ć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 2 z 4.

Ile czasu zajmuje lekcja „Budowanie i dołączanie wielokrotnego użytku bibliotek Bash”?

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

  1. Projektowanie funkcji z zakresem lokalnym i kodami zwrotnymi
  2. Budowanie i dołączanie wielokrotnego użytku bibliotek Bash
  3. Analizowanie flag i argumentów za pomocą getopts
  4. Przekazywanie tablic i map asocjacyjnych między funkcjami
← Powrót do DevOps Bootcamp