¿Cómo capturo una expresión con la terminal bash?

¿Cómo capturo una expresión con la terminal bash?

Tengo archivos que comparten la misma nomenclatura log_<date>.txtdispersos 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 .txtbit 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_.txtal 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 mvcomando real por seguridad. Debe realizar una prueba de ejecución una vez para ver qué sucede primero.

La nullglobopción shell hace que el bashshelleliminarpatrones incomparables en lugar de dejarlos sin expandir. Esto significa que el bucle no se ejecutaría en absoluto si no hubiera log_*.txtarchivos 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 globstaropció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 findpara 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_*.txten el directorio actual o debajo, y luego los pasaría en lotes a un sh -cscript 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, mmvo 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

información relacionada