Ho avuto successo con questo comando.
rename JPG jpg *.JPG
Dove rename
è un comando che dice alla shell di rinominare ogni occorrenza di JPG
a jpg
nella cartella corrente con tutti i nomi di file con estensione JPG
.
Se vedi Bareword "JPG" non consentito mentre "strict subs" è in uso alla riga 1 (eval 1) con questo approccio, prova:
rename 's/\.JPG$/.jpg/' *.JPG
Soluzione
Puoi risolvere l'attività in una riga:
find . -name '*.*' -exec sh -c '
a=$(echo "$0" | sed -r "s/([^.]*)\$/\L\1/");
[ "$a" != "$0" ] && mv "$0" "$a" ' {} \;
Nota:questo si interromperà per i nomi di file che contengono newline. Ma abbi pazienza per ora.
Esempio di utilizzo
$ mkdir C; touch 1.TXT a.TXT B.TXT C/D.TXT
$ find .
.
./C
./C/D.TXT
./1.TXT
./a.TXT
./B.TXT
$ find . -name '*.*' -exec sh -c 'a=$(echo "$0" | sed -r "s/([^.]*)\$/\L\1/"); [ "$a" != "$0" ] && mv "$0" "$a" ' {} \;
$ find .
.
./C
./C/D.txt
./a.txt
./B.txt
./1.txt
Spiegazione
Troverai tutti i file nella directory corrente (.
) che hanno il punto .
nel suo nome (-name '*.*'
) ed esegui il comando per ogni file:
a=$(echo "$0" | sed -r "s/([^.]*)\$/\L\1/");
[ "$a" != "$0" ] && mv "{}" "$a"
Questo comando significa:prova a convertire l'estensione del file in minuscolo (che rende sed
):
$ echo 1.txt | sed -r "s/([^.]*)\$/\L\1/"
1.txt
$ echo 2.TXT | sed -r "s/([^.]*)\$/\L\1/"
2.txt
e salva il risultato in a
variabile.
Se qualcosa è stato cambiato [ "$a" != "$0" ]
, rinomina il file mv "$0" "$a"
.
Il nome del file in elaborazione ({}
) passato a sh -c
come suo argomento aggiuntivo ed è visto all'interno della riga di comando come $0
.Rende sicuro lo script, perché in questo caso la shell prende {} come dato, non come parte di codice, come quando viene specificato direttamente nella riga di comando.(Grazie @gniourf_gniourf per avermi indicato questo problema davvero importante ).
Come puoi vedere, se usi {}
direttamente nello script, è possibile avere alcune shell-injection nei nomi dei file, qualcosa come:
; rm -rf * ;
In questo caso le injection saranno considerate dalla shell come parte del codice e verranno eseguite.
While-versione
Versione più chiara, ma un po' più lunga, dello script:
find . -name '*.*' | while IFS= read -r f
do
a=$(echo "$f" | sed -r "s/([^.]*)\$/\L\1/");
[ "$a" != "$f" ] && mv "$f" "$a"
done
Questo si interrompe ancora per i nomi di file contenenti nuove righe. Per risolvere questo problema, devi avere un find
che supporta -print0
(come GNU find
) e Bash (in modo che read
supporta il -d
interruttore delimitatore):
find . -name '*.*' -print0 | while IFS= read -r -d '' f
do
a=$(echo "$f" | sed -r "s/([^.]*)\$/\L\1/");
[ "$a" != "$f" ] && mv "$f" "$a"
done
Ciò si interrompe ancora per i file che contengono caratteri di fine riga finali (poiché verranno assorbiti dal a=$(...)
subshell. Se davvero vuoi un metodo infallibile (e dovresti!), con una versione recente di Bash (Bash≥4.0) che supporti il ,,
espansione dei parametri ecco la soluzione definitiva:
find . -name '*.*' -print0 | while IFS= read -r -d '' f
do
base=${f%.*}
ext=${f##*.}
a=$base.${ext,,}
[ "$a" != "$f" ] && mv -- "$f" "$a"
done
Torna alla soluzione originale
O in un find
go (torna alla soluzione originale con alcune correzioni che lo rendono davvero infallibile):
find . -name '*.*' -type f -exec bash -c 'base=${0%.*} ext=${0##*.} a=$base.${ext,,}; [ "$a" != "$0" ] && mv -- "$0" "$a"' {} \;
Ho aggiunto -type f
in modo che vengano rinominati solo i file normali. Senza questo, potresti ancora avere problemi se i nomi delle directory vengono rinominati prima dei nomi dei file. Se vuoi anche rinominare le directory (e collegamenti, pipe, ecc.) dovresti usare -depth
:
find . -depth -name '*.*' -type f -exec bash -c 'base=${0%.*} ext=${0##*.} a=$base.${ext,,}; [ "$a" != "$0" ] && mv -- "$0" "$a"' {} \;
in modo che find
esegue una ricerca in profondità.
Potresti obiettare che non è efficiente generare un bash
processo per ogni file trovato. Esatto, e la precedente versione del ciclo sarebbe quindi migliore.
Questo è più breve ma più generale, combinato dalla risposta di altri:
rename 's/\.([^.]+)$/.\L$1/' *
Simulazione
Per la simulazione, usa -n
, ovvero rename -n 's/\.([^.]+)$/.\L$1/' *
. In questo modo puoi vedere cosa verrà modificato prima che vengano eseguite le modifiche reali. Esempio di output:
Happy.Family.GATHERING.JPG renamed as Happy.Family.GATHERING.jpg
Hero_from_The_Land_Across_the_River.JPG renamed as Hero_from_The_Land_Across_the_River.jpg
rAnD0m.jPg1 renamed as rAnD0m.jpg1
Breve spiegazione sulla sintassi
- La sintassi è
rename OPTIONS 's/WHAT_TO_FIND_IN_THE_NAME/THE_REPLACEMENT/' FILENAMES
\.([^.]+)$
significa sequenza di qualsiasi cosa tranne il punto ([^.]
) alla fine della stringa ($
), dopo il punto (\.
).\L$1
significa punto (\.
) seguito da lettere minuscole (\L
) di 1 gruppo ($1
)- Il primo gruppo in questo caso è l'estensione (
[^.]+
) - Ti conviene usare le virgolette singole
'
invece delle virgolette doppie"
avvolgere la regex per evitare l'espansione della shell