Un modo migliore per rinominare i file in base a più modelli

molti file che ho scaricato hanno crap / spam nei loro nomi di file, ad es

[ www.crap.com ] file.name.ext

www.crap.com - file.name.ext

Ho trovato due modi per affrontarli, ma entrambi sembrano piuttosto goffo:

con l’espansione dei parametri:

 if [[ ${base_name} != ${base_name//\[+([^\]])\]} ]] then mv -v "${dir_name}/${base_name}" "${dir_name}/${base_name//\[+([^\]])\]}" && base_name="${base_name//\[+([^\]])\]}" fi if [[ ${base_name} != ${base_name//www.*.com - /} ]] then mv -v "${dir_name}/${base_name}" "${dir_name}/${base_name//www.*.com - /}" && base_name="${base_name//www.*.com - /}" fi # more of these type of statements; one for each type of frequently-encountered pattern 

e poi con eco / sed:

 tmp=`echo "${base_name}" | sed -e 's/\[[^][]*\]//g' | sed -e 's/\s-\s//g'` mv "${base_name}" "{tmp}" 

Mi sembra che l’espansione dei parametri sia la peggiore delle due, ma mi piace perché sono in grado di mantenere la stessa variabile assegnata al file per un’ulteriore elaborazione dopo la ridenominazione (il codice sopra riportato è usato in uno script chiamato per ogni file dopo il completamento del download del file).

Comunque speravo ci fosse un modo migliore / più pulito per fare quanto sopra che qualcuno più esperto di me potesse mostrarmi, preferibilmente in un modo che mi permettesse di riassegnare facilmente la vecchia / originale variabile al nuovo / rinominato file.

Grazie

Due risposte: usando perl rinomina o usando pura bash

Dato che ci sono persone che non amano il Perl, ho scritto la mia versione di bash

Rinominare i file usando il comando rename .

introduzione

Sì, questo è un tipico lavoro per il comando di rename che è stato progettato con precisione per:

 man rename | sed -ne '/example/,/^[^ ]/p' For example, to rename all files matching "*.bak" to strip the extension, you might say rename 's/\.bak$//' *.bak To translate uppercase names to lower, you'd use rename 'y/AZ/az/' * 

Campioni più orientati

Abbandona semplicemente tutti gli spazi e le parentesi quadre :

 rename 's/[ \[\]]*//g;' *.ext 

Rinominare tutti i caratteri .jpg numerando da 1 :

 rename 's/^.*$/sprintf "IMG_%05d.JPG",++$./e' *.jpg 

demo:

 touch {a..e}.jpg ls -ltr total 0 -rw-r--r-- 1 user user 0 sep 6 16:35 e.jpg -rw-r--r-- 1 user user 0 sep 6 16:35 d.jpg -rw-r--r-- 1 user user 0 sep 6 16:35 c.jpg -rw-r--r-- 1 user user 0 sep 6 16:35 b.jpg -rw-r--r-- 1 user user 0 sep 6 16:35 a.jpg rename 's/^.*$/sprintf "IMG_%05d.JPG",++$./e' *.jpg ls -ltr total 0 -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00005.JPG -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00004.JPG -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00003.JPG -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00002.JPG -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00001.JPG 

Sintassi completa per la corrispondenza della domanda SO, in modo sicuro

C’è un modo sicuro e sicuro usando l’utility rename :

Poiché questo è uno strumento comune perl , dobbiamo usare la syntax perl:

 rename 'my $o=$_; s/[ \[\]]+/-/g; s/-+/-/g; s/^-//g; s/-\(\..*\|\)$/$1/g; s/(.*[^\d])(|-(\d+))(\.[a-z0-9]{2,6})$/ my $i=$3; $i=0 unless $i; sprintf("%s-%d%s", $1, $i+1, $4) /eg while $o ne $_ && -f $_; ' * 

Regola di prova:

 touch '[ www.crap.com ] file.name.ext' 'www.crap.com - file.name.ext' ls -1 [ www.crap.com ] file.name.ext www.crap.com - file.name.ext rename 'my $o=$_; ... ... ...' * ls -1 www.crap.com-file.name-1.ext www.crap.com-file.name.ext touch '[ www.crap.com ] file.name.ext' 'www.crap.com - file.name.ext' ls -1 www.crap.com-file.name-1.ext [ www.crap.com ] file.name.ext www.crap.com - file.name.ext www.crap.com-file.name.ext rename 'my $o=$_; ... ... ...' * ls -1 www.crap.com-file.name-1.ext www.crap.com-file.name-2.ext www.crap.com-file.name-3.ext www.crap.com-file.name.ext 

… e così via…

… ed è sicuro mentre non usi -f flag per rename comando: il file non verrà sovrascritto e riceverai un messaggio di errore se qualcosa va storto.

Rinominare i file usando bash e i cosiddetti bashismi :

Preferisco farlo usando un’utilità dedicata, ma questo potrebbe essere fatto anche usando la pura bash (alias senza fork)

Non c’è nessun altro binario che bash (no sed , awk , tr o altro):

 #!/bin/bash for file;do newname=${file//[ \]\[]/.} while [ "$newname" != "${newname#.}" ] ;do newname=${newname#.} done while [ "$newname" != "${newname//[.-][.-]/.}" ] ;do newname=${newname//[.-][.-]/-};done if [ "$file" != "$newname" ] ;then if [ -f $newname ] ;then ext=${newname##*.} basename=${newname%.$ext} partname=${basename%%-[0-9]} count=${basename#${partname}-} [ "$partname" = "$count" ] && count=0 while printf -v newname "%s-%d.%s" $partname $[++count] $ext && [ -f "$newname" ] ;do :;done fi mv "$file" $newname fi done 

Per essere eseguito con i file come argomento, per esempio:

 /path/to/my/script.sh \[* 
  • Sostituzione di spazi e parentesi quadra per punto
  • Sostituzione di sequenze di .- , -. , -- o .. da solo uno - .
  • Verifica se il nome del file non differisce, non c’è nulla da fare.
  • Verifica se esiste un file con nome nuovo …
  • nome file, contatore ed estensione divisi per la creazione di un nuovo nome indicizzato
  • loop se esiste un file con newname
  • Finaly rinomina il file.

Approfitta del seguente modello classico:

  job_select /path/to/directory| job_strategy | job_process 

dove job_select è responsabile della selezione degli oggetti del lavoro, job_strategy prepara un piano di elaborazione per questi oggetti e job_process esegue infine il piano.

Ciò presuppone che i nomi dei file non contengano una barra verticale | né un carattere di nuova riga.

La funzione job_select

  # job_select PATH # Produce the list of files to process job_select() { find "$1" -name 'www.*.com - *' -o -name '[*] - *' } 

Il comando find può esaminare tutte le proprietà del file gestito dal file system, come tempo di creazione, tempo di accesso, tempo di modifica. È anche ansible controllare il modo in cui viene esplorato il filesystem dicendo di find non scendere nei filesystem montati, quanto sono permessi i livelli di ricorsione. È comune aggiungere pipe al comando find per eseguire selezioni più complesse in base al nome file.

Evita il comune errore di includere il contenuto delle directory nascoste nell’output della funzione job_select . Ad esempio, le directory CVS , .svn , .svk e .git vengono utilizzate dagli strumenti di gestione del controllo sorgente corrispondenti ed è quasi sempre sbagliato includere il loro contenuto nell’output della funzione job_select . Inavvertitamente eseguendo il batch di questi file, è ansible rendere inutilizzabile la copia di lavoro interessata.

La funzione job_strategy

 # job_strategy # Prepare a plan for renaming files job_strategy() { sed -e ' h [email protected]/www\..*\.com - *@/@ [email protected]/\[^]]* - *@/@ x G s/\n/|/ ' } 

Questo comando legge l’output di job_select e crea un piano per il nostro lavoro di ridenominazione. Il piano è rappresentato da linee di testo con due campi separati dal carattere | , il primo campo è il vecchio nome del file e il secondo è il nuovo file calcolato del file, sembra

 [ www.crap.com ] file.name.1.ext|file.name.1.ext www.crap.com - file.name.2.ext|file.name.2.ext 

Il particolare programma utilizzato per produrre il piano è sostanzialmente irrilevante, ma è comune usare sed come nell’esempio; awk o perl per questo. Passiamo attraverso il sed script usato qui:

 h Replace the contents of the hold space with the contents of the pattern space. … Edit the contents of the pattern space. x Swap the contents of the pattern and hold spaces. G Append a newline character followed by the contents of the hold space to the pattern space. s/\n/|/ Replace the newline character in the pattern space by a vertical bar. 

Può essere più facile usare diversi filtri per preparare il piano. Un altro caso comune è l’uso del comando stat per aggiungere tempi di creazione ai nomi dei file.

La funzione job_process

 # job_process # Rename files according to a plan job_process() { local oldname local newname while IFS='|' read oldname newname; do mv "$oldname" "$newname" done } 

Il separatore dei campi di input IFS viene regolato per consentire alla funzione di leggere l’output di job_strategy . La dichiarazione di oldname e newname come local è utile nei programmi di grandi dimensioni ma può essere omessa in script molto semplici. La funzione job_process può essere regolata per evitare di sovrascrivere i file esistenti e segnalare gli elementi problematici.

Informazioni sulle strutture dati nei programmi di shell Si noti l’uso delle pipe per trasferire i dati da uno stadio all’altro: gli apprendisti spesso si basano su variabili per rappresentare tali informazioni ma risulta essere una scelta maldestra. Invece, è preferibile rappresentare i dati come file tabulari o come flussi di dati tabulari che si spostano da un processo all’altro, in questa forma i dati possono essere facilmente elaborati da potenti strumenti come sed , awk , join , paste e sort – solo per citare i più comuni.

Se stai usando Ubunntu / Debian os usa il comando rename per rinominare più file alla volta.

Se vuoi usare qualcosa che non dipende da perl, puoi usare il seguente codice (chiamiamolo sanitizeNames.sh ). Mostra solo alcuni casi, ma è facilmente estensibile usando la sostituzione di stringhe, tr (e anche sed).

  #!/bin/bash ls $1 |while read f; do newfname=$(echo "$f" \ |tr -d '\[ ' \ # Removing opened square bracket |tr ' \]' '-' \ # Translating closing square bracket to dash |tr -s '-' \ # Squeezing multiple dashes |tr -s '.' \ # Squeezing multiple dots ) newfname=${newfname//-./.} if [ -f "$newfname" ]; then # Some string magic... extension=${newfname##*\.} basename=${newfname%\.*} basename=${basename%\-[1-9]*} lastNum=$[ $(ls $basename*|wc -l) ] mv "$f" "$basename-$lastNum.$extension" else mv "$f" "$newfname" fi done 

E usalo:

  $ touch '[ www.crap.com ] file.name.ext' 'www.crap.com - file.name.ext' '[ www.crap.com ] - file.name.ext' '[www.crap.com ].file.anothername.ext2' '[www.crap.com ].file.name.ext' $ ls -1 *crap* [ www.crap.com ] - file.name.ext [ www.crap.com ] file.name.ext [www.crap.com ].file.anothername.ext2 [www.crap.com ].file.name.ext www.crap.com - file.name.ext $ ./sanitizeNames.sh *crap* $ ls -1 *crap* www.crap.com-file.anothername.ext2 www.crap.com-file.name-1.ext www.crap.com-file.name-2.ext www.crap.com-file.name-3.ext www.crap.com-file.name.ext 

Puoi usare rnm

 rnm -rs '/\[crap\]|\[spam\]//g' *.ext 

Quanto sopra rimuoverà [crap] o [spam] dal nome del file.

È ansible passare più pattern di espressioni regolari chiudendoli con ; o sovraccarico dell’opzione -rs .

 rnm -rs '/[\[\]]//g;/\s*\[crap\]//g' -rs '/crap2//' *.ext 

Il formato generale di questa stringa di sostituzione è /search_part/replace_part/modifier

  1. search_part : regex per cercare.
  2. replace_part : stringa da sostituire con
  3. modificatore : i (senza distinzione tra maiuscole e minuscole), g (sostituisci globale)

maiuscolo minuscolo:

Una stringa di sostituzione del form /search_part/\c/modifier renderà minuscola la parte selezionata del nome del file (dalla regex search_part ) mentre \C (capital \ C) in sostituzione di parte lo renderà maiuscolo.

 rnm -rs '/[abcd]/\C/g' *.ext ## this will capitalize all a,b,c,d in the filenames 

Se hai molti schemi di -rs/f che devono essere affrontati, inserisci questi modelli in un file e passa il file con l’opzione -rs/f .

 rnm -rs/f /path/to/regex/pattern/file *.ext 

Puoi trovare alcuni altri esempi qui .

Nota:

  1. rnm usa regex PCRE2 (rivisto PCRE).
  2. È ansible annullare un’operazione di ridenominazione indesiderata eseguendo rnm -u

PS: sono l’autore di questo strumento.