GNU/Linux >> Linux Esercitazione >  >> Linux

Converti tutte le estensioni di file in minuscolo

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

Linux
  1. Rimuovere tutti i file/directory tranne un file?

  2. Mostra tutto il file fino alla partita?

  3. Salva tutto l'output del terminale in un file?

  4. Come posso trovare tutte le estensioni di file distinte in una gerarchia di cartelle?

  5. collegamento simbolico:trova tutti i file che si collegano a questo file

Come rimuovere tutti i file in una cartella tranne un file specifico in Linux

Come convertire un file Windows in un file UNIX

Modifica di tutte le estensioni di file in una cartella utilizzando la CLI in Linux

Converti .txt in .csv nella shell

Come posso convertire tar.bz2 in tar.gz?

Ottieni tutte le estensioni e il rispettivo numero di file in una directory