Příkaz AWK se datuje do raných dob Unixu. Je součástí standardu POSIX a měl by být dostupný na jakémkoli unixovém systému. A dále.
I když je AWK někdy zdiskreditován kvůli svému věku nebo nedostatku funkcí ve srovnání s víceúčelovým jazykem, jako je Perl, zůstává nástrojem, který rád používám ve své každodenní práci. Někdy pro psaní relativně složitých programů, ale také kvůli výkonným jednotkám, které můžete psát, abyste vyřešili problémy s datovými soubory.
Tak to je přesně účel tohoto článku. Ukazuje vám, jak můžete využít sílu AWK za méně než 80 znaků k provádění užitečných úkolů. Tento článek není zamýšlen jako úplný tutoriál pro AWK, ale přesto jsem na začátek zahrnul některé základní příkazy, takže i když nemáte žádné předchozí zkušenosti, můžete se chopit základních konceptů AWK.
Moje ukázkové soubory pro tento tutoriál AWK
Všechny jednolinky popsané v tomto článku budou testovány na stejném datovém souboru:
cat file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Kopii tohoto souboru můžete získat online na GitHubu.
Znát předdefinované a automatické proměnné v AWK
AWK podporuje několik předdefinovaných a automatických proměnných které vám pomohou napsat vaše programy. Mezi nimi často narazíte na:
RS –Oddělovač záznamů. AWK zpracovává vaše data jeden záznam po druhém. Oddělovač záznamů je oddělovač používaný k rozdělení vstupního datového toku do záznamů. Ve výchozím nastavení je to znak nového řádku. Pokud jej tedy nezměníte, záznam je jeden řádek vstupního souboru.
NR – Číslo aktuálního vstupního záznamu. Pokud pro své záznamy používáte standardní oddělovač nového řádku, shoduje se s aktuálním číslem vstupního řádku.
FS/OFS –Znaky použité jako oddělovač polí. Jakmile AWK načte záznam, rozdělí jej do různých polí na základě hodnoty FS
. Když AWK vytiskne záznam na výstupu, pole znovu spojí, ale tentokrát pomocí OFS
oddělovač namísto FS
oddělovač. Obvykle FS
a OFS
jsou stejné, ale není to povinné. „white space“ je výchozí hodnota pro oba.
NF – Počet polí v aktuálním záznamu. Pokud pro svá pole používáte standardní oddělovač „bílých míst“, bude se shodovat s počtem slov v aktuálním záznamu.
K dispozici jsou další víceméně standardní proměnné AWK, takže stojí za to zkontrolovat podrobnější informace ve vašem konkrétním implementačním manuálu AWK. Tato podmnožina však již stačí k tomu, abyste mohli začít psát zajímavé one-linery.
A. Základní použití příkazu AWK
1. Vytisknout všechny řádky
Tento příklad je většinou k ničemu, ale přesto bude dobrým úvodem do syntaxe AWK:
awk '1 { print }' file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Programy AWK se skládají z jednoho nebo více pattern { action }
prohlášení.
Pokud pro daný záznam (“řádek”) vstupního souboru, vzor vyhodnotí na nenulovou hodnotu (ekvivalent „true“ v AWK), příkazy v odpovídajícím akčním bloku jsou popraveni. Ve výše uvedeném příkladu od 1
je nenulová konstanta, { print }
akční blok se provede pro každý vstupní záznam.
Dalším trikem je { print }
je výchozí akční blok, který bude AWK používat, pokud žádný explicitně neurčíte. Takže výše uvedený příkaz lze zkrátit jako:
awk 1 file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Následující program AWK téměř stejně k ničemu spotřebovává svůj vstup, ale na výstupu nic neprodukuje:
awk 0 file
2. Odebrat záhlaví souboru
awk 'NR>1' file
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Pamatujte, že toto je ekvivalent explicitního psaní:
awk 'NR>1 { print }' file
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Tento jednořádkový řádek zapíše záznamy o vstupním souboru kromě úplně prvního, protože v takovém případě je podmínka 1>1
což zjevně není pravda.
Protože tento program používá výchozí hodnoty pro RS
, v praxi to zahodí první řádek vstupního souboru.
3. Tisk řádků v rozsahu
Toto je pouze zobecnění předchozího příkladu a nezaslouží si mnoho vysvětlování, kromě slova &&
je logické and
operátor:
awk 'NR>1 && NR < 4' file
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
4. Odstranění řádků obsahujících pouze mezery
awk 'NF' file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
AWK rozdělí každý záznam do polí na základě oddělovače polí zadaného v FS
variabilní. Výchozí oddělovač polí je jeden nebo několik prázdných znaků (také znám jako mezery nebo tabulátory). S těmito nastaveními bude každý záznam obsahující alespoň jeden znak bez mezer obsahovat alespoň jedno pole.
Jinými slovy, jediný případ, kdy NF
je 0 (“false”) je, když záznam obsahuje pouze mezery. Tento jednořádkový řádek tedy vytiskne pouze záznamy obsahující alespoň jeden znak bez mezery.
5. Odstranění všech prázdných řádků
awk '1' RS='' file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Tato jednolinka je založena na nejasném pravidle POSIX, které určuje, zda RS
je nastaven na prázdný řetězec, „pak jsou záznamy odděleny sekvencemi, které tvoří
V terminologii POSIX stojí za zmínku, že prázdný řádek je zcela prázdný řádek. Řádky, které obsahují pouze mezery, se nepočítají jako „prázdné“.
6. Extrahování polí
Toto je pravděpodobně jeden z nejběžnějších případů použití pro AWK:extrahování některých sloupců datového souboru.
awk '{ print $1, $3}' FS=, OFS=, file
CREDITS,USER
99,sylvain
52,sonia
52,sonia
25,sonia
10,sylvain
8,öle
,
,
,
17,abhishek
Zde jsem explicitně nastavil oddělovače vstupních i výstupních polí na koma. Když AWK rozdělí záznam na pole, uloží obsah prvního pole do $1, obsah druhého pole do $2 a tak dále. To zde nepoužívám, ale stojí za zmínku 0 $ je celý záznam.
V této jednovrstvě jste si možná všimli, že používám akční blok bez vzoru. V takovém případě se pro vzor předpokládá 1 („true“), takže akční blok se provede pro každý záznam.
V závislosti na vašich potřebách nemusí produkovat to, co bychom chtěli pro prázdné řádky nebo řádky obsahující pouze mezery. V tom případě by ta druhá verze mohla být o něco lepší:
awk 'NF { print $1, $3 }' FS=, OFS=, file
CREDITS,USER
99,sylvain
52,sonia
52,sonia
25,sonia
10,sylvain
8,öle
,
17,abhishek
V obou případech jsem předal vlastní hodnoty pro FS
a OFS
na příkazovém řádku. Další možností by bylo použít speciální BEGIN
bloku uvnitř programu AWK k inicializaci těchto proměnných před přečtením prvního záznamu. Takže v závislosti na vašem vkusu můžete raději napsat toto:
awk 'BEGIN { FS=OFS="," } NF { print $1, $3 }' file
CREDITS,USER
99,sylvain
52,sonia
52,sonia
25,sonia
10,sylvain
8,öle
,
17,abhishek
Zde stojí za zmínku, že můžete také použít END
bloky k provedení některých úkolů po přečtení posledního záznamu. Jako to uvidíme právě teď. Jak již bylo řečeno, přiznávám, že to není zdaleka dokonalé, protože čáry obsahující pouze bílé znaky nejsou zpracovány elegantně. Brzy uvidíme možné řešení, ale předtím si pojďme spočítat…
7. Provádění výpočtů po sloupcích
AWK podporuje standardní aritmetické operátory. A automaticky převede hodnoty mezi textem a čísly v závislosti na kontextu. Také můžete použít své vlastní proměnné k uložení mezilehlých hodnot. Vše, co vám umožňuje psát kompaktní programy pro provádění výpočtů na datových sloupcích:
awk '{ SUM=SUM+$1 } END { print SUM }' FS=, OFS=, file
263
Nebo ekvivalentně pomocí +=
zkrácená syntaxe:
awk '{ SUM+=$1 } END { print SUM }' FS=, OFS=, file
263
Upozorňujeme, že proměnné AWK není nutné před použitím deklarovat. Předpokládá se, že nedefinovaná proměnná obsahuje prázdný řetězec. Což se podle pravidel převodu typu AWK rovná číslu 0. Kvůli této funkci jsem se neobtěžoval explicitně řešit případ $1
obsahuje text (v záhlaví), mezery nebo prostě nic. Ve všech těchto případech se bude počítat jako 0 a nebude rušit naše sčítání. Samozřejmě by to bylo jiné, kdybych místo toho prováděl násobení. Proč byste tedy nepoužili sekci komentářů k navržení řešení pro tento případ?
8. Počítání počtu neprázdných řádků
Již jsem zmínil END
vládnout dříve. Zde je další možná aplikace pro počítání počtu neprázdných řádků v souboru:
awk '/./ { COUNT+=1 } END { print COUNT }' file
9
Zde jsem použil COUNT
proměnnou a zvýšil ji (+=1
) pro každý řádek odpovídající regulárnímu výrazu /./
. To znamená, že každý řádek obsahuje alespoň jeden znak. Nakonec se blok END používá k zobrazení konečného výsledku, jakmile je celý soubor zpracován. Na názvu COUNT
není nic zvláštního . Mohl jsem použít Count
, count
, n
, xxxx
nebo jakýkoli jiný název vyhovující pravidlům pro pojmenování proměnných AWK
Je však tento výsledek správný? Záleží na vaší definici „prázdného“ řádku. Pokud považujete pouze prázdné řádky (podle POSIX) za prázdné, pak je to správně. Ale možná byste raději považovali i řádky obsahující pouze mezery za prázdné?
awk 'NF { COUNT+=1 } END { print COUNT }' file
8
Tentokrát je výsledek jiný, protože pozdější verze ignoruje pouze prázdné řádky, zatímco původní verze ignoruje pouze prázdné řádky. Vidíte ten rozdíl? Nechal jsem tě, abys to pochopil sám. Pokud to není dostatečně jasné, neváhejte použít sekci komentářů!
Konečně, pokud vás zajímají pouze datové linky a vzhledem k mému konkrétnímu vstupnímu datovému souboru, mohu místo toho napsat toto:
awk '+$1 { COUNT+=1 } END { print COUNT }' file
7
Funguje to díky pravidlům převodu typu AWK. Jednočlenné plus ve vzoru vynutí ohodnocení $1 v číselném kontextu. V mém souboru obsahují datové záznamy v prvním poli číslo. Nedatové záznamy (nadpis, prázdné řádky, pouze prázdné řádky) obsahují text nebo nic. Všechny jsou při převodu na čísla rovny 0.
Všimněte si, že u tohoto nejnovějšího řešení by byl zahozen také záznam pro uživatele, který má nakonec 0 kreditů.
B. Použití polí v AWK
Pole jsou výkonnou funkcí AWK. Všechna pole v AWK jsou asociativní pole, takže umožňují přidružit libovolný řetězec k jiné hodnotě. Pokud znáte jiné programovací jazyky, možná je znáte jako hash , asociativní tabulky , slovníky nebo mapy .
9. Jednoduchý příklad pole AWK
Představme si, že chci znát celkový kredit pro všechny uživatele. Mohu uložit položku pro každého uživatele do asociativního pole a pokaždé, když narazím na záznam pro daného uživatele, zvýším odpovídající hodnotu uloženou v poli.
awk '+$1 { CREDITS[$3]+=$1 }
END { for (NAME in CREDITS) print NAME, CREDITS[NAME] }' FS=, file
abhishek 17
sonia 129
öle 8
sylvain 109
Uznávám, že tohle už není jednodílné. Většinou kvůli for
smyčka sloužící k zobrazení obsahu pole po zpracování souboru. Vraťme se tedy nyní ke kratším příkladům:
10. Identifikace duplicitních čar pomocí AWK
Pole, stejně jako ostatní proměnné AWK, lze použít jak v akčních blocích, tak i ve vzorech. Když toho využijeme, můžeme napsat jednořádkovou linku pro tisk pouze duplicitních řádků:
awk 'a[$0]++' file
52,01 dec 2018,sonia,team
++
operator je post-inkrementační operátor zděděný z rodiny jazyků C (jehož AWK je hrdým členem, díky Brianu Kernighanovi, který byl jedním z jeho původních autorů).
Jak jeho název napovídá, operátor po inkrementaci inkrementuje (“add 1”) proměnnou, ale pouze poté, co byla její hodnota převzata pro vyhodnocení zahrnujícího výrazu.
V takovém případě a[$0]
se vyhodnotí, aby se zjistilo, zda bude záznam vytištěn nebo ne, a jakmile bylo učiněno rozhodnutí, ve všech případech se položka pole inkrementuje.
Takže při prvním čtení záznamu a[$0]
je nedefinovaný, a tedy ekvivalentní nule pro AWK. Takže první záznam není zapsán na výstupu. Potom se tato položka změní z nuly na jednu.
Při druhém čtení stejného vstupního záznamu a[$0]
je nyní 1. To je „pravda“. Řádek bude vytištěn. Předtím je však položka pole aktualizována z 1 na 2. A tak dále.
11. Odstranění duplicitních řádků
Jako důsledek předchozího jednořádkového řádku můžeme chtít odstranit duplicitní řádky:
awk '!a[$0]++' file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Jediný rozdíl je v použití logického operátoru, nikoli operátoru (!
), které obracejí pravdivostní hodnotu výrazu. Co bylo nepravdivé, stává se pravdou a co bylo pravdivé, stává se falešným. Logika nemá absolutně žádný vliv na ++
zaúčtovat přírůstek, který funguje přesně jako předtím.
C. Magie oddělovače polí a záznamů
12. Změna oddělovačů polí
awk '$1=$1' FS=, OFS=';' file
CREDITS;EXPDATE;USER;GROUPS
99;01 jun 2018;sylvain;team:::admin
52;01 dec 2018;sonia;team
52;01 dec 2018;sonia;team
25;01 jan 2019;sonia;team
10;01 jan 2019;sylvain;team:::admin
8;12 jun 2018;öle;team:support
17;05 apr 2019;abhishek;guest
Tento program nastaví FS
a OFS
pro použití čárky jako oddělovače vstupních polí a středníku jako oddělovače výstupních polí. Protože AWK nemění výstupní záznam, pokud jste nezměnili pole, $1=$1
trik se používá k přinucení AWK přerušit záznam a znovu jej sestavit pomocí oddělovače výstupního pole.
Pamatujte, že výchozí blok akcí je { print }
. Takže to můžete přepsat explicitněji jako:
awk '$1=$1 { print }' FS=, OFS=';' file
CREDITS;EXPDATE;USER;GROUPS
99;01 jun 2018;sylvain;team:::admin
52;01 dec 2018;sonia;team
52;01 dec 2018;sonia;team
25;01 jan 2019;sonia;team
10;01 jan 2019;sylvain;team:::admin
8;12 jun 2018;öle;team:support
17;05 apr 2019;abhishek;guest
Možná jste si všimli, že oba tyto příklady také odstraňují prázdné řádky. Proč? Pamatujte na pravidla převodu AWK:prázdný řetězec je „false“. Všechny ostatní řetězce jsou „pravdivé“. Výraz $1=$1
je ovlivnění, které mění $1
. I toto je však výraz. A vyhodnotí se na hodnotu $1
-což je "false" pro prázdný řetězec. Pokud opravdu chcete všechny řádky, možná budete muset místo toho napsat něco takového:
awk '($1=$1) || 1 { print }' FS=, OFS=';' file
CREDITS;EXPDATE;USER;GROUPS
99;01 jun 2018;sylvain;team:::admin
52;01 dec 2018;sonia;team
52;01 dec 2018;sonia;team
25;01 jan 2019;sonia;team
10;01 jan 2019;sylvain;team:::admin
8;12 jun 2018;öle;team:support
17;05 apr 2019;abhishek;guest
Pamatujete si &&
operátor? Bylo to logické AND. ||
je logické OR. Závorka je zde nutná kvůli pravidlům přednosti operátorů. Bez nich by byl vzor chybně interpretován jako $1=($1 || 1)
namísto. Nechal jsem vám jako cvičení vyzkoušet, jak by se pak výsledek lišil.
A konečně, pokud vás aritmetika příliš nezajímá, vsadím se, že budete preferovat toto jednodušší řešení:
awk '{ $1=$1; print }' FS=, OFS=';' file
CREDITS;EXPDATE;USER;GROUPS
99;01 jun 2018;sylvain;team:::admin
52;01 dec 2018;sonia;team
52;01 dec 2018;sonia;team
25;01 jan 2019;sonia;team
10;01 jan 2019;sylvain;team:::admin
8;12 jun 2018;öle;team:support
17;05 apr 2019;abhishek;guest
13. Odstranění více mezer
awk '$1=$1' file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Jedná se o téměř stejný program jako předchozí. Ponechal jsem však oddělovače polí na výchozí hodnoty. Jako oddělovač vstupních polí se tedy používá více mezer, ale jako oddělovač výstupních polí se používá pouze jedna mezera. To má příjemný vedlejší efekt sloučení násobků mezery na jednu prostor.
14. Spojování linek pomocí AWK
OFS
jsme již použili , oddělovač výstupního pole. Jak jste možná uhodli, má ORS
protějšek k určení oddělovače výstupních záznamů:
awk '{ print $3 }' FS=, ORS=' ' file; echo
USER sylvain sonia sonia sonia sylvain öle abhishek
Zde jsem za každým záznamem místo znaku nového řádku použil mezeru. Tato jednovrstvá vložka je v některých případech dostačující, ale stále má určité nevýhody.
Nejspíše nezahazuje řádky obsahující pouze mezery (mezery navíc za öle z toho pocházejí). Takže možná místo toho použiji obyčejný regulární výraz:
awk '/[^[:space:]]/ { print $3 }' FS=, ORS=' ' file; echo
USER sylvain sonia sonia sonia sylvain öle abhishek
Nyní je to lepší, ale stále existuje možný problém. Bude to jasnější, když změníme oddělovač na něco viditelného:
awk '/[^[:space:]]/ { print $3 }' FS=, ORS='+' file; echo
USER+sylvain+sonia+sonia+sonia+sylvain+öle+abhishek+
Na konci řádku je navíc oddělovač – protože oddělovač pole se píše za každý záznam. Včetně toho posledního.
Abych to napravil, přepíšu program tak, aby zobrazoval vlastní oddělovač před záznam, počínaje druhým výstupním záznamem.
awk '/[^[:space:]]/ { print SEP $3; SEP="+" }' FS=, ORS='' file; echo
USER+sylvain+sonia+sonia+sonia+sylvain+öle+abhishek
Protože se o přidávání oddělovače starám sám, nastavil jsem na prázdný řetězec také standardní oddělovač výstupních záznamů AWK. Když se však začnete zabývat oddělovači nebo formátováním, může to být znamení, že byste měli zvážit použití printf
místo funkce print
prohlášení. Jak to právě teď uvidíme.
D. Formátování pole
O vztahu mezi programovacími jazyky AWK a C jsem se již zmínil. Mimo jiné ze standardní knihovny jazyka C AWK zdědí výkonný printf
funkce, která umožňuje velkou kontrolu nad formátováním textu odeslaného na výstup.
printf
Funkce bere jako první argument formát, který obsahuje jak prostý text, který bude vydán doslovně, tak zástupné znaky použité k formátování různých částí výstupu. Zástupné znaky jsou označeny %
charakter. Nejběžnější je %s
(pro formátování řetězce), %d
(pro formátování celých čísel) a %f
(pro formátování čísel s pohyblivou řádovou čárkou). Protože to může být poněkud abstraktní, podívejme se na příklad:
awk '+$1 { printf("%s ", $3) }' FS=, file; echo
sylvain sonia sonia sonia sylvain öle abhishek
Můžete si všimnout opaku print
příkaz printf
funkce nepoužívá OFS
a ORS
hodnoty. Takže pokud chcete nějaký oddělovač, musíte to výslovně zmínit, jako jsem to udělal přidáním mezery na konec formátovacího řetězce. Toto je cena, kterou musíte zaplatit za plnou kontrolu nad výstupem.
I když to vůbec není specifikátor formátu, je to skvělá příležitost představit \n
zápis, který lze použít v libovolném řetězci AWK k reprezentaci znaku nového řádku.
awk '+$1 { printf("%s\n", $3) }' FS=, file
sylvain
sonia
sonia
sonia
sylvain
öle
abhishek
15. Produkování tabulkových výsledků
AWK vynucuje formát dat záznamu/pole založený na oddělovačích. Nicméně pomocí printf
můžete také vytvořit tabulkový výstup s pevnou šířkou. Protože každý specifikátor formátu v printf
příkaz může přijmout volitelný parametr width:
awk '+$1 { printf("%10s | %4d\n", $3, $1) }' FS=, file
sylvain | 99
sonia | 52
sonia | 52
sonia | 25
sylvain | 10
öle | 8
abhishek | 17
Jak můžete vidět, zadáním šířky každého pole je AWK doplní doleva mezerami. U textu je obvykle vhodnější umístit výplň vpravo, čehož lze dosáhnout pomocí záporného čísla šířky. Také pro celá čísla můžeme chtít doplnit pole nulami místo mezer. To lze získat použitím explicitní 0 před šířkou pole:
awk '+$1 { printf("%-10s | %04d\n", $3, $1) }' FS=, file
sylvain | 0099
sonia | 0052
sonia | 0052
sonia | 0025
sylvain | 0010
öle | 0008
abhishek | 0017
16. Zacházení s čísly s pohyblivou řádovou čárkou
%f
formát si nezaslouží mnoho vysvětlování…
awk '+$1 { SUM+=$1; NUM+=1 } END { printf("AVG=%f",SUM/NUM); }' FS=, file
AVG=37.571429
... možná až na to, že téměř vždy chcete explicitně nastavit šířku pole a přesnost zobrazeného výsledku:
awk '+$1 { SUM+=$1; NUM+=1 } END { printf("AVG=%6.1f",SUM/NUM); }' FS=, file
AVG= 37.6
Zde je šířka pole 6, což znamená, že pole bude zabírat prostor 6 znaků (včetně tečky a případně doplněných mezerami vlevo jako obvykle). Přesnost .1 znamená, že chceme zobrazit číslo s 1 desetinným číslem za tečkou. Nechám vás hádat %06.1
místo toho zobrazí.
E. Použití řetězcových funkcí v AWK
Kromě printf
AWK obsahuje několik dalších pěkných funkcí pro manipulaci s řetězci. V této doméně mají moderní implementace jako Gawk bohatší sadu interních funkcí za cenu nižší přenositelnosti. Pokud jde o sebe, zůstanu zde jen s několika funkcemi definovanými v POSIX, které by měly fungovat všude stejně.
17. Převod textu na velká písmena
Tenhle používám hodně, protože pěkně řeší problémy s internacionalizací:
awk '$3 { print toupper($0); }' file
99,01 JUN 2018,SYLVAIN,TEAM:::ADMIN
52,01 DEC 2018,SONIA,TEAM
52,01 DEC 2018,SONIA,TEAM
25,01 JAN 2019,SONIA,TEAM
10,01 JAN 2019,SYLVAIN,TEAM:::ADMIN
8,12 JUN 2018,ÖLE,TEAM:SUPPORT
17,05 APR 2019,ABHISHEK,GUEST
Ve skutečnosti je to pravděpodobně nejlepší a nejpřenosnější řešení pro převod textu na velká písmena z shellu.
18. Změna části řetězce
Pomocí substr
můžete rozdělit řetězec znaků na danou délku. Zde jej používám k psaní velkého pouze prvního znaku třetího pole:
awk '{ $3 = toupper(substr($3,1,1)) substr($3,2) } $3' FS=, OFS=, file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,Sylvain,team:::admin
52,01 dec 2018,Sonia,team
52,01 dec 2018,Sonia,team
25,01 jan 2019,Sonia,team
10,01 jan 2019,Sylvain,team:::admin
8,12 jun 2018,Öle,team:support
17,05 apr 2019,Abhishek,guest
substr
Funkce převezme počáteční řetězec, index (založený na 1) prvního znaku k extrahování a počet znaků k extrahování. Pokud tento poslední argument chybí, substr
vezme všechny zbývající znaky řetězce.
Takže substr($3,1,1)
se vyhodnotí jako první znak $3
a substr($3,2)
ke zbývajícím.
19. Rozdělení polí do podpolí
Datový model záznamového pole AWK je opravdu pěkný. Někdy však chcete rozdělit samotná pole na několik částí na základě nějakého vnitřního oddělovače:
awk '+$1 { split($2, DATE, " "); print $1,$3, DATE[2], DATE[3] }' FS=, OFS=, file
99,sylvain,jun,2018
52,sonia,dec,2018
52,sonia,dec,2018
25,sonia,jan,2019
10,sylvain,jan,2019
8,öle,jun,2018
17,abhishek,apr,2019
Poněkud překvapivě to funguje, i když jsou některá z mých polí oddělena více než jedním prázdným znakem. Většinou z historických důvodů, kdy je oddělovačem jedna mezera, split
bude uvažovat „prvky jsou odděleny řadou mezer“. A nejenom jedním. FS
speciální proměnná se řídí stejnou konvencí.
V obecném případě však jeden znakový řetězec odpovídá jednomu znaku. Pokud tedy potřebujete něco složitějšího, musíte si uvědomit, že oddělovač polí je rozšířený regulární výraz.
Jako příklad se podívejme, jak by bylo zpracováno pole skupiny, které se jeví jako pole s více hodnotami pomocí dvojtečky jako oddělovače:
awk '+$1 { split($4, GRP, ":"); print $3, GRP[1], GRP[2] }' FS=, file
sylvain team
sonia team
sonia team
sonia team
sylvain team
öle team support
abhishek guest
Zatímco bych očekával zobrazení až dvou skupin na uživatele, u většiny z nich se zobrazí pouze jedna. Tento problém je způsoben více výskyty oddělovače. Takže řešení je:
awk '+$1 { split($4, GRP, /:+/); print $3, GRP[1], GRP[2] }' FS=, file
sylvain team admin
sonia team
sonia team
sonia team
sylvain team admin
öle team support
abhishek guest
Lomítka namísto uvozovek označují doslovný výraz jako regulární výraz, nikoli jako prostý řetězec, a znaménko plus znamená, že tento výraz bude odpovídat jednomu nebo několika výskytům předchozího znaku. V takovém případě je tedy každý oddělovač tvořen (nejdelší posloupností) jednou nebo několika po sobě jdoucími dvojtečkami.
20. Vyhledávání a nahrazování pomocí příkazů AWK
Když už mluvíme o regulárních výrazech, někdy chcete provést substituci, jako je sed s///g
příkaz, ale pouze na jednom poli. gsub
příkaz je to, co v takovém případě potřebujete:
awk '+$1 { gsub(/ +/, "-", $2); print }' FS=, file
99 01-jun-2018 sylvain team:::admin
52 01-dec-2018 sonia team
52 01-dec-2018 sonia team
25 01-jan-2019 sonia team
10 01-jan-2019 sylvain team:::admin
8 12-jun-2018 öle team:support
17 05-apr-2019 abhishek guest
gsub
Funkce převezme k hledání regulární výraz, náhradní řetězec a proměnnou obsahující text, který má být upraven. Pokud to později chybí, předpokládá se $0.
F. Práce s externími příkazy v AWK
Další skvělou funkcí AWK je, že můžete snadno vyvolat externí příkazy ke zpracování vašich dat. V zásadě existují dva způsoby, jak to udělat:pomocí system
instrukce pro vyvolání programu a ponechání mu smíchat jeho výstup ve výstupním proudu AWK. Nebo pomocí potrubí, aby AWK mohl zachytit výstup externího programu pro jemnější kontrolu výsledku.
To mohou být sama o sobě velká témata, ale zde je několik jednoduchých příkladů, které vám ukáží sílu těchto funkcí.
21. Přidání data na začátek souboru
awk 'BEGIN { printf("UPDATED: "); system("date") } /^UPDATED:/ { next } 1' file
UPDATED: Thu Feb 15 00:31:03 CET 2018
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
V tom AWK programu začnu zobrazením práce AKTUALIZOVÁNO. Poté program vyvolá externí date
příkaz, který odešle svůj výsledek na výstup hned po textu vytvořeném AWK v této fázi.
Zbytek programu AWK pouze odstraní aktualizační příkaz, který se případně nachází v souboru, a vytiskne všechny ostatní řádky (s pravidlem 1
).
Všimněte si next
prohlášení. Slouží k přerušení zpracování aktuálního záznamu. It is a standard way of ignoring some records from the input file.
22. Modifying a field externally
For more complex cases, you may need to consider the | getline VARIABLE
idiom of AWK:
awk '+$1 { CMD | getline $5; close(CMD); print }' CMD="uuid -v4" FS=, OFS=, file
99,01 jun 2018,sylvain,team:::admin,5e5a1bb5-8a47-48ee-b373-16dc8975f725
52,01 dec 2018,sonia,team,2b87e9b9-3e75-4888-bdb8-26a9b34facf3
52,01 dec 2018,sonia,team,a5fc22b5-5388-49be-ac7b-78063cbbe652
25,01 jan 2019,sonia,team,3abb0432-65ef-4916-9702-a6095f3fafe4
10,01 jan 2019,sylvain,team:::admin,592e9e80-b86a-4833-9e58-1fe2428aa2a2
8,12 jun 2018,öle,team:support,3290bdef-fd84-4026-a02c-46338afd4243
17,05 apr 2019,abhishek,guest,e213d756-ac7f-4228-818f-1125cba0810f
This will run the command stored in the CMD
variable, read the first line of the output of that command, and store it into the variable $5
.
Pay special attention to the close statement, crucial here as we want AWK to create a new instance of the external command each time it executes the CMD | getline
statement. Without the close statement, AWK would instead try to read several lines of output from the same command instance.
23. Invoking dynamically generated commands
Commands in AWK are just plain strings without anything special. It is the pipe operator that triggers external programs execution. So, if you need, you can dynamically construct arbitrary complex commands by using the AWK string manipulation functions and operators.
awk '+$1 { cmd = sprintf(FMT, $2); cmd | getline $2; close(cmd); print }' FMT='date -I -d "%s"' FS=, file
99 2018-06-01 sylvain team:::admin
52 2018-12-01 sonia team
52 2018-12-01 sonia team
25 2019-01-01 sonia team
10 2019-01-01 sylvain team:::admin
8 2018-06-12 öle team:support
17 2019-04-05 abhishek guest
We have already met the printf
function. sprintf
is very similar but will return the built string rather than sending it to the output.
24. Joining data
To show you the purpose of the close statement, I let you try out that last example:
awk '+$1 { CMD | getline $5; print }' CMD='od -vAn -w4 -t x /dev/urandom' FS=, file
99 01 jun 2018 sylvain team:::admin 1e2a4f52
52 01 dec 2018 sonia team c23d4b65
52 01 dec 2018 sonia team 347489e5
25 01 jan 2019 sonia team ba985e55
10 01 jan 2019 sylvain team:::admin 81e9a01c
8 12 jun 2018 öle team:support 4535ba30
17 05 apr 2019 abhishek guest 80a60ec8
As the opposite of the example using the uuid
command above, there is here only one instance of od
launched while the AWK program is running, and when processing each record, we read one more line of the output of that same process.
Závěr
That quick tour of AWK certainly can’t replace a full-fledged course or tutorial on that tool. However, for those of you that weren’t familiar with it, I hope it gave you enough ideas so you can immediately add AWK to your toolbox.
On the other hand, if you were already an AWK aficionado, you might have found here some tricks you can use to be more efficient or simply to impress your friends.
However, I do not pretend been exhaustive. So, in all cases, don’t hesitate to share your favorite AWK one-liner or any other AWK tips using the comment section below!