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 find
e 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 find
e 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 find
u 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 cut
e 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 sort
e 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 }'
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”
oops, nevermind ( necital som dokladne… ), my bad
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 “.\+\.[^.]\+$”
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.