Cómo renombrar múltiples archivos usando find

37

Quiero cambiar el nombre de varios archivos (file1 ... filen a file1_renamed ... filen_renamed) usando el comando findcommand:

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

Pero obteniendo este error:

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

Esto no funciona porque el nombre de archivo no se interpreta como variable de shell.

usuario164014
fuente

Respuestas:

46

La siguiente es una solución directa de su enfoque:

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

Sin embargo, esto es muy costoso si tiene muchos archivos coincidentes, porque inicia un nuevo shell (que ejecuta a mv) para cada coincidencia. Y si tiene caracteres divertidos en cualquier nombre de archivo, esto explotará. Un enfoque más eficiente y seguro es este:

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

También tiene la ventaja de trabajar con archivos con nombres extraños. Si lo findadmite, esto se puede reducir a

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

La xargsversión es útil cuando no se usa {}, como en

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

Aquí rmse llama una vez (o con muchos archivos varias veces), pero no para cada archivo.

Eliminé la basenamepregunta en ti, porque probablemente esté mal: te moverías foo/bar/file8a file8_renamed, no foo/bar/file8_renamed.

Ediciones (como se sugiere en los comentarios):

  • Agregado acortado findsinxargs
  • Adhesivo de seguridad agregado
Thomas Erker
fuente
En el caso de que xsea ​​inútil: la find . -type f -name 'file*' -exec mv {} "{}_renamed" \; xargsversión tiene la misma eficiencia que el primer ejemplo /
Costas
El xestá allí solo para arreglar directamente el enfoque del autor de la pregunta.
Thomas Erker
2
(1) Es muy peligroso usarlo {}directamente en un sh -c "…"comando shell ( ); siempre debe pasarlo como argumento. (2) No todas las versiones de findsoporte de la {}_renamedconstrucción. (3) No entiendo su declaración que xargses útil para eliminar archivos (en contraste con renombrarlos).
G-Man dice 'reinstalar a Monica' el
@ G-Man: La diferencia con xargsno es mvvs. rm, sino el uso de {}vs. sin. El primero es similar a mv file1 file1_renamed; mv file2 file2_renamedmientras que el segundo es rm file1 file2.
Thomas Erker
1
y si luego quisieras cambiar los archivos _renamed a sus nombres originales, ¿cómo harías eso?
mcmillab
17

Después de probar la primera respuesta y jugar un poco con ella, descubrí que se puede hacer un poco más corto y menos complejo usando -execdir:

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

Parece que también debería hacer exactamente lo que necesita.

Matijs
fuente
2
Con findimplementaciones que admiten -execdiry {}no en su conjunto, también es la más segura. Sin embargo, es posible que desee agregar un -ito mv(y -Tsi lo mvadmite)
Stéphane Chazelas
1
@ StéphaneChazelas aún mejor, en lugar de depender mvde un mensaje, o además de esto, puede (sin duda dependiendo de si su implementación de findsoporte) también puede usar lo -okdirque generará el comando que se ejecutará antes de ejecutarlo.
Matijs
Vale la pena mencionar que -depthtambién es una buena idea si también va a tocar los nombres de directorio.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
Argh, acaba de descubrir que -execdirtiene un inconveniente muy molesto, se findniega a hacer cualquier cosa si PATHcontiene algún camino relativo ... askubuntu.com/questions/621132/… find: The relative path XXX is included in the PATH environment
Ciro Santilli 新疆 改造 中心 法轮功 六四 六四
6

Otro enfoque es usar un while readbucle sobre la findsalida. Esto permite el acceso a cada nombre de archivo como una variable que se puede manipular sin tener que preocuparse por los costos adicionales / posibles problemas de seguridad de generar un sh -cproceso separado usando findla -execopción 's .

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

Y si el shell que se utiliza admite la -dopción de especificar un readdelimitador, puede admitir archivos con nombres extraños (por ejemplo, con una nueva línea) utilizando lo siguiente:

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

Quiero ampliar la primera respuesta y tener en cuenta que esto no funcionará para agregar al nombre de archivo ya que el ./prefijo de la ruta está presente en el argumento del nombre de archivo.

Modificando la respuesta de Thomas Erker, considero que este es un enfoque más genérico

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

Opciones de xargs:

--nullIndica que cada argumento pasado stdintermina con un carácter nulo ( \0). De esta forma, el nombre del archivo puede contener espacios; de lo contrario, cada palabra se tratará como un parámetro diferente para el mvcomando.

-I replace-strCada ocurrencia de replace-strserá reemplazada por el argumento leído stdin. Por lo tanto, puede cambiarlo por otra cadena si lo necesita.

Sdlion
fuente
¿Por qué hacer el en pringf "%f\0"lugar de un print0?
slm
1
@sim, porque -print0producirá ./prefijos si PATTERNcontiene metacaracteres de shell que se interponen en el camino al cambiar el nombre a algo que prefija los nombres originales. (por ejemplo, cambiar el nombre 0 - foo.txta 00 - foo.txt, 1 - bar.txta 01 - bar.txtetc.)
das-g
2

Yo era capaz de hacer algo similar con el for, findy mv.

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

Esto busca todos los config.ymlarchivos y los renombra aconfig.yml.bak

Varun Risbud
fuente
Esto tiene la ventaja de poder utilizar la expansión variable, por ejemplo. $ {i: r}. Buena respuesta.
Salami
Sí, puedes poner cualquier expresión en el fory realizar una operación masiva.
Varun Risbud