Przejdź do treści
Lekcja 46 z 117

Kurs › Moduł 4 › Lekcja 46

sed od podstaw

⏱ ok. 9 min czytania· moduł 4: Napisy, listy i przetwarzanie danych

Po tej lekcji: kursant zamienia tekst w plikach przez sed, wypisuje i usuwa wybrane linijki po numerach i wzorcach, a pliki konfiguracyjne edytuje bezpiecznie przez -i z kopią zapasową.

Wstęp

Audyt bezpieczeństwa. Na wszystkich serwerach trzeba wyłączyć logowanie hasłem do SSH. W pliku konfiguracyjnym jest linijka PasswordAuthentication yes, ma być no.

Możesz otworzyć edytor pięćdziesiąt razy. Szukać linijki, zmieniać, zapisywać. Przy trzydziestym serwerze zaczniesz się mylić, przy czterdziestym zaczniesz się zastanawiać nad karierą piekarza.

Albo jedna linijka, którą da się wpuścić w pętlę, w skrypt albo w Ansible:

sed -i.bak -E 's/^(PasswordAuthentication)[[:space:]]+yes/\1 no/' sshd_config

Bez otwierania pliku. Z kopią zapasową. Dziś rozbieramy to narzędzie od zera.

Korektorka przy taśmie

Wyobraź sobie wydawnictwo. Przy taśmie siedzi korektorka z czerwonym długopisem i kartką poleceń: „zamień ą na ę w trzeciej linijce, wykreśl każdą linijkę z krzyżykiem”. Taśma podaje jej tekst linijka po linijce. Ona sprawdza, czy linijka pasuje do polecenia, poprawia i puszcza dalej. Nie czyta całej książki naraz. Nie wraca do poprzednich stron.

To jest sed. Nazwa od stream editor, edytor strumienia. Pasuje idealnie do naszej taśmy w fabryce: może stać w środku rury, a może czytać plik.

Do ćwiczeń zrób sobie plik sshd_config w swoim folderze. To kopia, a nie prawdziwa konfiguracja serwera, więc możesz się mylić do woli:

# Konfiguracja serwera SSH (kopia do ćwiczeń)
#Port 22
#PermitRootLogin prohibit-password
PasswordAuthentication yes

# Limity
MaxAuthTries 6
#ClientAliveInterval 0
X11Forwarding yes

s///: znajdź i zamień

Najważniejsze polecenie seda to s, od substitute, czyli zamień. Składnia: s/co/na co/.

echo 'kot i kot' | sed 's/kot/pies/'
echo 'kot i kot' | sed 's/kot/pies/g'
echo 'Kot i KOT' | sed 's/kot/pies/gi'
pies i kot
pies i pies
pies i pies

Pierwsza linijka: zamienił tylko pierwszego kota. Domyślnie s zmienia pierwsze trafienie w każdej linijce i idzie dalej. Litera g na końcu, od global, każe zamienić wszystkie. Litera i każe ignorować wielkość liter. Kot, KOT, kOt, wszystkie idą do schroniska i wychodzą jako psy.

Po lewej stronie stoi wyrażenie regularne z poprzedniej lekcji. List gończy, a nie nazwisko:

sed 's/^#Port 22/Port 2222/' sshd_config | head -3
# Konfiguracja serwera SSH (kopia do ćwiczeń)
Port 2222
#PermitRootLogin prohibit-password

Daszek pilnuje, żebyśmy zmienili tylko linijkę, która zaczyna się od #Port, a nie jakiś komentarz w środku pliku, który przypadkiem wspomina o porcie 22.

A co, jeśli w tekście są ukośniki, na przykład ścieżki? Nie musisz każdego zabezpieczać ukośnikiem wstecznym. Separator może być dowolnym znakiem:

echo '/var/www/html' | sed 's|/var/www|/srv|'
/srv/html

Bez tego byłoby s/\/var\/www/\/srv/. W internecie nazywają to syndromem pochylonej wykałaczki. Serio, tak to się nazywa.

& i grupy: kawałki z lewej po prawej

Po prawej stronie możesz użyć tego, co znalazłeś po lewej. & to „cały znaleziony kawałek”, a \1, \2 to grupy w nawiasach. Z flagą -E nawiasy działają jak w grep -E:

echo 'port 22' | sed -E 's/[0-9]+/[&]/'
echo 'Ania 34' | sed -E 's/([A-Za-z]+) ([0-9]+)/\2 lat: \1/'
port [22]
34 lat: Ania

I to jest dokładnie nasz hak. ^(PasswordAuthentication)[[:space:]]+yes łapie nazwę opcji do pudełka numer 1, a po prawej \1 no wstawia ją z powrotem z nowym słowem. Nie musisz przepisywać nazwy opcji, więc nie zrobisz w niej literówki.

-n i p: drukuj tylko to, co chcę

Korektorka domyślnie wypuszcza na taśmę każdą linijkę, poprawioną albo nie. Flaga -n mówi: „nic nie wypuszczaj, chyba że każę”. A polecenie p, od print, to właśnie „kazę”.

sed -n '2,4p' sshd_config
#Port 22
#PermitRootLogin prohibit-password
PasswordAuthentication yes

Linijki od drugiej do czwartej. Kiedy ktoś mówi „błąd w linijce 1534 konfiguracji”, sed -n '1530,1540p' pokazuje okolicę bez otwierania edytora.

sed -n '/^#/p' sshd_config
sed -n '$p' sshd_config
# Konfiguracja serwera SSH (kopia do ćwiczeń)
#Port 22
#PermitRootLogin prohibit-password
# Limity
#ClientAliveInterval 0
X11Forwarding yes

Pierwsza komenda: linijki zaczynające się od krzyżyka, czyli to samo co grep. Druga: sam dolar to adres „ostatnia linijka”.

Adresy: które linijki poprawiamy

Przed każdym poleceniem możesz napisać adres, czyli „na których linijkach”. Jak polecenie dla korektorki: „tylko na stronach od 10 do 20”.

  • 3 to trzecia linijka.
  • 2,4 to od drugiej do czwartej.
  • $ to ostatnia.
  • /wzor/ to każda linijka pasująca do wzoru.
  • /od/,/do/ to od pierwszej linijki z „od” do najbliższej z „do”.
  • ! po adresie odwraca: wszystkie linijki oprócz tych.

Adres plus s daje bardzo precyzyjną zmianę: sed '/^MaxAuthTries/s/6/3/' zamienia szóstkę na trójkę, ale tylko w linijce z MaxAuthTries. Jeśli gdzieś indziej w pliku jest 6, nie zostanie ruszone.

d: wykreślanie linijek

Polecenie d, od delete, wyrzuca linijkę z taśmy.

sed '/^[[:space:]]*#/d; /^$/d' sshd_config
PasswordAuthentication yes
MaxAuthTries 6
X11Forwarding yes

Dwa polecenia oddzielone średnikiem: usuń komentarze, usuń puste linijki. Zostaje to, co serwer naprawdę widzi. Inne przykłady:

sed '1d' plik            # bez pierwszej linijki, np. bez nagłówka CSV
sed '/# Limity/,$d' plik  # od sekcji Limity do końca: wykreśl

a, i, c: dopisz, wstaw, podmień całą linijkę

sed '/^#Port/a Port 2222' sshd_config | sed -n '2,3p'
sed '/^#PermitRootLogin/c PermitRootLogin no' sshd_config | sed -n 3p
#Port 22
Port 2222
PermitRootLogin no

a dopisuje linijkę pod znalezioną, i wstawia nad nią, c podmienia całą linijkę. Oryginalny komentarz #Port 22 został, a pod nim pojawiło się prawdziwe ustawienie. Tak lubią audytorzy: widać, co było domyślnie, i co zmieniliśmy.

-i: edycja na miejscu, czyli tu robi się poważnie

Wszystko do tej pory tylko wypisywało wynik na ekran. Plik został nietknięty. Flaga -i, od in-place, zapisuje zmiany w pliku.

cp sshd_config k.conf
sed -i 's/^#Port 22/Port 2222/' k.conf
grep -n Port k.conf
2:Port 2222

A teraz zasada, którą powiesisz sobie nad łóżkiem. Najpierw bez -i, żeby zobaczyć wynik. Dopiero potem z -i. I najlepiej z kopią:

sed -i.bak 's/^X11Forwarding yes/X11Forwarding no/' k.conf
ls k.conf*
diff k.conf.bak k.conf
k.conf
k.conf.bak
9c9
< X11Forwarding yes
---
> X11Forwarding no

-i.bak (bez spacji!) robi kopię oryginału z końcówką .bak, a diff pokazuje dokładnie, co się zmieniło. Jak zdjęcie mieszkania przed remontem. Jak coś pójdzie nie tak, wiesz, gdzie była ściana.

Uwaga przy prawdziwym /etc/ssh/sshd_config. Zmiany w konfiguracji SSH ćwicz na maszynie wirtualnej. Po edycji sprawdź składnię przez sudo sshd -t i dopiero potem restartuj usługę: sudo systemctl restart ssh [Ubuntu] albo sudo systemctl restart sshd [RHEL]. Miej otwartą drugą sesję SSH. Jeśli coś zepsujesz, ta druga sesja jest Twoim kołem ratunkowym. Bez niej zostaje telefon do kogoś, kto ma klucz do serwerowni.

Jak to działa pod spodem

Sed ma w środku jedną kartkę roboczą, tak zwaną przestrzeń wzorca, po angielsku pattern space. Cykl wygląda tak:

  1. Wczytaj jedną linijkę na kartkę.
  2. Przejdź po wszystkich poleceniach po kolei. Jeśli adres pasuje, wykonaj polecenie na kartce.
  3. Jeśli nie ma -n, wypisz kartkę.
  4. Wyczyść kartkę i wróć do punktu 1.

Dlatego sed przerobi plik większy niż cała pamięć komputera. W każdej chwili trzyma jedną linijkę.

A -i? Sed nie umie pisać „w środek” pliku. Zapisuje wynik do nowego pliku tymczasowego w tym samym folderze, a na koniec podmienia go pod starą nazwą. Widać to po numerze i-węzła:

ls -i k.conf; sed -i 's/a/a/' k.conf; ls -i k.conf
878835 k.conf
878867 k.conf

Ta sama nazwa, inny plik. Jak remont, w którym zamiast malować pokój, budujesz obok nowy i przenosisz tabliczkę z numerem. Skutek uboczny: jeśli k.conf byłby dowiązaniem symbolicznym, GNU sed zastąpi go zwykłym plikiem, a oryginał, na który wskazywał, zostanie bez zmian. Sprawdziłem: po sed -i na dowiązaniu w oryginał nadal było MaxAuthTries 6. Na to jest --follow-symlinks.

[macOS] Sed na Macu to wersja BSD i różni się właśnie przy -i. Tam -i wymaga argumentu z końcówką kopii. Bez kopii piszesz sed -i '' 's/a/b/' plik, z pustym apostrofem. Na Linuksie sed -i '' ... potraktuje pusty napis jak skrypt, a nazwę pliku jak kolejny argument, więc też się wyłoży. Wersja, która działa na obu: sed -i.bak 's/a/b/' plik. Na Macu nie ma też \U i \L do zmiany wielkości liter, a a, i, c chcą ukośnika wstecznego i nowej linijki zamiast tekstu w tej samej linijce. Wygodnie: brew install gnu-sed, komenda gsed.

Ściąga flag

KomendaFlaga / polecenieCo robi
seds/co/naco/zamienia pierwsze trafienie w linijce
seds/.../.../gzamienia wszystkie trafienia w linijce
seds/.../.../iignoruje wielkość liter (GNU, na Macu też I)
sed& / \1po prawej: cały znaleziony kawałek / grupa nr 1
sed-Erozszerzone regeksy, jak grep -E
sed-n + pwypisuje tylko wskazane linijki
seddusuwa linijkę
seda / i / cdopisuje pod / wstawia nad / podmienia linijkę
sed3, 2,4, $adres: linijka 3, linijki 2–4, ostatnia
sed/wzor/, /od/,/do/adres wzorcem, zakres między wzorcami
sed=wypisuje numer linijki
sed-e / ;kilka poleceń naraz
sed-izapisuje zmiany w pliku (macOS: -i '')
sed-i.bakzapisuje zmiany i robi kopię .bak (działa wszędzie)
sed--follow-symlinksprzy -i edytuje plik, na który wskazuje dowiązanie (GNU)

⚠ Typowy błąd

sed 's/yes/no/' z.conf > z.conf
wc -c z.conf
0 z.conf

Zero bajtów. Plik wyparował. Dlaczego? Bo przekierowanie robi Bash, zanim sed w ogóle wystartuje. Bash otwiera z.conf do zapisu, czyli od razu go czyści. Potem odpala seda, a sed czyta... pusty plik. Piłowanie gałęzi, na której się siedzi, wersja dla adminów.

Naprawa: sed -i, albo zapis do innego pliku i potem mv. A jeśli to była prawdziwa konfiguracja: kopia zapasowa, jeśli ją masz, albo backup z ostatniej nocy. Masz backup z ostatniej nocy, prawda? Prawda?

I drugi klasyk, na Macu:

sed -i 's/yes/no/' z.conf

BSD sed bierze 's/yes/no/' za końcówkę kopii zapasowej, a nazwę pliku za skrypt, i wypisuje błąd o nieznanym poleceniu. Na Macu: sed -i '' 's/yes/no/' z.conf.

✎ Zadanie

Skopiuj /etc/ssh/sshd_config do swojego folderu jako moj_sshd (jeśli nie masz serwera SSH, użyj pliku z tej lekcji). Nie ruszaj oryginału.

  1. Jednym sed -i.bak w kopii: ustaw PermitRootLogin no (linijka ma przestać być komentarzem) i zmień X11Forwarding yes na X11Forwarding no. Sprawdź diffem.
  2. Wypisz sed -n same linijki z ustawieniami zawierającymi słowo Authentication, razem z ich numerami.
  3. Zapisz do czysty.conf wersję bez komentarzy i pustych linijek. Policz, ile ustawień zostało.
Pokaż rozwiązanie

Kopia prawdziwego sshd_config z Ubuntu 24.04 jako moj_sshd. Krok pierwszy, dwie zmiany jednym sedem:

sed -i.bak -E 's/^#?PermitRootLogin .*/PermitRootLogin no/; s/^X11Forwarding yes/X11Forwarding no/' moj_sshd
diff moj_sshd.bak moj_sshd
42c42
< #PermitRootLogin prohibit-password
---
> PermitRootLogin no
99c99
< X11Forwarding yes
---
> X11Forwarding no

#? to „krzyżyk albo go nie ma”, więc zadziała i na zakomentowaną linijkę, i na taką, którą ktoś już odkomentował. Daszek pilnuje, żebyśmy nie ruszyli komentarza z linijki 90, który tylko wspomina o PermitRootLogin. A w linijce 128 jest # X11Forwarding no z wcięciem, też nietknięte. Chirurgia, nie siekiera.

Krok drugi, ustawienia ze słowem Authentication z numerami:

sed -n '/^[^#]*Authentication/{=;p;}' moj_sshd
71
KbdInteractiveAuthentication no

[^#]* to „dowolnie dużo znaków, ale żadnego krzyżyka przed słowem”, czyli pomijamy komentarze. W klamrach dwa polecenia naraz: = wypisuje numer, p linijkę. Prawda, że grep -n zrobiłby to prościej? Zrobiłby. Ale zadanie było o sedzie, a na egzaminie nikt nie pyta, czy masz ochotę.

Krok trzeci:

sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d' moj_sshd > czysty.conf
wc -l < czysty.conf
8

Osiem ustawień w pliku, który ma ponad sto dwadzieścia linijek. Reszta to instrukcja obsługi, którą ktoś kiedyś pisał z wielką miłością.

Co dalej

Sed świetnie poprawia tekst, ale kiedy tekst ma kolumny, a Ty chcesz je liczyć i sumować, przyda się ktoś z kalkulatorem. W następnej lekcji awk: kolumny, warunki, sumy i raporty jedną linijką.