Ładne wypisywanie i długie teksty
Po tej lekcji: kursant wypisuje dane w równych kolumnach przez printf z formatami, wpisuje wielolinijkowe teksty i pliki konfiguracyjne here-dokiem i podaje komendzie tekst ze zmiennej przez here-string.
Wstęp
Ten sam skrypt. Te same dane. Lewy raport wysyłasz szefowi i dostajesz pytanie „co to jest?”. Prawy wysyłasz i dostajesz pytanie „możesz to robić co tydzień?”.
Różnica to dwie rzeczy: printf i here-doc. Dziś obie.
Czemu nie echo
Do tej pory wypisywaliśmy wszystko przez echo. I echo jest fajne, dopóki nie chcesz czegoś więcej. Zobacz:
echo "Linia1\nLinia2"
echo -e "Linia1\nLinia2"
printf 'Linia1\nLinia2\n'Linia1\nLinia2
Linia1
Linia2
Linia1
Linia2Zwykłe echo wypisało \n dosłownie. echo -e już zrozumiało. Ale tu jest haczyk: w różnych powłokach echo zachowuje się różnie. Pamiętasz dasha z lekcji 9? Inny urzędnik, inne zwyczaje. printf za to zachowuje się wszędzie tak samo. Dlatego w skryptach, które mają wyglądać porządnie, używa się printf.
printf, czyli formularz z rubrykami
printf działa jak formularz w urzędzie. Masz gotowy wzór z pustymi rubrykami: „Imię: ____, Wiek: ____”. Podajesz dane, a urzędnik wpisuje je w rubryki po kolei.
Wzór to pierwszy argument, nazywa się format. Rubryki to znaczki zaczynające się od procentu. Najważniejszy to %s, czyli „tu wstaw tekst”:
printf 'Witaj, %s!\n' "Eryk"Witaj, Eryk!Uwaga, printf sam nie dodaje nowej linii. Jeśli jej nie wpiszesz jako \n, następny tekst przyklei się do tego. Zapomnisz raz, a znak zachęty wyląduje Ci na końcu raportu.
A teraz magia. Jeśli podasz więcej danych niż rubryk, printf bierze formularz jeszcze raz. I jeszcze raz:
printf '%s\n' web01 web02 db01
printf '%s -> %s\n' web01 10.0.0.11 db01 10.0.0.21web01
web02
db01
web01 -> 10.0.0.11
db01 -> 10.0.0.21printf '%s\n' "${tablica[@]}" to najkrótszy sposób na wypisanie tablicy, każdy element w osobnej linijce. Używaliśmy tego już przy słownikach, teraz wiesz, czemu działa.
Szerokość rubryk: tu rodzą się tabelki
Między procent a literę możesz wpisać szerokość. To jak rubryka w formularzu, która ma określoną liczbę kratek.
printf '[%10s]\n' "prawo"
printf '[%-10s]\n' "lewo"[ prawo]
[lewo ]%10s to dziesięć kratek, tekst dosunięty do prawej. Z minusem, %-10s, do lewej. Nawiasy kwadratowe są tu tylko po to, żebyś widział spacje. Zasada w tabelkach: teksty do lewej, liczby do prawej. Wtedy jedności stoją pod jednościami, jak w słupku z podstawówki.
Dla liczb są osobne rubryki:
printf '[%5d]\n' 42
printf '[%05d]\n' 42
printf '%.2f\n' 3.14159
printf '%d%%\n' 87
printf '%x\n' 255[ 42]
[00042]
3.14
87%
ff%d to liczba całkowita. %05d dopełnia zerami, przydaje się do numerowania plików, żeby plik007 sortował się przed plik010. %.2f to liczba z dwoma miejscami po przecinku. %% to sam znak procentu, bo pojedynczy procent zarezerwował sobie formularz. A %x zamienia na szesnastkowy, co się przyda przy uprawnieniach i adresach.
I fajna rzecz dla admina: %d pilnuje, czy dostał liczbę.
printf '%d\n' "12abc"bash: printf: 12abc: invalid number
12Krzyknął, wpisał tyle, ile zrozumiał, i zwrócił kod błędu. Darmowy kontroler danych.
Dwie sztuczki Basha: data i zmienna
printf w Bashu umie wstawić datę bez wołania programu date:
printf '%(%Y-%m-%d %H:%M)T\n' -12026-11-03 22:17-1 znaczy „teraz”. A w nawiasie te same literki co w date +.
I printf -v, czyli „nie wypisuj, tylko włóż do pudełka”:
printf -v dzis '%(%F)T' -1
echo "Dziś: $dzis"Dziś: 2026-11-03Czyli funkcja kopia z lekcji 40 mogłaby wziąć datę bez uruchamiania date. Jeden proces mniej. [macOS] %( )T jest od Basha 4.2, -v działa i na starym 3.2.
Wszystko razem: raport dysków
#!/bin/bash
# raport.sh – krótki raport o dyskach serwera
wiersz() {
printf '%-20s %8s %8s %8s\n' "$@"
}
cat <<EOF
=============================================
RAPORT DYSKÓW: $(hostname)
Data: $(date '+%F %H:%M')
=============================================
EOF
wiersz "PUNKT MONTOWANIA" "ROZMIAR" "WOLNE" "PROCENT"
while read -r rozmiar wolne procent punkt; do
wiersz "$punkt" "$rozmiar" "$wolne" "$procent"
done < <(df -h --output=size,avail,pcent,target -x tmpfs -x devtmpfs | tail -n +2)=============================================
RAPORT DYSKÓW: vm
Data: 2026-11-03 22:18
=============================================
PUNKT MONTOWANIA ROZMIAR WOLNE PROCENT
/ 252G 25G 42%
/srv/dane 452M 312M 26%Format jest w jednym miejscu, w funkcji wiersz. Nagłówek i dane idą przez ten sam przycisk w windzie, więc zawsze trzymają się tych samych kolumn. Chcesz szerszą pierwszą kolumnę? Zmieniasz jedną liczbę. df --output wybiera kolumny, -x tmpfs wyrzuca dyski w pamięci, a tail -n +2 zjada nagłówek df, bo mamy własny.
[macOS] df --output to opcja z GNU. Macowy df jej nie zna, tam ten skrypt odpalisz tylko na Linuksie.
A ta górna ramka? To był here-doc. Czas na niego.
Here-doc: list w kopercie
Czasem chcesz wypisać nie jedną linijkę, tylko cały akapit. Ramkę raportu. Instrukcję użycia skryptu. Cały plik konfiguracyjny. Dwadzieścia echo pod rząd to jak pisanie listu, w którym każda linijka ma osobną kopertę.
Here-doc to jedna koperta na cały list. Mówisz komendzie: „czytaj od następnej linijki, aż zobaczysz słowo EOF”.
serwer=web01
cat <<EOF
Raport z serwera: $serwer
Data: $(date +%F)
Koszt: \$100
EOFRaport z serwera: web01
Data: 2026-11-03
Koszt: $100<<EOF otwiera kopertę, samo EOF w osobnej linijce ją zamyka. EOF to skrót od „end of file”, ale możesz użyć dowolnego słowa. W środku zmienne i $( ) działają jak w cudzysłowie. A jeśli chcesz dolara dosłownie, dajesz przed nim ukośnik.
A jeśli nie chcesz, żeby cokolwiek się rozwijało? Bierzesz słowo zamykające w apostrofy:
cat <<'EOF'
Raport z serwera: $serwer
Data: $(date +%F)
EOFRaport z serwera: $serwer
Data: $(date +%F)To jest koperta zaklejona na głucho. Co włożyłeś, to wyjdzie. Idealne, kiedy here-dokiem tworzysz… inny skrypt, który ma mieć w sobie dolary.
Here-doc można skierować do pliku. Tak się tworzy pliki konfiguracyjne w skryptach instalacyjnych:
cat > motd.txt <<EOF
Witaj na $serwer
Zanim coś zepsujesz, zrób kopię.
EOF
cat motd.txtWitaj na web01
Zanim coś zepsujesz, zrób kopię.A plik, do którego pisać może tylko root? Wtedy przez sudo tee:
sudo tee /etc/motd > /dev/null <<EOF
Witaj na $serwer
EOFtee to rozgałęziacz, pisze do pliku i na ekran. Ekran wyciszamy przez /dev/null. Czemu nie po prostu sudo cat > /etc/motd? Wyjaśnię w błędach. Uwaga: tee bez -a nadpisuje cały plik. Pliki w /etc zmieniaj najpierw na maszynie wirtualnej, a przed zmianą zrób kopię, na przykład funkcją kopia.
I jeszcze jedna odmiana, <<- z minusem. Pozwala wciąć tekst i słowo zamykające, żeby ładnie wyglądało w if-ie. Ale wycina tylko tabulatory. Nie spacje. Tylko tabulatory. Wrócimy do tego w błędach, bo to klasyka.
Here-string: kartka zamiast koperty
Here-doc to list. A czasem chcesz podać komendzie tylko jedną linijkę ze zmiennej. Do tego są trzy strzałki, <<<, czyli here-string. Karteczka podana przez okienko.
linia="web01 10.0.0.11 ubuntu"
read -r nazwa adres system <<< "$linia"
echo "$nazwa | $adres | $system"
bc <<< "scale=2; 7/3"
tr '[:lower:]' '[:upper:]' <<< "awaria"web01 | 10.0.0.11 | ubuntu
2.33
AWARIAPierwsza rzecz to rozbicie zmiennej na kawałki, widziałeś to już przy read -a. Druga to kalkulator bc z lekcji 38, bez echo i rury. Trzecia to podanie tekstu komendzie, która czyta tylko z wejścia.
Czemu nie echo "$linia" | read ...? Bo to ten sam problem co z mapfile w rurze: read działałby w podpowłoce i zmienne by przepadły. Here-string nie tworzy rury, więc zmienne zostają.
Jak to działa pod spodem
Here-doc i here-string to przekierowania, tak jak < z pliku. Bash bierze tekst, wkłada go do tymczasowego pliku albo do rury w pamięci, i podpina to komendzie jako wejście standardowe, czyli deskryptor 0. Komenda nawet nie wie, że tekst przyszedł ze skryptu. Myśli, że czyta z pliku. Dlatego here-doc działa z każdą komendą, która umie czytać z wejścia: cat, wc, mysql, ssh, bc.
wc -l <<EOF
jeden
dwa
trzy
EOF3A printf? To polecenie wbudowane, urzędnik robi to sam. Szerokość rubryki liczy w bajtach, nie w literach. I tu zaczyna się problem z polskimi znakami:
printf '[%-6s]\n' "lodz" "łódź"[lodz ]
[łódź]„Łódź” ma cztery litery, ale siedem bajtów, więc w sześciu kratkach już się nie mieści i printf nie dodał ani jednej spacji. Kolumna się rozjechała. Na serwerach rzadko masz polskie nazwy kont, ale w raportach dla ludzi już tak.
I druga pułapka, tym razem dla osób z polskim systemem. printf czyta liczby według ustawień regionalnych. W polskich ustawieniach przecinek dziesiętny to przecinek, nie kropka:
LC_ALL=pl_PL.UTF-8 bash -c 'printf "%.2f\n" 3.14159'
LC_ALL=pl_PL.UTF-8 bash -c 'printf "%.2f\n" 3,14159'bash: line 1: printf: 3.14159: invalid number
3,00
3,14Na serwerze z angielskimi ustawieniami ten sam skrypt działał, na laptopie z polskimi wywala się. W skryptach, które liczą ułamki, ustaw na górze export LC_NUMERIC=C i śpij spokojnie.
Ściąga flag
| Komenda | Flaga | Co robi |
|---|---|---|
printf 'format' dane | wypisuje dane według wzoru, bez automatycznego \n | |
printf | %s | tekst |
printf | %10s / %-10s | tekst w 10 kratkach, do prawej / do lewej |
printf | %d / %5d / %05d | liczba całkowita, w 5 kratkach, dopełniona zerami |
printf | %.2f | liczba z 2 miejscami po przecinku (uwaga na locale) |
printf | %x | liczba szesnastkowo |
printf | %% | znak procentu |
printf | %*s | szerokość z argumentu |
printf | %(%F)T | data według wzoru jak w date, -1 = teraz (Bash 4.2+) |
printf | -v zmienna | zapisuje wynik do zmiennej zamiast wypisywać |
cat <<EOF | here-doc, zmienne i $( ) działają | |
cat <<'EOF' | apostrofy | here-doc dosłowny, nic się nie rozwija |
cat <<-EOF | - | here-doc z wcięciem, wycina tylko tabulatory |
cat > plik <<EOF | > | here-doc prosto do pliku |
sudo tee plik > /dev/null <<EOF | here-doc do pliku roota (tee -a dopisuje) | |
komenda <<< "$v" | <<< | here-string, jedna linijka na wejście komendy |
⚠ Typowy błąd
Piszesz porządnie, z wcięciami, żeby ładnie wyglądało:
#!/bin/bash
if true; then
cat <<EOF
Witaj na serwerze
EOF
fi
echo "koniec"zly.sh: line 7: warning: here-document at line 3 delimited by end-of-file (wanted `EOF')
zly.sh: line 8: syntax error: unexpected end of fileBash szuka linijki, w której jest dokładnie EOF i nic więcej. Ty masz cztery spacje i EOF. Dla niego to zupełnie inne słowo. Więc czyta dalej, przez fi, przez echo, do końca pliku. Zjadł Ci resztę skryptu. A potem się dziwi, że if nie ma końca. To jak list, w którym zapomniałeś podpisu, więc listonosz wsadził do koperty jeszcze Twoją lodówkę.
ShellCheck powie Ci dokładnie, co zrobić:
SC1039 (error): Remove indentation before end token (or use <<- and indent with tabs).Naprawa: EOF zawsze od pierwszej kolumny. Albo <<-EOF i wcięcia tabulatorami. Uwaga, wiele edytorów zamienia Tab na spacje, więc <<- zdradza w najmniej spodziewanym momencie. Bezpieczniej trzymać EOF przy lewej krawędzi i nie kombinować.
I obiecany drugi błąd, z sudo:
sudo cat > /etc/motd <<EOF
Witaj
EOFbash: /etc/motd: Permission deniedsudo działa tylko na cat. Ale przekierowanie > robi Twoja powłoka, jeszcze zanim sudo w ogóle ruszy. A Twoja powłoka to zwykły użytkownik, który do /etc nie ma klucza. To jak dać świadkowi przepustkę VIP, a samemu stanąć przed bramką bez niej. Dlatego sudo tee: wtedy do pliku pisze tee, który ma uprawnienia roota.
✎ Zadanie
Wypisz printf-em tabelkę wszystkich kont z /etc/passwd, które mają powłokę /bin/bash: nazwa, UID i folder domowy, w równych kolumnach. Nagłówek tabelki zrób here-dokiem.
Rozwiązanie znajdziesz pod spodem.
Pokaż rozwiązanie
Zadanie: tabelka printf-em ze wszystkich kont z /etc/passwd, które mają powłokę /bin/bash. Nazwa, UID i folder domowy w równych kolumnach, a nagłówek here-dokiem.
#!/bin/bash
# Tabelka kont z powłoką /bin/bash
cat <<NAGLOWEK
Konta z powłoką /bin/bash na: $(hostname)
NAGLOWEK
printf '%-12s %6s %s\n' "NAZWA" "UID" "FOLDER DOMOWY"
printf '%-12s %6s %s\n' "-----" "---" "-------------"
while IFS=: read -r nazwa _ uid _ _ dom powloka; do
if [[ $powloka == /bin/bash ]]; then
printf '%-12s %6d %s\n' "$nazwa" "$uid" "$dom"
fi
done < /etc/passwdKonta z powłoką /bin/bash na: vm
NAZWA UID FOLDER DOMOWY
----- --- -------------
root 0 /root
eryk 1000 /home/erykCo tu się dzieje. Here-doc nie ma apostrofów wokół słowa NAGLOWEK, więc Bash rozwija w nim $(hostname). IFS=: mówi read, że kolumny oddziela dwukropek, a podkreślnik _ to pudełko na śmieci: kolumny, których nie potrzebujemy, wrzucamy tam i zapominamy. %-12s to napis dosunięty do lewej na dwanaście znaków, %6d to liczba dosunięta do prawej. Liczby do prawej, napisy do lewej, i tabelka wygląda jak z banku, a nie jak lista zakupów pisana w tramwaju.
Jeśli masz zainstalowanego PostgreSQL-a, wyskoczy też postgres. Tak, baza danych ma Basha. Czasem też chce pogadać z urzędnikiem.
Co dalej
Nożyczki z lekcji 41 tną według prostych wzorców z gwiazdką. A co, jeśli musisz znaleźć w logu wszystkie adresy IP albo sprawdzić, czy ktoś podał poprawny adres e-mail? W następnej lekcji wyrażenia regularne: grep -E, grep -o i [[ =~ ]]. Język, który wygląda jak kot przeszedł po klawiaturze, a jest najpotężniejszym narzędziem do tekstu, jakie znasz.