Come eseguo un'azione su tutti i file con un'estensione specifica nelle sottocartelle in modo elegante?


18

La mia migliore scommessa attuale è:

for i in $(find . -name *.jpg); do echo $i; done

Problema: non gestisce gli spazi nei nomi dei file.

Nota: mi piacerebbe anche un modo grafico per farlo, come il comando "albero".


Non puoi semplicemente mettere le virgolette in giro $i? Lo faccio e funziona benissimo. C'è qualcosa di sbagliato in questo approccio?
Umar Farooq Khawaja

Risposte:


26

Il modo canonico è fare

find . -name '*.jpg' -exec echo {} \;

(sostituire \;con +per passare più di un file alla echovolta)

o (specifico per GNU, anche se alcuni BSD ora lo hanno anche):

find . -name '*.jpg' -print0 | xargs -r0 echo

zsh:

for i (**/*.jpg(D)) echo $i

2
Se stai usando xargs -0 , dovresti abbinarlo a find -0
itsbruce

1
@ MichaelKjörling, nessuna citazione {} non fa differenza. {} è espanso da find, non dalla shell. Un miglioramento sarebbe quello di non utilizzare echoche espande sequenze di escape come \b(almeno le conformi Unix echo).
Stéphane Chazelas,

1
@sch Sei sicuro? La findpagina man viene mostrata find . -type f -exec file '{}' \;nella EXAMPLESsezione. Forse alcuni gusci tratteranno specialmente le parentesi graffe.
QuasarDonkey

1
Le citazioni non fanno male ma non fanno differenza. Tutti '{}', \{\}, {}, "{}", '{'}sono espansi dalla shell (qualunque sia Bourne-come shell) per un argomento di trovare cioè i due caratteri "{" e "}". Sostituisci "trova" con "trova eco", o printf '<%s>\n' findse vuoi ricontrollare.
Stéphane Chazelas,


2

Sono già state fornite risposte migliori.

Ma nota che gli spazi non sono l'unico problema nel codice che hai dato. anche i caratteri tab, newline e jolly sono un problema.

Con

IFS='
'
set -f
for i in $(find . -name '*.jpg'); do echo "$i"; done

Quindi solo i caratteri di nuova riga sono un problema.


1

Quello che hai menzionato è uno dei problemi di base che le persone affrontano quando provano a leggere i nomi dei file. A volte, le persone con una conoscenza limitata e con un'idea sbagliata della struttura di file e cartelle tendono a dimenticare che " In UNIX Tutto è un file ". Quindi non comprendono che devono gestire anche gli spazi, poiché il nome del file può essere composto da 2 o più parole con spazi.

Soluzione: uno dei modi più conosciuti per farlo è fare una lettura chiara .

Qui leggeremo tutti i file presenti e li terremo in una variabile, la prossima volta quando eseguiremo l'elaborazione desiderata dovremo semplicemente mantenere quella variabile tra virgolette che conserverà i nomi dei file con spazi. Questo è uno dei modi di base per farlo, tuttavia, le altre risposte fornite dalle altre persone qui funzionano altrettanto bene.

SCRIPT:

 find /path/to/files/ -iname "*jpg" | \
  while read I; do
   cp -v --parent "$I" /backup/dir/
  done

Qui sto leggendo i file dando loro il percorso e qualunque cosa io stia leggendo li mantengo all'interno di una variabile I, che citerò in un secondo momento durante l'elaborazione per preservare gli spazi in modo da elaborarli correttamente.

Spero che questo ti aiuti in qualche modo.


1
"tutto è un file" si riferisce a qualcos'altro. La tua soluzione è specifica per GUN e non affronta i caratteri backslash o newline nei nomi dei file. I cicli "while read" nelle shell (IMO) sono spesso un'indicazione di una cattiva pratica di scripting della shell.
Stéphane Chazelas,

@sch: huh, e io pensavo, va bene. Grazie per averlo sottolineato. Probabilmente dovrò guardarlo di più.
Il cavaliere oscuro

scusa, volevo dire "GNU", non "GUN" sopra (è la prima volta che mi rendo conto che sono degli anagrammi BTW ...)
Stéphane Chazelas,

1

Semplicemente con finde bash:

find . -name '*.jpg' -exec bash -c '
    echo "treating file $1 in bash, from path : ${1%/*}" 
' -- {} \;

In questo modo, usiamo $1in bash, come in uno script di base, che aprono belle prospettive per eseguire qualsiasi attività avanzata (o meno).

Un modo più efficiente (per evitare di dover iniziare un nuovo bash per ogni file):

find . -name '*.jpg' -exec bash -c 'for i do
    echo "treating file $i in bash, from path : ${i%/*}" 
  done' -- {} +

0

Nella versione recente di bash, è possibile utilizzare globstar opzione:

shopt -s globstar
for file in **/*.jpg ; do
    echo "$file"
done

Per azioni semplici, puoi anche saltare completamente il loop:

echo **/*.jpg

Sebbene funzioni in zsh (da cui proviene la funzione) e con ksh93 (con set -G), non dovrebbe essere usato in bash poiché la versione bash è sostanzialmente rotta (proprio come GNU grep -r) mentre scende in collegamenti simbolici alle directory (equivalenti a find -Lo zsh ***/*.jpg). Nota anche che, contrariamente a trovare, ommiterà di dotfile e non scenderà in dotdir (per impostazione predefinita).
Stéphane Chazelas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.