Renombrar archivos en lote con Bash: extensiones, espacios, minúsculas y números

Cambiar la extensión de cien fotos, quitar los espacios de los nombres o numerar una carpeta entera son tareas de un bucle de Bash. Aquí tienes cada caso con el resultado real, la expansión de parámetros que hace el trabajo y las tres precauciones que evitan perder archivos: ensayar antes, no sobrescribir y cuidar los comodines que no encuentran nada.

Todos los ejemplos parten de esta carpeta, fotos, con nombres mezclados en mayúsculas, con espacios y con varias extensiones para la misma cosa (cada script empieza con cd fotos || exit 1, para no renombrar nada en otra carpeta si el cd fallara):

terminal$ ls -1 fotos
IMG_0001.JPG
IMG_0002.JPG
IMG_0003.jpeg
Informe Final.TXT
Presupuesto 2026.ods
notas de la reunión.txt

Primero, el ensayo

Renombrar en lote es de las pocas operaciones que se hacen en un segundo y se deshacen en una tarde. La costumbre más útil es escribir el bucle con echo delante de mv: así ves cada orden que se ejecutaría, y cuando todo está bien quitas el echo:

#!/usr/bin/env bash
cd fotos || exit 1
echo "Ensayo:"
for archivo in *.JPG; do
    echo mv -- "$archivo" "${archivo%.JPG}.jpg"
done

echo "De verdad:"
for archivo in *.JPG; do
    mv -n -- "$archivo" "${archivo%.JPG}.jpg"
done
ls -1
salidaEnsayo:
mv -- IMG_0001.JPG IMG_0001.jpg
mv -- IMG_0002.JPG IMG_0002.jpg
De verdad:
IMG_0001.jpg
IMG_0002.jpg
IMG_0003.jpeg
Informe Final.TXT
Presupuesto 2026.ods
notas de la reunión.txt

${archivo%.JPG} quita .JPG del final del nombre y deja el resto, al que se le pega la extensión nueva. Las comillas alrededor de cada nombre son obligatorias: sin ellas, Informe Final.TXT llegaría a mv como dos argumentos. El -n de mv impide sobrescribir un archivo que ya exista, y -- evita que un nombre que empiece por guion se tome por una opción.

Quitar espacios

#!/usr/bin/env bash
cd fotos || exit 1
for archivo in *' '*; do
    nuevo=${archivo// /_}
    mv -n -- "$archivo" "$nuevo"
done
ls -1
salidaIMG_0001.JPG
IMG_0002.JPG
IMG_0003.jpeg
Informe_Final.TXT
Presupuesto_2026.ods
notas_de_la_reunión.txt

${archivo// /_} sustituye todos los espacios por guiones bajos; con una sola barra, ${archivo/ /_}, cambiaría solo el primero. El comodín *' '* elige únicamente los nombres que tienen al menos un espacio.

Todo a minúsculas

#!/usr/bin/env bash
cd fotos || exit 1
for archivo in *; do
    nuevo=${archivo,,}
    if [[ $archivo == "$nuevo" ]]; then
        continue
    fi
    if [[ -e $nuevo ]]; then
        echo "Ya existe «$nuevo», no toco «$archivo»"
        continue
    fi
    mv -- "$archivo" "$nuevo"
done
ls -1
salidaimg_0001.jpg
img_0002.jpg
img_0003.jpeg
informe final.txt
notas de la reunión.txt
presupuesto 2026.ods

${archivo,,} pasa el nombre a minúsculas (y ${archivo^^}, a mayúsculas), incluidas las letras con tilde. Esta sintaxis llegó en Bash 4: en el Bash 3.2 de macOS da un error, y allí hay que usar tr '[:upper:]' '[:lower:]', que no siempre trata bien los caracteres acentuados. Comprobar antes si el destino existe importa más de lo que parece: en macOS y Windows el sistema de archivos no distingue mayúsculas de minúsculas, y IMG.JPG e img.jpg son el mismo archivo.

Numerar

#!/usr/bin/env bash
cd fotos || exit 1
n=1
for archivo in IMG_*.JPG IMG_*.jpeg; do
    extension=${archivo##*.}
    extension=${extension,,}
    if [[ $extension == jpeg ]]; then
        extension=jpg
    fi
    printf -v nuevo 'vacaciones_%03d.%s' "$n" "$extension"
    mv -n -- "$archivo" "$nuevo"
    n=$((n + 1))
done
ls -1
salidaInforme Final.TXT
Presupuesto 2026.ods
notas de la reunión.txt
vacaciones_001.jpg
vacaciones_002.jpg
vacaciones_003.jpg

printf -v nuevo guarda el resultado en la variable en vez de imprimirlo, y %03d rellena con ceros hasta tres cifras, para que los archivos se ordenen bien: vacaciones_010 va detrás de vacaciones_009, mientras que vacaciones_10 iría delante de vacaciones_9. ${archivo##*.} se queda con lo que hay detrás del último punto, es decir, la extensión.

Cuidado con los comodines que no encuentran nada

#!/usr/bin/env bash
cd fotos || exit 1
for archivo in *.png; do
    echo "Renombraría: $archivo"
done

shopt -s nullglob
for archivo in *.png; do
    echo "Con nullglob: $archivo"
done
echo "Fin"
salidaRenombraría: *.png
Fin

Si un comodín no encuentra ningún archivo, Bash lo deja tal cual, y el bucle da una vuelta con el texto literal *.png: un mv ahí fallaría o, peor, crearía un archivo con ese nombre. Con shopt -s nullglob, un comodín sin coincidencias desaparece y el bucle no se ejecuta.

Un script reutilizable, con modo de ensayo

#!/usr/bin/env bash
# Uso: renombrar.sh [-n] CARPETA EXTENSIÓN_VIEJA EXTENSIÓN_NUEVA
# Cambia la extensión de los archivos de CARPETA. Con -n solo muestra lo que haría.
ensayo=0
if [[ ${1:-} == -n ]]; then
    ensayo=1
    shift
fi
if (( $# != 3 )); then
    echo "Uso: ${0##*/} [-n] CARPETA EXTENSIÓN_VIEJA EXTENSIÓN_NUEVA" >&2
    exit 2
fi
cd -- "$1" || exit 1
vieja=$2
nueva=$3

shopt -s nullglob
archivos=(*."$vieja")
if (( ${#archivos[@]} == 0 )); then
    echo "No hay archivos *.$vieja" >&2
    exit 1
fi

errores=0
for archivo in "${archivos[@]}"; do
    destino=${archivo%."$vieja"}.$nueva
    if [[ -e $destino ]]; then
        echo "Me salto «$archivo»: ya existe «$destino»" >&2
        errores=$((errores + 1))
        continue
    fi
    if (( ensayo )); then
        echo "Renombraría «$archivo» -> «$destino»"
    else
        mv -n -- "$archivo" "$destino" && echo "«$archivo» -> «$destino»"
    fi
done
if (( errores > 0 )); then
    exit 1
fi
salidaRenombraría «IMG_0001.JPG» -> «IMG_0001.jpg»
Renombraría «IMG_0002.JPG» -> «IMG_0002.jpg»

Con -n solo enseña lo que haría. Sin él, y en una carpeta donde ya existe IMG_0002.jpg, renombra lo que puede, se salta el conflicto y termina con código 1 para que quien lo llame sepa que algo quedó pendiente:

terminal$ ls -1 fotos
IMG_0001.JPG
IMG_0002.JPG
IMG_0002.jpg
IMG_0003.jpeg
Informe Final.TXT
Presupuesto 2026.ods
notas de la reunión.txt
$ bash renombrar.sh fotos JPG jpg; echo "código: $?"
«IMG_0001.JPG» -> «IMG_0001.jpg»
Me salto «IMG_0002.JPG»: ya existe «IMG_0002.jpg»
código: 1
$ ls -1 fotos
IMG_0001.jpg
IMG_0002.JPG
IMG_0002.jpg
IMG_0003.jpeg
Informe Final.TXT
Presupuesto 2026.ods
notas de la reunión.txt
  • QuieresExpansiónEjemplo con «Foto Final.JPG»
  • Quitar la extensión${f%.*}Foto Final
  • Solo la extensión${f##*.}JPG
  • Cambiar todos los espacios${f// /_}Foto_Final.JPG
  • Minúsculas (Bash 4+)${f,,}foto final.jpg
  • Mayúsculas (Bash 4+)${f^^}FOTO FINAL.JPG
  • Quitar un prefijo${f#Foto }Final.JPG
En muchas distribuciones existe además la orden rename, pero hay dos distintas con el mismo nombre: la de Perl (rename 's/\.JPG$/.jpg/' *.JPG), habitual en Debian y Ubuntu, y la de util-linux (rename .JPG .jpg *.JPG), en Fedora y Arch. Los bucles de esta página funcionan igual en todas partes.

Todos los scripts de esta página se ejecutaron con Bash 5.3 en Ubuntu 26.04, en un contenedor limpio sin acceso a la red, y pasaron ShellCheck 0.11 sin avisos antes de publicarla; las salidas están copiadas de esa ejecución.