Shellové skriptovanie: frekvencie prípon jednotlivých súborov

Zadanie

V aktuálnom adresári zistite frekvencie prípon jednotlivých súborov.

Prvé kroky

Základ je vyhľadať všetky súbory:

find . -type f

Ako získame len prípony?

Prípony v GNU find

V GNU finde máme možnosť použiť -printf a v ňom formátovací reťazec %f, ktorý vráti rovno názov súboru (basename) bez cesty.

find . -type f -printf "%f\n"

Prípony v POSIX find

find / -exec / basename

V POSIXovom finde však nič také nie je, ale môžeme si pomôcť inak: využijeme -exec v kombinácii s príkazom basename, ktorý vracia len názov súboru:

basename /etc/passwd

Výsledkom je

passwd

Ak to pokombinujeme, získame zoznam názvov súborov:

find . -type f -exec basename {} \;

find / xargs / basename

Rýchlejšia alternatíva je find/xargs, tá však vyžaduje špeciálne spracovanie súborov s medzerami. Vieme však využiť kombináciu -print0 vo find a -0 v xargs, čo vyrieši všetky problémy:

find . -type f -print0 | xargs -0 -n1 basename

Nezabudnime v xargs uviesť spracovávanie riadkov z findu v jednoriadkových dávkach (-n1). Príkaz basename totiž podporuje len jeden parameter a ak ich z xargs dostane viac, bude sa na to sťažovať.

Sekanie prípon

Sekanie prípon je prekérne: kým s jednoduchými príponami typu .txt nie je problém, s dvojpríponami štýlu .tar.gz sa jej ťažšie vysporiadať.

Pre jednoduchosť budeme spracovávať len jednoduché prípony.

Nefunkčný cut

Jedno riešenie je prosté: použiť cut, posekať riadok podľa bodky a zobrať poslednú položku. Žiaľ, to fungovať nebude, lebo v cute nevieme našpecifikovať poslednú vec. A naozaj vecí môže byť veľa, lebo:

javax.servlet-api-3.0.1.jar

Funkčný awk

awk je v mnohom naspeedovaný cut. V jeho jazyku môžeme použiť špeciálnu premennú $NF („number of fields”), ktorá udáva počet záznamov:

Skúsiť to možno na:

echo "javax.servlet-api-3.0.1.jar" | awk -F'.' '{ print $NF }'

Výsledkom bude:

jar

Môžeme to pokombinovať:

find . -type f -print0 | xargs -0 -n1 basename | awk -F'.' '{ print $NF }'

Tuto nefunkčná verzia cez expanziu premenných

Ak máme premennú s vhodným obsahom, vieme použiť finty s expanziou, ktorými vieme odsekávať najdlhšie prípony / predpony:

F=javax.servlet-api-3.0.1.jar
echo ${F##*.}

Modifikátor ## hovorí, že sa má odseknúť najdlhší prefix spĺňajúci shellovský regulárny výraz *., čo znamená:

  • ľubovoľný znak * opakovaný viackrát
  • nasledovaný bodkou .

Pozor na fakt, že v shellovských regulárnych výrazoch bodka neznamená ľubovoľný znak, ale má význam skutočnej bodky.

Výsledkom je:

jar

Problém je však s celkovou logikou. Museli by sme nastaviť nejakú premennú, ktorá sa následne expanduje, a to spraviť pre každý súbor. Skript by fungoval, ale zrejme by sme museli pristupiť k for-cyklu, čo by zápis skomplikovalo.

Počítanie frekvencií

Vieme využiť fintu s parametrom -c pri uniq, ktorá spočíta duplikáty. Jediný zádrheľ spočíva v nutnosti potriediť vstupné dáta:

... | sort | uniq -c

Celý príkaz teda:

find . -type f -print0 | xargs -0 -n1 basename | awk -F'.' '{ print $NF }'

Výstupom bude:

 4 xml
 1 xpi
 1 xsd
59 zip
 1 ZIP

Voliteľné triedenie

Ak chceme triediť podľa frekvencie výskytov (a nie podľa prípony), celé to pošleme do

sort -nk1

Triediť budeme numericky -n, podľa prvého stĺpca -k1, pričom štandardným oddeľovačom v sorte je prechod medzi znakom nebieleho miesta a bieleho miesta (a naopak):

find . -type f -print0 | xargs -0 -n1 basename | awk -F'.' '{ print $NF }' | sort | uniq -c | sort -nk1

Vylepšený formát

Ak chceme zameniť poradie stĺpcov (najprv prípona, potom počet), môžeme opäť použiť awk:

find . -type f -print0 | xargs -0 -n1 basename | awk -F'.' '{ print $NF }' | sort | uniq -c | sort -nk1 | awk '{ print $2 ": " $1 }' 

Súbory bez prípon

Súbor bez prípony zistíme ľahko: ak awk zistí, že počet položiek v NF je rovný jednej, znamená to, že vo vstupnom riadku nebola žiadna bodka.

Skript v awk teda môže vyzerať:

{ 
    if (NF == 1) 
        print "???"
    else 
        print $NF 
}

Tá istá jednoriadková verzia:

{ if (NF == 1) print "???"; else print $NF }

Finále

Megaoneliner vyzerá teda:

find . -type f -print0 | xargs -0 -n1 basename | awk -F'.' '{ if (NF == 1) print "???"; else print $NF }' | sort | uniq -c | sort -nk1 | awk '{ print $2 ": " $1 }'

4 thoughts on “Shellové skriptovanie: frekvencie prípon jednotlivých súborov

  1. xargs -0 -n1 basename je celkom zbytočné, pretože môžeme upraviť výstup find-u tak, aby hneď vypisoval len názov súboru a nie celú cestu:

    find -printf “%f \n”

      • Avsak ( last comment for today ), otazne je, ci v pripade hidden suborov je napr u suboru .bashrc pripona bashrc

        riesenim by imho bolo vyfiltrovat si subory s priponou napr pomocou takehoto grep-u grep “.\+\.[^.]\+$”

  2. To je ako v Piane, tri komentáre do dňa.

    Výklad prípon záleží na interpretácii, osobne by som .bashrc považoval za príponu. Samozrejme, dá sa to vylepšiť práve takto, ako navrhuješ.

    Bonusovo by sa mohlo mať niekde mapovanie “well-known” dvojprípon (.tar.gz, .tar.bz2, …), ktorými by sa ošetrili aj okrajové prípady.

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *