Sed
sed (název vychází ze zkratky "stream editor") je jednoduchý, ale mocný nástroj, která provádí nahrazování vzorků v proudu textových dat. Vstupní soubor je načítán po jednotlivých řádcích a na každý z nich sed postupně aplikuje pravidla napsaná v jednoduchém programovacím jazyce. Výstup pak zapisuje na standardní výstup (obvykle okno terminálu). Původní sed naprogramoval Lee E. McMahon v letech 1973–74.
Použití
editovatProgramovací jazyk pro sed vychází z editoru ed , ovšem na rozdíl od něj neprovádí změnu přímo v editovaném souboru, ale ve výpisu. Takže dokud výstup není přesměrován do souboru k žádné změně nedojde.
Pozn.: Pokud přesměrujete výstup do téhož souboru co z něj data načítáte, tak je výsledkem prázdný soubor!
- Příklad
- příkaz 25d pro sed znamená: Pokud je toto řádek č. 25, tak jej odstraň (tj. nevypisuj na standardní výstup) zatím co pro ed, tentýž příkaz znamená: Jdi na řádek 25 a smaž ho
Jak pracuje sed
editovatSed postupně načítá ze vstupního proudu dat jednotlivé řádky. Zdrojem vstupního proudu dat může být jednak soubor, ale také proud dat předávaný rourou. Podobně jako awk, sed řádky před aplikací příkazů analyzuje. Vyhoví-li řádek adrese, tak jej uloží do prostoru pro vzorky, který pak zpracovává podle nastavených příkazů. Adresou mohou být vzorky, nebo rozsah podle čísel řádku. Pokud není tzv. adresa uvedena, zpracovává sed postupně všechny načítané řádky.
Syntaxe
editovatMezi nejčastěji používané příkazy patří nahrazování řetězců (substituce), takže tento příkaz použiji pro demonstraci i v následujících ukázkách.
- Příklad použití na příkazovém řádku
$ sed '1,20 s/vzorek/jiný vzorek/g' soubor_dat.txt
- Slovy
- Přečti soubor soubor_dat.txt a zpracuj prvních dvacet řádků. U každého z nich zaměň všechny řetězce "vzorek" za řetězec "jiný vzorek".
- Rozbor příkazu
- 1,20 - vymezení zpracovávané oblasti (adresa)
- s - příkaz pro substituci, který očekává tři parametry
- vzorek - co nahradit
- jiný vzorek - čím nahradit
- g - parametr pro globální nahrazení, není-li uveden, nahradí se pouze první výskyt vzorku
- / - oddělovač
Poznámka: Jak jste si možná povšimli, příkaz v příkladu uzavřen do apostrofů. Je tomu tak proto, aby se zamezilo nežádoucí interpretaci znaků na příkazovém řádku shellu.
Oddělovače
editovatJako oddělovač se nejčastěji používá znak "/" ale lze také použít "_" nebo "|". Zlepšuje to čitelnost zvláště v takových případech, kdy by byl stejný znak použitý i u zpracovávaných vzorků. V takových případech je nutno ošetřit tyto znaky zpětným lomítkem "\" nicméně posuďte sami jaká je přehlednost takového zápisu viz následující příklad:
- Příklad
$ sed 's_/usr/share/qt3_/usr/share/qt4_' soubor_dat.txt $ sed 's|/usr/share/qt3|/usr/share/qt4|' soubor_dat.txt $ sed 's/\/usr\/share\/qt3/\/usr\/share\/qt4/' soubor_dat.txt
Závorky
editovat- Kulaté závorky
- se využívají u vzorků jako alternativa k proměnným - viz níže Sed a proměnné
- Hranaté závorky
- se využívají u regulárních výrazů pro vymezení rozsahu možných znaků u vzorků a adres
- Složené závorky
- používá sed k seskupování příkazů, v případě že je chcete použít v rámci regulárního výrazu, je musíte "escapovat" obráceným lomítkem. Některé takové konstrukce však lze nahradit. Např. znak "?" má stejný účinek jako výraz "\{0,1\}" (žádný nebo jeden), znak "+" odpovídá "\{1,\}" (jeden a víc) atp.
- Lomené závorky
- se využívají u vzorků a adres u regulárních výrazů pro vymezení začátku a konce slova, ovšem sed je v regulární výrazech nepoužívá.
Sed a proměnné
editovatSed jako takový s proměnnými nepracuje, nicméně podobného efektu jako je použití proměnných lze dosáhnout pomocí regulárních výrazů.
user@stroj:~/$ echo '(word 3336 556 3469 613 "790")' |sed ' s_(\(word\) \([0-9]*\) \([0-9]*\) \([0-9]*\) \([0-9]*\) "\([^"]*\)")_<\1><maxx>\2</maxx><minx>\4</minx><maxy>\3</maxy><miny>\5</miny><text>\6</text></\1>_g' # Následuje výsledek zpracování: <word><maxx>3336</maxx><minx>3469</minx><maxy>556</maxy><miny>613</miny><text>790</text></word>
Jak pracovat se zásobníkem
editovatJak už bylo zmíněno, sed nepracuje s vlastním řádkem, ale s prostorem pro vzorky, ve kterém řádky zpracovává a na standardní výstup posílá teprve obsah přežvýkaný sadou příkazů. Toto výchozí chování lze potlačit volbou "-n", pak se na výpisu zobrazí obsah prostoru pro vzorky teprve tehdy je-li tam odeslán příkazem p.
Kromě prostoru pro vzorky používá ještě zásobník (buffer) do kterého lze během zpracování odkládat data pro další zpracování. Před vlastním zpracováním je pak třeba data ze zásobníku přesunout do prostoru pro vzorky, teprve pak je možné je dále zpracovávat.
Příkaz | Příklad použití | Popis |
---|---|---|
p | user@stroj:~/$ echo 'Zpracovávaný řádek' |sed ' p;s/Zpracovávaný/Upravený/' Zpracovávaný řádek Upravený řádek |
Příkaz vypisuje obsah prostoru pro vzorky. V uvedeném příkladu byl příkazem p na standardní výstup vypsán obsah prostoru pro vzorky před jeho zpracováním. |
h | user@stroj:~/$ echo 'Zpracovávaný řádek' |sed ' h;s/Zpracovávaný/Upravený/;g' Zpracovávaný řádek |
Příkaz odstraní původní obsah zásobníku a přesune místo něj do zásobníku obsah z prostoru pro vzorky. Pro lepší demonstraci byl v příkladu použit také příkaz g, ten nahradil upravený obsah prostoru pro vzorky obsahem zásobníku, který, jak vidno, příkaz h přesunul do zásobníku ještě před jeho úpravou. |
H | user@stroj:~/$ echo 'Zpracovávaný řádek' |sed ' H;s/Zpr/Upr/;H;s/acov/av/;H;s/áva/e/;H;g' Zpracovávaný řádek Upracovávaný řádek Upravávaný řádek Upravený řádek |
Příkaz přidá k původnímu obsahu zásobníku obsah z prostoru pro vzorky. Aby bylo lépe patrné jak je postupně obsah prostoru pro vzorky zpracováván, je průběžně přidáván příkazem H k obsahu zásobníku a na závěr vypsán příkazem g. První řádek nemá žádný obsah, protože zásobník byl původně prázdný. |
g | user@stroj:~/$ echo 'Zpracovávaný řádek' |sed ' g' user@stroj:~/$ echo 'Zpracovávaný řádek' |sed ' p;s/Zpracovávaný/Upravený/;g' Zpracovávaný řádek user@stroj:~/$ echo 'Zpracovávaný řádek' |sed ' p;s/Zpracovávaný/Upravený/;h;g' Zpracovávaný řádek Upravený řádek |
Příkaz vypíše obsah zásobníku a pak ho z něj odstraní. Jak funguje už sice docela objasňují příklady uvedené u předchozích příkladů, ale aby to bylo dostatečně zřejmé přidal jsem ještě další dva. V prvním příkaz g vypíše prázdný řádek, protože zásobník je zatím prázdný. Ve druhém příkladu příkaz p vypsal obsah prostoru pro vzorky aby bylo patrné že ten prázdný není. Teprve ve třetím příkladu je upravený obsah prostoru pro vzorky vložen příkazem h do zásobníku, odkud může být příkazem g vypsán. |
G | user@stroj:~/$ echo 'Zpracovávaný řádek' |sed ' G' Zpracovávaný řádek user@stroj:~/$ echo 'Zpracovávaný řádek' |sed ' p;s/Zpracovávaný/Upravený/;G' Zpracovávaný řádek Upravený řádek user@stroj:~/$ echo 'Zpracovávaný řádek' |sed ' p;s/Zpracovávaný/Upravený/;h;G' Zpracovávaný řádek Upravený řádek Upravený řádek |
Příkaz připojí obsah prostoru pro vzorky k obsahu zásobníku, ten pak vypíše. |
x | user@stroj:~/$ echo 'Zpracovávaný řádek' |sed ' x' user@stroj:~/$ echo 'Zpracovávaný řádek' |sed ' p;s/Zpracovávaný/Upravený/;x' Zpracovávaný řádek user@stroj:~/$ echo 'Zpracovávaný řádek' |sed ' p;s/Zpracovávaný/Upravený/;h;x' Zpracovávaný řádek Upravený řádek |
Příkaz prohodí obsah zásobníku, s obsahem prostoru pro vzorky. |
Příklad použití ve skriptu
editovat#!/bin/bash # grep3 - vrátí na výstupu tři řádky, řádek s hledaným vzorkem a řádek před a za case $# in 1);; *) echo "Použití: $0 vzorek";exit;; esac; sed -n ' '/$1/' !{ # protože je uveden znak "!", bude následující sada příkazu aplikována # na řádek, který NEOBSAHUJE vyhovující vzorek x # příkaz x vloží tento řádek do zásobníku a místo původního obsahu, který # zapíše do prostoru pro vzorky, a ten následujícím příkazem vyprázdní.. d # ..tím se zajistí že se na výpisu nevypíše nic } # obsah zásobníku je tak postupně nahrazován, dokud sed nenarazí na řádek # vyhovující hledané adrese '/$1/' { x # vyhovující řádek byl vložen do zásobníku a jeho původní obsah - předchozí # řádek je vložen do prostoru pro vzorky, odkud je vzápětí vypsán # na standardní výstup.. p # ...pokud hledanému vzorku vyhovuje i následující řádek, tak si prohodí místa # s předchozím čekajícím řádkem v zásobníku a tam počká jak to dopadne s # následujícím řádkem.. x # vyhovující řádek zatím vypíšeme na standardní výstup... p # ...a poskočíme při zpracování rovnou na další řádek... n # ...který také hned vypíšeme. p # aby byl výpis přehlednější, přidáme pro jistotu řádek s oddělovacími znaky a\ --- # ..a vyčistíme zásobník, tím že do něj přidáme prázdný řádek x }'
Stejný skript lze pochopitelně zapsat i jinak, s menším počtem operací
#!/bin/bash case $# in 1);; *) echo "Použití: $0 vzorek";exit;; esac; sed -n ' '/$1/' !{ # stejně jako v předchozím skriptu protože je uveden znak "!", bude následující # sada příkazů aplikována na řádek, který NEOBSAHUJE vyhovující vzorek h # příkaz h vložil data z prostoru pro vzorky do zásobníku. Jelikož prostor # pro vzorky zůstal po této akci prázdný, nevypíše se na standardní výstup # nic } # obsah zásobníku je tak stejně jako v předchozím skriptu postupně nahrazován, # dokud sed nenarazí na řádek vyhovující hledané adrese '/$1/' { # ten pak následujícím příkazem připojí k řádku co čeká v zásobníku.. H # ..skočí na následující řádek.. n # ..který také přidá do zásobníku H # poté prohodí obsah zásobníku a prostoru pro vzorky, kde už sedí a čeká # na své zpracování následující řádek x # a obsah prostoru pro vzorky, ve kterém jsou nyní pospojované řádky # vypíše na standardní výstup p # a stejně jako předtím přidá pro lepší přehlednost řádek s oddělovačem a\ --- }'
Vícenásobné zpracování vstupu
editovatVětšina lidí si vystačí s jednoduchým nahrazením a málokdy používá složitější konstrukce příkazů. V jejich skriptech pak lze nalézt podobné konstrukce
- Špatný příklad
user@stroj:~/$ sed 's/START/start/' soubor_dat.txt | sed 's/KONEC/konec/' soubor_dat.txt user@stroj:~/$ cat soubor_dat.txt | sed 's/START/start/' | sed 's/KONEC/konec/' soubor_dat.txt
Chyba spočívá v tom, že pro zpracování proudu dat jsou v prvním příkladu spuštěny dva procesy a v tom druhém dokonce tři! Přičemž stejného výsledku lze dosáhnout jedním procesem a seskupením příkazů buď použitím parametru "-e", nebo jejich spojením do jedněch uvozovek a oddělením pomocí středníku.
- Správný příklad
user@stroj:~/$ sed -e 's/START/start/' -e 's/KONEC/konec/' soubor_dat.txt user@stroj:~/$ sed 's/START/start/;s/KONEC/konec/' soubor_dat.txt
Skripty
editovatV případě složitějších konstrukcí je výhodnější než na příkazový řádek zapsat soubor příkazů do zvláštního souboru. V něm se každý příkaz píše na nový řádek, tudíž je není nutné oddělovat středníky.
- Příklad - výpis souboru soubor_prikazu_pro_sed
- Použití
user@stroj:~/$ sed -f soubor_prikazu_pro_sed soubor_dat.txt
Další možností je napsat takový soubor příkazů rovnou jako spustitelný skript (tj. je třeba mu potom nastavit právo pro spouštění - x) viz následující výpis souboru nahrada.sed
#!/bin/sed -f
Ten pak lze použít takto:
user@stroj:~/$ ./nahrada soubor_dat.txt
- Poznámka
- V sadách příkazů pro sed lze používat také komentáře. Podobně jako bash považuje sed za komentář všechny řádky, které začínají znakem "#"
Adresy
editovatVzorky
editovatTabulky příkazů
editovatPříkazy pro základní editaci
příkaz | popis příkazu | počet parametrů | příklad |
---|---|---|---|
a | připojí text za řádek | 1 | |
i | vloží text před řádek | 1 | |
N | přidá nový řádek | 0 | |
d | odstraní řádek | 1 | |
c | nahradí textový blok | 1 | |
s | nahradí řetězec (substituce) | 3 | |
y | Transformace znaků | 3 |
Informace o zpracovávaných řádcích
příkaz | popis příkazu | počet parametrů | příklad |
---|---|---|---|
= | Vypíše číslo zpracovávaného řádku | 0 | |
1 | Zobrazí ve výpisu řídící znaky | 0 |
Příklady
editovatPočítání řádků
editovatPřes sed se dá mimo jiné také spočítat počet řádků v souboru
user@stroj:~/$ sed -n '$=' soubor.txt
ale následující kombinace je, přesto že se spouští dva procesy, rychlejší
user@stroj:~/$ wc -l soubor.txt | awk '{print $1}'
Zajímá vás proč? Protože 'sed se snaží jednotlivé řádky analyzovat, zatímco wc s parametrem "-l" je pouze počítá a druhý proces zpracovává už jen jediný předaný řádek.
A pro doplnění týž úkol realizovaný čistě přes awk
user@stroj:~/$ awk 'BEGIN{radek=0};{radek++};END{print radek}' soubor.txt
Z hlediska rychlosti je na tom při počítání awk mnohem lépe..
Sed jako alternativa head a tail
editovatSed - nahrazeni retezce promennou
editovatread zmena
read new
sed -e 's/'"$zmena"'/'"$new"'/' kontakty.db