Utilizzo di sed per rinominare in massa i file


89

Obbiettivo

Cambia questi nomi di file:

  • F00001-0708-RG-biasliuyda
  • F00001-0708-CS-akgdlaul
  • F00001-0708-VF-hioulgigl

a questi nomi di file:

  • F0001-0708-RG-biasliuyda
  • F0001-0708-CS-akgdlaul
  • F0001-0708-VF-hioulgigl

Codice Shell

Testare:

ls F00001-0708-*|sed 's/\(.\).\(.*\)/mv & \1\2/'

Per eseguire:

ls F00001-0708-*|sed 's/\(.\).\(.*\)/mv & \1\2/' | sh

La mia domanda

Non capisco il codice sed. Capisco quale sia il comando di sostituzione

$ sed 's/something/mv'

si intende. E capisco un po 'le espressioni regolari. Ma non capisco cosa sta succedendo qui:

\(.\).\(.*\)

oppure qui:

& \1\2/

Il primo, a me, sembra proprio che significhi: "un singolo carattere, seguito da un singolo carattere, seguito da qualsiasi sequenza di lunghezza di un singolo carattere" - ma sicuramente c'è dell'altro. Per quanto riguarda l'ultima parte:

& \1\2/

Non ne ho idea.


Risposte:


152

Innanzitutto, dovrei dire che il modo più semplice per farlo è usare i comandi prename o rinomina.

Su Ubuntu, OSX (pacchetto Homebrew rename, pacchetto MacPorts p5-file-rename) o altri sistemi con rinomina perl (prename):

rename s/0000/000/ F0000*

o su sistemi con rinomina da util-linux-ng, come RHEL:

rename 0000 000 F0000*

È molto più comprensibile rispetto al comando sed equivalente.

Ma per quanto riguarda la comprensione del comando sed, la manpage sed è utile. Se esegui man sed e cerchi & (usando il comando / per cercare), troverai che è un carattere speciale in s / foo / bar / replacements.

  s/regexp/replacement/
         Attempt  to match regexp against the pattern space.  If success‐
         ful,  replace  that  portion  matched  with  replacement.    The
         replacement may contain the special character & to refer to that
         portion of the pattern space  which  matched,  and  the  special
         escapes  \1  through  \9  to refer to the corresponding matching
         sub-expressions in the regexp.

Pertanto, \(.\)corrisponde al primo carattere, a cui è possibile fare riferimento \1. Quindi .corrisponde al carattere successivo, che è sempre 0. Quindi \(.*\)corrisponde al resto del nome del file, a cui può fare riferimento\2 .

La stringa di sostituzione mette tutto insieme usando &(il nome del file originale) e \1\2che è ogni parte del nome del file tranne il 2 ° carattere, che era uno 0.

Questo è un modo piuttosto criptico per farlo, IMHO. Se per qualche motivo il comando rename non fosse disponibile e volessi usare sed per fare il rename (o forse stavi facendo qualcosa di troppo complesso per rinominare?), Essere più esplicito nella tua regex lo renderebbe molto più leggibile. Forse qualcosa come:

ls F00001-0708-*|sed 's/F0000\(.*\)/mv & F000\1/' | sh

Essere in grado di vedere cosa sta effettivamente cambiando nella s / ricerca / sostituzione / lo rende molto più leggibile. Inoltre non continuerà a risucchiare i caratteri dal tuo nome file se lo esegui accidentalmente due volte o qualcosa del genere.


1
sul mio server RHEL, la sintassi per rinominare sarebbe "rinomina 0000 000 F0000 *"
David LeBauer

1
È molto probabile che renamesia esso stesso un collegamento "rinominato" . ie renameè stato "rinominato" da prename.. ad es., in Ubuntu: readlink -f $(which rename)output /usr/bin/prename... Il renamemenzionato da David è un programma completamente diverso.
Peter.O

1
Buon punto, Peter. Ho aggiornato la risposta per indirizzare entrambe le utilità di ridenominazione.
Edward Anderson

3
Per eseguire il debug, rimuovere il tubo in sh alla fine. I comandi verranno visualizzati sullo schermo.
Ben Mathews

1
Sei sicuro che sia un buon consiglio da dare per convogliare dati casuali sh? questo è potenzialmente pericoloso in quanto può essere eseguito codice arbitrario (stai trattando i dati come codice).
gniourf_gniourf

46

hai avuto la tua spiegazione su sed, ora puoi usare solo la shell, senza bisogno di comandi esterni

for file in F0000*
do
    echo mv "$file" "${file/#F0000/F000}"
    # ${file/#F0000/F000} means replace the pattern that starts at beginning of string
done

1
Bello ma non puoi fare riferimenti con le parentesi.
Leonidas Tsampros

28

Ho scritto un piccolo post con esempi sulla ridenominazione in batch utilizzando un sedpaio di anni fa:

http://www.guyrutenberg.com/2009/01/12/batch-renaming-using-sed/

Per esempio:

for i in *; do
  mv "$i" "`echo $i | sed "s/regex/replace_text/"`";
done

Se la regex contiene gruppi (ad esempio \(subregex\), puoi usarli nel testo sostitutivo come \1\, \2ecc.


Tieni presente che le risposte di soli link sono scoraggiate (i link tendono a diventare obsoleti nel tempo). Considera la possibilità di modificare la tua risposta e di aggiungere una sinossi qui.
kleopatra

non così efficiente, ma fa il lavoro per duecento file. Votato.
Varun Chandak

23

Il modo più semplice sarebbe:

for i in F00001*; do mv "$i" "${i/F00001/F0001}"; done

o, in modo portatile,

for i in F00001*; do mv "$i" "F0001${i#F00001}"; done

Questo sostituisce il F00001prefisso nei nomi dei file con F0001. crediti a Mahesh qui: http://www.debian-administration.org/articles/150


3
Dovresti citare correttamente le interpolazioni delle variabili; mv "$i" "${i/F00001/F0001}". Ma +1
tripleee

7

Il sedcomando

s/\(.\).\(.*\)/mv & \1\2/

significa sostituire:

\(.\).\(.*\)

con:

mv & \1\2

proprio come un normale sedcomando. Tuttavia, le parentesi &e gli \nindicatori lo cambiano leggermente.

La stringa di ricerca corrisponde (e ricorda come modello 1) il singolo carattere all'inizio, seguito da un singolo carattere, seguito dal resto della stringa (ricordato come modello 2).

Nella stringa di sostituzione, è possibile fare riferimento a questi modelli abbinati per utilizzarli come parte della sostituzione. Puoi anche fare riferimento all'intera porzione abbinata come &.

Quindi quello che sedsta facendo quel comando è creare un mvcomando basato sul file originale (per l'origine) e il carattere 1 e 3 in poi, rimuovendo efficacemente il carattere 2 (per la destinazione). Ti darà una serie di linee nel seguente formato:

mv F00001-0708-RG-biasliuyda F0001-0708-RG-biasliuyda
mv abcdef acdef

e così via.


1
Questa è stata una buona spiegazione, ma potrebbe essere utile sottolineare come si utilizza il comando sed con altri comandi per rinominare effettivamente i file. Ad esempio:ls | sed "s/\(.\).\(.*\)/mv & \1\2/" | bash
jcarballo

@jcarballo: è pericoloso analizzare ls, filtrare sede poi passare attraverso un guscio! è soggetto all'esecuzione di codice arbitrario con nomi di file contraffatti. Il problema è che i dati dovrebbero essere trattati come dati, e qui sono tipicamente serializzati in codice senza alcuna precauzione. Vorrei che paxdiablo potesse eliminare questa risposta perché in realtà non mostra una buona pratica. (Mi sono imbattuto in questa domanda perché un principiante ha ripetuto casualmente | shun comando che non ha funzionato e dopo aver visto questa domanda e le risposte pensava che avrebbe funzionato meglio - Sono inorridito!) :).
gniourf_gniourf

3

La roba backslash-paren significa, "mentre fai corrispondere lo schema, tieniti stretto alle cose che corrispondono qui". Successivamente, sul lato del testo sostitutivo, puoi recuperare quei frammenti ricordati con "\ 1" (primo blocco tra parentesi), "\ 2" (secondo blocco) e così via.


1

Se tutto ciò che stai facendo è rimuovere il secondo personaggio, indipendentemente da cosa sia, puoi farlo:

s/.//2

ma il tuo comando sta creando un mvcomando e lo invia alla shell per l'esecuzione.

Non è più leggibile della tua versione:

find -type f | sed -n 'h;s/.//4;x;s/^/mv /;G;s/\n/ /g;p' | sh

Il quarto carattere viene rimosso perché findantepone a ogni nome di file "./".


Vorrei che tu potessi eliminare questa risposta. Anche se forse andava bene nel caso molto specifico dell'OP, ci sono molte persone che vedono risposte come questa e non le capiscono, e invocano casualmente | shun comando che non funziona, nella speranza che funzioni meglio. È orribile! (e inoltre, non è una buona pratica). Spero che capirai!
gniourf_gniourf

1

Usando perl rename (un must nella casella degli strumenti):

rename -n 's/0000/000/' F0000*

Rimuovere l' -ninterruttore quando l'output sembra buono da rinominare per davvero.

avvertimento Esistono altri strumenti con lo stesso nome che possono o meno essere in grado di farlo, quindi fai attenzione.

Il comando rename che fa parte del util-linuxpacchetto, non lo farà.

Se esegui il seguente comando ( GNU)

$ rename

e vedi perlexpr , allora questo sembra essere lo strumento giusto.

In caso contrario, per renderlo predefinito (di solito già il caso) su Debiane derivato come Ubuntu:

$ sudo apt install rename
$ sudo update-alternatives --set rename /usr/bin/file-rename

Per archlinux:

pacman -S perl-rename

Per le distribuzioni della famiglia RedHat:

yum install prename

Il pacchetto "prename" si trova nel repository EPEL .


Per Gentoo:

emerge dev-perl/rename

Per * BSD:

pkg install gprename

o p5-File-Rename


Per utenti Mac:

brew install rename

Se non hai questo comando con un'altra distribuzione, cerca nel tuo gestore di pacchetti per installarlo o fallo manualmente :

cpan -i File::Rename

La vecchia versione standalone può essere trovata qui


l'uomo rinomina


Questo strumento è stato originariamente scritto da Larry Wall, il padre di Perl.


0

Le parentesi catturano stringhe particolari da utilizzare per i numeri con barra rovesciata.


0
 ls F00001-0708-*|sed 's|^F0000\(.*\)|mv & F000\1|' | bash

Orribile! soggetto all'esecuzione di codice arbitrario (forse non nel contesto specifico della domanda, ma ci sono molte persone che vedono risposte come questa e provano a digitare a caso qualcosa che assomigli, ed è spaventosamente pericoloso!). Vorrei che tu potessi cancellare questa risposta (inoltre, ne hai un'altra buona qui, che ho votato positivamente).
gniourf_gniourf

0

Ecco cosa farei:

for file in *.[Jj][Pp][Gg] ;do 
    echo mv -vi \"$file\" `jhead $file|
                           grep Date|
                           cut -b 16-|
                           sed -e 's/:/-/g' -e 's/ /_/g' -e 's/$/.jpg/g'` ;
done

Quindi, se sembra ok, aggiungi | shalla fine. Così:

for file in *.[Jj][Pp][Gg] ;do 
    echo mv -vi \"$file\" `jhead $file|
                           grep Date|
                           cut -b 16-|
                           sed -e 's/:/-/g' -e 's/ /_/g' -e 's/$/.jpg/g'` ;
done | sh

0

Alcuni esempi che funzionano per me:

$ tree -L 1 -F .
.
├── A.Show.2020.1400MB.txt
└── Some Show S01E01 the Loreming.txt

0 directories, 2 files

## remove "1400MB" (I: ignore case) ...

$ for f in *; do mv 2>/dev/null -v "$f" "`echo $f | sed -r 's/.[0-9]{1,}mb//I'`"; done;
renamed 'A.Show.2020.1400MB.txt' -> 'A.Show.2020.txt'

## change "S01E01 the" to "S01E01 The"
## \U& : change (here: regex-selected) text to uppercase;
##       note also: no need here for `\1` in that regex expression

$ for f in *; do mv 2>/dev/null "$f" "`echo $f | sed -r "s/([0-9] [a-z])/\U&/"`"; done

$ tree -L 1 -F .
.
├── A.Show.2020.txt
└── Some Show S01E01 The Loreming.txt

0 directories, 2 files
$ 

-1
for i in *; do mv $i $(echo $i|sed 's/AAA/BBB/'); done

4
Benvenuto in SO. Si prega di considerare l'aggiunta di una spiegazione del codice. Aiuterà altri utenti a comprenderlo.
Digvijay S

Questa risposta è buona ma è una risposta quasi duplicata di una risposta altamente votata sopra.
Eric Leschinski
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.