[Megoldva] Debian Linux-on fájl tulajdonságok lekérdezése stat használatával ACL jogosultságok mellett.

Sziasztok!

Rekurzívan szeretnék egy adott mappában lévő fájlokról bizonyos információkat kinyerni, a stat programra gondoltam:

find . -exec stat --printf '%A\t%a\t%s\t%f\t%F\t%h\t%i\t%u\t%g\t%U\t%G\t%W\t%Y\t%Z\t%X\t%w\t%y\t%z\t%x\t%N\n' {} \; > test.csv

A dolog "majdnem jó" :) a "gond" hogy vannak ACL jogokkal rendelkező  fájlok amiknél lehagyja a jogosultságok végén a "+" jelet.

Ha az alábbi parancsot használom:

find . -exec ls -l -d {} \; > test.txt

Akkor az egyik ACL-el rendelkező fájlhoz tartozó sor:

-rw-rw-r--+ 1 www-data www-data 8 jún   16 12:07 ./back1/file3.txt

Ugyanez a stat használatával:

-rw-rw-r--    664    8    81b4    szabályos fájl    1    786444    33    33    www-data    www-data    0    1592302074    1592306178    1592302568    -    2020-06-16 12:07:54.936730699 +0200    2020-06-16 13:16:18.413802694 +0200    2020-06-16 12:16:08.887845035 +0200    './back1/file3.txt'

Mint látható a "-rw-rw-r--" oszlop végéről hiányzik a "+" jel. (A további feldolgozás miatt szükségem van rá, mert nélküle nem kérdezi  le az ACL jogokat egy másik script amit nem szeretnék módosítani..)

A probléma megoldására az alábbi Perl script-et írtam mely működik is csupán nagyon "lassú":
 

#!/usr/bin/perl

#karakterkészlet beállítása
#use Encode;
#use utf8;
#use open ':std', ':encoding(UTF-8)';

$_=`date`;
chomp($_);
print STDERR "\nStarted!\t( $_ )\n\nGet file list, please wait few minute...\n\n";

@filelist=`find .`;

$filelistCount=@filelist;
$i=0;
print STDERR "File list get commplette!\t( files num: $filelistCount )\n\n";

$_=`date`;
chomp($_);

print STDERR "Start processing, please wait...\t( $_ )\n\n";

print "Premissions BITS\tPremissions OCT\tSize of BYTE\tFiletype RAW\tFiletype STRING\tHard link num\tInode ID\t";
print "Owner user ID\tOwner grup ID\tOwner user\tOwner grup\t";
print "Create time UNIX\tModify time UNIX\tChanged time UNIX\tAccess time UNIX\tCreate time\tModify time\tChanged time\tAccess time\t";
print "Filename";

foreach $_ (@filelist)
{
        #
        chomp($_);
        #print "$file\t";
        $file=quotemeta($_);
        $fileStat=`ls -l -d $file | awk '{ printf \$1 }'`;

        print "\n$fileStat";
        $fileStat=`stat --printf \'\\t\%a\\t\%s\\t\%f\\t\%F\\t\%h\\t\%i\\t\%u\\t\%g\\t\%U\\t\%G\\t\%W\\t\%Y\\t\%Z\\t\%X\\t\%w\\t\%y\\t\%z\\t\%x\\t\%N\' $file`;


        print $fileStat;
        $i++;

        $_=`date`;
        chomp($_);

        print STDERR "\rProcessing... ( $filelistCount / $i )\t( $_ )";
}

$_=`date`;
chomp($_);

print STDERR "\n\nCommplette processing\t( $_ )\n\n";

print STDERR "Finish!\n\n";

Jelenleg 917873 fájlon dolgozom, maga a find parancs kb 30 másodperc alatt végez, de a script kb 9 óra alatt futott le.

Amennyiben az "egysoros find -exec stat..." megoldást használom a lefutási idő 40-60 perc ami még éppen elfogadható...

Ha külön külön futtatom le az ls-t és a stat-ot egymás után és utána "fűzöm össze" akkor a nagy idő különbség miatt valós kockázata lesz hogy nem fognak egyezni a stat és az ls által kiírt adatok...

Van megoldás rá hogy a stat is odategye azt a "+", esetleg hogy 10-20 szorosára gyorsuljon a feldolgozás?

(A date használata, különösen a ciklusban kb 25% lassít, csak a "tesztelés" véget tettem bele, sajnos a kihagyásuk sem oldja meg a sebességbeli problémát.)

Bármilyen építő jellegű ötletet szívesen fogadok.

Köszönettel: Novarobot.

Hozzászólások

Szerkesztve: 2020. 06. 17., sze - 16:13

A cím... #facepalm... Mellet, mint női mellet. Mellett, mint valami mellett.

Javítva :-)

Szerkesztve: 2020. 06. 17., sze - 16:14

Nomostan bash scriptet írni Perl nyelven azért az elég bátor, vagy inkább botor dolog. Tessék szépen odaadni a könyvtárfeldolgozó eljárásnak a paraméterként kapott könyvtárat, ez a könyvtárfeldolgozó rutin nyissa meg, olvassa végig, és ha az adott elem fájl, akkor stat() és mehet a kiírás, ha dir, akkor meg tessék szépen odaadni a könyvtárfeldolgozó eljárásnak... Satöbbi. (Rekurzió: lásd rekurzió). Ha végigiolvastad a könyvtárat, akkor kilépsz a könyvtárfeldolgozó rutinból.

"a date rendszerhívások" - az nem rendszerhívás, az külső program futtatása. Annál még a fapados "scalar (localtime)" is jobb, ha nem nagyon lényeges, hogy az időadat milyen "szépen" van formázva.

Szerkesztve: 2020. 06. 17., sze - 16:12

"Van megoldás rá hogy a stat is odategye azt a "+" "? Röviden a válasz: nincs, mert ahhoz kéne a libacl, az meg nem köll a stat-nak:


[16:09:55] root@zeller:/]# ldd $(which stat)
        linux-vdso.so.1 =>  (0x00007ffcc5df0000)
        libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f22f030c000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f22eff3e000)
        libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f22efcdc000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f22efad8000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f22f0533000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f22ef8bc000)
[16:09:55] root@zeller:/]#

A többivel kb. minddel egyetértek, de ezzel nem:

$ stat --printf='%A\t%n\n' test
-rw-rw-r--    test
$ /bin/ls -l test
-rw-rw-r--+ 1 zgabor zgabor 0 jún   17 16:40 test
$ ldd /bin/ls $(which stat)
/bin/ls:
    linux-vdso.so.1 =>  (0x00007ffd74649000)
    libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007fb94ae2c000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb94aa62000)
    libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007fb94a7f2000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fb94a5ee000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fb94b04e000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb94a3d1000)
/usr/bin/stat:
    linux-vdso.so.1 =>  (0x00007ffec6198000)
    libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f32e641e000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f32e6054000)
    libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f32e5de4000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f32e5be0000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f32e6640000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f32e59c3000)

ugye látod, hogy az ls-nek sincs libacl-je, oszt mégis kiírja a pluszot?

Köszönöm az észrevételeket, a megfogalmazásbeli és gépelési problémákat javítottam.

Ez a könyvtárbejárós megoldás meggondolandó, már elkezdtem a megvalósítását bár sebességbeli és működésbeli kétségeim vannak ugyanakkor mindenképpen érdemes kipróbálni.

A gondom hogy a szükséges információk lekérdezésére nem nyújt megoldást, mert nem a "find ."  a lassú hanem a foreach (egyébként maga a programhívás a lassú) és ez nem lesz gyorsabb attól ha máshogy kérdezem le a fáj listát...

(A kész fájl listán végigmenni szerintem a leggyorsabb, bár nem a "legszebb" megoldás.

A Perl ben lévő stat() függvény meg nem tudja az összes szükséges információt lekérdezni és rendszerenként eltérő a visszakapott tömb szerkezete.

Elfelejtettem írni hogy pontosan miket kérdezek le a fájlokról:

%A   hozzáférési jogok ember által olvasható formában   <---- Ugye ez az amivel szenvedek
%a   hozzáférési jogok oktálisan (figyeljen a „#” és „0” printf jelzőkre)
%s   teljes méret, bájtokban
%f   nyers mód hexadecimálisan
%F   fájltípus
%h   hard linkek száma
%i   I-node szám
%u   a tulajdonos felhasználói azonosítója
%g   a tulajdonos csoportazonosítója
%U   a tulajdonos felhasználóneve
%G   a tulajdonos csoportneve
%W   a fájl születési ideje az Epoch óta eltelt másodpercekként
%Y   utolsó módosítás ideje az Epoch óta eltelt másodpercekként
%Z   utolsó változtatás ideje az Epoch óta eltelt másodpercekként
%X   utolsó hozzáférés ideje az Epoch óta eltelt másodpercekként
%w   a fájl ember által olvasható születési ideje
%y   utolsó módosítás ember által olvasható ideje
%z   utolsó változtatás ember által olvasható ideje
%x   utolsó hozzáférés ember által olvasható ideje
%N   idézőjelek közé tett fájlnév, követéssel, ha szimbolikus link

Sajnos se a Perl beépített "stat" függvénye se az "ls" nem tud ezek közül mindent (az "ls" csak létrehozási, változtatási és a hozzáférési időt nem tudja...)

A getfacl-t meg direkt csak azokra a fájlokra hívom meg ahol van ACL, mert minden fájlra nagyon nagyon sokáig tartana, pont ezért kell nekem az a "+" jel.

Eredetileg shell ből akartam megoldani hogy valahogyan összekötni az ls-t meg a stat -ot hogy egymásután írja ki mindegyik a maga "részét" de az nem jött össze.

Ilyesmikkel próbálkoztam:

find . -exec (ls -l -d {} & stat --printf '%A\t%a\t%s\t%f\t%F\t%h\t%i\t%u\t%g\t%U\t%G\t%W\t%Y\t%Z\t%X\t%w\t%y\t%z\t%x\t%N\n' {} ) \; > test.csv

find . -exec (ls -l -d {} ; stat --printf '%A\t%a\t%s\t%f\t%F\t%h\t%i\t%u\t%g\t%U\t%G\t%W\t%Y\t%Z\t%X\t%w\t%y\t%z\t%x\t%N\n' {} ) \; > test.csv

find . -exec ls -l -d {} & stat --printf '%A\t%a\t%s\t%f\t%F\t%h\t%i\t%u\t%g\t%U\t%G\t%W\t%Y\t%Z\t%X\t%w\t%y\t%z\t%x\t%N\n' {}  \; > test.csv

De sehogy nem jött össze, meg hát nagyon bosszant hogy mindegyik paranccsal van valami "apróság" ami miatt nem jó.

Van még javaslat, hogy merre nézelődjek, Perl hívás ami esetleg jó lehet vagy alternatív stat amit meg tudok hívni?

" nem a "find ."  a lassú hanem a foreach (egyébként maga a programhívás a lassú) és ez nem lesz gyorsabb attól ha máshogy kérdezem le a fáj listát " - a find, pláne útvonal nélkül nagyon ronda - nem tudhatod, hogy melyik és milyen lesz épp a PATH-on, ez az egyik. A másik, hogy ezzel a teljes adathalmazt belapátolod a memóriába, amin mész végig, és a ciklusban elindítod az ls-t, az awk-t a stat-ot minden fájlra, sőt, még indul egy date is, szintén minden fájl esetében.
Ezeknek a külső programoknak az indítása "tart sokáig", ez az, ami iszonyatosan "drága" művelet. A "stat..." parancsot például egy stat() függvénnyel nagyjából ki tudod váltani (meg némi körítés azok miatt a dolgok miatt, amit a stat() nem ad vissza, de használni szeretnél).

Én átgondolnám, hogy _pontosan_ mi a feladat, milyen adatok relevánsak a kimeneten - illetve hogy ezeket a stat() illetőleg a Linux::ACL getfacl)() -je hogyan tudja neked prezentálni. Az acl-es rész trükkösebb lesz, a getfacl() által visszaadott adatszerkezetet kell vizsgálni, hogy uperm, gperm, other-en kívül van-e benne valami definiálva.

find . -exec ls -l -d {} \; -exec stat --printf '%A\t%a\t%s\t%f\t%F\t%h\t%i\t%u\t%g\t%U\t%G\t%W\t%Y\t%Z\t%X\t%w\t%y\t%z\t%x\t%N\n' {}  \; > test.csv

Egy komoly hibája van: ha valamely ls esetleg hibastátuszt ad vissza, akkor a hozzá tartozó stat már nem fog lefutni (bár ez szerintem nem baj, meg nyilván akkor már úgy is mindegy).

Nagyon ez se lesz gyors, hiszen a fájlonként végrehajtott 2 db fork+exec ami az ls-hez és stat-hoz kell, az eléggé megfogja a dolgot. Azaz valóban jó volt az irány, hogy perl-ből csináld meg, csak épp te ott nem a perl natív rendszerhívásokra "leforduló" műveleteit használod (*), hanem pontosan ugyanúgy indítasz néhány fork+exec párost. (Mondjuk az én példám szerintem jobban spórol ezekkel, mint a tied, de igazából nem ez lesz a döntő.)

(*) Nyilván, ha se a perl-beli ls, se a perl-beli stat nem adja vissza a kívánt eredményt, akkor nem sok lehetőséged van.

Azt mindenképpen tisztán kell látnod, hogy még ebben az esetben is van némi időcsúszás egy fájl ls-sel és stat-tal lekért adatainak begyűjtése közben, azaz így is belefuthasz abba, hogya két művelet közben valami módosítja a fájlt - legfeljebb naívan bízhatsz benne, hogy ly csekély az időkülönbség a két processz indítása között, hogy nem valószínű, hogy tényleg bele is fogsz futni. Ez viszont klasszikus hibás feltételezés, szóval mission critical ezsközt ne alapozz rá. Ha viszont ezt a "csekély" veszélyt bevállalod, akkor már azt is megteheted, hogy:

- futtatsz egy find -ot, és az eredményét eltárolod

- ezek után futtatsz egy ciklust, ami előbb N. db fájlnévre futtat ls X1 X2 X3 X4 ... parancsot, és stat X1 X2 X3 X4 ... parancsot is, mely parancsok eredményét külön gyűjtöd mondjuk LS meg STAT néven

- és a végén egyet innen, egyet onnan alapon összepároztatod az összetartozó adatokat.

Ez annyival "jobb" mint a te kódod, hogy nem fájlonként futtat 2 külső parancsot (2 fork + 2 exec árán), hanem 2, 3, 4, 5, ... N fájlonként. Nyilván az ideális az lenne, ha a lehető legkevesebbszer kellene a külső parancsot meghívni - azaz hívásonként a lehető legtöbb paramétert adnád oda nekik, amennyit egyáltalán lehet. Erre van egy nagyszerű, xargs nevű segédprogram, csak épp egy baj van: az "ls -ld fájlnév" sokkal rövidebb sztring, mint a "stat --printf='.........' fájlnév", azaz nem tudod biztosítani, hogy amennyi paraméter jó az ls-nek, az jó a stat-nak is, szóval ezt valahogy külön kezelni kell. (Ennek egyik módja pl., hogy először az ls-eket futtatod, és utána a stat-okat. Ekkor persze megint megnőtt a fájl adatának lekérései közötti idő.)

De hogy még tovább bonyolítsam, mivel a UNIX / Linux világ számára teljesen természetes, hogy egy fájlnévben szóköz, tabulátor, SOREMELÉS karakter előfordulhat, ha mindenképpen törekszel a biztonságra, akkor már minimum arra odafigyelsz, hogy pl. a find kapja meg a -print0 paraméterét, hogy ne soremeléssel, hanem NULL bájttal legyenek elválasztva a fájlnevek, és a továbbiakban is (pl. ha tényleg beveted az xargs-ot, akkor annak is kell egy -0 opció.)

Szóval zűrös.

Írtatok jó néhány gondolat ébresztő dolgot, ami alapján elindulhatok és ezt köszönöm.

(Éjszakai rendszerátállást felügyelek éppen, meg kell várnom a végét így van időm értelmezni / hasznosítani a tanácsokat).

Számomra is egyértelmű volt hogy maga a (nem "rendszer hívás" de a "shell" sem "korrekt", mert mi van ha bash vagy akármi más hirtelen nem tudom mi lenne a megfelelő kifejezés... :D ) külső programindítás a "drága" művelet. (számítottam is rá, csak alábecsültem az idejét)

Az is világos (ahogy utaltam rá, bár nem fejtettem ki) hogy ha külön hívom meg az ls-t és külön a stat ot akkor elég 2 programindítás amivel kb 1 órára csökken a futási idő (hozzávetőlegesen 7* sebességnövekedés...).

Ahogy te is írtad már az sem "szép" hogy egymás után közvetlenül hívom meg az ls-t és a stat-ot mert így kicsi az esélye a közbeni módosításnak, de közel sem 0 ami bizony komoly szoftvertervezési hiba ( ilyen hibák felkutatásával is foglalkozom, persze nem profi szinten így ki kellene takarítani onnan...).

A Linux "megengedő" fájlneveihez már volt (bal)szerencsém,​ van egy szép mappanevem ezen szoktam tesztelni (amit egyébként az alapértelmezett fájlkezelőben hoztam létre és szó nélkül engedte):

0123456789öüóqwertzuiopőúasdfghjkléáűíyxcvbnm,.-%:_-?!+'"()[]{}@&#><,;÷ß$Ł×¤đĐÄ`˙´¨¸~ˇ^^˘°˛=

A quotemeta() fv az eddigi (hasonló) programjaimba (ahogy itt is) segített ezen...

Nemrég tudtam meg hogy az új sor is megengedett, és minden a 0 bájt és a / kivételével (\t, \n \r stb ...) ilyen fájlnévvel még nem teszteltem...

Az ls-t pl így használtam a jelen problémára (bár korábban ezt sem írtam) : find -print0 | xargs -0 ls -ld | awk '{ print $1 }'​ , persze -exec esetén nem jött össze:

find -print0 -exec cat {} | xargs -0 ls -ld | awk '{ printf "$1" }' \;
find -print0 -exec echo {} | xargs -0 ls -ld | awk '{ printf "$1" }' \;
find -print0 -exec xargs -0 ls -ld | awk '{ printf "$1" }' \;

(Még valamit nagyon elszúrok ami alapvető "hiányosságaimra" vezethető vissza).

Így nem tudom ebben az esetben sem az xargs -t használni:

find . -exec ls -l -d {} \; -exec stat --printf '%A\t%a\t%s\t%f\t%F\t%h\t%i\t%u\t%g\t%U\t%G\t%W\t%Y\t%Z\t%X\t%w\t%y\t%z\t%x\t%N\n' {} \; > test.csv

Próbáltam -exec sh -c "..." vel is de úgy sem ment, már ez sem fut le:

find . -exec ls -l -d {} | awk { printf "$1" } \; -exec stat --printf '%A\t%a\t%s\t%f\t%F\t%h\t%i\t%u\t%g\t%U\t%G\t%W\t%Y\t%Z\t%X\t%w\t%y\t%z\t%x\t%N\n' {} \; > test.csv

Pedig szerintem ez egész gyors lenne...

Közben vadászgatom a Perl fv-ket hogy ki tudom e legózni valahogy a szükséges kimeneti formátumot...

Sokféle hiba van a find-os próbálkoásaidban, a leglényegesebb, hogy a | jelet nem véded le. De mivel a find az exec-nél - tudtommal - nem egy teljes shell-t indít, ezért az, hogy egy find-exec-jében egy a | b tipusú parancsot próbálsz indítani, az nem fog működni, ahhoz tényleg sh -c 'a | b' formában kell odaírni azt a parancsot.

 

De mi a baj azzal a find példával, amit én írtam? (Már eltekintve az ls és a stat indítása között eltelő időtől) ?

Az ls -l -d kimenete pl. :

drwxr-xr-x 2 root root 4096 jún   18 10:23 ./tmp

Nekem le kell vágnom a végét, és az új sor karaktert is hogy vele egy sorba írja a stat is a maga részét...

Rossz parancsot írtam fentebb, ez lenne a helyes:

ls -l -d ./tmp | awk '{ printf "%s", $1 }'

drwxr-xr-x

Egyébként nincs baj a példával sőt nagyon tetszik remélem össze tudom rakni és mérni egy sebességet...

Részben sikerült, az alábbi parancs (sokadik próbálkozásra) "jó" lett:

find . -exec sh -c "ls -l -d {} | awk '{ printf \"%s\" , \$1 }' " \; -exec stat --printf '\t%a\t%s\t%f\t%F\t%h\t%i\t%u\t%g\t%U\t%G\t%W\t%Y\t%Z\t%X\t%w\t%y\t%z\t%x\t%N\n' {}  \; > ./test.csv

Kimenetének egy sora:

drwxr-xr-x    755    4096    41ed    könyvtár    2    271421    0    0    root    root    01592468603    1592468603    1592469496    -    2020-06-18 10:23:23.724711602 +0200    2020-06-18 10:23:23.724711602 +0200    2020-06-18 10:38:16.315489278 +0200    './tmp'

Ez így jó, de valahogy bele kellene fűzni még azt a -print0 és xargs -0 mert a fent írt "szép mappanevemen" bizony elhasal.

Ez eddig még sehogy nem jött össze, ha -exec et használok nem tudom az xargs el fogadni (az sem egyértelmű számomra hogy a find figyelembe veszi a -print0 akkor is ha -exec van.)

Hátulról előre haladva:

1) mivel a -exec meg a -print0 ugyanúgy csak egy "feltétel", mint pl. a -name vagy a -size, természetesen print0-t is írhatsz -exec után - pont ahogy a saját magad által összeállított -exec sh .... \; -exec stat ... \; esetén a 2. -execet is odaírtad. Az már más kérdés, hogy minek tennéd, hiszen ezzel a nyakatekert megoldással már nem kell az xargs, hisz összegyűjtötted a szükséges infókat. Én az xargs-ot (meg a -print0 -t ahhoz javasolta, ha mégis ki akarod vinni az ls és stat futtatást a find fennhatósága alól.

2) A ronda mappaneveden biz el fog hasalni a dolog, de leginkább azért, mert azt maga az ls és a stat is kiírja. Az ls-nek ugyan van erre eszköze (hint: -b), de a stat-nak nincs. Szóval max az lehetne a segítség, hogy a stat paramétereiből kiszeded a legvégén szereplő %N-et, és egy harmadik -exec ls -b {} segítségével íratnád ki a speciálisan formázott fájlnevet. (Összefoglalom: ha még 50%-kal több fork+exec-et raknál bele, lehet kész lennél.) Bár belegondolva, e helyett már használhatnád a find beépített -ls-jét.

Ez pedig egy teljesen másik megközelítés: ha az általam írt

find ... -exec ls ... \; -exec stat .. \;

paranccsal az a baj, hogy az ls túl sok mindent ír ki, és ráadásul 1 fájl adatai 2 sorban jelennek meg (felváltva ls, majd stat; utána megint ls, megint stat), akkor a helyedben a következőt csinálám (csak hogy legyen kicsit UNIX cli kezdőknek hangulata is a dolognak) :

- van egy paste nevű parancs, amely a paramétereként megadott fájlok azonos "pozícióban" levő sorait összeragasztja. A kimeneten az első sor az összes fájl első sorának tabulátorokkal (vagy általam megadott szeparátorral) összeragasztott adata; a második sor az összes második sor összeragasztva, és í. t. Trükkös módon megadható neki a - a standard input jelzéseként, ráadásul akár többször is. Ilyen módon a find mögé rakd oda, hogy | paste - - (azaz két önálló paraméterként a - ) és máris egy sorban lesz egy fájl adata - elől amit az ls ír, utábna pedig amt a stat.

- és miután már egy sorban van minden, akkor kidobhatod a francba azokat, amik az ls-féle kimenetből nem érdekesek; pl. egy | awk '.... ' hozzáragasztásával.

Eredménye valami ilyesmi lesz:

find ... -exec ls ... \; -exec stat .. \; | paste - - | awk '{ $2=$3=$4=$5=$6=$7=$8=$9=""; print;}'

Jbb-e ez, mint a te megoldásod? A franc se tudja, de szerintem olcsóbb.

Te minden fájlra hívsz

- exec-cel egy shellt

- ami minimum hív egy ls-t meg egy awk -t, tehát még 2 processzt

- aztán jön a másik exec a stat-tal.

Azaz numfiles *4 processz (meg a find)

Az én megoldásom pedig numfiles * 2 + 2 processzt (plusz find). Ez egy viszonylag egyszerű egyenletrendszer, amiből könnyen kiszámolható, hogy

0 db fájl esetén a tied olcsóbb

1 db fájl esetén egyenlő (processz számban)

és 2 vagy több fájl esetén a tied nagyon elszalad processz létrehozásban - ez a megoldás pedig közelítőleg fele annyi processzt kreál.

Ellenben a te megoldásod fájlonként kb 40-50 bájttal kevesebbet ír ki, míg az én megoldásomban ez 2x is átmegy egy-egy pipe-on (tipikusan memóriastruktúra) és csak a legvégső kiíráskor csökken le olyasmire, mint a te megoldásod. *Szerintem* gyorsabb lesz sokkal, és nem eszik annyival több memóriát, hogy problémát okozzon. (Hint: a pipe-k memóriahasználata korlátos.)

Ez nagyon tetszik, nem ismertem a paste -t parancsot.

Amiket eddig kipróbáltam:

Így nem tudok 2 parancsot egymás után lefuttatni: 

find . -print0 | xargs -0 ls -l -d | awk '{ printf("%s\n",$1) }'

Így meg elhasal a "különleges" karaktereknél:

find . -exec sh -c "ls -l -d {} " \;

Így működik, csak nem lehet beletenni az AWK -t : 

find . -exec sls -l -d {} " \;

A te megoldásod kipróbáltam, 63 perc alatt futott le ami olyan 7* sebesség növekedés! (ez sebesség számomra már tökéletes...)

A javaslatod alapján összeállított teljes parancs így nézett ki:

find . -exec ls -l -d {} \; -exec stat --printf '\t%a\t%s\t%f\t%F\t%h\t%i\t%u\t%g\t%U\t%G\t%W\t%Y\t%Z\t%X\t%w\t%y\t%z\t%x\t%N\n' {}  \; | paste - - | awk '{ $2=$3=$4=$5=$6=$7=$8=$9=""; print;}' > ../testN1.csv

Kimenetének egy sora:

drwxr-xr-x         755 4096 41ed könyvtár 8 55967748 0 0 root root 0 1306217484 1591992664 1592490373 - 2011-05-24 08:11:24.000000000 +0200 2020-06-12 22:11:04.956520765 +0200 2020-06-18 16:26:13.078972104 +0200 './dbs'

Ezzel 2 gondom van még, egyrészt a TAB-okat (az 1. kivételével) SPACE re cserélte, a másik gond, hogy "elcsúsznak" az oszlopok, ha pl symlink a fájl vagy ha szóköz van a nevében. (Az ls fájlnév mezője miatt)

Arra gondoltam hogy az AWK -ban a mezőelválasztót TAB ra kellene tenni. (ami még mindig nem tökéletes mert elvileg lehetne TAB is a fájlnévben de olyan fájl egyenlőre nincs és nem is valószínű hogy lesz mert a legtöbb program nem is tud ilyet létrehozni).

Majd AWK -val kiíratni az első 11 karaktert és minden oszlopot a $1 kivételével (szerintem tud ilyet, de még nem sikerült "beidomítani")...

(Mivel az ls SPACET míg a stat TAB ot használ szeparátorként)

Persze ehhez meg kellene oldani hogy maradjanak a TAB ok.

Ezzel próbálkoztam (csak a végét írom):

..... | paste - - | awk -F '\t' '{ $1=""; print $0;}'

Erre szépen levágta az ls kimenetét (ezt is vártam tőle :D ) de a kimenetbe továbbra is SPACE van nem TAB illetve azt hogy az első 11 karakter kiírja azt fogalmam sincs hogy kellene vele megoldani...

Arra is gondoltam hogy kiveszem a paste parancsot, és helyére írok egy perl scriptet ami:

Standard bemenetről olvas.

Megnézi az 1. karaktert

Ha az 1. karakter TAB akkor kiírja az egész sort;

Ha nem TAB akkor kiírja az első 11 karaktert...

És ezzel kész is...

Mert ha az adott sorba az ls írt akkor biztos hogy nem TAB al kezdődik a sor és az első 11 karakter érdekel, ahol a 11. karakter vagy SPACE vagy + (vagy . de olyan sem lesz nálam) és így a soremelés sem fog kiíródni.

Ha meg a stat írt a sorba akkor biztos hogy TAB al kezdődik a sor és mehet az egész sor \n el a végén.

A Perl script-et még nem próbáltam ki annak nem tudom a sebességét, de szerintem több lesz mint 63 perc...

Remélem az awk idomításában tudsz egy kicsit segíteni... :)

Addig kipróbálom a Perl script-es verziót.

Egy dologra érdemes ügyelni (tapasztalatból mondom...): Látott már olyat a világ, hogy az "ls" parancs kimeneti formátuma/kapcsolói változtak, miközben arra épített az alkalmazás, hogy az ls -kisszékhóember parancs kimenete pont a neki megfelelő formátumban ad vissza mindent. Na erre anno olyan workaround született, hogy a rég ls parancsot át kellett másolni minden újonnan épített gépre, és az adott alkalmazás technikai userénél az azt tartalmazó könyvtárat oda kellett biggyeszteni a PATH elejére.
 

Két egymás alatti hozzászólásomból az elsőt is figyeld, ott megemlítettem egy félmondatban az ls "-b" opcióját. Innentől nem lesz gondod a fájlnévben levő tab, soremelés és egyéb nyalánkságok kezelésével. És mintha a stat is hasonlót csinálna.

A másik, hogy ha problémás a space és tab kezelése és egységesíteni akarod, akkor az awk-val a kiíratás előtt csinálhatsz egy gsub( " ", "\t", $0 ); -t. De ha ez nem segít, az valószínűleg azért van, mert hiába állítod a -F opcióval az FS - azaz az (input) FIELD SEPARATOR - értékét, attól még az OFS (OUTPUT FIELD SEPARATOR) az alapértelmezés szerint változatlanul space, szóval a BEGIN mintában azt is állítsd át pl. így (és ekkor a -F opció már el is hagyható):

BEGIN {FS=OFS="\t" ; }

..... | paste - - | awk -F '\t' '{ $1=""; print $0;}'

Erre szépen levágta az ls kimenetét (ezt is vártam tőle :D ) de a kimenetbe továbbra is SPACE van nem TAB illetve azt hogy az első 11 karakter kiírja azt fogalmam sincs hogy kellene vele megoldani...

 

Erre is reagálnék:

 

még mielőtt eldobod a $1 -et, ments el azt a szükséges 11 karaktert (csak a parancs legvége)

 

| awk 'BEGIN { FS = OFS = "\t" ; } { eleje=substr( $0, 1, 11 ) ; $1 = "" ; print eleje $0 ; }'

az race condition-re reagálnék. lehet, hogy most elfogadható sebességgel fut és még *elég* konzisztensnek tartod egy lefutás kimenetét, de ha lényeg hogy konzisztens eredményt kapj, akkor snapshot-on dolgozz, ott viszont már nyomhatod több pass-ban az adatgyűjtést (find és getfacl -R).

Szerkesztve: 2020. 06. 21., v - 14:14

Végigmértem a különböző futási időket.

A find . parnacs önnamgában 2 másodperc és több perc között fut végig a 917 873 db fájlon.

Tapasztalatom szerint a leglassabb ha a "képernyőre" kérem a kiirtást, a leggyorsabb ha memóriába (pl Perl ben: @find=`find .`; ) a fájlba történő irányítás meg valahol a kettő között.

Egyébként látszik hogy a PIPE-n keresztül is picit lassul a futás.

Az hogy a find egymás után 2 parancsot is lefuttasson volt a megoldás alapja, köszönet érte Zhay -nak:

find . -exec ls -l -d {} \; -exec stat --printf '%A\t%a\t%s\t%f\t%F\t%h\t%i\t%u\t%g\t%U\t%G\t%W\t%Y\t%Z\t%X\t%w\t%y\t%z\t%x\t%N\n' {}  \;

Innentől a megoldás már meg volt, csak lassan fogtam fel, hogy ezt is tovább lehet irányítani.

Külön köszönöm a paste, és az awk útmutatót.

A két -exec kiegészített find parancs hozzávetőlegesen 60 perc alatt fut le a már említett 917873 fájlon, ha fájlba irányítom, ha PIPE-ra vagy ha Perl -ből hívom meg a futási idő 10% kevesebbet változik. Egyedül ha a "képernyőre" iratom ki nő meg jelentősen (kb 50% al)

Az alábbi Perl script re PIPE-olva is kipróbáltam:

#!/usr/bin/perl

while(<>)
{
	if($_=~/^\t/)
	{
		print $_;
	}
	else
	{
		print substr($_,0,11);
	}
}

Gyakorlatilag mind a Zahy segítségével összerakott paste + awk mind a fent írt Perl script re PIPE -olva 63 -65 perc alatt fut le.

Végül az alábbi Perl scriptet írtam:

#!/usr/bin/perl

#karakterkészlet beállítása
#use Encode;
#use utf8;
#use open ':std', ':encoding(UTF-8)';

#########Bemeneti paraméterek kezelése, késöbb bővíthető ha kell...

#Default értékek a változókhoz...
%argumentsList=
	(
		"CMD"			=>	$0,
		"workDir"		=>	"",
		"output"		=>	"",
		"findCMD"		=>	"find",
		"statCMD"		=>	"stat",
		"lsCMD"			=>	"ls",
	);

#lehetséges argumentumok listálya
%valueVariableArguments=
	(
		"-wd"			=>	"workDir",
		"--work-dir"  	=>	"workDir",
		"-of"			=>	"output",
		"-find"			=>	"findCMD",
		"-stat"			=>	"statCMD",
		"-ls"			=>	"lsCMD"
	);

%nonValueVaraibleArguments=
	(
		"-get-acl"		=>	"getAcl",			#még nincs kész,
		"-hide-message"	=>	"hideMessage",		#egyszerr
	);

foreach $argument (@ARGV)
{
	if($argument=~/^-/ || $argument=~/^--/)
	{
		if(exists($nonValueVaraibleArguments{$argument}))
		{
			$argumentsList{$nonValueVariableArguments{$argument}}=1;
		}
		else
		{
			($variable,$value) = split(/=/,$argument);

			#if(exists($valueVaraibleArguments{$variable}))
			if(exists($valueVariableArguments{"-wd"}))
			{
				$argumentsList{$valueVariableArguments{$variable}}=$value;
			}
			else
			{
				#ISMERETLEN PARAMÉTER...
				print STDERR "Ismeretlen argumentum: $argument ... ($variable\t$value\t$valueVariableArguments{$variable})\n";
				exit;
			}
		}
	}
	else
	{
		if( ! $argumentsList{"workDir"} )
		{
			$argumentsList{"workDir"}=$argument;
			print "!\n";
		}
		else
		{
			$argumentsList{"output"}=$argument;
		}
	}
}

if($argumentsList{"workDir"} eq "")
{
	$argumentsList{"workDir"}=".";
}

#print "\nargumentsList\n";
#foreach $_ (keys(%argumentsList))
#{
#	print $_."\t".$argumentsList{$_}."\n";
#}
#
#print "\nvalueVariableArguments\n";
#foreach $_ (keys(%valueVariableArguments))
#{
#	print $_."\t".$valueVariableArguments{$_}."\n";
#}
#
#print "\nnonValueVaraibleArguments\n";
#foreach $_ (keys(%nonValueVaraibleArguments))
#{
#	print $_."\t".$nonValueVaraibleArguments{$_}."\n";
#}

$_=`date`;
chomp($_);

print STDERR "\nKezdés!\t( $_ )\n\nFájl lista lekérdezése, ez percekig eltarthat TÜRELEM...\n\n";

@filelist=`find $argumentsList{"workDir"}`;

$filelistCount=@filelist;

print STDERR "Fájl lista lekérdezve!\t( Fájlok száma: $filelistCount )\n\n";

$_=`date`;
chomp($_);

print STDERR "Feldolgozás indítása...\t( $_ )\n\n";

if( $argumentsList{"output"} )
{
	open(STDOUT, $argumentsList{"output"}) or die "Kimeneti fájlnyitás sikertelen: $!\n";
}

print "Fájlnév\t";
print "Jogosultságok (BIT)\tJogosultságok (OCT)\tMéret (BYTE)\tFájltipus (RAW)\tFájltipus\tHard linkek száma\tInode ID\t";
print "Tulajdonos ID\tCsoport ID\tTulajdonos név\tcsoportnév\t";
print "Létrehozási idő UNIX\tMódosítási idő UNIX\tVáltoztatási idő UNIX\tHozzáférési idő UNIX\t";
print "Létrehozási idő\tMódosítási idő\tVáltoztatási idő\tHozzáférési idő\t";
print "Teljes fájlnév\tACL jogosultságok oszlopai...\n";

open(PIPE,"find ".$argumentsList{"workDir"}." -print -exec ".$argumentsList{"statCMD"}." --printf '\\t\%A\\t\%a\\t\%s\\t\%f\\t\%F\\t\%h\\t\%i\\t\%u\\t\%g\\t\%U\\t\%G\\t\%W\\t\%Y\\t\%Z\\t\%X\\t\%w\\t\%y\\t\%z\\t\%x\\t\%N\\n' {}  \\; -exec ".$argumentsList{"lsCMD"}." -l -d {} \\; |");

$i=0;
while (<PIPE>)
{
	#print $_."--\n";
	if($_=~/^\t/)	#Ha tab akkor a stat írt...
	{
		#print $_;
		$part2=substr($_,0,10);		#A tab-al kezdve másolni 10 karateren a jogosultságokat (pl: \tdrwxrwxrwx)
		$part3=substr($_,11);	#A 11. karaktertől a tab-al kezdődöen az egész sztring másolása, a \n el a végén
	}
	elsif( $_=~/^\./ || $_=~/^\// )	#Ha . vagy / kezdődik akkor a find által írt fájlnév.
	{
		chomp($_);		#a \n törölni a végéről.
		$part1=$_;		#Fájlnév másolása.
	}
	else 	#Ha nem tab-al nem .-al és nem /.el kezdődik, akkor az ls írt.
	{
		#Kiírni egymás után a par1, part2
		#majd az ls kimenetéből a 11. karaktert (szóköz, vagy + esetlegesen . de olyan attributumot nem használok)
		#kiírni a part3 is...

		print $part1.$part2.substr($_,10,1).$part3;
	}

    $i++;

    print STDERR "\rFeldolgozás... ( $filelistCount / ".int($i/3)." )";
}

$_=`date`;
chomp($_);

print STDERR "\n\nFeldogozás befejezve\t( $_ )\n\n";

print STDERR "Kész, kilépés!\n\n";

És egy futás eredménye:

root@KVMserver:/backup# ./FileStatV4.pl ./NFS/ -of=>out.csv

Kezdés!	( 2020. jún. 21., vasárnap, 00:45:03 CEST )

Fájl lista lekérdezése, ez percekig eltarthat TÜRELEM...

Fájl lista lekérdezve!	( Fájlok száma: 917873 )

Feldolgozás indítása...	( 2020. jún. 21., vasárnap, 00:45:05 CEST )

Feldolgozás... ( 917873 / 917873 )

Feldogozás befejezve	( 2020. jún. 21., vasárnap, 01:46:38 CEST )

Kész, kilépés!

(A program jó, de tele van helyesírási gépelési hibákkal majd javítom most örülök hogy fut...)

Néhány módosítás csináltam még:

- PIPE -on keresztül nyitom meg a find parancsot hogy ne "egyben kapjam meg" azt a kb 500MB adatot.

Kicsit utána néztem a PIPE -nak, és ha jól értem pl az alábbi példaparancs esetén find . | awk '{print $1}' egy PID en fut a parancs és a Linux kernel "felváltva" futtatja őket.

Tehát futtatja egy ideig a find . -ot, majd ahogy gyűlik az adat kicsit futtatja a awk '{print $1}' .

Most ha Perl-ből az alábbi módon nyitom meg: open(PIPE,"find ...|"); akkor "párhuzamosan" fog futni a két parancs, és az adatokat mint fájl olvasás tudom fogadni egy ciklusban.

-Beletettem még a find parancsba egy -print -et, így a fájlnév 2* szerepel (ez volt az eredeti koncepció csak a stat a "szabványos" fájlnevet speciális karakterek esetén hibásan jelenítette meg.

Sajnos a "-b" opció nem volt hatással, de a + -print opció megoldotta.

Egyébként ez duplán hasznos, mert symlink esetén elő lehet állítani olyan speciális fájlnevet ahol nem lehet megállapítani hogy melyik fájlnév van linkelve melyikre.

pl.: './alma' -> 'banan.txt' -> './gyumolcs.txt' ...

Nem egyszerű eldönteni hogy most mi a helyzet?.

A példában az 1. fájl ./alma' -> 'banan.txt ami megengedett fájlnév.

A 2. fájl ./gyumolcs.txt

A stat minden fájlnevet ' ... ' közé helyez, és symlink esetén egymás mögé teszi őket az 'fájl1'SPACE->SPACE'fájl2' elválasztással.

De feldolgozásnál ha valamelyik fájlnévben szerepel ' -> ' rész akkor borul a dolog és nem lehet eldönteni hogy akkor most mi mire van irányítva...

A + -print el ez is meg van oldva mert külön le van tárolva az 1. fájl neve....

-Megcseréltem az ls és a stat sorrendjét, "race condition" megfontolások miatt, valamint az ls most már csak a "+" vagy a "SPACE"-t teszi ki a jogosultság bitek is a stat tól származnak.

A célom hogy legalább minden sor legyen konzisztens, és ne fordulhasson elő hogy a jogosultság bitek és a jogosultság "szám" nem egyezik, most csak az ACL jogok térhetnek el.

Lényegében néhány századmásodperccel a stat után nézzük meg, ha akkor volt ACL de most nincs nem probléma mert magát a tényleges ACL-t ezen script futása után kérdezem le max nem kell lekérdezni.

Ha közben érkezett meg az ACL jogosultság az is jó mert akkor már azt is "frissiben" kérdezem le.

Így minden információm meg van hogy az esetleges inkonzisztens információkat később a következő script"szintjén" kezeljem

-Továbbá Zeller javaslatára beletettem -find -stat -ls paramétereket is, hogy a rendszer parancsokat felül lehessen bírálni, meg a kimenetet át lehessen "belülről" irányítani.

Van még néhány hasznos ötlet ezeket még lehet beleírom, vagy csak akkor ha majd kellenek.

Ez a script számomra tökéletes, majd még csiszolgatom mikor szükségessé válik de a jelenlegi feladatot elvégzi mégpedig megfelelő sebességgel.

Köszönöm a sok segítséget, tanácsot .

Novarobot

Ha már Perl!

#!/usr/bin/perl

use strict;
use warnings;
use File::Find qw(finddepth);
use File::stat;

my @files;

print scalar localtime . "\n";

finddepth(sub {
 return if($_ eq '.' || $_ eq '..');
 push @files, $File::Find::name;
}, @ARGV);

printf "Fájlok száma: %s \n", scalar @files;

print scalar localtime . "\n";

foreach my $filename (@files) {
  my $sb = stat($filename);
  printf "File is %s, size is %s, perm %04o, mtime %s\n",
    $filename, $sb->size, $sb->mode & 07777,
    scalar localtime $sb->mtime;
}

print scalar localtime . "\n";

Az elmélet az, amikor mindent ismerünk, de semmi nem működik. A gyakorlat az, amikor minden működik, de senki nem tudja, miért.

ÖÖÖ hát jó, eddig úgy gondoltam hogy egész "jó" vagyok Perl -ben, de legalább ez is elmúlt...

Igazából csak ez magas egy kicsit:

use File::Find qw(finddepth);
Mit csinál a qw(finddepth); a végén, miért kell ez ide mért nem elég a use File::Find?
Esetleg ezzel jelzem hogy a finddepth legyen a File::Find névterébe?
 

 

finddepth(sub { return if($_ eq '.' || $_ eq '..'); push @files, $File::Find::name; }, @ARGV);
Itt egy sub rutint add át 1. paraméterként a finddepth-nek?
Ha jól látom . és .. neveknél return, míg minden más push olva a files tömbbe? 

A Perl stat függvényét kipróbáltam én is, csak nagyon nem azt és nem úgy működik ahogy nekem kellet volna, 
Egy darabig eljutottam vele, míg úgy nézett ki hogy nem lehet megoldani a külső programok behívását elég gyorsan.

Egyébként még egy kérdés így a végére:

Mit, vagy inkább hogy hívja meg a find az -exec paraméterben lévő programot?

Írtam ez a pár soros ki Per program (../SharedPIPE.pl):

#!/usr/bin/perl

@pipeList ;

foreach (@ARGV)
{
	open($pipeList[@pipeList],"|".$_) || die print STDERR "Fáj nyitás sikertelen: ".$_."\n";
}

while (<STDIN>)
{
	chomp($_);
	foreach $i (@pipeList)
	{
		print {$i} $_."\0";
	}
}

Meghívom, és ez a kimenet fogad:

novarobot@AclTestDebian10:~/t$ find . | ../SharedPIPE.pl "xargs -0 ls -d -1" "xargs -0 ls -d -l" 
drwxr-xr-x 4 root root 4096 jún   21 16:32 .
drwxr-xr-x 2 root root 4096 jún   21 16:32 ./1
drwxr-xr-x 2 root root 4096 jún   21 16:32 ./2
.
./1
./2

Most ez a find esetén:

novarobot@AclTestDebian10:~/t$ find . -exec ls -d -1 {} \; -exec ls -d -l {} \;
.
drwxr-xr-x 4 root root 4096 jún   21 16:32 .
./1
drwxr-xr-x 2 root root 4096 jún   21 16:32 ./1
./2
drwxr-xr-x 2 root root 4096 jún   21 16:32 ./2

Gondoltam hasznos lenne ha a előző hozzászólásomba lévő script find nélkül fáj lista alapján is tudna dolgozni, de valami olyat tud a find amit én nem birok reprodukálni csak $return=`ls -d -l`; módon ami egyesével hívva ami nagyon lassú.

Most akkor ezt a find pl hogy oldja meg? Hogyan lehetne Perl alól reprodukálni?

Próbáltam egyébként a tee -vel: find . | tee >(ls -d -1) >(ls -d -l) de pontosan ugyan azt csinálja mint a SharedPIPE.pl Perl script-em...

Az a baj, hogy nem Perlül gondolkodsz :) Mindenre van megoldásuk, de te feleslegesen hívod a külső programokat.

Majd válaszolok a többire is hamarosan.

Az elmélet az, amikor mindent ismerünk, de semmi nem működik. A gyakorlat az, amikor minden működik, de senki nem tudja, miért.