[Megoldva] Find parancs találatában speciális karakter van, és ez miatt megáll

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.

Hozzászólások

A `parallel`-t nem ismerem, de talán:

find ... -print0 | parallel -0 ...
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'
Szerkesztve: 2024. 03. 14., cs – 11:49

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 {}
 

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.

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?

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.

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.

Szerkesztve: 2024. 03. 14., cs – 16:19

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

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)

Szerkesztve: 2024. 03. 14., cs – 17:48

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