[Linuxtrent] Re: Script (o simile) di conversione automatica foto

  • From: Flavio Stanchina <flavio@xxxxxxxxxxxxx>
  • To: linuxtrent@xxxxxxxxxxxxx
  • Date: Thu, 08 Jan 2015 20:11:54 +0100

On 08/01/2015 18:41, Shinsan Hattori wrote:
> Il 6 gennaio 2015 01:40, Antonio Galea <antonio.galea@xxxxxxxxx> ha scritto:
>> 2015-01-05 11:50 GMT+01:00 Flavio Stanchina <flavio@xxxxxxxxxxxxx>:
>>> On 04/01/2015 02:38, Antonio Galea wrote:
>>>>   for f in `find src/ -name \*.jpg`
>>>>   do
>>>>      [...]
>>>
>>> Passando gli argomenti in questo modo, prima o poi si incappa nel limite di
>>> lunghezza della riga di comando. Usate
>>>
>>>   find src -name '*.jpg' | while read f; do ...
>>>
>>> ...che tra l'altro parallelizza (almeno in parte) l'operazione di lettura
>>> della directory e l'effettiva elaborazione dei file.
>>
>> Ciao Flavio,
>> grazie della utilissima puntualizzazione - nel caso in questione la
>> tua versione è sicuramente necessaria, visto che le immagini possono
>> essere molte migliaia.
> 
> Non ho capito bene quale fosse il problema della precedente
> formulazione ma mi fido...

Se tu scrivi il comando così:
  for f in $(find src -name "*.jpg"); do ...

la shell esegue find, raccoglie tutto l'output e poi lo usa per sostituire
$(find ...) costruendo un luuuungo comando del tipo:
  for f in src/1.jpg src/2.jpg ...; do

Il problema è che la linea di comando ha un limite di lunghezza. Si parla
di centinaia di migliaia di byte, ma se hai migliaia di file e/o percorsi
molto lunghi, prima o poi ci arriverai.

Un problema secondario è che in questo modo prima la shell esegue find e
raccoglie tutto l'output, poi inizia ad elaborare i file. Con una pipeline
l'elaborazione avviene in parallelo.

> Anche perché la forma proposta da Flavio
> inizia a risolvere un bug che stavo cercando di estirpare da solo (per
> ora senza successo): i files con spazi all'interno del nome. Mentre
> inizialmente questi generavano due diversi valori per f, usando il
> "find src -name '*.jpg' | while read f; do ..." questo non accade.

Certo, dovresti usare
  for f in "$(find src -name "*.jpg")"; do ...

O meglio ancora
  for f in "$(find src -name '*.jpg')"; do ...

per evitare ambiguità con le virgolette, visto che l'argomento di find non
contiene variabili da espandere.

> Tuttavia il problema si ripresenta al momento in cui viene chiamato
> convert: [...]
> 
> Da quel che ho visto in giro mi è parso di capire che per evitare
> questo genere di problemi la cosa migliore è indicare le variabili
> nella forma "${f}", ma sembra non bastare.

Ti riferisci all'uso di virgolette o alle parentesi graffe? Le virgolette
servono proprio a questo, le graffe no.

In caso di dubbi, "man bash" e leggiti la sezione QUOTING (tutta, anche se
è lunga e complicata).

> Quel che ancora non ho risolto
> è il test della data, che deve tener conto della possibile differente
> estensione tra i due files [...]

Spiega, perché mai il test sulla data dovrebbe tener conto delle differenti
estensioni?

> echo "comando lanciato: convert "${1}" -filter Lanczos -define
> filter:lobes=4 -resize 1920x1080\> $(echo "${2}" | sed
> 's/\(.*\)\..*/\1.jpg/')"

Qui il quoting è decisamente sbagliato. Prova con:
 echo "comando lanciato: convert \"${1}\" -filter Lanczos -define
  filter:lobes=4 -resize 1920x1080\> \"$(echo "${2}" | sed
's/\(.*\)\..*/\1.jpg/')\""

Peraltro, userei una variabile temporanea per il nome del file di
destinazione, così semplifichi i comandi e risulta più chiaro quello che
sta succedendo...

> # BUG: le variabili non vengono assate correttamente, i nomi di files
> contenenti spazi generano errori
> convert "${1}" -filter Lanczos -define filter:lobes=4 -resize
> 1920x1080\> $(echo "${2}" | sed 's/\(.*\)\..*/\1.jpg/')

Il problema con gli spazi sta qui: l'argomento di destinazione deve essere
tra virgolette.
 "$(echo "${2}" | sed 's/\(.*\)\..*/\1.jpg/')"

(lasciamo come esercizio per il lettore capire perché le abbondanti
virgolette non causano ambiguità di interpretazione)

Inoltre, invece di mettere il backslash prima di >, metti l'argomento tra
apici.

> # individua tutti i files presenti nella cartella src. Poi se il mime-type
> # corrisponde a image li converte chiamando resize_and_convert
> find "${src}" -name '*.*' | while read f;
> do
> t="${dst}"${f#"${src}"}

Il quoting qui è un po' incomprensibile: perché non
 t="${dst}${f#${src}}"

...e comunque ti conviene usare opportunamente l'opzione -printf di find
per avere direttamente i percorsi relativi a src, così non devi tagliare e
ricucire stringhe in modi difficili da seguire.

> type=$(file --mime-type "${f}" | sed 's/\(^.*\)\(: \)\(.*\)\(\/.*\)/\3/')

Invece di impazzire con regular expression complicate, usa l'opzione -b di
file per omettere il nome del file, tanto lo sai già.

> if [ "${type}" == "image" ]
> then

...e per eliminare completamente sed sopra, usa
 if expr "$type" : 'image/'; then ...

> ## BUG: il controllo deve tener conto che il file di origine e
> ## quello di destinazione potrebbero non avere la stessa estensione
> test "${f}" -nt  "${t}" && resize_and_convert "${f}" "${t}"

Ah, adesso capisco: il problema non è il controllo, ma il fatto che $t non
contiene il nome di destinazione che poi userai davvero...

Io sposterei il controllo nella funzione resize_and_convert, così generi il
nome del file una volta sola.

Non proseguo con appunti sul quoting nel resto dello script, perché valgono
i princìpi già indicati sopra.

-- 
Ciao, Flavio

Those who do not understand Unix are condemned to reinvent it, poorly.
-- Henry Spencer
-- 
Per iscriversi  (o disiscriversi), basta spedire un  messaggio con OGGETTO
"subscribe" (o "unsubscribe") a mailto:linuxtrent-request@xxxxxxxxxxxxx


Other related posts: