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í

editovat

Programovací 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

editovat

Sed 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

editovat

Mezi 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

editovat

Jako 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é

editovat

Sed 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

editovat

Jak 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

editovat

Vě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

editovat

V 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 "#"

Tabulky příkazů

editovat

Pří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

editovat

Počítání řádků

editovat

Př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

editovat

Sed - nahrazeni retezce promennou

editovat

read zmena
read new

sed -e 's/'"$zmena"'/'"$new"'/' kontakty.db