GNU/Linux >> Znalost Linux >  >> Linux

Začínáme s příkazem AWK [Příručka pro začátečníky]

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:

RSOddě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/OFSZnaky 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ří a jeden nebo více prázdných řádků.“

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!


Linux
  1. Začínáme s awk, výkonným nástrojem pro analýzu textu

  2. Začínáme se Zsh

  3. Začínáme s příkazem tac systému Linux

  1. Začínáme s linuxovým příkazem cat

  2. Začínáme s ls

  3. Začínáme s PostgreSQL na Linuxu

  1. Začínáme s GnuCash

  2. Začínáme s Etcher.io

  3. Začínáme s Multipass – spouštění virtuálních počítačů Ubuntu