Tengo archivos que comparten la misma nomenclatura log_<date>.txt
dispersos en un directorio anidado root
. ¿Dónde <date>
está la fecha en que se crearon los archivos en el formato AAAAmmddHHMM? Por problemas de compatibilidad de código, me gustaría cambiar el nombre de todos los archivos a<date>_log.txt
Sé que tendría que capturar la <date>
expresión, pero ¿cómo lo hago en bash? Además, ¿cómo lo hago en un directorio anidado?
Respuesta1
Digamos que tiene el nombre de uno de estos archivos en la variable de shell name
. Luego, se elimina el log_
bit del valor de la variable con ${name#log_}
, y se elimina el .txt
bit con ${name%.txt}
. Lo que queda es la fecha, y el formato en el que está esencialmente no es interesante. Sería fácil tomar el valor eliminado y agregarlo _log_.txt
al final para crear el nuevo nombre.
Para hacer esto con los archivos en un solo directorio, use un bucle. Tenga en cuenta que ahora también tenemos que eliminar el nombre del directorio para el inicio del valor de la variable.
shopt -s nullglob
for name in root/log_*.txt; do
newname=root/${name#root/log_} # remove prefix, add root/ back
newname=${newname%.txt}_log.txt # remove suffix, add _log.txt back
printf 'Would move "%s" to "%s"\n' "$name" "$newname"
# mv -i "$name" "$newname"
done
He comentado el mv
comando real por seguridad. Debe realizar una prueba de ejecución una vez para ver qué sucede primero.
La nullglob
opción shell hace que el bash
shelleliminarpatrones incomparables en lugar de dejarlos sin expandir. Esto significa que el bucle no se ejecutaría en absoluto si no hubiera log_*.txt
archivos en el directorio dado.
Esto se puede aplicar a cualquier archivo en el directorio actual (incluido el directorio actual) de forma recursiva mediantemás o menossimplemente modificando el patrón que recorremos:
shopt -s globstar nullglob
for name in ./**/log_*.txt; do
dirpath=${name%/*} # get path of directory
newname=$dirpath/${name#$dirpath/log_} # remove prefix, add directory path back
newname=${newname%.txt}_log.txt # remove suffix, add _log.txt back
printf 'Would move "%s" to "%s"\n' "$name" "$newname"
# mv -i "$name" "$newname"
done
Los otros cambios incluyen habilitar la globstar
opción Shell, que nos permite usar **
para hacer coincidir subdirectorios. Además, selecciono la ruta del directorio para poder anteponerla al nuevo nombre.
También puedes usar find
para generar la entrada al bucle:
find . -type f -name 'log_*.txt' -exec sh -c '
for name do
dirpath=${name%/*} # get path of directory
newname=$dirpath/${name#$dirpath/log_} # remove prefix, add directory path back
newname=${newname%.txt}_log.txt # remove suffix, add _log.txt back
printf "Would move \"%s\" to \"%s\"\n" "$name" "$newname"
# mv -i "$name" "$newname"
done' sh {} +
Esto encontraría todos los archivos normales cuyos nombres coincidan log_*.txt
en el directorio actual o debajo, y luego los pasaría en lotes a un sh -c
script en línea que esencialmente haría el mismo trabajo que el bucle en la segunda variación anterior.
Respuesta2
Lo mejor es utilizar una herramienta para cambiar el nombre de archivos por lotes como zsh
's zmv
, mmv
o una de las variantes basadas en Perl de rename
:
zsh << 'EOF'
autoload zmv
zmv 'log_(*).txt' '${1}_log.txt'
EOF
O
prename 's/log_(.*)\.txt/${1}_log.txt/' log*.txt