Sám pro sebe jsem si rozdělil sedovské skripty do tří kategorií:
- triviality, často jednopříkazové ‚skripty‘, typicky příkaz s
- složitější skripty s příkazy pro práci s pracovním a paměťovým prostorem (h, H, g, G, x) a skripty, které kromě předchozího obsahují skoky a cykly, příkazy N;P;D a víc řádků v pracovním prostoru
- vychytávky a šílenosti
Triviality ne vždy triviální
Trable s jednoduchými skripty většinou plynou z neznalosti regulárních výrazů.
Problémy mohou plynout i z drobných rozdílů mezi různými verzemi sedu. Kromě obligátního odkazu na manuálové stránky a na stránku sedovských one-linerů (http://sed.sourceforge.net/sed1line.txt), dodejme několik tipů:
Jemnosti adresování
Jak funguje rozsah /RE1/RE2/příkaz?
Sed začne aplikovat příkaz na řádce vyhovující RE1 a na řádkách následujících, až do řádky, na které najde RE2 včetně. Poté opět nedělá nic, až do dalšího výskytu RE1 atd…
$ sed -n '/start/,/stop/p' text radka 2 start radka 3 radka 4 stop radka 6 start-stop radka 7 radka 8 radka 9 radka 10
Proč výpis neskončil řádkou 6? Pokud sed najde RE1, hledá RE2 až odnásledující řádky (na rozdíl od awk).
Dále by nás nemělo zaskočit, že zadáme-li rozsah řádek číselně od větší než do, provede se příkaz také a to pro řádku od.
5,6 p tiskne řádky 5 a 6 5,5 p tiskne řádku 5 5,3 p tiskne řádku 5 (!)
Vhodné uvozovky
Nejlépe jednoduché, dvojité použijme pouze pokud potřebujeme do skriptu propašovat shellovou proměnnou.
Příklad chyby: chceme vytisknout poslední řádku souboru
$ sed -n "$p" text
Rozdíly v chování regulárních výrazů
Původní sed nedovoluje použít \n v nahrazovací části s příkazu. Také podporuje pouze BRE, basic regular expressions, viz man 5 regexp. Nepodporuje plus, otazník a svislítko.
Gnu sed umí \n, \t, a čtyřkové verze s volbou -r používají ERE, ansi sekvence a \osmičkové\decimální\hexa sekvence. Nedokumentovaná volba -r zapínající ERE existuje už v sedu 3.02.
Příklad: chci nový řádek za každým výskytem znaku @
pouze gnu sed 4.xx: sed -e ‚s/@/@\n/g‘
Odlišné chování příkazů s a N
Dojde-li k náhradě s/vzor/obraz/p, v kterém případě se tiskne a kdy ne?
- některé implementace tisknou pattern space pouze při sed -n (jinak spoléhají se na defaultní výstup)
- všechny Gnu sedy tisknou s///p vždy
Od verze Gnu sed v3.02.80 a vyšší příkaz N na poslední řádce vytiskne pracovní prostor. Původní sed tiše zhasl, jak to popisuje i kniha Sed & Awk,
$ echo "jedna radka" | sed -e 'N' $
zatímco modernější vypíší
$ echo "jedna radka" | sed -e 'N' jedna radka $
Oprava tradičních skriptů: místo starého „N“ vložit sekvenci"$d;N"
Co připojím na konec, je až na konci
Pro příkazy a (append), c (change), ale také r soubor jde výstup mimo pracovní prostor a připojuje se až za výstup sedu pro zpracovávaný cyklus.
Příkaz i (insert) tiskne svůj výstup i uprostřed zpracování aktuálního cyklu, tiskne prostě tam, kde ve skriptu je.
Není na světě jenom sed …
Místo složitého regexpu nebo sedscriptu může být pohodlnější použít pajpu a kombinovat s prográmky jako tr, cut, tac …, nebo využít výrazy shellu jako je ${foo%%neco}.
Chci vynechat čtyři poslední řádky
tac | sed '1,4d' | tac
(no jistě, tady by si vystačil tail, je to jenom příklad)
Složitější skripty, s pokročilejšími příkazy nebo cykly
Pokročilejší skripty využívají schopnost pracovního prostoru načíst do sebe víc než jednu řádku. Pracovní a paměťový prostor se používá jako roura.
Porovnávání s předchozí řádkou
Jednoduchý příklad využití paměťového prostoru je sedovská emulace příkazu uniq.
sed 'x;G;/^\(.*\)\n\1$/d;g'
Porovnávání s předešlou řádkou a použití bloku umožnuje udělat akci na začátku, nebo konci výskytu sekvence řádků obsahující VZOR.
# provede něco na řádce za blokem řádek obsahujícím VZOR /VZOR/!{ x /VZOR/i\ AKCE - KONEC VZORu x } h # --- end ---
Stavová informace v sedu
Obecně vzato – jakými způsoby lze v sedu uchovávat informaci „v prohledávaném textu se nacházím mezi značkami begin a end “? Následující příklady pocházejí od Grega Ubbena – cílem je vstup kopírovat na výstup vyjma řádek mezi značkami begin a end (značky leží na začátku řádky).
Metoda 1 – používá paměťový prostor
Jsem-li na řádce za begin a není-li to zrovna řádka end, v paměťovém prostoru mám schovanou značku begin.
/^end/ h x /^begin/{ x; d; } x /^begin/ h
Metoda 2 – zda jsem v bloku, mi říká pozice v programu
Kvůli zpracování vnitřku bloku se v programu nachází extra smyčka.
#n run this script using the sed -n flag /^begin/{ p : loop n /^end/!b loop } p
Metoda 3 – používám rozsah adres
Pokud jde o bloky řádek, v daném případě asi nejhezčí postup.
/^begin/,/^end/{ /^begin/b /^end/b d }
Transformace výseku řádky „hsGs“
Stane se, že z řádky na vstupu potřebujeme transformovat jenom výsek. Originální řádku si nejprve uschováme do paměti, v pracovním prostoru si jí ořízneme a zmutujeme podle potřeby a nakonec sestavíme výsledek.
h s/zacatek\(VZOR\)zbytek/\1/ # tranformace vzor-->cil ... G s/\(.*\)\n\(zacatek\)VZOR\(zbytek\)/\2\1\3/ # nyni mam zacatekCILkonec
Podobně lze obejít fakt, že v nahrazovací části s-příkazu původního sedu nelze použít newline.
h s/.*// G s/^\(\n\)puvodni radka/v nahrade pouzivam \1 jako newline/
Paměťová omezení
Originální sed omezoval objem pracovního i paměťového prostoru. Tyto proměnné byly původně určeny pro uchovávání několika málo řádek a jejich maximální velikost je třeba vyhledat v manuálových stránkách (např. 8192 bytů). Gnu implementace toto omezení nemá.
Příklad – emulace tac, výpis souboru s převráceným pořadím řádek
sed -n '1!G; h; $p'
pro velké soubory s non-gnu sedem selže.
Ve skriptech obsahující cykly se sed často nespoléhá na implicitní vstup a výstup na začáttku a na konci, ale cyklí a cyklí a mezitím čte a zapisuje si po svém.
Kolovrátek N;P;D
Zhusta používaná technika se dá nazvat cyklus N;P;D. V našem modelovém příkladě vynecháváme čtyři poslední řádky, čistě sedovsky
sed -e ':a' -e '$d;N;2,4ba' -e 'P;D'
Sed je stream editor, vstup zpracovává postupně a pouze jednou, takže netuší, kolik řádek v souboru je. Když chceme mazat čtyři poslední řádky, vytvoříme si z pracovního prostoru 4-řádkovou rouru, kterou na posledním řádku ($d) zahodíme. Do té doby (tj. od pátého řádku dál) vždy z výfuku roury vytiskeme řádek (P), z roury ho odstraníme (D) a rouře do tlamy natlačíme řádek nový (N). Na začátku skriptu si rouru musíme tiše naplnit pomocným cyklem (2,4ba).
Mazaná řádka bude vždy aspoň jedna, což plyne ze způsobu, jakým sed nakládá s rozsahem adres.
To by pro dnešek stačilo. Příště dokončíme složitější skripty a dojde i na zmíněné vychytávky a šílenosti.