wc, uniq i tr — brojanje, uklanjanje duplikata i zamena znakova

Tri komande koje same za sebe ne rade skoro ništa. Ali se u lancu sa grep i sort pojavljuju toliko često da bez njih gotovo nijedna ozbiljna obrada teksta ne prolazi.

Redom: wc broji, uniq uklanja ponavljanja, tr menja pojedinačne znakove.

wc — brojanje

$ wc /var/log/syslog
  4821  58394 512847 /var/log/syslog

Tri broja: redovi, reči, bajtovi. U praksi ti skoro uvek treba samo jedan:

$ wc -l /var/log/syslog
4821 /var/log/syslog

$ wc -w esej.txt
1247 esej.txt

$ wc -c slika.png
248391 slika.png

Opcija -c broji bajtove, a -m znakove. Na ćiriličnom ili bilo kom tekstu sa našim slovima to nije isto:

$ echo -n "džemper" | wc -c
9
$ echo -n "džemper" | wc -m
7

Slovo ž u UTF-8 zauzima dva bajta. Kad brojiš znakove, koristi -m.

Trik sa imenom fajla

$ wc -l spisak.txt
42 spisak.txt

$ wc -l < spisak.txt
42

Kad se koristi preusmeravanje, wc ne zna ime fajla pa ga ne ispisuje. Neophodno kad rezultat ide u promenljivu:

$ broj=$(wc -l < spisak.txt)
$ echo "Redova: $broj"
Redova: 42

Više o razlici između < i imena fajla kao argumenta ima na stranici Piping i redirekcija.

Zamka: wc -l ne broji redove nego znakove za novi red

$ printf "prvi\ndrugi\ntreci" > bez-kraja.txt
$ wc -l bez-kraja.txt
2 bez-kraja.txt

$ cat bez-kraja.txt
prvi
drugi
treci

Vidiš tri reda, wc kaže dva. Poslednji red nema znak za novi red na kraju, pa se ne broji. Ovo se dešava sa fajlovima koje je napravio Windows program ili neuredna skripta.

Otporna varijanta, koja broji i takav poslednji red:

$ grep -c "" bez-kraja.txt
3

Brojanje fajlova

$ ls /etc | wc -l
198

Radi, ali ne broji skrivene fajlove i puca ako neko ime sadrži znak za novi red. Pouzdanije je preko find:

$ find /etc -maxdepth 1 -type f | wc -l
83
$ find /var/log -type f -name "*.log" | wc -l
27

Više fajlova odjednom

$ wc -l *.txt
   42 spisak.txt
  128 beleske.txt
    9 todo.txt
  179 total

Red total na kraju je česta greška u skriptama — ako računaš sa poslednjim redom kao rezultatom, dobićeš zbir umesto broja za poslednji fajl.

uniq — uklanjanje ponavljanja

Najvažnija stvar na celoj stranici: uniq uklanja samo susedne duplikate. Ako isti red stoji na dva mesta u fajlu a između njih ima nečeg drugog, uniq ga neće primetiti.

$ cat imena.txt
ana
marko
ana
petar
marko

$ uniq imena.txt
ana
marko
ana
petar
marko

Ništa se nije desilo. Zato uniq gotovo nikad ne stoji sam, nego iza sort:

$ sort imena.txt | uniq
ana
marko
petar

Za ovaj konkretan slučaj postoji i kraći zapis:

$ sort -u imena.txt
ana
marko
petar

-c — brojanje pojavljivanja

Ovo je razlog zbog kog uniq uopšte postoji, jer to sort -u ne ume:

$ sort imena.txt | uniq -c
      2 ana
      2 marko
      1 petar

Sortiranje po učestalosti, opadajuće, dobija se drugim prolazom kroz sort:

$ sort imena.txt | uniq -c | sort -rn
      2 ana
      2 marko
      1 petar

Ovaj obrazac — sort | uniq -c | sort -rn — vredi zapamtiti napamet. To je najbrži način da iz bilo kog loga saznaš šta se najčešće dešava:

$ awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -5
   4821 203.0.113.45
   1203 198.51.100.7
    847 192.0.2.19
    412 203.0.113.88
    198 198.51.100.23

Prva kolona pristupnog loga je IP adresa, pa je ovo spisak najaktivnijih posetilaca. Kolone izdvaja awk, a isto bi uradio i cut.

-d i -u — samo duplikati ili samo jedinstveni

$ sort imena.txt | uniq -d
ana
marko

$ sort imena.txt | uniq -u
petar

Praktična primena — pronalaženje duplih korisničkih imena ili istih UID-ova u /etc/passwd:

$ cut -d: -f3 /etc/passwd | sort | uniq -d
1001

Prazan ispis znači da je sve u redu. Ako nešto ispiše, dva korisnika dele isti UID, što je stvarni bezbednosni problem.

Ignorisanje dela reda

$ uniq -i imena.txt        # ne razlikuje velika i mala slova
$ uniq -f 2 log.txt        # preskoči prve dve kolone
$ uniq -s 16 log.txt       # preskoči prvih 16 znakova

Opcija -s je zlata vredna kod logova, gde svaki red počinje vremenskom oznakom pa nijedna dva reda nisu identična:

$ cut -c17- /var/log/syslog | sort | uniq -c | sort -rn | head -3
    284 systemd[1]: Started Session 42 of user alen.
    112 kernel: [UFW BLOCK] IN=eth0 OUT=
     47 cron[891]: (root) CMD (/usr/local/bin/backup.sh)

tr — zamena i brisanje znakova

tr radi nad pojedinačnim znakovima, ne nad rečima ni obrascima. Za obrasce postoji sed.

Prva stvar koja iznenađuje: tr ne prima ime fajla. Isključivo čita sa standardnog ulaza.

$ tr 'a' 'A' spisak.txt
tr: extra operand 'spisak.txt'

$ tr 'a' 'A' < spisak.txt
$ cat spisak.txt | tr 'a' 'A'

Oba poslednja oblika rade; prvi je efikasniji jer ne pokreće još jedan proces.

Zamena

$ echo "linux je zabavan" | tr 'a-z' 'A-Z'
LINUX JE ZABAVAN

Čitljivije, preko imenovanih skupova:

$ echo "LINUX" | tr '[:upper:]' '[:lower:]'
linux

Skupovi koje ćeš koristiti: [:alpha:], [:digit:], [:alnum:], [:space:], [:punct:].

Zamena razmaka novim redom, čime se rečenica pretvara u spisak:

$ echo "jedan dva tri" | tr ' ' '\n'
jedan
dva
tri

-d — brisanje

$ echo "tel: 013-345-678" | tr -d '[:alpha:] :-'
013345678

Najkorisnija primena je uklanjanje Windows znakova za kraj reda:

$ file skripta.sh
skripta.sh: Bourne-Again shell script, ASCII text, with CRLF line terminators

$ tr -d '\r' < skripta.sh > /tmp/cisto.sh && mv /tmp/cisto.sh skripta.sh

Obrati pažnju na privremeni fajl. Ovo bi uništilo podatke:

$ tr -d '\r' < skripta.sh > skripta.sh    # NE!

Shell isprazni odredišni fajl pre nego što tr uopšte krene, kao što je objašnjeno u lekciji Piping i redirekcija.

-s — sabijanje ponavljanja

$ echo "previse     razmaka      ovde" | tr -s ' '
previse razmaka ovde

Nezamenljivo pre obrade po kolonama, jer cut -d' ' svaki višak razmaka tretira kao praznu kolonu:

$ df -h | tr -s ' ' | cut -d' ' -f1,5
Filesystem Use%
/dev/sda2 38%
/dev/sda3 63%

-c — sve osim navedenog

$ echo "Pozovi 013-345-678 danas" | tr -cd '[:digit:]\n'
013345678

Čita se: obriši sve što nije cifra ili novi red. Kombinacija -cd je najbrži način da se iz teksta izvuku samo brojevi.

Sve tri zajedno

Klasičan zadatak — najčešće reči u tekstu:

$ tr -cs '[:alpha:]' '\n' < esej.txt | tr '[:upper:]' '[:lower:]' | sort | uniq -c | sort -rn | head -5
    142 i
     98 je
     67 se
     41 linux
     38 na

Korak po korak: prvi tr pretvara sve što nije slovo u novi red i sabija ponavljanja, drugi svodi na mala slova, sort grupiše, uniq -c broji, drugi sort ređa opadajuće.

Šest komandi, nijedna ne zna šta je „reč" — a rezultat je frekvencijski rečnik. To je cela ideja Linuxa u jednom redu.

Još jedan koristan lanac — provera koliko je jedinstvenih IP adresa pokušalo prijavu preko SSH-a:

$ grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort -u | wc -l
47

Sitnice

Generisanje slučajne lozinke

$ tr -cd '[:alnum:]' < /dev/urandom | head -c 16; echo
Kf3mZq8xTn2pWbLd

/dev/urandom je beskonačan izvor slučajnih bajtova; tr propušta samo slova i cifre, a head -c zaustavlja posle šesnaest znakova.

Kada je awk bolji izbor

Ako ti treba brojanje po uslovu, a ne po celom redu, lanac sort | uniq -c je zaobilazan:

$ awk '{brojac[$1]++} END {for (i in brojac) print brojac[i], i}' access.log | sort -rn | head

Brže je na velikim fajlovima jer ne zahteva sortiranje. Ali je i teže za čitanje — a lanac sa uniq pamtiš i posle godinu dana.

Kratka referenca

wc

  • -l redovi · -w reči · -c bajtovi · -m znakovi
  • wc -l < fajl — bez ispisa imena fajla
  • grep -c "" — brojanje redova otporno na fajl bez završnog novog reda

uniq (zahteva prethodno sortiranje)

  • -c — prebroj pojavljivanja
  • -d — samo duplikati · -u — samo jedinstveni
  • -i — bez razlike velikih i malih slova
  • -f N / -s N — preskoči N kolona / znakova
  • sort -u — kraće kad ti treba samo uklanjanje duplikata

tr (čita samo sa standardnog ulaza)

  • tr 'a' 'b' — zameni znakove
  • -d — obriši navedene znakove
  • -s — sabij ponovljene znakove u jedan
  • -c — primeni na sve osim navedenih
  • [:upper:], [:lower:], [:digit:], [:alnum:], [:space:]

Vežba

$ cd /var/log
  1. Prebroj koliko redova u syslog sadrži reč error, bez obzira na velika i mala slova.
  2. Izlistaj pet korisnika sa najviše prijava u auth.log, sortirano po broju.
  3. Iz fajla /etc/passwd izdvoj sve različite shell-ove koje korisnici koriste, sa brojem korisnika po svakom.
  4. Napravi fajl sa nekoliko redova koji imaju višak razmaka, pa ga očisti tako da između reči ostane po jedan razmak.
  5. Utvrdi koliko ukupno različitih reči ima u fajlu /etc/services, bez razlike velikih i malih slova.

Peti zadatak traži sve tri komande u jednom lancu.


Komande sa kojima se ove tri najčešće spajaju su grep, sort, cut i awk, a način spajanja objašnjen je u lekciji Piping i redirekcija.

Cela lekcija je deo vodiča Linux osnove.

Comments

Popular posts from this blog

Početak u Linuxu — šta je i zašto se uči

Konverzija tipova podataka u Pythonu

groupadd