¿Cómo cambio la extensión de varios archivos de forma recursiva desde la línea de comandos?

Respuestas:

85

Una forma portátil (que funcionará en cualquier sistema compatible con POSIX):

find /the/path -depth -name "*.abc" -exec sh -c 'mv "$1" "${1%.abc}.edefg"' _ {} \;

En bash4, puedes usar globstar para obtener globos recursivos (**):

shopt -s globstar
for file in /the/path/**/*.abc; do
  mv "$file" "${file%.abc}.edefg"
done

El renamecomando (perl) en Ubuntu puede cambiar el nombre de los archivos usando la sintaxis de expresión regular perl, que puede combinar con globstar o find:

# Using globstar
shopt -s globstar
files=(/the/path/**/*.abc)  

# Best to process the files in chunks to avoid exceeding the maximum argument 
# length. 100 at a time is probably good enough. 
# See http://mywiki.wooledge.org/BashFAQ/095
for ((i = 0; i < ${#files[@]}; i += 100)); do
  rename 's/\.abc$/.edefg/' "${files[@]:i:100}"
done

# Using find:
find /the/path -depth -name "*.abc" -exec rename 's/\.abc$/.edefg/' {} +

Ver también http://mywiki.wooledge.org/BashFAQ/030

geirha
fuente
la primera simplemente agrega la nueva extensión a la anterior, no reemplaza ...
user2757729
3
@ user2757729, lo reemplaza. "${1%.abc}"se expande al valor de $1excepto sin .abcal final. Vea las preguntas frecuentes 100 para obtener más información sobre las manipulaciones de cadenas de shell
geirha
Ejecuté esto en una estructura de archivos de ~ 70k archivos ... al menos en mi shell definitivamente no lo reemplazó.
user2757729
1
@ user2757729 Probablemente dejó el "abc" en${1%.abc}...
kvnn
1
En caso de que alguien más se pregunte qué está haciendo el guión bajo en el primer comando, es necesario porque con sh -c el primer argumento se asigna a $ 0 y los argumentos restantes se asignan a los parámetros posicionales . Entonces el _es un argumento ficticio, y '{}' es el primer argumento posicional para sh -c.
hellobenallan
48

Esto hará la tarea requerida si todos los archivos están en la misma carpeta

rename 's/.abc$/.edefg/' *.abc

Para cambiar el nombre de los archivos de forma recursiva, use esto:

find /path/to/root/folder -type f -name '*.abc' -print0 | xargs -0 rename 's/.abc$/.edefg/'
Chakra
fuente
8
O rename 's/.abc$/.edefg/' /path/to/root/folder/**/*.abcen una versión moderna de Bash.
Adam Byrtek
¡Muchas gracias Adam por darme un consejo sobre cómo usar * .abc en carpetas de forma recursiva!
Rafał Cieślak
Gran consejo, gracias! ¿Dónde puedo encontrar más documentación sobre la pequeña sintaxis de regex? Al igual que lo que está sal principio, y qué otras opciones puedo usar allí. Gracias !
Anto
@ AdamByrtek Acabo de fallar con tu sugerencia sobre Utopic. ('no tales archivos' o algo así.)
Jonathan Y.
14

Un problema con los cambios de nombre recursivos es que cualquiera que sea el método que use para ubicar los archivos, pasa toda la ruta rename, no solo el nombre del archivo. Eso dificulta hacer cambios de nombre complejos en carpetas anidadas.

Utilizo findla -execdiracción para resolver este problema. Si utiliza en -execdirlugar de -exec, el comando especificado se ejecuta desde el subdirectorio que contiene el archivo coincidente. Entonces, en lugar de pasar todo el camino a rename, solo pasa ./filename. Eso hace que sea mucho más fácil escribir la expresión regular.

find /the/path -type f \
               -name '*.abc' \
               -execdir rename 's/\.\/(.+)\.abc$/version1_$1.abc/' '{}' \;

En detalle:

  • -type f significa solo buscar archivos, no directorios
  • -name '*.abc' significa que solo coinciden los nombres de archivo que terminan en .abc
  • '{}'es el marcador de posición que marca el lugar donde -execdirse insertará la ruta encontrada. Las comillas simples son necesarias para permitirle manejar nombres de archivos con espacios y caracteres de shell.
  • Las barras invertidas después -typey -nameson el carácter de continuación de línea bash. Los uso para hacer que este ejemplo sea más legible, pero no son necesarios si pones tu comando en una sola línea.
  • Sin embargo, -execdirse requiere la barra diagonal inversa al final de la línea. Está ahí para escapar del punto y coma, que termina el comando ejecutado por -execdir. ¡Divertido!

Explicación de la expresión regular:

  • s/ inicio de la expresión regular
  • \.\/coincidir con los principales ./ que -execdir pasa. Use \ para escapar del. y / metacaracteres (nota: esta parte varía según su versión de find. Vea el comentario del usuario @apollo)
  • (.+)coincide con el nombre del archivo. Los paréntesis capturan la coincidencia para su uso posterior.
  • \.abc escapar del punto, unir el abc
  • $ anclar el partido al final de la cadena

  • / marca el final de la parte de "coincidencia" de la expresión regular, y el comienzo de la parte de "reemplazo"

  • version1_ agregue este texto a cada nombre de archivo

  • $1hace referencia al nombre de archivo existente, porque lo capturamos con paréntesis. Si usa varios conjuntos de paréntesis en la parte de "coincidencia", puede consultarlos aquí usando $ 2, $ 3, etc.
  • .abcel nuevo nombre del archivo terminará en .abc. No es necesario escapar del metacarácter de puntos aquí en la sección "reemplazar"
  • / fin de la expresión regular

antes de

tree --charset=ascii

|-- a_file.abc
|-- Another.abc
|-- Not_this.def
`-- dir1
    `-- nested_file.abc

Después

tree --charset=ascii

|-- version1_a_file.abc
|-- version1_Another.abc
|-- Not_this.def
`-- dir1
    `-- version1_nested_file.abc

Sugerencia: renamela opción 's -n es útil. Realiza una ejecución en seco y le muestra qué nombres cambiará, pero no realiza ningún cambio.

Cristiano largo
fuente
Gracias por la explicación detallada, pero extrañamente, cuando intento esto, --execdirno pasó en primer lugar, ./por lo que \.\/se puede omitir la parte de la expresión regular. Puede que sea porque estoy en OSX, no en ubuntu
apollo
5

Otra forma portátil:

find /the/path -depth -type f -name "*.abc" -exec sh -c 'mv -- "$1" "$(dirname "$1")/$(basename "$1" .abc).edefg"' _ '{}' \;
aNeutrino
fuente
44
Bienvenido a Ask Ubuntu! Si bien esta es una respuesta valiosa, recomiendo expandirla (editando) para explicar cómo y por qué funciona ese comando.
Eliah Kagan
0

Esto es lo que hice y funcionó bastante como quería. Usé el mvcomando Tenía varios .3gparchivos y quería cambiarles el nombre a todos.mp4

Aquí hay un breve resumen para ello:

for i in *.3gp; do mv -- "$i" "ren-$i.mp4"; done

Que simplemente escanea a través del directorio actual, recoge todos los archivos .3gp, luego cambia el nombre (usando el mv) a ren-name_of_file.mp4

KhoPhi
fuente
Eso cambiará el nombre file.3gpa file.3gp.mp4, que podría no ser lo que la mayoría de la gente quiere.
muru
@muru pero el principio aún existe. Simplemente seleccione cualquier extensión y luego especifique la extensión de destino para cambiarles el nombre. El .3gpo .mp4aquí fueron solo para fines ilustrativos.
KhoPhi
La sintaxis es preferible, simple y fácil de entender. Sin embargo, usaría basename "$i" .mp4para eliminar la extensión anterior en lugar de "ren- $ i.mp4".
TaoPR
Cierto. Buen punto.
KhoPhi
0

Encontré una manera fácil de lograr esto. Para cambiar las extensiones de muchos archivos de jpg a pdf, use:

for file in /path/to; do mv $file $(basename -s jpg $file)pdf ; done
Pacífico
fuente
0

Me gustaría usar el comando MMV del paquete del mismo nombre :

mmv ';*.abc' '#1#2.edefg'

Los ;partidos cero o más */y corresponde #1en el reemplazo. El *corresponde a #2. La versión no recursiva sería

mmv '*.abc' '#1.edefg'
MvG
fuente
0

Renombrar archivos y directorios con find -execdir | rename

Si va a cambiar el nombre de los archivos y directorios no simplemente con un sufijo, entonces este es un buen patrón:

PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
  find . -depth -execdir rename 's/findme/replaceme/' '{}' \;

La -execdiropción impresionante hace un cden el directorio antes de ejecutar el renamecomando, a diferencia de -exec.

-depth asegúrese de que el cambio de nombre ocurra primero en los niños y luego en los padres, para evitar posibles problemas con la falta de directorios principales.

-execdir es necesario porque el cambio de nombre no funciona bien con las rutas de entrada que no son de nombre base, por ejemplo, lo siguiente falla:

rename 's/findme/replaceme/g' acc/acc

El PATHpirateo es obligatorio porque -execdirtiene un inconveniente muy molesto: findes extremadamente obstinado y se niega a hacer nada -execdirsi tiene alguna ruta relativa en su PATHvariable de entorno, por ejemplo ./node_modules/.bin, no:

find: la ruta relativa './node_modules/.bin' se incluye en la variable de entorno PATH, que es insegura en combinación con la acción -execdir de find. Elimina esa entrada de $ PATH

Ver también: ¿Por qué usar la acción '-execdir' no es seguro para el directorio que está en la RUTA?

-execdires una extensión de búsqueda de GNU para POSIX . renameestá basado en Perl y proviene del renamepaquete. Probado en Ubuntu 18.10.

Cambiar el nombre de la solución alternativa anticipada

Si sus rutas de entrada no provienen find, o si ha tenido suficiente de la molestia relativa de la ruta, podemos usar algunas anticipaciones de Perl para cambiar el nombre de los directorios de manera segura como en:

git ls-files | sort -r | xargs rename 's/findme(?!.*\/)\/?$/replaceme/g' '{}'

No he encontrado un análogo conveniente para -execdircon xargs: https://superuser.com/questions/893890/xargs-change-working-directory-to-file-path-before-executing/915686

Se sort -rrequiere para garantizar que los archivos vengan después de sus respectivos directorios, ya que las rutas más largas vienen después de las más cortas con el mismo prefijo.

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
fuente