Menu

Sedmé cvičení (5. 4.)

Sed

Příkazy sedu načítáme z řádky nebo ze souboru:

sed 'příkazy' soubor1 soubor2 ...
sed -f 'soubor_s_příkazy' soubor1 soubor2 ...
sed -e 'příkazy' -f 'soubor_s_příkazy' soubor1 soubor2 ...

Důležitý přepínač je -n – nevypisovat defaultně na výstup. Další je -r zapínající extended regulární výrazy. Příkazy od sebe můžeme oddělovat středníky nebo znaky nového řádku. Syntaxe příkazů sedu je vždy následující:

[adresa1[,adresa2]]příkaz[parametry]

Pokud není uvedena adresa, provede se příkaz pro každý řádek. Pokud je uvedena pouze adresa1, provede se příkaz pro řádek odpovídající adrese. Pokud je uvedena i adresa2, provede se příkaz pro všechny řádky mezi adresou1 a adresou2 (včetně). Adresa může být číslo řádku, $ pro poslední řádek nebo /regexp/.

Sed pracuje se dvěma prostory – pattern space (PS), do kterého se načítá každá nová řádka a hold space (HS), s jehož obsahem manipulujeme sami. Nic dalšího nemáme, žádné proměnné apod. Pouze příkazy pro manipulaci s obsahem pattern space a hold space. Seznam příkazů naleznete v manuálové stránce sedu. Nejpoužívanější jsou:

n ... vypsání současného PS (není-li -n) a načtení 
      dalšího řádku do PS
N ... připojení dalšího řádku do PS
d ... smazání PS, začne od začátku
D ... smazání prvního řádku PS, začne od začátku, 
      ale nenačítá vstup, je-li PS neprázdný
p ... vypíše PS na standardní výstup
P ... vypíše první řádek PS

h ... kopíruje PS do HS
H ... připoujuje PS do HS
g ... kopíruje HS do PS
G ... připojuje HS do PS
x ... prohazuje obsah PS a HS

:l ... značka l
bl ... skočí na značku l
tl ... skočí na značku l, pokud došlo od posledního 
       načtení řádku k úspěšnému nahrazení s///
Tl ... skočí na značku l, pokud nedošlo od posledního 
       načtení řádku k úspěšnému nahrazení s///
s/// ... nahrazení (viz minulá hodina)
y/// ... transliterace (jako tr, bez rozsahů)

q ... ukončí zpracování vstupu a vypíše PS (není-li -n)

Příklady

Zobrazit řešení
  1. Vypište pouze liché řádky souboru.
  2. sed -n 'p;n'
    
  3. Vypište pouze tělo e-mailu, bez hlaviček.
  4. sed '1,/^$/d'
    
  5. Vypište pouze hlavičky From:, To: a Subject: a poté tělo e-mailu, bez ostatních hlaviček.
  6. sed -rn '/^(From:|To:|Subject:)/{p;d};1,/^$/!p'
    
  7. Rozepište věty na vstupu tak, aby na každém řádku byla jedna věta. Věta se pozná tak, že začíná velkým písmenem a končí tečkou. Předpokládejte, že se nikde nevyskytují vícenásobné mezery, ani žádné tabulátory. Jednodušší varianta navíc předpokládá, že žádná věta není obsažena na dvou řádcích, těžší již nikoliv. Zkuste napsat alespoň nějaké řešení a potom nejelegantnější.
  8. Lehčí varianta:
    sed -r 's#([.!?]) *#\1\n#g'
    
    Těžší varianta:
    sed -rn 'H;${x;s#\n# #g;s#([.!?]) *#\1\n#g;p}'
    
  9. Napište v sedu tac.
  10. sed -n 'x;1!H;${x;p}'
    
  11. Napište v sedu skript, který setřídí posloupnost 0 a 1 zadanou na každém řádku standardního vstupu.
  12. #!/bin/sed -f
    :l
    s/10/01/g
    tl
    
  13. Napište skript, který z textu vymaže slova délky nanejvýš 3. Zkuste napsat alespoň nějaké řešení a potom nejelegantnější.
  14. sed -r 's/\b[A-Za-z]{1,3}\b//g'
    
  15. Zamyslete se, jak v sedu implementovat grep -o pro fixní regulární výraz.
  16. V manuálové stránce se dočteme, že přepínač -o způsobí, že se budou vypisovat pouze části řádků, které odpovídají hledanému výrazu. Stačí nám tedy matchnout celý řádek a smazat neodpovídající části.
    sed -r 's/^.*('"$REGEX"').*$/\1/g'
    
    Nicméně, tento program má dva problémy. Vypíše i ty řádky, na kterých se daný vzor vůbec nevyskytuje a kvůli žravosti * neodpovídá vypsaná část řádku nejdelšímu odpovídajícímu úseku (pokud REGEX sám obsahuje * či +). První zmíněný problém vyřešíme jednoduše.
    sed -rn 's/^.*('"$REGEX"').*$/\1/;T;p'
    
    Pokud by se navíc v REGEXu vyskytovaly backreference, musíme jejich čísla zvýšit o jedna (protože jsme přidali jednu vnější závorku navíc). To nás ale pro fixní regex netrápí. Řešení druhého problému je komplikovanější. Mimo sed by nám mohly pomoct perlové regulární výrazy, které obsahují tzv. líný kvantifikátor *?. S našimi prostředky budeme muset přizpůsobit řešení konkrétnímu regulárnímu výrazu. Např. pokud bychom hledali a.*b, řešení by vypadalo následovně:
    sed -rn 's/[^a]*(a.*b).*$/\1/;T;p'
    
    V některých případech bychom mohli vytvořit regulární výraz odpovídající převráceným řetězcům k těm, které odpovídají výrazu REGEX a pomocí rev poté odřezat vždy neodpovídající konce.
  17. Napište v sedu skript, který slouží vícenásobné prázdné řádky do jednoho. Za prázdný považujte řádek obsahující libovolný počet pouze bílých znaků (mezer, tabulátorů).
  18. #!/bin/sed -nrf
    # Najdeme-li prázdný řádek, uložíme do HS \n
    /^\s*$/ {
      s/.*/\n/
      h
    }
    # Najdeme-li neprázdný řádek:
    /\S/ {
      x
      /^\n$/ {   # Pokud byl v HS \n, smažeme ho, vypíšeme
        s/.*//   # prázdný řádek a nakonec vypíšeme samotný
        p        # neprázdný řádek
      }
      x;p
    }
    $ {          # Pokud byl poslední řádek prázdný, vypíšeme ho
      /^\s*$/ {
        s/.*//
        p
      }
    }
    
  19. Napište v sedu rev (bez přepínačů).
  20. sed '/\n/!G;s/\(.\)\(.*\n\)/&\2\1/;//D;s/.//'