sed od podstaw
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_configBez 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 yess///: 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 piesPierwsza 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-passwordDaszek 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/htmlBez 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: AniaI 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 yesLinijki 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 yesPierwsza 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”.
3to trzecia linijka.2,4to 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_configPasswordAuthentication yes
MaxAuthTries 6
X11Forwarding yesDwa 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śla, 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 noa 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.conf2:Port 2222A 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.confk.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:
- Wczytaj jedną linijkę na kartkę.
- Przejdź po wszystkich poleceniach po kolei. Jeśli adres pasuje, wykonaj polecenie na kartce.
- Jeśli nie ma
-n, wypisz kartkę. - 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.conf878835 k.conf
878867 k.confTa 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
| Komenda | Flaga / polecenie | Co robi |
|---|---|---|
| sed | s/co/naco/ | zamienia pierwsze trafienie w linijce |
| sed | s/.../.../g | zamienia wszystkie trafienia w linijce |
| sed | s/.../.../i | ignoruje wielkość liter (GNU, na Macu też I) |
| sed | & / \1 | po prawej: cały znaleziony kawałek / grupa nr 1 |
| sed | -E | rozszerzone regeksy, jak grep -E |
| sed | -n + p | wypisuje tylko wskazane linijki |
| sed | d | usuwa linijkę |
| sed | a / i / c | dopisuje pod / wstawia nad / podmienia linijkę |
| sed | 3, 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 | -i | zapisuje zmiany w pliku (macOS: -i '') |
| sed | -i.bak | zapisuje zmiany i robi kopię .bak (działa wszędzie) |
| sed | --follow-symlinks | przy -i edytuje plik, na który wskazuje dowiązanie (GNU) |
⚠ Typowy błąd
sed 's/yes/no/' z.conf > z.conf
wc -c z.conf0 z.confZero 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.confBSD 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.
- Jednym
sed -i.bakw kopii: ustawPermitRootLogin no(linijka ma przestać być komentarzem) i zmieńX11Forwarding yesnaX11Forwarding no. Sprawdźdiffem. - Wypisz
sed -nsame linijki z ustawieniami zawierającymi słowoAuthentication, razem z ich numerami. - Zapisz do
czysty.confwersję 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_sshd42c42
< #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_sshd71
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.conf8Osiem 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ą.