Come rinominare più file usando find


37

Voglio rinominare più file (file1 ... filen in file1_renamed ... filen_renamed) usando il comando findcommand:

find . -type f -name 'file*' -exec mv filename='{}' $(basename $filename)_renamed ';'

Ma ottenere questo errore:

mv: cannot stat filename=./file1’: No such file or directory

Questo non funziona perché il nome file non viene interpretato come variabile di shell.

Risposte:


46

Di seguito è una correzione diretta del tuo approccio:

find . -type f -name 'file*' -exec sh -c 'x="{}"; mv "$x" "${x}_renamed"' \;

Tuttavia, questo è molto costoso se hai molti file corrispondenti, perché avvii una shell nuova (che esegue un mv) per ogni corrispondenza. E se hai personaggi divertenti in qualsiasi nome di file, questo esploderà. Un approccio più efficiente e sicuro è questo:

find . -type f -name 'file*' -print0 | xargs --null -I{} mv {} {}_renamed

Ha anche il vantaggio di lavorare con file con nomi strani. Se lo findsupporta, questo può essere ridotto a

find . -type f -name 'file*' -exec mv {} {}_renamed \;

La xargsversione è utile quando non si utilizza {}, come in

find .... -print0 | xargs --null rm

Qui rmviene chiamato una volta (o con molti file più volte), ma non per tutti i file.

Ho rimosso il basenamein te domanda, perché è probabilmente sbagliato: ci si sposta foo/bar/file8a file8_renamed, non è foo/bar/file8_renamed.

Modifiche (come suggerito nei commenti):

  • Aggiunto abbreviato findsenzaxargs
  • Aggiunto adesivo di sicurezza

Nel caso ciò xsia inutile: la find . -type f -name 'file*' -exec mv {} "{}_renamed" \; xargsversione ha la stessa efficienza del primo esempio /
Costas,

L' xè lì solo per direttamente fissare l'approccio del richiedente.
Thomas Erker,

2
(1) È molto pericoloso usare {}direttamente in un sh -c "…"comando shell ( ) - dovresti sempre passarlo come argomento. (2) Non tutte le versioni findsupportano il {}_renamedcostrutto. (3) Non capisco la tua affermazione che xargsè utile per rimuovere i file (al contrario di rinominarli).
G-Man dice "Reinstate Monica" il

@ G-Man: la differenza con xargsnon è mvvs. rm, ma l'uso di {}vs. senza. Il primo è simile a mv file1 file1_renamed; mv file2 file2_renamedmentre il secondo lo è rm file1 file2.
Thomas Erker,

1
e se poi volessi riportare i file _renamed ai loro nomi originali, come lo faresti?
mcmillab,

17

Dopo aver provato la prima risposta e aver giocato un po 'con essa, ho scoperto che può essere fatto leggermente più breve e meno complesso usando -execdir:

find . -type f -name 'file*' -execdir mv {} {}_renamed ';'

Sembra che dovrebbe anche fare esattamente quello che ti serve.


2
Con findimplementazioni che supportano -execdire {}non nel loro insieme, è anche la più sicura. Potresti voler aggiungere un -ia mvperò (e -Tse mvlo supporta)
Stéphane Chazelas

1
@ StéphaneChazelas ancora meglio, invece di fare affidamento su mvun prompt, o in aggiunta ad esso, puoi (senza dubbio a seconda che la tua implementazione findlo supporti) anche utilizzare -okdirche produrrà il comando da eseguire prima di eseguirlo.
Matijs,

Vale la pena ricordare che -depthè anche una buona idea se tocchi anche i nomi delle directory.
Ciro Santilli 12 改造 中心 法轮功 六四 事件

Argh, appena scoperto che -execdirha uno svantaggio molto fastidioso, si findrifiuta di fare qualsiasi cosa se PATHcontiene dei percorsi relativi ... askubuntu.com/questions/621132/… find: The relative path XXX is included in the PATH environment
Ciro Santilli 13 改造 中心 法轮功 六四 事件

6

Un altro approccio consiste nell'utilizzare un while readloop over findoutput. Ciò consente di accedere a ciascun nome di file come variabile che può essere manipolata senza doversi preoccupare di ulteriori costi / potenziali problemi di sicurezza della generazione di un sh -cprocesso separato utilizzando findl' -execopzione.

find . -type f -name 'file*' |
    while IFS= read file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

E se la shell utilizzata supporta l' -dopzione per specificare un readdelimitatore, è possibile supportare file con nomi strani (ad es. Con una nuova riga) utilizzando quanto segue:

find . -type f -name 'file*' -print0 |
    while IFS= read -d '' file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

3

Voglio espandere la prima risposta e notare che questo non funzionerà per accodarsi al nome del file poiché il ./prefisso del percorso è presente nell'argomento del nome del file.

Modificando la risposta di Thomas Erker, trovo questo un approccio più generico

find . -name PATTERN -printf "%f\0" | xargs --null -I{} mv {} "prefix {} suffix"

opzioni xargs:

--nullIndica che ogni argomento passato attraverso stdintermina con un carattere null ( \0). In questo modo il nome file può contenere spazi, altrimenti ogni parola sarà minacciata come parametro diverso per il mvcomando.

-I replace-strOgni occorrenza di replace-strsarà sostituita dall'argomento letto da stdin. Quindi, puoi cambiarlo per altre stringhe se ne hai bisogno.


Perché pringf "%f\0"invece di a print0?
slm

1
@sim, perché -print0produrrà ./prefissi se PATTERNcontiene metacaratteri di shell che si intromettono quando si rinomina in qualcosa che precede i nomi originali. (ad esempio rinominare 0 - foo.txtin 00 - foo.txt, 1 - bar.txtin 01 - bar.txtecc.)
das-g,

2

Sono stato in grado di fare qualcosa di simile con il for, finde mv.

for i in $(find . -name 'config.yml'); do mv $i $i.bak; done

Questo trova tutti i config.ymlfile e li rinominaconfig.yml.bak


questo ha il vantaggio di poter usare l'espansione variabile, ad es. $ {i: r}. Bella risposta.
Salame,

Sì, puoi praticamente inserire qualsiasi espressione in fored eseguire un'operazione in blocco.
Varun Risbud,
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.