Start Free Now
Limited Time Offer: Get 50% OFF Starter & Basic Yearly Plans 🎉

Bash dla początkujących: automatyzacja obróbki wideo

Sep 27, 2026

Dlaczego powłoka pozostaje najlepszym spoiwem procesu wideo

Bash jest domyślną powłoką większości systemów z rodziny Linux oraz macOS. Każda osoba, która kiedykolwiek wpisała polecenie w terminalu, już z niego korzystała — nawet jeśli nie nazywała tego skryptowaniem. Skrypt to zapisany w pliku tekstowym ciąg poleceń, który uruchamiasz jedną komendą, możesz powtórzyć dowolną liczbę razy i przekazać współpracownikowi bez tłumaczenia, co gdzie kliknąć.

W pracy z materiałami wideo ten model pasuje wyjątkowo dobrze, ponieważ najważniejsze narzędzia to programy linii poleceń. FFmpeg i ffprobe czytają oraz przetwarzają strumienie, narzędzia graficzne składają miniatury, generatory transkrypcji przyjmują plik audio i zwracają tekst, a klienty usług sieciowych wysyłają zapytanie i zapisują odpowiedź. Każde z tych narzędzi ma argumenty wejściowe i produkuje plik. Skrypt łączy je w powtarzalny łańcuch: pobierz materiał, sprawdź metadane, przeskaluj, wytnij fragment, nałóż znak wodny, wyeksportuj trzy warianty, przenieś wyniki do katalogu docelowego i zapisz raport.

Ręcznie to kilkadziesiąt poleceń na jeden klip. W skrypcie jedno wywołanie. Różnica nie polega wyłącznie na oszczędności czasu. Ręczne polecenia są niepowtarzalne: przy dwudziestym pliku łatwo pominąć krok, wpisać inną wartość parametru jakości albo zapomnieć o fladze przyspieszającej start odtwarzania. Skrypt wykonuje dokładnie tę samą sekwencję za każdym razem, a gdy trzeba coś zmienić, zmienia się jedno miejsce w kodzie.

Warto od razu wyznaczyć granicę kompetencji. Powłoka jest znakomita w orkiestracji: wywoływaniu zewnętrznych programów, zarządzaniu plikami i katalogami, kontroli kolejności oraz obsłudze błędów na poziomie procesu. Nie jest dobra do skomplikowanej logiki domenowej, przetwarzania złożonych struktur danych ani zaawansowanej matematyki. Jeśli zadanie brzmi „uruchom to, sprawdź tamto, przenieś plik, zapisz wynik”, skrypt powłoki będzie szybszy i czytelniejszy niż pełnoprawna aplikacja. Jeśli budujesz rozbudowany system z bazą danych, interfejsem sieciowym i testami jednostkowymi, wybierz język ogólnego przeznaczenia, a powłoce zostaw rolę cienkiej warstwy wywołującej.

Trzy scenariusze, w których automatyzacja zwraca się najszybciej:

  • Eksport na wiele platform jednocześnie. Jeden materiał źródłowy, kilka proporcji kadru, kilka rozdzielczości, miniatury oraz plik dźwiękowy przygotowany do transkrypcji.
  • Nocne przetwarzanie partii. Dwadzieścia nagrań z sesji zdjęciowej, które rano mają mieć jednolite parametry i uporządkowane nazwy.
  • Kontrola jakości przed publikacją. Automatyczne sprawdzenie, czy plik zawiera strumień wideo, czy czas trwania się zgadza i czy pierwsze klatki nie są puste.

Próg wejścia jest niski. Do produktywnej pracy wystarczy kilkanaście konstrukcji: zmienne, cytowanie, warunki, pętle, funkcje, kody wyjścia oraz kilka narzędzi systemowych. Reszta to praktyka i świadome decyzje projektowe.

Środowisko pracy i stały układ katalogów

Najwięcej czasu traci się nie na pisanie logiki, lecz na diagnozowanie środowiska. Brakujące narzędzie albo uruchomienie skryptu z nieoczekiwanego katalogu potrafi zablokować pracę na godzinę, mimo że rozwiązanie jest trywialne. Dlatego zaczynamy od krótkiej inwentaryzacji.

which bash
bash --version
ffmpeg -version | head -n 1
ffprobe -version | head -n 1

Jeśli FFmpeg nie jest zainstalowany, w macOS wystarczy menedżer pakietów pokroju Homebrew, a w systemach linuksowych instalacja przez menedżer dystrybucji. Warto dołożyć narzędzie do statycznej analizy skryptów powłoki, na przykład ShellCheck. Taki analizator zachowuje się jak szybki recenzent: wskazuje brakujące cudzysłowy, błędne porównania, nieużywane zmienne i pułapki związane z kodami wyjścia, zanim uruchomisz kod na prawdziwych materiałach.

Jednolity układ projektu

Chaos w katalogach to najczęstsza przyczyna błędów w automatyzacji. Ustal układ raz i trzymaj się go konsekwentnie:

projekt-wideo/
├── bin/          binarki i skrypty wykonywalne
├── lib/          biblioteki funkcji wielokrotnego użytku
├── config/       pliki konfiguracyjne
├── input/        materiały źródłowe
├── output/       wyniki
├── temp/         pliki pośrednie
└── logs/         dzienniki działania

Dzięki temu skrypt odwołuje się do ścieżek relatywnych, a sprzątanie plików tymczasowych sprowadza się do opróżnienia jednego katalogu. Trzymanie materiałów źródłowych osobno chroni przed przypadkowym nadpisaniem oryginałów — reguła, której wartość docenisz dopiero po pierwszym błędzie. Katalog roboczy dla plików pośrednich powinien leżeć na szybkim dysku lokalnym, a nie na zasobie sieciowym, gdzie operacje wejścia i wyjścia stają się wąskim gardłem całego procesu.

Nagłówek skryptu i tryb ścisły

Każdy skrypt zaczyna się od wskazania interpretera oraz od trybu ścisłego:

#!/usr/bin/env bash
set -euo pipefail
chmod +x bin/przetworz.sh
./bin/przetworz.sh

Linia set -euo pipefail to standard w profesjonalnych skryptach. -e przerywa działanie przy pierwszym błędzie, -u zgłasza problem przy użyciu niezdefiniowanej zmiennej, a pipefail sprawia, że błąd w środku potoku nie zostanie zignorowany. Bez tych ustawień skrypt potrafi „przejść” przez krytyczny błąd i wygenerować uszkodzone pliki. To najgorsza klasa problemów, bo objaw pojawia się daleko od przyczyny.

Dobrą praktyką jest ustalenie kolejności pracy: najpierw mały katalog testowy z trzema plikami o różnych proporcjach i długościach, potem cała partia. Test na jednym, przypadkowo wybranym pliku to złudzenie bezpieczeństwa. Warto też zapisać wersję FFmpeg używaną w projekcie, ponieważ różnice między wydaniami potrafią zmieniać domyślne zachowanie filtrów i kodeków, a wtedy wynik na serwerze nie zgadza się z tym, co widziałeś lokalnie.

Zmienne, argumenty i dyscyplina cytowania

Zmienne działają jak etykiety przypięte do wartości. Nie wymagają deklarowania typu, ale wymagają konsekwencji w cytowaniu — to najczęstsze źródło błędów w skryptach początkujących.

#!/usr/bin/env bash
set -euo pipefail

WEJSCIE="${1:-input}"
WYJSCIE="${2:-output}"
BITRATE="4M"
ROZSZERZENIE="mp4"

echo "Źródło: $WEJSCIE"
echo "Cel:    $WYJSCIE"
mkdir -p "$WYJSCIE"

Zapis ${1:-input} oznacza: weź pierwszy argument, a jeśli go nie podano, użyj wartości domyślnej. To wygodny sposób na skrypty działające zarówno bez argumentów, jak i z pełną konfiguracją. Argumenty specjalne warto znać na pamięć: $0 to nazwa skryptu, $1–$9 to kolejne parametry, $# to liczba argumentów, $@ to wszystkie argumenty razem, a $? to kod wyjścia ostatniego polecenia.

Kody wyjścia mają w automatyzacji ogromne znaczenie. Zero oznacza sukces, każda inna wartość to błąd. Możesz na nich budować logikę: jeśli ffprobe nie odczyta pliku, materiał jest prawdopodobnie uszkodzony i nie warto marnować na niego czasu procesora ani przepustowości łącza. Własne kody wyjścia przydają się, gdy skrypt jest wywoływany przez harmonogram zadań albo inny skrypt nadrzędny, który na podstawie wyniku decyduje o powtórzeniu próby.

Tablice pozwalają trzymać listy wartości bez ręcznego parsowania tekstu:

FORMATY=("16:9" "9:16" "1:1")
for f in "${FORMATY[@]}"; do
  echo "Przygotowuję format $f"
done

Cytowanie "${FORMATY[@]}" jest konieczne — bez cudzysłowów elementy zawierające spacje rozpadną się na osobne argumenty. Przy pracy z nazwami plików wideo, które często zawierają odstępy, nawiasy i znaki diakrytyczne, ten szczegół decyduje o tym, czy skrypt w ogóle zadziała.

Zmienne środowiskowe i konfiguracja poza kodem

Parametry, które zmieniają się między środowiskami, nie powinny być wpisane na stałe w skrypt. Adres usługi, nazwa katalogu wyjściowego, poziom jakości dla wersji roboczej i produkcyjnej — wszystko to najlepiej trzymać w zmiennych środowiskowych. Na początku pracy skrypt sprawdza, czy wymagane wartości istnieją, i odmawia startu, jeśli ich brakuje. Taka wczesna porażka jest tania, natomiast awaria w połowie długiej partii jest kosztowna, bo wymaga ręcznego ustalenia, które pliki zdążyły się przetworzyć.

Warto ustalić jasną kolejność pierwszeństwa: argumenty wiersza poleceń nadpisują zmienne środowiskowe, a te nadpisują wartości domyślne zapisane w konfiguracji. Dzięki temu ten sam skrypt działa lokalnie na twoim komputerze i na serwerze nocnym, bez duplikowania kodu i bez ryzyka, że dwa pliki konfiguracyjne zaczną się rozjeżdżać.

Sterowanie przepływem: warunki, pętle i tryby pracy

Skrypt bez logiki warunkowej jest jedynie listą komend. Prawdziwa automatyzacja zaczyna się tam, gdzie program sam podejmuje decyzję.

if [[ ! -f "$PLIK" ]]; then
  echo "Brak pliku: $PLIK" >&2
  exit 1
fi

case "$TRYB" in
  szybki)  PRESET="veryfast"; CRF=28 ;;
  jakosc)  PRESET="slow";     CRF=18 ;;
  *)       echo "Nieznany tryb: $TRYB" >&2; exit 2 ;;
esac

Instrukcja case jest wygodniejsza niż łańcuch if/elif, gdy mapujesz nazwy trybów na parametry kodowania. Użytkownik wpisuje jakosc, zamiast pamiętać, że odpowiada temu konkretny zestaw flag. Takie mapowanie sprawia, że skrypt staje się zrozumiały dla całego zespołu, również dla osób, które nie czują się pewnie w terminalu.

Warto rozróżnić dwa style porównań. [[ ]] to konstrukcja powłoki obsługująca wzorce, wyrażenia regularne i bezpieczniejsze porównania tekstowe. [ ] to klasyczny test zgodny z POSIX, który inaczej zachowuje się przy pustych zmiennych. Jeśli nie masz powodu do zachowania zgodności z inną powłoką, używaj [[ ]].

Iteracja po plikach bez pułapek

Najbezpieczniejszym wzorcem jest pętla for z rozwijaniem wzorca i obsługą sytuacji, w której nic nie pasuje:

shopt -s nullglob
for plik in input/*.mov input/*.mp4; do
  echo "Przetwarzam: $(basename "$plik")"
done

Opcja nullglob sprawia, że gdy żaden plik nie pasuje do wzorca, pętla nie wykona się ani razu z dosłowną nazwą input/*.mp4. Bez niej skrypt próbowałby otworzyć nieistniejący plik i zgłosił mylący błąd. Dla bardzo dużej liczby plików lepszym rozwiązaniem jest find z -print0 oraz xargs -0, które poprawnie obsługują spacje i znaki specjalne w nazwach.

Gdy materiałów są dziesiątki tysięcy, warto iterować po strumieniu, a nie po tablicy. Wczytywanie wszystkich ścieżek do pamięci jest możliwe, ale niepotrzebne — potok find | while read zużywa stałą ilość pamięci niezależnie od rozmiaru partii.

FFmpeg w skrypcie: walidacja, konwersja i warianty formatów

FFmpeg to serce większości procesów wideo. Poniżej przepisy, które można łączyć w skrypty.

Odczyt metadanych i walidacja materiału

ffprobe -v error -select_streams v:0 -show_entries stream=width,height,r_frame_rate,duration -of default=noprint_wrappers=1 "$PLIK"

Zapisz wynik do zmiennej i sprawdź, czy plik w ogóle ma strumień wideo. Pusta odpowiedź oznacza, że materiał jest uszkodzony albo to nie jest plik wideo — można go wtedy pominąć, zamiast marnować czas na nieudaną konwersję. Walidacja przed przetwarzaniem jest najtańszą formą optymalizacji: kilka milisekund sprawdzenia oszczędza minuty bezcelowej pracy procesora.

Warto też sprawdzić czas trwania. Pliki krótsze niż sekunda to zwykle artefakty nagrywania, a materiały trwające kilka godzin niemal zawsze wymagają innego profilu kodowania niż krótkie klipy. Prosta reguła progowa pozwala automatycznie rozdzielić ruch na dwie ścieżki i uniknąć sytuacji, w której nocna partia utknie na jednym uszkodzonym pliku.

Konwersja i skalowanie z zachowaniem proporcji

ffmpeg -y -i "$PLIK" -vf "scale=1920:-2:flags=lanczos" -c:v libx264 -preset medium -crf 21 -c:a aac -b:a 192k -movflags +faststart "$WYJSCIE"

Parametr scale=1920:-2 zachowuje proporcje i wyrównuje wysokość do liczby parzystej, czego wymaga większość kodeków. Flaga +faststart przenosi metadane na początek pliku, dzięki czemu materiał szybciej startuje w odtwarzaczach internetowych. To drobiazg, który realnie wpływa na to, czy widz zostanie przy materiale dłużej niż kilka sekund.

Warianty pionowe, miniatury i ścieżka dźwiękowa

ffmpeg -y -i "$PLIK" -vf "crop=ih*9/16:ih,scale=1080:1920" out-pion.mp4
ffmpeg -y -i "$PLIK" -vf "fps=1/30" "temp/klatka-%03d.jpg"
ffmpeg -y -i "$PLIK" -vn -c:a aac -b:a 160k out-audio.m4a

Każdy z tych przepisów to jedna linia. Największą wartość daje połączenie ich w skrypt, który dla jednego materiału źródłowego produkuje komplet wersji dla kilku platform razem z miniaturami i plikiem dźwiękowym. Zespół uruchamia jedno polecenie i otrzymuje spójny zestaw plików, zamiast zastanawiać się za każdym razem, jakie parametry wpisać.

Przy kadrowaniu do formatu pionowego pamiętaj, że obcięcie może uciąć istotne elementy kadru. Lepszym rozwiązaniem bywa rozmyte tło: najpierw skala do pełnej wysokości, potem tło z rozmytej kopii, a na końcu nałożenie oryginału wyśrodkowanego. Wymaga to filtra złożonego, ale eliminuje ryzyko, że najważniejszy fragment rozmowy wyląduje poza kadrem.

Wycinanie fragmentów, łączenie i normalizacja dźwięku

Kolejne zadania, które wracają w każdym projekcie, to wycinanie ujęć, sklejanie ich w całość i wyrównanie głośności. Wycięcie fragmentu bez ponownego kodowania jest błyskawiczne, ale wymaga rozpoczęcia cięcia w klatce kluczowej:

ffmpeg -y -ss 00:00:12 -to 00:00:28 -i "$PLIK" -c copy temp/ujecie.mp4

Szybkie cięcie na kopii strumienia ma sens wtedy, gdy materiał ma być dalej obrabiany. Jeśli wynik ma trafić do publikacji, lepiej wyciąć z ponownym kodowaniem, bo granice klatek kluczowych potrafią przesunąć początek ujęcia o kilka dziesiątych sekundy. To szczegół, który słychać i widać dopiero wtedy, gdy klipy są odtwarzane jeden po drugim.

Łączenie klipów najwygodniej zrobić przez listę plików i demultiplekser konkatenacji. Warunek jest jeden: wszystkie klipy powinny mieć ten sam kodek, rozdzielczość i częstotliwość próbkowania. Jeśli różnią się parametrami, wystarczy wcześniej sprowadzić je do wspólnego profilu — najlepiej funkcją w bibliotece, którą wywołujesz na każdym elemencie listy przed sklejeniem.

Wyrównanie głośności między nagraniami z różnych mikrofonów to typowy problem montażu. Filtr normalizacji głośności mierzy materiał i sprowadza go do ustalonego poziomu, dzięki czemu widz nie sięga po regulator głośności przy każdym przejściu. W skrypcie wystarczy wpiąć ten filtr do łańcucha przed eksportem, a efekt będzie spójny dla całej partii, niezależnie od tego, kto nagrywał poszczególne ujęcia.

Jak wybierać parametry kodowania

Dobór ustawień to kompromis między jakością, czasem i rozmiarem pliku. Warto mieć kilka sprawdzonych profili zamiast zgadywać przy każdym zadaniu.

  • Szybki szkic roboczy — preset bardzo szybki, wysoka wartość parametru jakości. Materiał wygląda dobrze na telefonie, konwersja jest błyskawiczna. Nadaje się do wewnętrznych konsultacji, nie do publikacji.
  • Standard publikacyjny — preset średni, parametr jakości w okolicach 20–22. Rozsądny punkt równowagi dla większości treści internetowych.
  • Materiał promowany — preset wolny, niższa wartość parametru jakości. Dłuższe kodowanie, ale lepsza jakość w ciemnych scenach i przy dużym ruchu w kadrze.
  • Archiwum — kodek o większej efektywności i wysoki profil jakości. Liczy się trwałość zapisu, nie rozmiar pliku.

Zapisz te profile jako funkcje w bibliotece, zamiast kopiować długie polecenia w kilku miejscach. Gdy zmieniasz politykę jakościową, edytujesz jeden fragment kodu i cała partia przechodzi na nowe ustawienia.

Przetwarzanie wsadowe, równoległość i kolejka oparta na katalogach

Przetwarzanie po jednym pliku jest bezpieczne, ale wolne. Powłoka oferuje kilka prostych sposobów na wykorzystanie wielu rdzeni bez budowania skomplikowanej infrastruktury.

Najprostsze podejście to równoległość przez xargs -P, gdzie liczba oznacza maksymalną liczbę jednoczesnych procesów:

find input -name '*.mov' -print0 | xargs -0 -n 1 -P 4 ./bin/konwertuj.sh

Pamiętaj, że równoległość nie zawsze oznacza szybciej. Jeśli wąskim gardłem jest dysk, karta graficzna albo zewnętrzna usługa sieciowa, zbyt wiele procesów naraz pogorszy wyniki i zwiększy ryzyko przekroczenia limitów zapytań. Rozsądny punkt startowy to połowa liczby rdzeni, a przy zadaniach korzystających z akceleracji sprzętowej najczęściej jeden lub dwa procesy jednocześnie. Zmierz czas dla wariantu z dwoma i z ośmioma procesami, zamiast zakładać, że więcej znaczy lepiej.

Kolejka oparta na katalogach

Dla zadań wymagających kolejności sprawdzi się prosty system, w którym przeniesienie pliku oznacza zmianę stanu:

KOLEJKA="temp/kolejka"
GOTOWE="temp/gotowe"
mkdir -p "$KOLEJKA" "$GOTOWE"

for zadanie in "$KOLEJKA"/*; do
  [[ -e "$zadanie" ]] || continue
  if ./bin/konwertuj.sh "$zadanie"; then
    mv "$zadanie" "$GOTOWE"/
  else
    echo "Błąd: $zadanie" >> logs/bledy.log
  fi
done

Taki wzorzec jest odporny na przerwanie — przeniesienie pliku do katalogu „gotowe” następuje dopiero po sukcesie. Jeśli proces zostanie zatrzymany, wznowienie pracy polega na ponownym uruchomieniu skryptu, bez ryzyka podwójnego przetworzenia. Ma to szczególne znaczenie przy długich kolejkach, które trwają kilka godzin i mogą zostać przerwane restartem komputera.

Kolejny poziom to rozdzielenie zadań na etapy z osobnymi katalogami: do zrobienia, w trakcie, gotowe, błędy. Taki układ czyta się jak tablica na ścianie i każdy członek zespołu od razu widzi, ile pracy czeka, a ile utknęło. Diagnostyka sprowadza się do policzenia plików w katalogach, bez zaglądania w kod.

Ponawianie zadań, które zawiodły

Nie każde niepowodzenie oznacza, że materiał jest bezużyteczny. Chwilowy brak łączności, przeciążony dysk sieciowy albo zbyt duża równoległość to problemy przejściowe. Warto oddzielić błędy trwałe od przejściowych i dla tych drugich zaplanować ograniczoną liczbę powtórzeń z rosnącym odstępem: najpierw kilka sekund, potem minuta, potem kilka minut. Po wyczerpaniu limitu plik trafia do katalogu z błędami i wymaga uwagi człowieka. Bez takiego rozróżnienia albo tracisz materiały, które wystarczyło przetworzyć drugi raz, albo w nieskończoność powtarzasz zadanie, które nigdy się nie uda.

Funkcje, biblioteki i oddzielenie konfiguracji

Gdy skrypt przekracza sto linii, warto podzielić go na funkcje. Funkcja to nazwany blok kodu, który może przyjmować argumenty i zwracać kod wyjścia.

log() {
  printf '[%s] %s\n' "$(date '+%H:%M:%S')" "$*" | tee -a logs/run.log
}

waliduj() {
  local plik="$1"
  [[ -s "$plik" ]] || { log "Pusty lub brakujący plik: $plik"; return 1; }
  ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of csv=p=0 "$plik" >/dev/null 2>&1 || { log "To nie jest wideo: $plik"; return 1; }
}

Słowo local ogranicza zasięg zmiennej do funkcji i chroni przed przypadkowym nadpisaniem zmiennej globalnej o tej samej nazwie — błąd, który w większych skryptach bywa bardzo trudny do znalezienia. Dobrą praktyką jest rozdzielenie konfiguracji od logiki. Wartości stałe trzymaj w osobnym pliku i wczytuj go przez source. W pliku config/ustawienia.sh znajdą się wtedy tylko przypisania:

source config/ustawienia.sh
ROZDZIELCZOSC="1920x1080"
PODPIS="dzial-marketingu"

Katalog bin/ powinien zawierać cienkie skrypty wejściowe, a katalog lib/ biblioteki z funkcjami wielokrotnego użytku. Taki podział sprawia, że dodanie nowego formatu wyjściowego to zmiana w jednym miejscu, a nie w pięciu skryptach jednocześnie. Ustal też konwencję nazewnictwa funkcji opisującą intencję, na przykład przygotuj_wersje_pionowe, a nie krok3. Po tygodniu przerwy nazwa krok3 nie powie ci nic, a nazwa opisowa od razu wyjaśnia, co robi dany fragment.

W bibliotece trzymaj funkcje, które nie znają kontekstu projektu: skalowanie, wycinanie fragmentu, generowanie miniatury, walidację. Jeśli funkcja zawiera nazwę konkretnej kampanii albo konkretnej platformy społecznościowej, prawdopodobnie należy do skryptu wejściowego, a nie do biblioteki wielokrotnego użytku.

Obsługa błędów, logowanie i praca bez nadzoru

Największa różnica między skryptem amatorskim a produkcyjnym nie polega na liczbie funkcji, lecz na sposobie traktowania błędów. Skrypt amatorski zakłada, że wszystko się uda. Skrypt produkcyjny zakłada, że coś pójdzie nie tak, i wie, co wtedy zrobić.

Podstawowym narzędziem jest trap, który pozwala posprzątać po zakończeniu pracy, także po przerwaniu przez użytkownika:

TEMP="temp/roboczy"
trap 'rm -rf "$TEMP"; log "Zakończono, katalog tymczasowy usunięty"' EXIT
trap 'log "Przerwano przez użytkownika"; exit 130' INT

Logowanie powinno działać jednocześnie na ekranie i w pliku, najlepiej z sygnaturą czasu. Przy przetwarzaniu setek plików dziennik jest jedynym sposobem, by ustalić, który materiał zawiódł i dlaczego. Rozdzielaj poziomy komunikatów: informacje bieżące, ostrzeżenia i błędy krytyczne. Jeśli skrypt działa w tle albo jest uruchamiany przez harmonogram, dziennik staje się twoim jedynym oknem na jego zachowanie.

Przydatny wzorzec to funkcja obsługi błędów wywoływana przy nieoczekiwanym zakończeniu:

blad() {
  local kod=$?
  log "Błąd w linii ${BASH_LINENO[0]}, kod $kod"
  exit "$kod"
}
trap blad ERR

Dzięki temu w dzienniku pojawia się numer linii, co skraca diagnozę z godzin do minut. Warto też rejestrować czas trwania każdego kroku, aby po kilku uruchomieniach wiedzieć, który etap jest rzeczywistym wąskim gardłem. Jeżeli jedna operacja odpowiada za osiemdziesiąt procent czasu partii, optymalizacja reszty kroków nie zmieni niczego odczuwalnego.

Bezpieczeństwo danych i dostępów

Klucze dostępu i tokeny przekazuj przez zmienne środowiskowe, nigdy nie wpisuj ich na stałe w skrypt ani nie dodawaj do repozytorium. Cytuj każdą zmienną: rm -rf "$KATALOG" zachowa się zupełnie inaczej niż to samo polecenie bez cudzysłowów, gdy zmienna będzie pusta. Ustaw restrykcyjne uprawnienia dla plików konfiguracyjnych zawierających dane wrażliwe. Testuj skrypty na kopii danych, zanim uruchomisz je na materiale, którego nie da się odtworzyć.

Sprawdzaj obecność wymaganych zmiennych środowiskowych na początku działania i kończ z czytelnym komunikatem, jeśli ich brakuje. Skrypt, który wywraca się w połowie po dziesięciu minutach pracy z powodu brakującego klucza, jest znacznie droższy w utrzymaniu niż skrypt, który odmawia startu natychmiast.

: "${KLUCZ_API:?Ustaw zmienną KLUCZ_API w środowisku}"

Ta jedna linia zastępuje kilka warunków i działa w każdej nowoczesnej wersji powłoki.

Uruchamianie o stałej porze

Gdy proces działa stabilnie, naturalnym krokiem jest uruchamianie go bez nadzoru. W systemach linuksowych i w macOS standardem jest harmonogram zadań systemowych. Zasady są zawsze takie same: ustaw pełne ścieżki lub jawnie przejdź do katalogu projektu, przekieruj wyjście standardowe i błędy do pliku dziennika z datą w nazwie, dodaj mechanizm blokady zapobiegający równoległemu uruchomieniu dwóch instancji, ogranicz czas działania i zaplanuj powtórzenia z rosnącym odstępem.

LOCK="temp/potok.lock"
if ! mkdir "$LOCK" 2>/dev/null; then
  echo "Inna instancja już działa" >&2
  exit 0
fi
trap 'rmdir "$LOCK"' EXIT

Monitorowanie warto ograniczyć do trzech pytań: czy ostatnie uruchomienie zakończyło się sukcesem, ile plików przetworzono i czy liczba błędów nie rośnie. Jeśli odpowiadasz na te pytania jednym spojrzeniem w dziennik, system jest wystarczająco obserwowalny.

Typowe pomyłki i kryteria wyboru narzędzia

Lista błędów, które kosztują najwięcej czasu, jest zaskakująco krótka i powtarzalna. Warto znać ją na pamięć, zamiast uczyć się na własnych porażkach.

  • Brak cudzysłowów wokół zmiennych. Nazwa pliku ze spacją rozpadnie się na dwa argumenty. Zawsze pisz "$ZMIENNA", również w tablicach i wywołaniach funkcji.
  • Poleganie na bieżącym katalogu. Skrypt uruchomiony z innego miejsca nie znajdzie plików. Ustal katalog roboczy jawnie na początku: cd "$(dirname "$0")".
  • Ignorowanie kodów wyjścia. Bez trybu ścisłego i sprawdzania $? skrypt kontynuuje po błędzie i produkuje bezwartościowe pliki.
  • Nadpisywanie oryginałów. Wyniki zapisuj zawsze do osobnego katalogu, nigdy obok źródła.
  • Zbyt agresywna równoległość. Za dużo procesów naraz wysyca dysk i pamięć, a zysku nie ma.
  • Krucha nazwa pliku wyjściowego. Nazwa typu out.mp4 nadpisze poprzedni wynik przy drugim uruchomieniu. Używaj nazwy bazowej materiału źródłowego i sufiksu opisującego wariant.
  • Brak sprzątania. Pliki tymczasowe rosną w gigabajty i pewnego dnia zapełniają dysk w środku nocnego przetwarzania.
  • Mieszanie konfiguracji z logiką. Zmiana jednego parametru wymaga wtedy edycji wielu miejsc i łatwo o niespójność.
  • Testowanie na produkcji. Trzymaj mały katalog z trzema plikami o różnych proporcjach i przepuszczaj przez niego każdą zmianę.
  • Zakładanie, że materiał wejściowy jest poprawny. Pliki z telefonów, kamer i programów do nagrywania ekranu mają bardzo różne parametry. Walidacja na wejściu niemal zawsze się opłaca.

Mniej oczywista pułapka: skrypt, który działa, nie znaczy, że jest zrozumiały. Komentarze przy nietrywialnych fragmentach oraz nazwy funkcji opisujące intencję oszczędzają godziny przy powrocie do projektu po dłuższej przerwie. Krótki plik z opisem argumentów, przykładowym wywołaniem i listą wymaganych narzędzi bywa równie wartościowy jak sam kod.

Uważaj też na ciche błędy w potokach. Jeśli używasz pipefail, błąd po lewej stronie potoku zatrzyma całość. Jeśli nie używasz, wynik może być niekompletny, a skrypt zgłosi sukces. Wybór należy do ciebie, ale powinien być świadomy.

Kiedy powłoka, a kiedy inny język

Wybór narzędzia powinien wynikać z charakteru zadania, nie z przyzwyczajenia. Poniższe kryteria pomagają podjąć decyzję świadomie.

Wybierz skrypt powłoki, gdy zadanie polega na wywoływaniu programów linii poleceń, potrzebujesz zarządzać plikami i katalogami, logika to głównie warunki i kolejność kroków, skrypt ma działać na serwerze bez dodatkowych zależności, a ty chcesz szybko zweryfikować pomysł przed budową większego systemu.

Wybierz język ogólnego przeznaczenia, gdy przetwarzasz złożone struktury danych i dokumenty JSON, potrzebujesz bazy danych lub trwałego stanu, wymagasz testów jednostkowych i typowania, budujesz interfejs sieciowy albo aplikację dla wielu użytkowników, a logika zawiera złożone obliczenia i transformacje.

W praktyce najlepsze systemy łączą oba podejścia. Skrypt powłoki pozostaje warstwą orkiestrującą: sprawdza dostępność zależności, ustawia zmienne środowiskowe, wywołuje programy i pilnuje kolejności. Cięższa logika trafia do osobnego programu, który skrypt wywołuje jak każde inne narzędzie. Taki podział jest czytelny i pozwala wymieniać części bez przepisywania całości.

Praktyczna wskazówka: jeśli łapiesz się na tym, że parsujesz JSON za pomocą narzędzi tekstowych i wyrażeń regularnych, to sygnał, że potrzebujesz dedykowanego skryptu w innym języku albo narzędzia takiego jak jq, które zrobi to poprawnie i czytelnie.

FAQ: pytania, które pojawiają się najczęściej

Czy skrypty powłoki mają sens, jeśli nie znam Linuksa? Tak. W macOS terminal działa od razu, a w Windows możesz korzystać z podsystemu linuksowego lub emulacji Git Bash. Składnia pozostaje praktycznie identyczna, a wiedza przenosi się między systemami bez większych zmian.

Czy muszę umieć programować, żeby automatyzować obróbkę wideo? Nie w klasycznym sensie. Wystarczy rozumieć zmienne, warunki i pętle. Reszta to znajomość narzędzi, które wywołujesz, a każde z nich ma własną dokumentację i mnóstwo przykładów.

Kiedy przejść do języka ogólnego przeznaczenia? Gdy logika zaczyna przypominać aplikację: parsowanie złożonych struktur danych, obsługa bazy, zaawansowana współbieżność z pełną kontrolą, testy jednostkowe. Powłoka świetnie orkiestruje, ale nie zastąpi języka ogólnego przeznaczenia w rozbudowanej logice.

Jak przetwarzać pliki równolegle bez przeciążania komputera? Zacznij od dwóch procesów jednocześnie, obserwuj obciążenie procesora i dysku, a dopiero potem zwiększaj liczbę. Przy zadaniach korzystających z akceleracji sprzętowej najczęściej optymalna jest wartość jeden.

Co zrobić, gdy skrypt zatrzyma się w połowie? Zaprojektuj go tak, aby każdy krok kończył się przeniesieniem pliku do katalogu „gotowe”. Wtedy wznowienie pracy to ponowne uruchomienie, bez ryzyka dublowania wyników.

Jak przechowywać klucze dostępu? Wyłącznie w zmiennych środowiskowych poza repozytorium, z restrykcyjnymi uprawnieniami dla plików konfiguracyjnych. Skrypt powinien sprawdzić, czy klucz istnieje, i zakończyć się czytelnym komunikatem, jeśli go brakuje.

Czy warto używać analizy statycznej? Tak. Analizator taki jak ShellCheck wyłapuje większość problemów z cytowaniem i kodami wyjścia, zanim uruchomisz skrypt na prawdziwych danych. To kilka sekund pracy, które oszczędzają godziny debugowania.

Ile czasu zajmuje napisanie pierwszego użytecznego skryptu? Zwykle kilka godzin, jeśli ograniczysz się do jednej operacji, na przykład konwersji do jednego formatu z walidacją wejścia. Kolejne warianty dodaje się stopniowo, a każdy z nich jest samodzielnie prosty.

Jak dokumentować skrypty dla zespołu? Krótki opis z argumentami, przykładowym wywołaniem i listą wymaganych narzędzi wystarcza w większości przypadków. Warto dopisać, gdzie trafiają wyniki i gdzie szukać dzienników.

Czy skrypt powinien zapisywać stan między uruchomieniami? Przy krótkich partiach nie jest to konieczne. Przy kolejkach trwających wiele godzin warto zapisywać listę zakończonych plików i pomijać je przy ponownym starcie — pozwala to wznowić pracę dokładnie w miejscu przerwania.

Lista kontrolna przed pierwszym uruchomieniem na pełnej partii

  • Nagłówek wskazujący interpreter oraz tryb ścisły na początku pliku.
  • Wszystkie zmienne w cudzysłowach, w tym tablice i argumenty funkcji.
  • Jawnie ustawiony katalog roboczy i ścieżki liczone względem niego.
  • Osobne katalogi na wejście, wyjście, pliki tymczasowe i dzienniki.
  • Funkcja walidująca materiał przed przetwarzaniem, oparta na ffprobe.
  • Logowanie z sygnaturą czasu, jednocześnie na ekran i do pliku.
  • Sprzątanie plików tymczasowych po zakończeniu lub przerwaniu pracy.
  • Test na trzech plikach o różnych formatach, proporcjach i długościach.
  • Dane wrażliwe wyłącznie w zmiennych środowiskowych, nigdy w kodzie.
  • Krótki opis w repozytorium wyjaśniający, jak uruchomić skrypt i co robią argumenty.

Naturalnym kolejnym krokiem jest stopniowe rozbudowywanie procesu. Zacznij od jednej operacji, na przykład konwersji do jednego formatu z walidacją. Potem dodaj wariant pionowy, następnie miniatury, później automatyczne generowanie napisów, a na końcu kolejkę zadań i uruchamianie o stałej porze. Każdy z tych elementów jest samodzielnie prosty, a razem tworzą system, który przekształca żmudną, ręczną obróbkę w powtarzalny proces wykonywany jednym poleceniem.

Alexander

Alexander