
Me gustaría cambiar el nombre de varios archivos que se encuentran en diferentes carpetas. Más específicamente, me gustaría agregar ceros en el medio del nombre del archivo, para que todos los nombres de archivo tengan tres dígitos (y por lo tanto aparezcan en un orden lógico).
Para ser más específico, tengo más de 130 carpetas con más de 400 archivos .nii en cada carpeta. Cada archivo .nii tiene el siguiente patrón:
swu_run1_P_139_Vol_1.nii
- P_### varía de P76 a P277 (número de participante)
- Vol_### varía de 1 a 405 (número de volumen)
Como el volumen varía de 1 a 405, significa que cualquier lista comienza con 100, en lugar de ir del 1 al 405 (por ejemplo, comienza: 100 - 101 - 102 [..] - 109 -10- 110 - 111 etc.). Una forma de resolver esto es agregar ceros al nombre del archivo y hacer que todo tenga tres dígitos, por ejemplo:
swu_run1_P_277_Vol_1.nii -> swu_run1_P_277_Vol_001.nii
swu_run1_P_277_Vol_2.nii -> swu_run1_P_277_Vol_002.nii
swu_run1_P_277_Vol_10.nii -> swu_run1_P_277_Vol_010.nii
swu_run1_P_277_Vol_120.nii -> swu_run1_P_277_Vol_120.nii
Tengo poca experiencia con sistemas Unix/Linux, pero usando hilos anteriores, logré generar el siguiente código. Tiene dos partes:
1. Cambie el nombre de varios archivos, agregando ceros en el medio del nombre del archivo:
rename Vol_ Vol_0 *Vol_[0-9].nii
Si ejecuto esto en una subcarpeta, aparece el siguiente mensaje de error:
Error: rename: swu_run1_P_275_Vol_1.nii: rename to swu_run1_P_275_Vol_01.nii failed: No such file or directory
Por extraño que parezca, está agregando el cero al Vol_1-9. Sin embargo, no añade un cero a ningún número que ya tenga dos o tres dígitos:
swu_run1_P_277_Vol_1.txt -> swu_run1_P_277_Vol_01.nii
swu_run1_P_277_Vol_10.txt -> swu_run1_P_277_Vol_10.nii
swu_run1_P_277_Vol_100.txt -> swu_run1_P_277_Vol_100.nii
¿Parece que hay algún tipo de bucle extraño con la expresión (intenta cambiar el nuevo Vol_01 y muestra el mensaje de error)? ¿Y por qué no agrega un cero a los dos/tres dígitos?
2. Busque todos .nii
los archivos en las subcarpetas relevantes y luego cambie el nombre por lotes:
find . -iname "*.nii" -execdir rename Vol_ Vol_0 *Vol_[0-9].nii '{}' \;
Mi comprensión de este código es la siguiente:
find . -iname "*.nii"
busca todos los archivos .nii, tanto en la carpeta actual como en las subcarpetas-execdir
le dice que aplique la siguiente expresión a la carpeta actual y las subcarpetas, es decir, agregando un cerorename Vol_ Vol_0 *Vol_[0-9].nii
agrega ese cero (usando el formato dedel texto textear lista de archivos)'{}'
está ahí para el nombre de la ruta del archivo\;
¿Está ahí para finalizar la expresión -execdir?
Si intento ejecutar el código en más carpetas, aparece el siguiente mensaje de error:
Error: rename: *Vol_[0-9].nii: rename to *Vol_0[0-9].nii failed: No such file or directory
Creo que recibo el mensaje de error porque -execdir no se ejecuta en las subcarpetas, pero no sé cómo solucionarlo.
Preferiría no ingresar manualmente a cada subcarpeta para ejecutar el shell, entonces, ¿tiene alguna sugerencia sobre cómo mejorar este código (y hacerlo funcionar)? ¿Y por qué recibo el mensaje de error "No existe tal archivo o directorio"?
Respuesta1
Si tuviéramos el nombre swu_run1_P_277_Vol_1.nii
en la variable name
, entonces ${name##*_}
sería ( se elimina 1.nii
la coincidencia de cadena de prefijo más larga ).*_
Tomar eso y eliminarlo .nii
nos deja con el número que queremos llenar con ceros hasta un ancho de tres caracteres:
name=swu_run1_P_277_Vol_1.nii
number=${name##*_}
number=${number%.nii}
En el bash
shell, la forma más fácil (posiblemente) de completar el número con ceros es con printf
:
printf '%.3d' "$number" # would print 001 (with no newline)
Podemos construir el nuevo nombre al mismo tiempo:
printf '%s_%.3d.nii' "${name%_*.nii}" "$number"
Ese printf
comando se imprimiría swu_run1_P_277_Vol_001.nii
eliminando la cadena de sufijo que coincide _*.nii
con el nombre original, agregando _
el número con ceros y luego la .nii
cadena de sufijo.
Para colmo, podemos imprimir la cadena resultante.directamenteen una nueva variable con printf -v newname ...
.
Juntando esto para un solo nombre:
name=swu_run1_P_277_Vol_1.nii
number=${name##*_}
number=${number%.nii}
printf -v newname '%s_%.3d.nii' "${name%_*.nii}" "$number"
Entonces es sólo una cuestión de mv "$name" "$newname"
.
Bien, entonces, ¿cómo hacerlo para todos los archivos relevantes?
Supongamos que todos los archivos relevantes coinciden con el patrón global *Vol_*.nii
, entonces, con find
,
find . -type f -name '*Vol_*.nii' -exec bash -c '
for pathname do
dirpath=${pathname%/*} # or: dirpath=$(dirname "$pathname")
name=${pathname##*/} # or: name=$(basename "$pathname")
number=${name##*_}
number=${number%.nii}
printf -v newname "%s_%.3d.nii" "${name%_*.nii}" "$number"
printf "Would rename %s --> %s\n" "$pathname" "$dirpath/$newname"
# mv "$pathname" "$dirpath/$newname"
done' bash {} +
El bash -c
script en línea aquí es llamado find
con lotes de nombres de rutas encontrados que cumplen con los criterios -type f
(es un archivo normal) y -name
(tiene un nombre particular). El script recorre estos nombres de ruta y extrae el nombre del archivo name
y el nombre de la ruta del directorio dirpath
.
Luego realiza las mismas operaciones que hicimos antes para llegar a un nuevo nombre, que se almacena en newname
y luego cambia el nombre del archivo.
Bueno, he comentado el mv
comando real por seguridad. Primero debe ejecutar esto una vez y ver que el resultado sea correcto. Si utiliza herramientas GNU, es posible que también desee utilizarlas mv -b
para realizar copias de seguridad de archivos si hay colisiones de nombres.
Como nota al margen, en el zsh
caparazón, el patrón globular
./**/*Vol_*.nii(n)
se expandiría a todos esos nombresen orden numérico(y recursivamente hacia abajo en subdirectorios):
$ print -rC1 ./**/*Vol_*.nii(n)
./swu_run1_P_277_Vol_1.nii
./swu_run1_P_277_Vol_2.nii
./swu_run1_P_277_Vol_10.nii
./swu_run1_P_277_Vol_120.nii