Sziasztok. Olyan problémába ütköztem, hogy szerettem volna egy olyan parancsot megírni és futtatni, ami egy megadott útvonalon rekurzívan megkeresi az összes jpg, jpeg, png kiterjesztésű filet, majd ezeknek a fileoknak az exif adataiból kikeresi az orientetion adatát, majd ha nem üres és nem egyenlő eggyel, akkor írja ki ezt a file nevet (a végső cél egy konverzió lesz, de egyelőre kiiratni akartom, mert hibára fut).Az alábbi parancs lett a végeredmény:
find "/home/Data/Pictures" -type f \( -iname \*.jpg -o -iname \*.jpeg -o -iname \*.png \) | parallel 'orientation="$(identify -format '%[EXIF:Orientation]\n' -- "{}" 2> /dev/null)"; if [ -n "$orientation" ] && [ "$orientation" != 1 ]; then echo "{}"; fi'
Ez le is futna rendben, ahogy egy apró tesztben kipróbáltam, viszont mikor az éles könyvtáron akartam kipróbálni, akkor jött egy hibaüzenet x másodperc múlva, miszerint a "Fender '62 Jaguar.jpg" -vel gondja van. És nem csoda, mert van benne egy ' jel, ami speciális karakternek minősül, és ezért pukkan meg.
Hogyan kellene kiegészíteni ezt a parancsot, hogy a speciális karaktereket ne annak értelmezze, hanem a file részének?
Alapvetően a cél a következő lenne:
Egy megadott útvonalon rekurzívan az összes képet keresse meg (mondjuk jpg, jpeg, png kiterjesztés, de tőlem mime alapján is kikeresheti az összes képformátumot), és amit talált képet, azon meg kell vizsgálnia, hogy az EXIF Orientation értéke van/létezik, és az nem egyenlő eggyel. Ha ilyet talál, akkor le kell futtatnia azon a fotón a következő parancsot: convert path/to/file -auto-orient /path/to/file
Mind az útvonal, mind a filenevek tartalmaznak speciális karaktereket.
- 612 megtekintés
Hozzászólások
A `parallel`-t nem ismerem, de talán:
find ... -print0 | parallel -0 ...
- A hozzászóláshoz be kell jelentkezni
Ugyanúgy elhasal tőle, viszont ahogy tesztelgettem, az is a baja, hogy vannak olyan könyvtárak az útvonalon, amiben van . és space is. Pl.: 2022.06. Karlovy Vary
- A hozzászóláshoz be kell jelentkezni
find "/home/Data/Pictures" -type f \( -iname \*.jpg -o -iname \*.jpeg -o -iname \*.png \) -print0 | parallel -0 'orientation="$(identify -format "%[EXIF:Orientation]\n" -- "{}" 2> /dev/null)"; if [ -n "$orientation" ] && [ "$orientation" != 1 ]; then echo "{}"; fi'
- A hozzászóláshoz be kell jelentkezni
Ugyanúgy elhasal tőle, viszont ahogy tesztelgettem, az is a baja, hogy vannak olyan könyvtárak az útvonalon, amiben van . és space is. Pl.: 2022.06. Karlovy Vary
- A hozzászóláshoz be kell jelentkezni
abban a hónapban mi is épp ott voltunk :)
4 és fél éve csak vim-et használok. elsősorban azért, mert még nem jöttem rá, hogy kell kilépni belőle.
- A hozzászóláshoz be kell jelentkezni
parallel-t én se ismerem, de pl. xarg-al használva a -print0 / --null páros szokott ezen a problémán segíteni.
A space-en meg nem tudom mi segít, xarggal ez működik rendesen (ha van a fájlnévben space, ha nincs):
find -type d -print0 | xargs -0 -I "{}" file {}
- A hozzászóláshoz be kell jelentkezni
xargs-nak meg lehet adni ennyi parancsot egyben? Vagy hogy? Mert xargs-ot használtam már 1-1db parancs futtatására, de ez itt jóval komplexebb most.
- A hozzászóláshoz be kell jelentkezni
Nekem ez nem akad fent space-es fájl, se könyvtárneven:
find -name "*jpg" -print0 | xargs -0 -I"{}" sh -c 'orientation=$(identify -format "%[EXIF:Orientation]\n" -- "{}" 2> /dev/null); if [ -n "$orientation" ] && [ "$
orientation" != 1 ]; then echo "{}"; fi'
- A hozzászóláshoz be kell jelentkezni
Igen, ez nem hoz hibát, köszi :) Bár fura, mert az IF vizsgálatnál belevesz olyan fileokat is a kiírásba, aminek orientation értéke 1, holott az van, hogy nem egyenlő 1-el.
- A hozzászóláshoz be kell jelentkezni
Valamiért ott gondolom a $orientation-ben lesz space vagy valami furcsaság.
- A hozzászóláshoz be kell jelentkezni
Igazából nekem mindegy hogy milyen módon van megoldva, csak működjön úgy, hogy ne zavarja ha a file nevében vagy az útvonalon vannak speciális karakterek, mint . ' vagy space :)
Alapesetben az alábbi scriptet próbáltam meg a find "/home/Data/Pictures" -type f \( -iname \*.jpg -o -iname \*.jpeg -o -iname \*.png \) összekötni:
orientation="$(identify -format '%[EXIF:Orientation]\n' -- "$file")"
if [ -n "$orientation" ] && [ "$orientation" != 1 ]; then
echo "$file"
fi
A $file takarná azt a filenevet, amivel dolgozott.
- A hozzászóláshoz be kell jelentkezni
Nagyon sok sebből vérzik amit csinálsz.
Az első gyanús jel, hogy azt írod, "jött egy hibaüzenet", de be nem idéznéd nekünk. Ilyet nem csinálunk. Úgy kérdezünk, hogy megmutatjuk, mit csináltunk, és milyen váratlan eredményt kaptunk (nem körülbelül, hanem pontosan).
Nade nézzük a szkriptet.
A parallel (már ha a moreutils-féléről beszélünk, ami nekem az Ubuntuban van; nem tudom van-e másmilyen vele inkompatibilis) nem a stdin-jéről olvas, hanem az argumentumait dolgozza fel. Tehát semmi értelme pipe-olni a find-ból, és mivel nem kap fájlnév argumentumot, nem tudja hogy mit csináljon mivel. Talán egy xargs maradt ki?
A doksija szerint a {} jelet csak akkor értelmezi speciálisan, ha adsz neki -i kapcsolót, de nem adsz.
A manpage példáiban is látott módon nem adhatsz neki mini shell szriptet paraméterként, hanem vagy egy bináris nevét és paramétereit adod át külön paraméterekként, vagy sh -c bevezetéssel lehet egy mini inline shell szkriptet.
A doksija a {} használatát nem részletezi, de a find doksija igen. Ide simán behelyettesítődik a fájlnév, nem kap semmiféle escape-elést ami majd megvédené őt egy shell script parserétől és az lehámozná róla. Sajna ilyet nem lehet. Talán érdemes külső shell szkriptet írni segítségül, aki fájlneveket kap (mindegyiket egy külön paraméterben) és arra futtatja le a kívánt bonyolult kódot.
(Apró szőrszálhasogatás: azt vágod hogy a kódodban az első "{}" nem megvédi (idézőjelek közé teszi), hanem épphogy kiveszi ebből a kontextusból? A {} előtt bezársz egy zárójelpárt, utána újranyitsz egyet.)
De lépjünk vissza egy lépést. Mi a cél?
Az identify programnak megadhatsz több fájlnevet is. Meg biztos olyan formátumot is, hogy írja ki a kívánt választ (orientation-t) meg a fájlnevet is egy sorba. Egyetlen identify parancsnak betolsz csomó fájlnevet, aztán grepelsz a kívánt orientation-re. Csak akkor kell több parancs, egymás után, ha az argumentumlista túl nagy lenne. Erre a standard megoldás a find | xargs. Igaz, ez egy szálon (egy CPU-n) fog futni.
Ehelyett bedobod a parallel-t a képbe, tehát megdolgoztatod mindegyik processzort, de cserébe minden identify progi egyetlen fájlnevet kap(na elvileg az elképzelésed szerint), vagyis mindegyik fájlra kell a rendszerrek forkolnia és execelnie. Lenne egy fogadásom hogy amit a párhuzamosítással nyersz, azt ezzel sokszorosan elveszíted.
Persze a legjobb lenne a két módszer előnyeit ötvözni, lám, az xargs-nak van erre kapcsolója, nincs szükség olyan "fantasztikusan ügyes" külső progira, ami párhuzamosan ugyan, de egyszerre mindig csak egy fájllal hajtja végre a progit. Nyilván ennek is megvan a maga létjogosultsága, de ez most nem az.
Építkezz apró blokkokként. Egy ekkora nagy izé bonyolult parancssor nem működik. Nem egy hiba van benne, hanem sok, nemcsak szintaktikai hanem koncepcionális is. Szedd részekre. Működik a find önmagában? Gondolom igen, oké. Működik a parallel? Teszteld úgy, hogy kívülről is a find helyett egy fix bemenetet adsz (első körben egyszerű fájlnevet), meg belül is a bonyolult identify-os szkript helyett egy tök egyszerű echo-t. Ha megy, akkor lehet egyesével bonyolítani a komponenseket, illetve kipróbálni trükkös fájlnevekkel is.
Gondold végig, mi a cél, mennyi energiát érdemes belefektetni. Ha egyszer fog lefutni a gépeden, és az a kérdés hogy 10 mp vagy 10 perc alatt, érdemes-e sok-sok munkát belefeccölni és fórumozók segítségnyújtását kérni az optimalizáláshoz, érdemes-e bármiféle párhuzamosítást belerakni?
- A hozzászóláshoz be kell jelentkezni
Köszönöm a hasznos tanácsokat. Természetesen külön-külön mindegyik rész működik. Sőt az egész is működik megfelelően, hanem sem a path-ban, sem a filenévben nincsenek különleges karakterek, mint space, pont, ' stb. Ezért gondoltam, hogy ez lehet a baja. Viszont amiket írtál paraméterezési hibákat, azalapján sehogysem kellene működnie, viszont egyszerű nevekkel, útvonalakkal működik, így viszont ez nekem fura.
A cél a következő. Egy megadott útvonalon rekurzívan az összes képet keresse meg (mondjuk jpg, jpeg, png kiterjesztés, de tőlem mime alapján is kikeresheti az összes képformátumot), és amit talált képet, azon meg kell vizsgálnia, hogy az EXIF Orientation értéke van/létezik, és az nem egyenlő eggyel. Ha ilyet talál, akkor le kell futtatnia azon a fotón a következő parancsot: convert path/to/file -auto-orient /path/to/file
Mind az útvonal, mind a filenevek tartalmaznak speciális karaktereket. Ez lenne az eredeti cél.
- A hozzászóláshoz be kell jelentkezni
Hoppá, tényleg van párhuzamosan (hehe) két parallel progi is, legalábbis Ubuntuban (moreutils és parallel csomag), amik jó inkompatibilisek. (Ennyit arról, hogy ami szkriptet írsz, az mennyire lesz portábilis.)
Ennek megfelelően korábbi észrevételeim nagy része tárgytalan a "parallel" csomaggal, ezer bocsi.
Úgy látom, van valami olyan, hogy {=perl expression=}, és azon belül Q(string), ezzel próbálkozz hogy be tudja-e helyettesíteni a fájlnevet shell quoted formában. {=Q($_)=} vagy valami hasonló talán.
- A hozzászóláshoz be kell jelentkezni
Virágozzék száz virág... Párhuzamosan :-) Egyébként meg én első körben Perl-t javasoltam volna, ott nincs gond a girbegurba, el... szóval nem teljesen "szép" útvonallal/fájlnévvel...
- A hozzászóláshoz be kell jelentkezni
Biztos jó ötlet, csak ahhoz (is) hülye vagyok :)
- A hozzászóláshoz be kell jelentkezni
Valahogy állítsd angolra az üzenetek nyelvét, mert a 'gondja van' nem mond semmit, és a google-ba sem lehet érdemes beírni.
Egyébként amit beidéztél, azt nem vélem átlátni, hogy pl. nincs -exec, viszont a pipe jobboldalán van pár {kapcsos zárójelpár}.
find "/home/Data/Pictures" -type f \( -iname \*.jpg -o -iname \*.jpeg -o -iname \*.png \) |
while IFS='\n' read -r File; do
D=$(dirname "$File")
F=$(basename "$File")
echo "Most alkoss valamit $File alapjan"
done
- A hozzászóláshoz be kell jelentkezni
Nagy +1. Én is nemrég fedeztem fel újra ezt a pipe + while read-done trükköt, talán a Stackexchange-en találtam rá. Nekem egy szigorúan POSIX kompatibilis shell scriptbe kellett legutóbb, ami egy mappában rekurzívan átnevezi a fájlokat, mappákat kisbetűsre, ezt általában régi DOS-os archívumaimra szoktam ráereszteni. Meg egy másik scriptbe is nagyon jó volt, ami aszinkron frissítette a lemonbar-ban az egyes modulokat, hogy ne tartsák fel egymást frissülésben.
Na már most a find -exec meg a find | whatever közvetlen pipe-olás rendre elszarta azokat a fájlneveket, amikben szóköz volt, hiába voltak a változók "-jelek között. A while-read-done simán megcsinálta, ráadásul olvashatóbb is, mint egy egysoros, km hosszú parancs. Tényleg elegáns és aranyat ér. A kollégának is javaslom, hogy maradjon ennél a megoldásnál, ez olvasható, szétszedi a működést is több sorra, így később is érteni, hogy mit csinál a kód, könnyebben karbantartható.
A másik nagyon hasznos kincs, a rendszeresen alulértékelt, sokak által elfelejtett case-esac szerkezet, nagyon sok esetben mentett meg ez is bonyolult feltételű if-elseif-else-fi szerkezetektől. Nem hiába a shell programozásnak is megvan a maga művészete, hogy mit hogyan lehet a legelegánsabban, POSIX-szel legkompatibilisebben megoldani.
“The world runs on Excel spreadsheets.” (Dylan Beattie)
- A hozzászóláshoz be kell jelentkezni
Két megoldás is született: (hála Zsugabubus és NevemTeve-nek)
1.
find "/home/Data/Pictures" -type f \( -iname \*.jpg -o -iname \*.jpeg -o -iname \*.png \) -print0 | xargs -0 -I"{}" sh -c 'orientation=$(identify -format "%[EXIF:Orientation]\n" -- "{}" 2> /dev/null); if [ -n "$orientation" ] && [ "$orientation" != 1 ]; then echo "{}"; fi'
2.
#!/bin/bash
find "/home/Data/Pictures" -type f \( -iname \*.jpg -o -iname \*.jpeg -o -iname \*.png \) |
while IFS='\n' read -r File; do
D=$(dirname "$File")
F=$(basename "$File")
orientation="$(identify -format '%[EXIF:Orientation]\n' "$File" 2> /dev/null)"
if [ -n "$orientation" ] && [ "$orientation" != 1 ]; then
echo "$File" >> teszt.txt
# convert "$File" -auto-orient "$File"
fi
done
- A hozzászóláshoz be kell jelentkezni