sed a spracovanie viacriadkových záznamov

Pri jednom nemenovanom projekte musel kolega spracovávať obrovský počet záznamov (5,6GB) v textovom súbore a generovať z neho isté štatistické záznamy. Všetko by to bolo jednoduché… keby niektoré záznamy neboli vo vstupnom súbore v nekorektnom tvare. Zjednodušene povedané, súbor mal vyzerať takto

Terry Jones
Terry Gilliam
Michael Palin
Graham Chapman
John Cleese
Carol Cleveland
Neil Innes

V skutočnosti však vyzeral nasledovne: niektoré záznamy boli rozdelené na dva riadky, pričom druhý riadok záznamu bol (našťastie) oddelený tabulátorom.

Terry Jones
Terry Gilliam
Michael 
    Palin
Graham Chapman
John
    Cleese
Carol Cleveland
Neil Innes

Problém pri spracovávaní akýmkoľvek nástrojom spočíva v úvahe „ak sa riadok začína tabulátorom, pripoj ho k predošlému riadku a oba spojené riadky vypíš.” Inak povedané, musíme si pamätať nielen aktuálny riadok, ale aj riadok, ktorý sa nachádzal pred ním a na základe prítomnosti tabulátora ich buď vypíšeme spojené, alebo osve.

Našťastie máme sed, ktorým je táto úloha malina.

Čo má spoločné sed a stará školská kalkulačka? Pamäť, ktorá dokáže udržať jedno číslo. Na kalkulačke existovalo tlačidlo M+, ktorým sa číslo na displeji uložilo do pamäte. Presnejšie povedané, číslo na displeji sa pripočítalo k aktuálnemu číslu v pamäti. (Protipólom bolo M-, ktorým sa od čísla v pamäti odčítavalo aktuálne číslo, ale to teraz nie je dôležité.) Dvojicu dopĺňalo MR (memory recall), ktorým sa číslo z pamäte a zobrazilo na displeji a MC (memory clear), ktorým sa pamäť vymazala (nastavila na nulu.)

Podobnú filozofiu má aj sed. Jeho pamäť sa nazýva hold buffer a dokáže udržať v sebe (jeden) riadok textu. Ukážme si to na príklade skriptu, ktorý spojí všetky riadky zo vstupu do jedného.

Filozofia bude nasledovná: každý riadok prilepíme do hold buffera (čím ich budeme spájať dohromady), na čo použijeme sedovský príkaz H. Ten vezme aktuálny riadok, predlepí pred neho znak konca riadka \n a prilepí ho na koniec obsahu hold buffera. Ak narazíme na posledný riadok (vzor $), vymeníme obsah hold buffera s aktuálnym riadkom, nahradíme všetky znaky \n medzerou a kompletný spojený obsah vypíšeme. (Poznamenajme, že posledný riadok sa tiež prilepí do hold buffera, keďže sa naň aplikuje prvý vzor.

{
  # každý riadok prilepíme k obsahu hold buffera
  H
}
${
  # vymeníme "pamäť" a aktuálny riadok
  x
  # zrušíme konce riadkov, namiesto nich medzery
  s/\n/ /g
  # celý zlepenec vytlačíme
  p
}

Ako vyriešime náš pôvodný problém? Zhruba analogickou úvahou:

  • ak sa riadok začína tabulátorom, prilepíme ho k hold bufferu. V tomto bufferi nebude nič, alebo sa v ňom bude nachádzať riadok s prvou časťou dát, ktorá mala byť pôvodne na celom riadku. (V príklade: riadok \tPalin prilepíme k riadku Michael.)
  • ak sa riadok nezačína tabulátorom, vytiahneme z hold buffera text, ktorý sa v ňom naakumuloval, nahradíme znaky nových riadkov medzerami a vypľujeme ho. Samotný riadok vložíme do hold buffera, aby bol spracovaný v ďalšom kroku. V konečnom dôsledku uskutočníme výmenu textu z pattern buffera s textom v hold bufferi, na čo slúži príkaz x. Otázka je, kedy sa vypľuje na výstup samotný text aktuálneho riadka (príkaz x totiž sám o sebe nič nevypisuje) — buď sa to vykoná v rámci vypisovania kompletného zlúčeného riadku v ďalšom cykle, alebo v rámci ďalšej výmeny hold buffera a pattern buffera.

Výsledný skript je nasledovný:

/^\t/ {
    H
    b
}
{
    x
    s/\n\t/ /
    p
}

Samotné spustenie je jednoduché. Vypneme implicitný výpis riadkov (-n) a skript pre sed načítame zo súboru -f fix.sed).

sed -n -f fix.sed data.txt

Ďalšie zdroje